Skip to content

升级 llm-compat 到 v0.10.0:根治总结/校对被拒绝检测误判 - #60

Merged
zj1123581321 merged 4 commits into
mainfrom
card/VideoTranscriptAPI-20260822-01
Aug 22, 2026
Merged

升级 llm-compat 到 v0.10.0:根治总结/校对被拒绝检测误判#60
zj1123581321 merged 4 commits into
mainfrom
card/VideoTranscriptAPI-20260822-01

Conversation

@zj1123581321

Copy link
Copy Markdown
Collaborator

背景

生产上「内容总结」失败率从 7 月的 0.7%(3/451)涨到 8 月的 4.7%(14/300),校对层
calibration_status=partial 同期从 4.3%(11/256)涨到 7.9%(22/277)。

根因不在模型能力,在依赖库 llm-compat ≤v0.9.1 的拒绝检测:默认关键词表把「违反」
「无法提供」「涉及敏感」这类普通题材词当作拒绝信号,并且做的是全文无长度、无位置约束
的子串匹配
。任何正常长文只要正文里出现这些词,就被整条 content_fallbacks 降级链判为
「模型拒绝」,逐个模型试完后抛 All models refused

实测复现(绕过 llm-compat 直发同一网关、同一 prompt、同一模型):

http_status: 200 | model: deepseek-v4-pro | finish_reason: stop
refusal_field: None | content_len: 13402 | completion_tokens: 9294
REFUSAL_KEYWORD_HITS: {'违反': 4}
  context: '...严重违反诊疗技术规范或常规:严重程度需由鉴定部门评判...'

模型返回的是一篇 13402 字的完整总结,只因正文里出现 4 次「违反」被整条链丢弃。失败率
上升是因为 8 月起法律类内容(刑事律师在线咨询等)转录量变高,不是缺陷本身变化。

已向上游提 issue 并获修复:zlxlabs/llm-compat#22

改动

llm-compat v0.8.0v0.10.0。v0.10.0 把默认词表换成严格的拒绝言语行为正则(「第一人称

  • 情态否定 + 任务动词」),并要求命中位置在正文前 120 字内、全文不超过 300 字。

两处调用侧改动:

  1. 显式传 on_all_refused="raise"。v0.10.0 把这个默认值从 "raise" 改成了
    "return_best"(链耗尽时把「推断层候选里正文最长的那个」救回来返回)。本仓不采用该默认:
    我们有诚实状态模型(summary_status / calibration_status),链耗尽时宁可诚实标
    failed,也不能把一段疑似拒绝的正文当成总结写进数据库——那是本仓的 P1 红线「静默出错」。
    显式写死,不做成配置项。
  2. 删除死 import detect_provider。v0.9.0 把它的返回值从 str 改成 ProviderDetection
    (Breaking),本仓全仓只有这一处 import、无任何调用点,直接删掉。

未采用 v0.10.0 的其余新参数(refusal_keywords_mode / refusal_max_content_length /
refusal_head_window)——默认值已是本 PR 想要的行为,抄进调用方反而让日后上游调参失效。

