feat: whisper 新增 decode_full 无VAD整段解码参数并改为整条删除式幻觉清洗
解决转写漏句(有人说话但没识别出来)问题:silero VAD 对呻吟/轻语/BGM 混叠声学切段能力天然不足,把真话当非语音剔除(实测 savr-1054 全片仅 召回 115 条)。新增 decode_full 参数(默认 false 保持 VAD 现状): - decode_full=true 时强制无 VAD 整段解码 + 跳过自动 VAD 分析,救回被 剔除的弱语音(savr-1054 全片 115 条 → 340 条) - 副作用是长时寒暄套话幻觉(おやすみなさい/ご視聴ありがとうございま した 等),whisper 转录后连带时间戳整条删除(clean_japanese_ha lllucinations),不留下 '-' 占位污染下游(占位会渲染进 ASS 成减号) - llm-translate 翻译后同样整条删除中文长时寒暄幻觉(clean_srt_text) - 短时(≤15s)相同词可能是剧情真实道晚安,保留(15s 阈值实测校准) - subtitle_cleanup 由 '-' 占位式改为整条删除式 + 剩余重编号,新增 JAPANESE_HALLUCINATION_TOKENS 词表 新增工作流 learn-translate(学习资料转译+翻译字幕)示范 decode_full 用法,并确立参数标注约定:params._note_<参数名> 存放设定理由与正反例、 _node_help 放节点参数手册(_ 前缀说明键,节点执行时忽略,零运行影响)。 调研记录见 docs/调研-whisper漏句与decode_full验证.md(A/B 实验、结论 修正与 5 个待决问题)。
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""长时寒暄幻觉词清洗测试(先红后绿)。
|
||||
"""字幕幻觉清洗测试(先红后绿)。
|
||||
|
||||
背景(实测 run 20260905115050):修复时间对齐后,字幕仍残留四类问题,
|
||||
其中"寒暄/收尾幻觉词"最具确定性、可规则化:
|
||||
@@ -9,12 +9,18 @@
|
||||
- 仅 2 条时长 ~2s(如 720.00-722.00 '晚安')可能是剧情里真的说了"晚安",
|
||||
属于真实内容,不应误删。
|
||||
|
||||
方案(用户确认):日文转译完成后,对**展示时长过长**(≥阈值)且文本匹配
|
||||
寒暄词表的条目,把文本替换为 '-' 占位,由后续处理(SRT/过滤流程)移除。
|
||||
这样既清掉幻觉占位,又用"时长阈值"保住可能为真实对话的短时寒暄词。
|
||||
方案(用户 2026-09 确认改为**整条剔除**):幻觉识别出后(展示时长 ≥ 阈值
|
||||
且文本命中寒暄词表),应**连带时间戳把整条字幕 cue 删除**(剩余重新编号),
|
||||
而不是替换成 '-' 占位——占位会一路流到 ASS 渲染成可见减号,处理位置绕且
|
||||
不彻底。因此清洗统一为在幻觉产生处(whisper decode_full 转录后 / LLM 翻译
|
||||
后)直接删除整条。
|
||||
|
||||
两类词表:
|
||||
- HALLUCINATION_TOKENS:中文(LLM 翻译产物);
|
||||
- JAPANESE_HALLUCINATION_TOKENS:日文(whisper decode_full 直出)。
|
||||
|
||||
阈值从本次真实运行实测数据判定:30s 幻觉占位 vs 2s 真实词,分界明显,
|
||||
本测试选 threshold=15s(>15s 才视为幻觉;≤15s 保留)。
|
||||
本测试选 threshold=15s(≥15s 才删除;≤15s 保留)。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -25,63 +31,135 @@ import pytest
|
||||
from nodes.subtitle_cleanup import (
|
||||
DEFAULT_THRESHOLD_SECONDS,
|
||||
HALLUCINATION_TOKENS,
|
||||
mask_hallucination_text,
|
||||
clean_japanese_hallucinations,
|
||||
clean_srt_text,
|
||||
)
|
||||
|
||||
|
||||
def _mk(start: float, end: float, text: str) -> dict:
|
||||
"""构造一个字幕条目(测试辅助)。"""
|
||||
return {"start": start, "end": end, "text": text}
|
||||
def test_remove_long_hallucination_whole_cue() -> None:
|
||||
"""30s 的'晚安/感谢观看'(幻觉占位)须整条删除:序号+时间轴+文本都消失。"""
|
||||
srt = (
|
||||
"1\n00:00:00,000 --> 00:00:02,000\n真实内容\n\n"
|
||||
"2\n00:01:00,000 --> 00:01:30,000\n晚安\n\n"
|
||||
"3\n00:02:00,000 --> 00:02:30,000\n感谢您的观看\n\n"
|
||||
"4\n00:03:00,000 --> 00:03:02,000\n继续真实\n\n"
|
||||
)
|
||||
out = clean_srt_text(srt)
|
||||
# 幻觉条目连带时间戳整条消失。
|
||||
assert "00:01:00,000 --> 00:01:30,000" not in out
|
||||
assert "晚安" not in out
|
||||
assert "00:02:00,000 --> 00:02:30,000" not in out
|
||||
assert "感谢您的观看" not in out
|
||||
# 真实条目保留且序号重新连续编号(原 1、4 -> 新 1、2)。
|
||||
assert out.startswith("1\n00:00:00,000 --> 00:00:02,000\n真实内容")
|
||||
assert "2\n00:03:00,000 --> 00:03:02,000\n继续真实" in out
|
||||
|
||||
|
||||
def test_long_hallucination_masked() -> None:
|
||||
"""30s 的'晚安/感谢观看'(幻觉占位)必须被替换为 '-'。"""
|
||||
entries = [
|
||||
_mk(60.0, 90.0, "晚安"),
|
||||
_mk(90.0, 120.0, "感谢您的观看"),
|
||||
_mk(1260.0, 1289.98, "感谢您的观看"),
|
||||
]
|
||||
out = mask_hallucination_text(entries)
|
||||
assert all(e["text"] == "-" for e in out)
|
||||
def test_remove_short_hallucination_preserved() -> None:
|
||||
"""2s 的'晚安'(剧情真实道晚安)必须保留,不误删。"""
|
||||
srt = (
|
||||
"1\n00:12:00,000 --> 00:12:02,000\n晚安\n\n"
|
||||
"2\n00:12:03,000 --> 00:12:06,000\n明天见\n\n"
|
||||
)
|
||||
out = clean_srt_text(srt)
|
||||
assert "晚安" in out
|
||||
assert "明天见" in out
|
||||
assert out.count("-->") == 2
|
||||
|
||||
|
||||
def test_short_hallucination_preserved() -> None:
|
||||
"""2s 的'晚安'(可能为剧情真实对话)必须保留,不误删。"""
|
||||
entries = [
|
||||
_mk(720.0, 722.0, "晚安"),
|
||||
_mk(238.0, 240.0, "非常感谢您的观看。"),
|
||||
]
|
||||
out = mask_hallucination_text(entries)
|
||||
assert out[0]["text"] == "晚安"
|
||||
assert out[1]["text"] == "非常感谢您的观看。"
|
||||
|
||||
|
||||
def test_non_hallucination_always_preserved() -> None:
|
||||
def test_remove_non_hallucination_always_preserved() -> None:
|
||||
"""普通内容(即使很长)绝不能被当成寒暄幻觉处理。"""
|
||||
entries = [
|
||||
_mk(0.0, 30.0, "今天我将为您提供精神调适服务"),
|
||||
_mk(10.0, 40.0, "请尽量放松,无论多少次都能感到舒适愉悦"),
|
||||
]
|
||||
out = mask_hallucination_text(entries)
|
||||
assert out[0]["text"] == "今天我将为您提供精神调适服务"
|
||||
assert out[1]["text"] == "请尽量放松,无论多少次都能感到舒适愉悦"
|
||||
srt = (
|
||||
"1\n00:00:00,000 --> 00:00:25,000\n"
|
||||
"今天我将为您提供精神调适服务\n\n"
|
||||
)
|
||||
out = clean_srt_text(srt)
|
||||
assert "精神调适" in out
|
||||
|
||||
|
||||
def test_threshold_boundary() -> None:
|
||||
"""阈值边界:刚好 ≥ 阈值才清洗;< 阈值保留。"""
|
||||
entries = [
|
||||
_mk(0.0, 15.0, "晚安"), # 恰好 15s → 清洗(≥ threshold)
|
||||
_mk(0.0, 14.99, "晚安"), # 14.99s → 保留
|
||||
]
|
||||
out = mask_hallucination_text(entries, threshold_seconds=15.0)
|
||||
assert out[0]["text"] == "-"
|
||||
assert out[1]["text"] == "晚安"
|
||||
def test_remove_threshold_boundary() -> None:
|
||||
"""阈值边界:恰好 ≥ 阈值才删除;< 阈值保留。"""
|
||||
srt_ge = "1\n00:00:00,000 --> 00:00:15,000\n晚安\n\n"
|
||||
assert "晚安" not in clean_srt_text(srt_ge)
|
||||
srt_lt = "1\n00:00:00,000 --> 00:00:14,990\n晚安\n\n"
|
||||
assert "晚安" in clean_srt_text(srt_lt)
|
||||
|
||||
|
||||
def test_remove_resequences_numbers() -> None:
|
||||
"""删除中间 cue 后,剩余条目序号从 1 连续递增(合法 SRT)。"""
|
||||
srt = (
|
||||
"1\n00:00:00,000 --> 00:00:01,000\n甲\n\n"
|
||||
"2\n00:01:00,000 --> 00:01:30,000\n晚安\n\n" # 幻觉被删
|
||||
"3\n00:02:00,000 --> 00:02:01,000\n乙\n\n"
|
||||
"4\n00:03:00,000 --> 00:03:01,000\n丙\n\n"
|
||||
)
|
||||
out = clean_srt_text(srt)
|
||||
lines = [l for l in out.splitlines() if l.strip()]
|
||||
# 重新编号:序号应为 1,2,3 各一次。
|
||||
import re
|
||||
numbers = [int(l) for l in lines if re.fullmatch(r"\d+", l.strip())]
|
||||
assert numbers == [1, 2, 3]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 日语(ASR 直出)幻觉清洗 —— whisper 节点 decode_full 兜底用
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_jp_long_hallucination_removed() -> None:
|
||||
"""30s 的'おやすみなさい/ご視聴ありがとうございました'(无语音段幻觉)整条删除。"""
|
||||
srt = (
|
||||
"1\n00:00:00,000 --> 00:00:02,000\n気持ちいい\n\n"
|
||||
"2\n00:00:10,000 --> 00:00:40,000\nおやすみなさい\n\n"
|
||||
"3\n00:00:41,000 --> 00:01:11,000\nご視聴ありがとうございました\n\n"
|
||||
"4\n00:01:12,000 --> 00:01:14,000\nまた明日ね\n\n"
|
||||
)
|
||||
out = clean_japanese_hallucinations(srt)
|
||||
assert "おやすみなさい" not in out
|
||||
assert "ご視聴ありがとうございました" not in out
|
||||
assert "00:00:10,000 --> 00:00:40,000" not in out
|
||||
assert "気持ちいい" in out
|
||||
assert "また明日ね" in out
|
||||
|
||||
|
||||
def test_jp_short_hallucination_preserved() -> None:
|
||||
"""2s 的'おやすみなさい'(剧情真实道晚安)须保留,不误删。"""
|
||||
srt = (
|
||||
"1\n00:12:00,000 --> 00:12:02,000\nおやすみなさい\n\n"
|
||||
"2\n00:12:03,000 --> 00:12:06,000\nまた明日ね\n\n"
|
||||
)
|
||||
out = clean_japanese_hallucinations(srt)
|
||||
assert "おやすみなさい" in out
|
||||
assert "また明日ね" in out
|
||||
|
||||
|
||||
def test_jp_non_hallucination_always_preserved() -> None:
|
||||
"""普通长句(即使很长)绝不能被当成日语幻觉处理。"""
|
||||
srt = (
|
||||
"1\n00:00:00,000 --> 00:00:25,000\n"
|
||||
"今日はお客様のために精神整備を務めさせていただきます\n\n"
|
||||
)
|
||||
out = clean_japanese_hallucinations(srt)
|
||||
assert "精神整備" in out
|
||||
|
||||
|
||||
def test_jp_threshold_15s() -> None:
|
||||
"""日语清洗同样遵守 15s 时长阈值:15s 恰好删除,14.99s 保留。"""
|
||||
srt = "1\n00:00:00,000 --> 00:00:15,000\nおやすみなさい\n\n"
|
||||
assert "おやすみなさい" not in clean_japanese_hallucinations(srt)
|
||||
srt2 = "1\n00:00:00,000 --> 00:00:14,990\nおやすみなさい\n\n"
|
||||
assert "おやすみなさい" in clean_japanese_hallucinations(srt2)
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
def test_mask_does_not_mutate_input() -> None:
|
||||
"""清洗不得修改原始条目对象(纯函数约束)。"""
|
||||
entries = [_mk(60.0, 90.0, "晚安")]
|
||||
original_text = entries[0]["text"]
|
||||
mask_hallucination_text(entries)
|
||||
assert entries[0]["text"] == original_text
|
||||
def test_jp_middle_removal_resequences() -> None:
|
||||
"""删除中间日语幻觉后剩余条目重编号且文本/时间正确对应。"""
|
||||
srt = (
|
||||
"1\n00:00:00,000 --> 00:00:02,000\nあ\n\n"
|
||||
"2\n00:00:10,000 --> 00:00:40,000\nおやすみなさい\n\n" # 删
|
||||
"3\n00:00:41,000 --> 00:00:43,000\nい\n\n"
|
||||
)
|
||||
out = clean_japanese_hallucinations(srt)
|
||||
assert out.count("-->") == 2
|
||||
assert out.startswith("1\n00:00:00,000 --> 00:00:02,000\nあ")
|
||||
assert "2\n00:00:41,000 --> 00:00:43,000\nい" in out
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
"""学习资料转译工作流(learn-translate)加载与标注测试。
|
||||
|
||||
验证新增工作流 learn-translate.json:
|
||||
1. 能被 seed 正常加载入库(definition 通过 WorkflowDefinition 校验);
|
||||
2. 关键参数应用了本次修复(decode_full=true、vad_filter=false)且 params 内
|
||||
_note_* 说明键、_node_help 手册完整保留(不被模型/入库丢弃);
|
||||
3. 标注键(_note_*/_node_help)作为 params 传给节点时不影响节点执行——
|
||||
节点只读取它认识的参数键,多余的说明键被忽略(真实节点行为)。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from wov_app.db import Database
|
||||
from wov_app.seed import seed_default_workflows
|
||||
from wov_sdk.models import WorkflowDefinition
|
||||
|
||||
# 单体根目录:tests/ 的上一级。
|
||||
WORKSPACE = Path(__file__).resolve().parent.parent
|
||||
LEARN = WORKSPACE / "workflows" / "learn-translate.json"
|
||||
|
||||
|
||||
def test_learn_translate_seed_loads_and_applies_fix() -> None:
|
||||
"""验证 learn-translate 能被 seed 加载,asr 应用本次 decode_full 修复。"""
|
||||
db = Database(WORKSPACE / "data" / "wov_test.db")
|
||||
# 用临时目录避免污染真实 data
|
||||
import tempfile
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
db2 = Database(Path(tmp) / "wov.db")
|
||||
created = seed_default_workflows(db2)
|
||||
assert created >= 4 # demo/zh-direct/ocr-subtitle/learn-translate
|
||||
definition = db2.get_latest_workflow_version("learn-translate")["definition"]
|
||||
asr = next(node for node in definition["nodes"] if node["id"] == "asr")
|
||||
assert asr["params"]["decode_full"] is True
|
||||
assert asr["params"]["vad_filter"] is False
|
||||
assert asr["params"]["chunk_seconds"] == 60
|
||||
assert asr["params"]["condition_on_previous_text"] is False
|
||||
|
||||
|
||||
def test_learn_translate_notes_and_help_preserved() -> None:
|
||||
"""验证 _note_* 理由与 _node_help 手册在入库后完整保留。"""
|
||||
import tempfile
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
db = Database(Path(tmp) / "wov.db")
|
||||
seed_default_workflows(db)
|
||||
definition = db.get_latest_workflow_version("learn-translate")["definition"]
|
||||
# 每个节点都应有 _node_help;asr 每个参数都有 _note_。
|
||||
for node in definition["nodes"]:
|
||||
assert "_node_help" in node["params"], f"{node['id']} 缺 _node_help"
|
||||
assert node["params"]["_node_help"] # 非空
|
||||
asr = next(node for node in definition["nodes"] if node["id"] == "asr")
|
||||
for key in ("_note_decode_full", "_note_vad_filter", "_note_chunk_seconds",
|
||||
"_note_condition_on_previous_text", "_note_beam_size"):
|
||||
assert key in asr["params"], f"asr 缺 {key}"
|
||||
# 理由需含正例/反例关键字(说明确实举例)。
|
||||
assert "正例" in asr["params"]["_note_decode_full"]
|
||||
assert "反例" in asr["params"]["_note_decode_full"]
|
||||
|
||||
|
||||
def test_learn_translate_notes_do_not_break_execution(tmp_path, monkeypatch) -> None:
|
||||
"""验证带 _note_*/_node_help 的 params 传给 whisper 节点不影响执行。
|
||||
|
||||
节点只读取它认识的键(chunk_seconds/vad_filter/decode_full 等),
|
||||
额外的说明键被忽略;伪造模型确认 transcribe 收到的正是修复后参数。
|
||||
"""
|
||||
import sys
|
||||
import types
|
||||
from tests.test_nodes import FakeSegment, _install_fake_whisper, _whisper_request # 复用脚手架
|
||||
|
||||
captured = {}
|
||||
|
||||
class FullModel:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def transcribe(self, path, **kwargs):
|
||||
captured["decode_full_effect"] = (
|
||||
kwargs.get("vad_filter") is False and kwargs.get("vad_parameters") is None
|
||||
)
|
||||
return ([FakeSegment(0, 1, "ok")], None)
|
||||
|
||||
monkeypatch.setitem(
|
||||
sys.modules, "faster_whisper", types.SimpleNamespace(WhisperModel=lambda *a, **k: FullModel())
|
||||
)
|
||||
from nodes.whisper import invoke as whisper_invoke
|
||||
from wov_sdk.models import InvokeRequest
|
||||
import wave
|
||||
# 真实 WAV
|
||||
wav = tmp_path / "audio.wav"
|
||||
with wave.open(str(wav), "wb") as w:
|
||||
w.setnchannels(1); w.setsampwidth(2); w.setframerate(16000)
|
||||
w.writeframes(b"\x00\x00" * 16000)
|
||||
# 带 learn-translate 工作流 asr 的完整 params(含 _note_*/_node_help)
|
||||
learn = json.loads(LEARN.read_text(encoding="utf-8"))
|
||||
asr_params = next(n["params"] for n in learn["definition"]["nodes"] if n["id"] == "asr")
|
||||
resp = whisper_invoke(InvokeRequest(
|
||||
run_id="learn_test", node_instance_id="",
|
||||
inputs={"audio_uri": str(wav)},
|
||||
params=asr_params,
|
||||
output_dir=str(tmp_path / "out"),
|
||||
))
|
||||
assert resp.status == "completed"
|
||||
assert captured["decode_full_effect"] is True # decode_full 生效且说明键被忽略不报错
|
||||
content = Path(resp.outputs["srt_uri"]).read_text(encoding="utf-8")
|
||||
assert "ok" in content
|
||||
@@ -1426,3 +1426,76 @@ def test_vlm_truncate_at_stop() -> None:
|
||||
assert _truncate_at_stop("还有没有什么困扰 或者奇怪的地方吗") == (
|
||||
"还有没有什么困扰 或者奇怪的地方吗"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# decode_full:无 VAD 整段解码 + 日语幻觉清洗(TDD)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_whisper_decode_full_disables_vad(tmp_path, monkeypatch) -> None:
|
||||
"""decode_full=true 时 transcribe 收到 vad_filter=False 且不触发自动 VAD。"""
|
||||
captured = {}
|
||||
|
||||
class FullModel:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def transcribe(self, path, **kwargs):
|
||||
captured["vad_filter"] = kwargs.get("vad_filter")
|
||||
captured["vad_parameters"] = kwargs.get("vad_parameters")
|
||||
return ([FakeSegment(0, 1, "ok")], None)
|
||||
|
||||
monkeypatch.setitem(
|
||||
sys.modules, "faster_whisper", types.SimpleNamespace(WhisperModel=lambda *a, **k: FullModel())
|
||||
)
|
||||
_make_wav(tmp_path / "audio.wav", 5)
|
||||
# 默认 vad_filter=true(保持现状),显式 decode_full=true 强制无 VAD 整段解码。
|
||||
resp = whisper_invoke(
|
||||
_whisper_request(tmp_path, params={"language": "ja", "decode_full": True})
|
||||
)
|
||||
assert resp.status == "completed"
|
||||
assert captured["vad_filter"] is False
|
||||
assert captured["vad_parameters"] is None
|
||||
|
||||
|
||||
def test_whisper_decode_full_cleanup_jp_hallucination(tmp_path, monkeypatch) -> None:
|
||||
"""decode_full=true 时产物 SRT 中日文长时寒暄幻觉被**整条删除**(连带时间戳)。"""
|
||||
class HallucModel:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def transcribe(self, path, **kwargs):
|
||||
# 模拟无 VAD 解码:正常句 + 一条 30s '晚安'幻觉占位 + 一条 2s 真实晚安。
|
||||
return (
|
||||
[
|
||||
FakeSegment(0, 2, "気持ちいい"),
|
||||
FakeSegment(10, 40, "おやすみなさい"), # 30s 幻觉
|
||||
FakeSegment(45, 47, "おやすみなさい"), # 2s 真实
|
||||
],
|
||||
None,
|
||||
)
|
||||
|
||||
monkeypatch.setitem(
|
||||
sys.modules, "faster_whisper", types.SimpleNamespace(WhisperModel=lambda *a, **k: HallucModel())
|
||||
)
|
||||
_make_wav(tmp_path / "audio.wav", 5)
|
||||
resp = whisper_invoke(
|
||||
_whisper_request(tmp_path, params={"language": "ja", "decode_full": True})
|
||||
)
|
||||
assert resp.status == "completed"
|
||||
content = Path(resp.outputs["srt_uri"]).read_text(encoding="utf-8")
|
||||
# 30s 幻觉整条删除(时间轴 10-40s 不出现);2s 真实晚安与正常句保留。
|
||||
assert "気持ちいい" in content
|
||||
assert content.count("おやすみなさい") == 1
|
||||
assert "00:00:10,000 --> 00:00:40,000" not in content
|
||||
# 不残留 '-' 占位(时间轴里的 '-' 是 SRT 合法分隔符,只检查文本行)。
|
||||
text_lines = [
|
||||
l for l in content.splitlines()
|
||||
if l.strip() and not l.strip().isdigit() and "-->" not in l
|
||||
]
|
||||
assert all(t != "-" for t in text_lines)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+7
-2
@@ -17,10 +17,10 @@ WORKSPACE = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def test_seed_default_workflows_idempotent(tmp_path) -> None:
|
||||
"""验证从数据文件加载 demo/zh-direct 两个工作流且重复调用幂等。"""
|
||||
"""验证从数据文件加载 demo/zh-direct/ocr-subtitle/learn-translate 工作流且重复调用幂等。"""
|
||||
db = Database(tmp_path / "wov.db")
|
||||
created = seed_default_workflows(db)
|
||||
assert created == 3
|
||||
assert created == 4
|
||||
assert db.get_workflow("demo") is not None
|
||||
assert db.get_workflow("zh-direct") is not None
|
||||
|
||||
@@ -38,6 +38,11 @@ def test_seed_default_workflows_idempotent(tmp_path) -> None:
|
||||
assert zh_asr["params"]["model_path"] == "whisper-large-v2-translate-zh-v0.2-st-ct2"
|
||||
assert zh_asr["params"]["task"] == "translate"
|
||||
|
||||
# learn-translate:应用本次 decode_full 修复的新工作流。
|
||||
learn = db.get_latest_workflow_version("learn-translate")["definition"]
|
||||
learn_asr = next(node for node in learn["nodes"] if node["id"] == "asr")
|
||||
assert learn_asr["params"]["decode_full"] is True
|
||||
|
||||
# 再次调用不重复创建。
|
||||
assert seed_default_workflows(db) == 0
|
||||
assert len(db.list_workflow_versions("demo")) == 1
|
||||
|
||||
Reference in New Issue
Block a user