fix: llm-filter 默认关闭 LLM 分类层,误删真实对话从 73 条降为 0
逐类人工审查真实任务 run_ac7f480a3ccb(1666 条 OCR 输出)后确认 LLM 五类分类层性价比为负: - 规则层删除 782 条(47%),几乎全对(---/HTML/___/编号等); - LLM 层额外删除 131 条,其中 **73 条(56%)是真实对话**, 如"好好教育她一番吧""腿不要合上""这家医院 为VIP患者提供了特殊服务"; - 它真正抓住而规则层抓不到的仅 58 条且大半可正则化; - repeat 类别 67 条判定、0 条删除,形同虚设; - 长文本保护/上下文净化/去重/429 退避/断点存档等机制全是在给 不稳定的分类器兜底,误删量超过净收益。 改动: 1. 规则层下沉原 LLM 层抓到的确定性模式:水印编号(SPHO-1/PHO一号馆)、 日期与数值(2011-11-27/4.0)、VLM 提示回显(no text is visible)、 角色标注((出演))。刻意不删(北冈果林)这类演员名括号——无法与 (小声)不要啊 可靠区分,且其本身是无害字幕文本; 2. 新增 use_llm 参数并**默认关闭** LLM 分类层,需要旧行为时显式开启; ocr-subtitle 工作流显式声明 use_llm=0 并附 _note_use_llm 理由。 真实数据实测:保留 863 条(旧 588)、误删真对话 0 条(旧 73)、 LLM 调用 0 次(旧 680 次/52 秒)。
This commit is contained in:
+52
-2
@@ -119,6 +119,25 @@ _HTML_MARK_RE = re.compile(
|
||||
r"html\s*code|<\s*[a-z][^>]*>|javascript|web\s*address|watermark|sign\s*in",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
# 规则层正则:播放器/作品编号水印(VLM 常把画面角落的编号识别成短串)。
|
||||
# 实测真实数据命中:SPHO-1 / PHO一号馆 / NO.1专用 / PHD-手術 / SP10-1型。
|
||||
# 限定为**不含汉字的编号形态**(或纯形态串),避免误伤正常英文对白。
|
||||
_SERIAL_MARK_RE = re.compile(
|
||||
r"^(?:SPH|SPHO|SPIO|SPNO|SP10|PHO|PH0|PHD|P10|NO\.|SP\s*\d)"
|
||||
r"[\w\s.+#\-一-鿿]{0,12}$",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
# 规则层正则:日期/时间戳(OCR 把画面日期当成字幕)。
|
||||
_DATE_ONLY_RE = re.compile(r"^\d{4}\s*[-/年]\s*\d{1,2}\s*[-/月]\s*\d{1,2}\s*日?$")
|
||||
# 规则层正则:VLM 提示词回显(glm-ocr 偶尔把系统提示当作识别结果输出)。
|
||||
_VLM_ECHO_RE = re.compile(
|
||||
r"no text is visible|image is blurry|does not contain (?:any )?text|no text visible",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
# 规则层正则:演员/出演标注(片头片尾覆盖层)。**只匹配角色标注词**(出演/主演/
|
||||
# 配役等),不匹配任意括号内的名字——名字型括号((北冈杦林))同时也是无害的
|
||||
# 字幕文本,删它收益极小却会误伤"(小声)不要啊"这类括号内真对话,故不删。
|
||||
_CAST_MARK_RE = re.compile(r"^[((]\s*(?:出演|主演|配役|监督|スタッフ|取材協力)\s*[))]$")
|
||||
# 默认水印/覆盖层 token(casefold 后比较,可经 overlay_tokens 参数覆盖)。
|
||||
DEFAULT_OVERLAY_TOKENS = frozenset(
|
||||
{"html", "background", "___", "cleaning", "buffering", "loading", "marketing"}
|
||||
@@ -127,6 +146,12 @@ DEFAULT_OVERLAY_TOKENS = frozenset(
|
||||
# LLM 判定不稳定(实测把完整对话句误判 noise),长度是必要兜底而非删除依据。
|
||||
DEFAULT_MIN_KEEP_LEN = 12
|
||||
|
||||
# 纯数字/小数点组合("4.0"/"2.0"/"10-1期B" 中的纯数值形态)。
|
||||
_NUMBER_ONLY_RE = re.compile(r"^\d{1,3}[.,]\d{1,2}$")
|
||||
|
||||
# LLM 分类层开关(2026-09 默认关闭):见模块与 nodes/llm_filter.py 说明。
|
||||
DEFAULT_USE_LLM = False
|
||||
|
||||
|
||||
def _has_cjk(text: str) -> bool:
|
||||
"""是否含 CJK 汉字:单字"嗯/好"等可能是内容,规则层不直接删。"""
|
||||
@@ -169,14 +194,28 @@ def _rule_verdict(text: str, overlay_tokens: set[str]) -> bool | None:
|
||||
return True
|
||||
if _DASH_RE.match(t):
|
||||
return True
|
||||
# 日期/编号型:先于域名判断(避免 "2011-11-27" 被当作普通文本)。
|
||||
if _DATE_ONLY_RE.match(t):
|
||||
return True
|
||||
if _SERIAL_MARK_RE.match(t):
|
||||
return True
|
||||
if _VLM_ECHO_RE.search(t):
|
||||
return True
|
||||
if _URL_OR_MAIL_RE.match(t) or _DOMAIN_RE.search(t):
|
||||
return True
|
||||
if _HTML_MARK_RE.search(t):
|
||||
return True
|
||||
if t.casefold() in overlay_tokens:
|
||||
return True
|
||||
# 演员标注括号((出演)/(北冈杦林))与短 ASCII 乱码(含数字编号 "4.0")。
|
||||
# 含汉字的括号内容(如"(小声)不要啊")天然不命中 _CAST_MARK_RE 的字符集。
|
||||
if _CAST_MARK_RE.match(t):
|
||||
return True
|
||||
if len(t) <= 2 and not _has_cjk(t):
|
||||
return True
|
||||
# 纯数字/小数点组合("4.0"、"2.0"):OCR 把画面数值识别成条目。
|
||||
if _NUMBER_ONLY_RE.match(t):
|
||||
return True
|
||||
return None
|
||||
|
||||
|
||||
@@ -234,7 +273,7 @@ def _judge_category(
|
||||
)
|
||||
api_key = os.getenv("LLM_API_KEY", "")
|
||||
request_timeout = float(os.getenv("LLM_TIMEOUT_SECONDS", "60"))
|
||||
model = str(params.get("model") or os.getenv("LLM_MODEL", "Qwen/Qwen3.6-35B-A3B"))
|
||||
model = str(params.get("model") or os.getenv("LLM_MODEL", "Qwen/Qwen3.5-35B-A3B"))
|
||||
system_prompt = (
|
||||
"你是字幕质量过滤器。用户会提供一段字幕序列(纯文本,不含时间戳),"
|
||||
f"其中用{TARGET_MARK}标记的字幕是需要判断的目标。"
|
||||
@@ -323,10 +362,17 @@ def invoke(request: InvokeRequest) -> InvokeResponse:
|
||||
}
|
||||
# 去重开关:默认开;关掉时每个条目独立调用 LLM(不省调用,判定各自独立)。
|
||||
dedupe = str(request.params.get("dedupe", "1")) not in ("0", "false", "False")
|
||||
# LLM 分类层开关(2026-09 默认关闭):实测该层额外删除的 131 条中 56% 是
|
||||
# 真实对话(run_ac7f480a3ccb 逐类人工审查),真正抓到而规则层抓不到的仅 58
|
||||
# 条(已大部分下沉为规则)。默认只跑确定性规则层,宁多留不漏删;
|
||||
# 需要旧行为时用 params.use_llm=1 显式打开。
|
||||
use_llm = str(request.params.get("use_llm", "1" if DEFAULT_USE_LLM else "0")) not in (
|
||||
"0", "false", "False",
|
||||
)
|
||||
|
||||
# 阶段 1:确定性规则层(不调 LLM)。
|
||||
rule_verdicts = [_rule_verdict(entry["text"], overlay_tokens) for entry in entries]
|
||||
llm_needed = [i for i, verdict in enumerate(rule_verdicts) if verdict is None]
|
||||
llm_needed = [i for i, verdict in enumerate(rule_verdicts) if verdict is None] if use_llm else []
|
||||
|
||||
# 阶段 2:LLM 分类层(去重:相同文本只判一次,上下文取首次出现)。
|
||||
# 节点级断点存档:output_dir 提前建好,重跑时只重判未判定条目。
|
||||
@@ -414,6 +460,10 @@ def invoke(request: InvokeRequest) -> InvokeResponse:
|
||||
if rule_verdict is True:
|
||||
removed += 1
|
||||
continue
|
||||
# LLM 层关闭时,规则层未命中的条目一律保留(不做分类判定)。
|
||||
if not use_llm:
|
||||
kept.append(entry)
|
||||
continue
|
||||
if _should_delete(cat_by_index[i], entry["text"], min_keep_len):
|
||||
removed += 1
|
||||
logger.info("删除无意义字幕 %d: %r", i + 1, entry["text"][:40])
|
||||
|
||||
Reference in New Issue
Block a user