config/*、prompt、processors、coordinator、api 均未改动。

跨版本 Breaking 核对

版本 Breaking 本仓影响
v0.9.0 detect_provider() 返回值 strProviderDetection 仅一处死 import,已删
v0.9.0 register_provider(..., caps=) 注册期全 schema 校验 未使用该 API
v0.9.0 build_request_payload 等新增 strict 关键字参数 有默认值,llm.py:792 调用不受影响
v0.9.1 新增 mimo-* provider family 纯新增
v0.10.0 默认词表 → 严格正则 + 前 120 字窗 + 全文 ≤300 字门槛 本 PR 要的修复,零配置生效
v0.10.0 on_all_refused 默认 raisereturn_best 已显式回填 "raise"
v0.10.0 ChatResult 新增 refusal_suspected / refusal_evidence 有默认值,存量 mock 无需跟进

测试

新增 tests/unit/test_refusal_contract.py:7 格不变式轴表(正文长度 × 拒绝句式形态),
调用真实的 llm_compat.refusal.detect_refusal,用 wire-format 响应 dict 驱动,
不 mock 被测库。

# 场景 期望
1 >300 字长文含「违反」(复刻生产语料特征) 非拒绝
2 >300 字长文含全部旧词表命中词 非拒绝
3 短中文句首拒绝 拒绝
4 短英文句首拒绝 拒绝
5 含转折的伪拒绝(「我不能协助…但可以…」) 非拒绝
6 finish_reason=content_filter(声明层) 拒绝
7 content=None + finish_reason=stop(畸形响应) 拒绝

另有两条:构造期 spy 断言 on_all_refused="raise" 确实传入 SyncLLMClient
终态异常链 ContentPolicyError → map_llm_compat_error → RetryableError 保持不变。

断言约束力已实证(不是恒真断言)——同一段语料在新旧两版下结果相反:

# v0.8.0(缺陷版本)
long_with_wei_fan:  len=478  detect_refusal=True
long_all_legacy:    len=1738 detect_refusal=True

# v0.10.0(本 PR)
9 passed

升级前该文件 3 红(两格长文误判 + 缺 on_all_refused),升级后 9 绿。

全量 uv run pytest tests/unit2769 passed

部署备注

合并后需重建 n305 镜像才在生产生效。

zj1123581321 and others added 3 commits August 22, 2026 20:00
在升级前锁定 7 格不变式轴表;长文含题材词与 on_all_refused 构造参数
在 v0.8.0 下预期失败,升级 v0.10.0 后应变绿。

Co-authored-by: Cursor <cursoragent@cursor.com>
修复拒绝检测对长文题材词的无约束子串误判(上游 issue #22)。

Co-authored-by: Cursor <cursoragent@cursor.com>
不使用 v0.10.0 默认 return_best,保持链耗尽时诚实标 failed;
删除未使用的 detect_provider import(v0.9.0 breaking)。

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

github-actions Bot commented Aug 22, 2026

Copy link
Copy Markdown

Required Gate v2 — 状态面板

当前状态:pass · 可合并

当前裁决:code_pass / primary_pass

Gate 历史(v1;来源为持久化 gate_terminal 制品)

Run Attempt Head 状态 收件人动作
32572354986 1 abe82fd pass 可合并
32573032258 1 6f5d680 pass 可合并

历史行按 run_id + run_attempt 去重并只增不删;删除本评论后可由 gate_terminal 制品重建。

@github-actions

github-actions Bot commented Aug 22, 2026

Copy link
Copy Markdown

⚙️ Review ledger state(机器状态记录,非评审结论)

这是 review ledger 的机器状态记录,不代表评审结论,通常无需任何操作。

机器状态明细
  • Commit: 6f5d680605d0aad4a33ae6034e31f7a5f4e19f87
  • Round: 2
  • Status / findings: pass / 0
  • Reviewer: codex-sub
  • Comparison: new_head; persistent/resolved/new = 0/1/0

完整数据保存在 codex-review-ledger-v2 artifact;此 sticky comment 仅保存 v2 epoch 的跨 rerun 连续游标。

gate 主审 finding testing-global-client-state-leak:
test_on_all_refused_raise_passed_at_construction 调真实 set_default_config,
把 mock 客户端写进 llm 模块的 _sync_client / _default_config 且不恢复。

已实证泄漏为真:临时关掉本 fixture 后,同会话内后置探针立刻断言失败
(global _sync_client leaked as MagicMock)。此前全量绿只是因为下游碰全局的
测试恰好都自己重设,把泄漏盖住了。

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>
Agent-Executor: cc
Agent-Session: 9dd95690-cd40-438a-aed2-eb0c1279caa7
Agent-Effort: xhigh
@zj1123581321
zj1123581321 merged commit 6773369 into main Aug 22, 2026
8 of 10 checks passed
@zj1123581321
zj1123581321 deleted the card/VideoTranscriptAPI-20260822-01 branch August 22, 2026 12:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant