Repository navigation
feat(judge): A1 裁判集扩到语法维度(动词位置/格的使用/时态 advisory)+ 闸门收窄(ADR-0023 §4.3 第④步) - #137
Merged
Merged
Conversation
按用户拍板的 A 方案,逐条闭环评审的三张 RED 与必须现在补的黄卡: - RED#1 接线缝缺失(dead-green:7 任务全绿而生产路径语法裁判恒不生效)⇒ Task 1 的 Files/Interfaces 显式登记接缝:processor.get_loaded_nlp()(只读、不触发加载/下载)+ _make_judge/_JudgeRecorder + 端点注入 judge= + 走端点的接线回归用例。 - RED#2 fail-closed 缺失(降级态仍可 converged:true)⇒ Task 1 落判据「converged==true MUST 蕴含所有已接线确定性层都真跑过」;降级时不返回裁决(非 dict 哨兵 ⇒ judge_unreliable/judge_error);禁止用塞人造 violation 实现。Global Constraints 新增该条与既有断言冲突裁决规则。 - RED#3 同步调用超时/取消无效 + SSE 不可达 except ⇒ 新增 Task 8(dispatch wait_for + judge to_thread + 修 error 帧语义)。 - 黄卡:Task 2 补 Step 0 并把官方 10 篇 0/10 误报基线(编排者已实测)落成用例;Task 6/7 补可证伪 Produces 签名;Task 7 补 tests/test_server.py 并改写那条三态全绿的 dead-green 回归闸;Task 7 立论冲突选 b(提示词按文本族参数化,由产品层装配)。 - Out of Scope 精确化:「不改内核结构」但允许经注入缝/可选参数改变裁判与提示词语义(与暂缓的 Pi 式重构不冲突,同向)。 - 新增 backlog 登记表(B1 输入静默截断 / B2 截断不可见 / B3 token 闸只算 completion / B4 services⇄tools 双向依赖与 import 期拉起被排除模块)。
实测依据:delector/nlp_engine/processor.py:192-198 —— Android 分支显式 nlp=None、不加载不下载 ⇒ 引擎恒 rules_only。若按评审原建议「降级即禁止收敛」,循环的 argmin 回退会返回原文 = ADR §10.3 记过的「用户点了润色拿回原文」回归(Android 上功能报废)。 故修正为:① 运行期 fail-open + judge_engine/judge_degraded 忠实透出 + 前端可见提示;② 声明/门禁层 fail-closed(判据表/发行说明在 judge_degraded==true 时不得当「全引擎零违规」的证据)。目标(没检查不得被当成检查通过)不变。
计划 docs/plans/2026-10-15-a1-judge-expansion.md 的 Task 1+2 合并交付(评审 RED#1 的直接结论:只改 writing.py 而不同批接线 = dead-green)。 - writing.py:新增 collect_violations(统一形状;新键 violations 优先 / legacy suggestions 回退且只取 level==error);analyze_a1_email 只增不改地追加 violations + judge_engine,并接线既有两件算力 detect_determiner_noun_agreement / detect_preposition_case(显式传 nlp,沿用 analyze_essay_text 惯例)。 - processor.py:新增只读 get_loaded_nlp() 与无下载 try_load_nlp_no_download()(逐个候选调 _load_spacy_model,绝不含 spacy.cli.download 兜底;成功写回既有缓存、失败不写任何全局)。 为什么需要第二个:只用只读版会让桌面首次润色也落在 rules_only(模型惰性加载,别处还未触发 ⇒ 引擎标记长期假降级);直接调既有 _resolve_nlp_model() 会走 download(出网 + pip 子进程)。 - main.py:_run_polish 先只读、再在 asyncio.to_thread 内本地加载,经 _make_judge/_JudgeRecorder 真注入 judge=;_PolishOutcome 追加 judge_engine;两个 payload 末尾追加 judge_degraded(只增不改)。 - 降级诚实性按计划分层承担:运行期 fail-open(实测 processor.py:192-198 的 Android 分支 nlp=None;若禁止收敛,argmin 回退会返回原文 = ADR §10.3 的功能报废回归),声明/门禁层 fail-closed(由 Task 7 判据表承担)。 - 退回一处越界:maker 一度把 _a1_violations_if_letter 改成按规则族分流(非书信体保留语法族)—— 那属 Task 7 的 HITL 产品行为变更(语法违规开始计数后,模型修不动的作文会回退原文),已退回旧语义并新增一条钉子钉住「本批不改该行为」(钉子先断言语法错真实存在,非真空)。 - pin 裁决:test_real_judge_is_wired_by_default 由「内核默认裁判 = analyze_a1_email」改造为「生产链路在装配点注入 A1 裁判」,属强化(能拦 dead-green)而非放宽。 - 官方 A1_SCHREIBEN_TEIL2 10 篇 sample_email 零违规硬门落成用例(基线由编排者实测 0/10)。 门禁:406 passed(5 个套件);ruff check . 全绿;mypy --strict delector 66 files 0;前端契约闸 PASS;CRLF。变异自证 5 组(含端点不传 judge= ⇒ 红、闸门改回分流 ⇒ 钉子红)。复核:CRV APPROVED(无红卡,4 黄卡不阻塞)。
评审黄卡:Global Constraints 要求「前端 MUST 给出可见提示」,但此前没有任何任务的 Files 覆盖 static/ ⇒ 该 MUST 无生产者;现挂到 Task 7 的 Files(非阻断提示,不是 error toast)。 Fog Unknown F:try_load_nlp_no_townload 不读 _android_runtime,Android 上模型缺失会每次重试本地加载(无记忆);且若 _load_spacy_model 的路径回退命中,Android 反而能拿到 rules+spacy —— 真机未验证,归属已暂缓的点检。
由来:双席位评审的 RED#3 与 AGENT-SDK-PATTERNS 模式 3 要点 4。**不改**循环的停止策略/预算/收敛归属(Task 4 的 27 条断言原样),只补三处缝: ① dispatch 超时(polish_loop.py):remaining_now 在调用前**重算**(不复用轮首那个——本轮 chat 已花时间),timeout = min(DEFAULT_PER_CALL_TIMEOUT_S, max(remaining_now, 0));asyncio.TimeoutError ⇒ errors-as-messages 喂回模型(不炸循环、不冒泡),收口仍由轮首预算前置检查给原因码。注意 3.11+ 里 asyncio.TimeoutError is TimeoutError,故工具自抛 TimeoutError 也会被归为执行超时—— wait_for 方案的固有代价,已接受并注释。 ② 同步裁判移出事件循环:新增 _call_judge,两处调用点(v_0 基线 + 每轮收尾候选)都走它;iscoroutinefunction 为真则直接 await(否则 to_thread 会把 coroutine 对象当返回值 ⇒ 静默假收敛)。收益:长裁判不再阻塞同进程 /api/health 与其它路由;task.cancel() 掐得断等待(线程本身掐不断是 asyncio 既有语义,docstring 已如实写明)。 ③ 修 main.py 的不可达分支:_run_polish 早把 LLMError 转成 HTTPException(502, detail=…),而 _polish_event_stream 写的 except llm_client.LLMError 永不命中 ⇒ error 帧永不发出、用户只拿到前端通用文案。选 (b) 流层捕 HTTPException 并发 detail 帧(理由:_run_polish 的失败契约对两种调用方保持同一形状;流已开头改不了状态码,error 帧是唯一能送达人话的通道;前端 writer.js 早已按 ev.event==='error'/ev.detail 消费)。 测试:+5 条(dispatch 超时喂回 / 心跳不被阻塞 / 取消可掐断在途裁判 / async 裁判须 await / SSE error 帧带 detail)。反挂死红线:所有 sleep 均有 wait_for/固定次数兜底,无 join、无无界 while;实测耗时 1.52/0.60/0.50/<0.005/0.04s。 评审 RED 卡的处理:CRV APPROVED(无红卡),其 1 条 flaky 黄卡已修(心跳窗口改跨调用取 max,余量从 ~0 提到 ~10 tick)并 20 连跑 PASS=20/20 + 变异自证(把 to_thread 改回同步直调 ⇒ 该用例红)。 门禁:411 passed(5 套件);ruff check . 全绿;mypy --strict delector 66 files 0;契约闸 PASS;CRLF。 编排者注:本批两次派单被基础设施掐掉(10003/10101),flaky 那条 1 行修理由编排者按 CRV 开方直接改并自证(偏离 maker-checker,已如实记录)。
新增三个确定性检测器并**同批接线**进 analyze_a1_email 的语法族分支(评审 RED#1 的教训:能力与接缝同批落地,否则 dead-green): - detect_subject_verb_agreement(numerus):只判「复数主语 + 单数动词」单向(反向第三人称单数形式不规则、corrected_form 不可靠 ⇒ 宁漏不误,docstring 已写明)。 - delector/data/a1_verb_case.py(29 条人工登记:Dat 7 / Acc 14 / Dat+Acc 8,禁 LLM 生成,逐分组来源注释)+ detect_verb_case_government(rektion):比对表面形式 vs 目标格(直判 morph.Case 会因同形歧义误报),且**只检查被支配的格位**(Acc 动词只看 oa 宾语 ⇒ 自由第三格合法句不报)。 - detect_verb_second_position(wortstellung):主句 V2 + 八条豁免(从句/疑问/祈使/并列/引语/无主语-多主语/逗号插入/换行 Vorfeld),主语 NP 内部修饰沿 head 链豁免(含焦点小词封闭词表:Auch/Nur/Sogar…;时间副词 MUST NOT 入表,否则负样本漏报)。 永久硬门加宽(两轮返工的产物):官方 10 篇 sample_email 门之外,新增 COMMON_CORRECT_SENTENCES(47 句)逐句断言语法族零违规 + judge_engine=='rules+spacy'(防降级假绿),清单上方附**类覆盖清单**(NP 内修饰/焦点小词/自由第三格/及物不及物两可/前置状语/从句前置/疑问祈使)。 返工记录(评审 + 编排者实跑各抓出一类误报,全部先写用例跑红再修): ① öffnen/bezahlen/schmeckt 移入 Dat+Acc 且改写不成立的 Acc 组断言; ② V2 把主语 NP 内部的副词修饰当独立前置成分(Meine sehr große Familie wohnt in Berlin.); ③ V2 漏豁免焦点小词(Auch meine Familie wohnt…/Nur mein Bruder kommt…)+ rektion 把自由第三格当违规(Ich lese meinem Bruder das Buch.)。 编排者另做 30 句常见正确德语广度扫雷(零误报)后定稿。 打包面:polish_loop/polish_tools 之后,a1_verb_case 也登记进 package_windows.py + build-release.yml 两块 + tests/test_server.py 的 data_dict_modules(守卫集合是硬编码的,不登记即永久失明)。 门禁:97 passed(本面四套件)+ 打包面守卫 6 passed;ruff 全绿;mypy --strict delector 67 files 0;CRLF。
新增 tools/bench_judge_fp.py + tests/test_judge_fp_bench.py:可复跑的确定性标定实验(只标定不接线、不裁决)。 - 正样本 = 官方 A1_SCHREIBEN_TEIL2 的 10 篇 sample_email(逐句 35 句);负样本 = 两维各 >=5 句手工定向错误句 + 正确对照组,逐句标注期望(否则 FP/FN 不可分)。 - 输出含误报/漏报样例全文(只报比例无法证伪判据);退出码 0=标定完成 / 1=样本集无效 / 2=环境不可用(import spacy 移入受护区 + probe_environment 单一归口,有测试钉住)。 - 实测数据:正样本两维 FP 均 0/35;A 维(情态框架)负样本 7 报 6 漏 1(漏的那句是「Ich weiß, dass ich muss heute arbeiten.」—— 实词夹在情态动词与末位动词之间的整族不可见,是该探测的结构性边界);B 维(过去锚+时态)7/7 全报、对照 0/6。按脚本阈值:A 维 error 档不成立、warning 档成疑;B 维两档均成立。 - 诚实性修正两轮:① 探测函数 _detect_modal_frame_order 原 docstring 称「逗号并列主句不会报」与实测相反,已如实分列漏报族/误报族;② 样本外追加实测的 3 句逗号并列正确句(实测报 True)原先只住 docstring,按评审要求搬进产物:verdict 文本(text 与 --json 同源)+ 独立区块「样本外追补(不入计数)」+ 模块常量落盘(原文可检索);并写明「真实文本 FP 不得以本表 0 外推;若计入对照组则 warning 档亦不成立」。冻结计数与阈值常量未动。 门禁:tests 10 passed;ruff 全绿;mypy --strict tools/bench_judge_fp.py 与 --follow-imports=skip 均 0;CRLF。 裁决(接线为 error / 降级 warning / 不做)属 Task 6 的 HITL 步骤,待用户拍板。 注:本条消息曾因 shell 反引号替换丢失两处正文,已就地重建(本地未推送提交,仅改消息)。
Task 6 收尾(按用户 2026-10-15 HITL 裁决:A 维不做、B 维接线为 warning): - 移植 tools/bench_judge_fp.py 已验证的 B 维探测为 writing.detect_past_anchor_tense_conflict(severity=warning、error_type=tempus;冻结脚本未动)。 - 按 level 路由接线:error ⇒ violations(既有语义),warning ⇒ 新增并列键 advisories —— collect_violations 一字符未动(既有 pin 要求 violations 保留格式族 warning)。 - advisory 可见化(否则是 no-op):端点 _JudgeRecorder 扩为同时接住 judge_engine 与 advisories;响应体 result 追加 judge_advisories(只增不改);前端在 overlay notes 区非阻断呈现。 - 不阻断收敛有用例钉住:带 advisory 与去掉该句时 (error_count, converged, reason) 逐字段相同。 - A 维未接线(全仓唯一实现仍是冻结的标定记录),符合裁决。 Task 7 技术面: - 闸门按规则族分流(main.py violations_for(kind, verdict) + _text_kind + _polish_violations_of):格式/字数族(a1_ 前缀)仅书信体;语法族(artikel/kasus/praeposition/numerus/rektion/wortstellung + tempus)对任意德语文本生效。 - 非书信文本的产品语义变更(已写进三处 docstring 并有用例钉住):模型修不动 ⇒ no_improvement ⇒ argmin 回退原文 + 前端人话原因码。 - 提示词按文本族参数化:run_polish_loop 只加一个可选参数 system_prompt(None ⇒ A1 默认,向后兼容;停止策略/预算/收敛归属一律不动);pick_system_prompt:书信 ⇒ 内核 A1 逐字,散文 ⇒ 产品层 POLISH_PROSE_SYSTEM_PROMPT(删 A1 自查指令、保留 JSON 契约与逐句/顺序约束)。 - 前端非阻断提示:judge_degraded 与 judge_advisories 在 overlay notes 区呈现(不是 error toast)。 - 死绿守卫改写:三态全绿的 test_ai_polish_non_letter_text_is_not_rolled_back 改为两条能区分行为的断言;goethe 侧旧钉子按新语义显式翻转命名(断言零放宽)。 黄卡清理(独立验收 4 条,全部非阻断):① _text_kind docstring 登记 greeting.valid 判据的三处边界后果(含"书信缺称呼语 ⇒ 格式族永远不被计数"这一继承边界与候选侧翻转欠账);② _JudgeRecorder docstring 收窄(argmin 回退路径下 advisories 可能与出口文本不一致,如实登记);③ 前端降级提示在 no-key 路径抑制(原先"本次仅做格式/字数检查"在循环根本没跑时不实);④ analyze_a1_email docstring 登记 warning 按族分居两键。 门禁:450 passed(4 套件,含硬门全绿);ruff 全绿;mypy --strict delector 67 files 0;契约闸 PASS;CRLF。 注:本批 maker 会话在报告前被 API 掐断(改动已落盘);4 条黄卡由编排者逐条核到行号并复跑门禁后代为提交。批内另有一条既有网络型 flaky(test_get_audio_tts_returns_mp3 走 Edge TTS,单独跑绿)已排除。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概要
抬掉 ADR-0023 §4.3 的门槛(第 ④ 步:把裁判集从 6 条书信格式/字数浅规则扩到 A1 真实评分维度的语法层)。计划正本:
docs/plans/2026-10-15-a1-judge-expansion.md(8 任务,台账 8/8,全程 maker-checker 双席位)。88856abjudge=+ 无下载的本地模型加载)+ 官方 10 篇范文零违规硬门39e6d79numerus(主谓一致)/rektion(动词支配格,29 条人工登记词表)/wortstellung(主句 V2 + 8 豁免);+ 加宽后的 51 句永久硬门209cc24cf9ea76deede44dispatch超时、同步裁判移出事件循环、修 SSE 那个永不命中的except为什么先做这件事(评审给出的物理证据)
开工前按
01-Rules/LAYERED-ARCHITECTURE.md(Pi-Agent 三层)+AGENT-SDK-PATTERNS做了双席位评审,最重要的发现是 RED#1 dead-green:计划 7 个任务里没有任何一个写"谁把nlp交给裁判",而_run_polish从不传judge=、Out-of-Scope 又禁改内核 ⇒ 语法裁判在生产恒不生效,而 7 个任务的测试仍可全绿、§4.3 判据表还能被打勾解除。因此把 Task 1/2 合并为原子变更(能力与接缝同批落地),并新增端点级回归用例;变异自证里"端点不传judge="会让用例红。本 PR 最有价值的产出:三次"误报类"返工 + 硬门加宽
Ich öffne meinem Bruder die Tür./Ich bezahle meinem Vater das Essen./Ich schmecke den Wein.全被报错Meine sehr große Familie wohnt in Berlin.Auch meine Familie wohnt in Berlin./Nur mein Bruder kommt heute./Ich lese meinem Bruder das Buch.三次都先写成用例跑红再修(不是改断言)。官方 10 篇范文门放过了全部三类 ⇒ 硬门从"10 篇范文"扩到
COMMON_CORRECT_SENTENCES(实测 51 句 + 类覆盖清单 +judge_engine=='rules+spacy'防降级假绿),编排者另做 30 句常见正确德语广度扫雷 FP=0 后定稿。HITL 裁决(用户拍板)
tools/bench_judge_fp.py作记录。judge_advisories+ 前端非阻断提示。§4.3 解除判据表(完整表见计划;签批归用户)
DEEPSEEK_API_KEY)建议:只允许限定表述(如「裁判已扩到 A1 语法维度:动词位置 + 格的使用三规则 error 级、时态一致性 advisory;语序经实测评审判定不做」),不建议解禁 §4.3 原文所禁的无条件"已实现"。
门禁
已知边界(随判据表登记,非遗漏)
① 书信缺称呼语 ⇒ 判据
greeting.valid归 prose ⇒ 格式族(含a1_greeting_missing)永远不被计数(继承自旧闸门);② 候选侧 kind 逐候选翻转(旧欠账未收口);③ 非书信文本模型修不动 ⇒no_improvement⇒ 回退原文(有断言 + 三处 docstring + 前端人话原因码 —— 这是本 PR 最值得复看的产品语义变更);④ advisory 在 argmin 回退路径下可能与出口文本不对应(不进任何计数);⑤ warning 按族分居violations/advisories两键。过程记账
39e6d79提交信息里的「47 句」是未实测口径,实测为 51 句(以本 PR 与计划为准)。209cc24的消息曾因 shell 反引号替换丢正文,已用 heredoc 重建(本地未推送时完成,消息尾部注明)。