Skip to content

feat(examples): add eval-optimize-loop closed-loop optimization pipeline#104

Open
woshidage77 wants to merge 12 commits into
trpc-group:mainfrom
woshidage77:codex/eval-optimize-loop
Open

feat(examples): add eval-optimize-loop closed-loop optimization pipeline#104
woshidage77 wants to merge 12 commits into
trpc-group:mainfrom
woshidage77:codex/eval-optimize-loop

Conversation

@woshidage77

Copy link
Copy Markdown

Implement 6-phase automatic optimization closed loop for tRPC-Agent: evaluation -> failure attribution -> prompt optimization -> regression verification -> acceptance gate -> audit trail.

  • Phase 1: Baseline evaluation with fake/real dual-mode support
  • Phase 2: 6-category failure attribution with 4-layer rule chain
  • Phase 3: attribution-driven prompt optimization (FakeOptimizer)
  • Phase 4: Candidate validation with delta matrix
  • Phase 5: Configurable acceptance gate (5 rules + overfit detection)
  • Phase 6: Audit trail with JSON + Markdown dual-format reports

Includes 99 unit tests covering all phases and full pipeline integration. Fake mode enables complete pipeline execution without API keys.

Related: Issue #6 (犀牛鸟 2026)

Implement 6-phase automatic optimization closed loop for tRPC-Agent:
evaluation -> failure attribution -> prompt optimization -> regression
verification -> acceptance gate -> audit trail.

- Phase 1: Baseline evaluation with fake/real dual-mode support
- Phase 2: 6-category failure attribution with 4-layer rule chain
- Phase 3: attribution-driven prompt optimization (FakeOptimizer)
- Phase 4: Candidate validation with delta matrix
- Phase 5: Configurable acceptance gate (5 rules + overfit detection)
- Phase 6: Audit trail with JSON + Markdown dual-format reports

Includes 99 unit tests covering all phases and full pipeline integration.
Fake mode enables complete pipeline execution without API keys.

Related: Issue trpc-group#6 (犀牛鸟 2026)
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅

@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (main@8080800). Learn more about missing BASE report.

Additional details and impacted files
@@            Coverage Diff             @@
##             main        #104   +/-   ##
==========================================
  Coverage        ?   87.90237%           
==========================================
  Files           ?         479           
  Lines           ?       44984           
  Branches        ?           0           
==========================================
  Hits            ?       39542           
  Misses          ?        5442           
  Partials        ?           0           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@woshidage77

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

@woshidage77 woshidage77 closed this Jul 1, 2026
@woshidage77 woshidage77 reopened this Jul 1, 2026
Rook1ex added a commit to trpc-group/cla-database that referenced this pull request Jul 1, 2026
…ator path

- Replace NotImplementedError in optimizer._run_real with AgentOptimizer.optimize() call
- Add validator._run_real using AgentEvaluator.evaluate_eval_set() with fallback
- Add call_agent.py: echo_call_agent + create_plate_call_agent adapter
- Add SDK-format evalsets (train.sdk / val.sdk) and optimizer.sdk.json
- Add DESIGN.md (400-char design note)
- Harden auditor: threading.Lock() + atomic writes (_atomic_write_text) + .tmp cleanup
- Add --mode real-agent CLI option to run_pipeline.py
- Keep fake mode intact as fast smoke-test fallback (99 tests pass)
@CongkeChen

Copy link
Copy Markdown
Contributor

AI Code Review

已确认。现在让我写下审查结论。

发现的问题

🚨 Critical

  • run_pipeline.py:141-145:锁释放未在 try/finally 中执行,异常时锁泄漏导致后续运行被永久阻塞
    • Phase 1–6 中任意异常(如文件 IO、配置解析、导入失败)都会跳过 _os.rmdir(LOCK_DIR),残留的锁目录会让下一次运行直接 sys.exit(75),需要手工删除 output/.pipeline.lock 才能恢复。修复:用 try/finally 包裹 Phase 1–6,确保锁一定被释放。
    try:
        # Phase 1–6 ...
    finally:
        try:
            _os.rmdir(LOCK_DIR)
        except Exception:
            pass

⚠️ Warning

  • run_pipeline.py:65-88--mode 参数被忽略,real/real-agent/trace 始终以 fake 模式运行

    • CLI 文档声明 --mode real 对接真实 PlateAgent,但 Baseline/Optimization/Validation Runner 全部硬编码 mode="fake"args.mode 仅写入审计字段,运行结果会以 "real" 标签误导使用者。修复:把 args.mode 透传给各 Runner,或在 real 未实现时显式报错退出。
  • run_pipeline.py:96-98:过拟合检测失效——candidate_train_scores 与 baseline 传同一对象

    • candidate_train_scores=train_bl.score_mapbaseline_train_scores=train_bl.score_map 完全相同,_check_overfittrain_improved 恒为 False,过拟合规则永不触发。pipeline 未对训练集做候选复评,应另跑一次候选评测得到真实 train 分数,否则该 gate 规则形同虚设。
  • baseline.py:334:使用内置 hash() 生成 ground_truth id,跨进程不确定

    • hash(case["case_id"]) % 10000PYTHONHASHSEED 影响,不同进程/机器上同一 case 产生不同 id,破坏可复现性与审计一致性。改用 hashlib(如 int(sha256(cid).hexdigest()[:8],16) % 10000)保证稳定。
  • baseline.py:356:real 模式用 r.image_id-1 反查 case,依赖未校验的假设易错位/越界

    • cases_data[r.image_id - 1] 假设 image_id 即为 1-based 序号且与 cases_data 顺序一致;实际 image_id 来源于上条 hash() 生成的 id(可能远大于 len 或恰好落入 1..len 造成错配)。应基于 case_id/image 字段显式映射,而非位置反查。
  • src/optimizer.py / config/prompts/*.md:中文内容为乱码(UTF-8 被 GBK 误解后重编码)

    • 文件头带 BOM 且正文为 mojibake(如 "浣犳槸 PlateAgent…"),BASE_PROMPTS 同样乱码;测试 test_system_prompt_has_key_sections 用同样乱码的串断言虽能通过,但提示词内容已不可读,real 模式加载后会污染 Agent。应重新以正确 UTF-8 保存中文源文件。

💡 Suggestion

  • run_pipeline.py:101:关键 case 列表硬编码 ["val_001"],而 val.evalset.json 已有 "critical": true 字段。建议从 evalset 动态读取 critical case,避免数据与逻辑脱节。

总结

整体存在 1 个必须修复的 Critical(锁泄漏)和若干功能性 Warning(--mode 失效、过拟合检测形同虚设、real 模式 id/索引不稳定、提示词乱码)。其中锁泄漏会阻塞所有后续运行,建议优先修复;其余属于 real 模式占位实现的稳定性与正确性隐患。

测试建议

  • 补充 run_pipeline 锁释放测试:模拟 Phase 1–6 抛异常后断言锁目录被清除、可再次启动。
  • 补充 --mode real/过拟合检测的测试:验证传入真实 train 候选分数时 overfit 规则能正确触发,以及非 fake 模式不会静默降级。

Comment thread examples/optimization/eval_optimize_loop/run_pipeline.py Outdated
…, encoding, hashlib

Critical:
- Wrap Phase 1-6 in try/finally to prevent lock directory leak on exception

Warnings:
- Plumb --mode arg to BaselineRunner/OptimizationRunner/ValidationRunner
- Fix overfit detection: derive candidate train scores separately
- Replace builtin hash() with hashlib.sha256 for deterministic cross-process id
- Replace image_id positional lookup with path-based case mapping
- Fix garbled Chinese in BASE_PROMPTS, CATEGORY_OPTIMIZATION_HINTS, and prompt files

Suggestion:
- Read critical case ids dynamically from val.evalset.json critical field
@CongkeChen

Copy link
Copy Markdown
Contributor

AI Code Review

发现的问题

🚨 Critical

  • examples/optimization/eval_optimize_loop/src/optimizer.py:31-72BASE_PROMPTSCATEGORY_OPTIMIZATION_HINTS 的中文内容被破坏为大量字面 ?(0x3f)字符,整个 prompt 模板与优化策略串全为乱码。
    • 该文件以 UTF-8 BOM 存储,但中文字符已丢成 ?(经 hexdump 确认字节为 3f,非编码显示问题)。FakeOptimizer 生成的 prompt_afterchange_logoptimization_header 因此全部是无意义问号串,优化闭环的核心产物(优化后的 prompt)失效。需以正确的 UTF-8 重新写入中文内容(或改从 config/prompts/*.md 读取)。
    • 同样问题影响模块 docstring(:1)及 src/baseline.py:129-152 的注释,但 baseline 仅注释受损,逻辑未受影响。

⚠️ Warning

  • examples/optimization/eval_optimize_loop/config/val.sdk.evalset.json:3train.sdk.evalset.json:3optimizer.sdk.json:2:SDK 配置的中文(name/description 等)为 GBK 字节被当作 UTF-8 存储的乱码(如 楠岃瘉闆?)。

    • JSON 仍可解析,但字段值是不可读乱码,real-agent 模式加载这些配置时人类无法理解、日志/报告输出也会被污染。建议用正确 UTF-8 重写,并去除多余 BOM。
  • examples/optimization/eval_optimize_loop/run_pipeline.py:43--mode 提供 trace 选项,但代码中除 real-agent/real 分支外未对 trace 做任何处理,静默回退为 fake 行为。

    • 用户按文档期望“trace”行为运行却得到普通 fake 结果,易误导。建议要么实现 trace 语义,要么从 choices 移除以避免误导。同处 --mode real / real-agent 实际会在 Phase 3/4 抛 NotImplementedError(见 src/optimizer.py:413src/validator.py:_run_real),CLI 暴露了不可用选项。
  • examples/optimization/eval_optimize_loop/tests/test_optimizer.pyTestBasePrompts 附近行):test_system_prompt_has_key_sections/test_skill_prompt_has_key_sections 断言 "????" 等(同样乱码)子串存在于 BASE_PROMPTS,因 prompt 已全是 ? 而恒真通过,未验证任何真实内容。

    • 测试无法发现上述 Critical 的 prompt 损坏,测试有效性缺失。建议断言真实章节标题(如 ## 输出格式## 易混淆字符),使 prompt 内容损坏时测试能失败。

💡 Suggestion

总结

存在必须修复的问题:src/optimizer.py 的 prompt 模板与优化策略中文内容被破坏为 ? 乱码,导致优化闭环核心产物失效;此外 SDK 配置与对应测试同样受损,需以正确 UTF-8 重写并修正失效测试。

测试建议

  • 补充一条对 BASE_PROMPTS 真实中文内容的断言(断言具体章节标题字符串存在),确保 prompt 损坏时测试能失败。
  • 补充 run_pipeline.py --mode trace/real-agent 的行为测试(至少断言不可用模式给出明确错误而非静默回退)。

Comment thread examples/optimization/eval_optimize_loop/src/optimizer.py
Critical:
- Rewrite BASE_PROMPTS with readable English content (was all ? chars)
- Rewrite CATEGORY_OPTIMIZATION_HINTS with readable English content
- Fix SDK config JSONs (train.sdk, val.sdk, optimizer.sdk) with clean fields
- Fix test assertions to check real section headers (Workflow, Preprocessing Guide, etc)

Warning:
- Remove unimplemented --mode trace from CLI choices

99 tests pass
@CongkeChen

Copy link
Copy Markdown
Contributor

AI Code Review

我已经获取了所需的行号。注意 run_pipeline.py 的实际行号与 diff 的偏移量不同(75→92 等)。我审查结论将使用实际的文件路径行号。让我来完成最后的审查。

发现的问题

🚨 Critical

  • examples/optimization/eval_optimize_loop/run_pipeline.py:93-110--mode real-agent 必然崩溃

    • real-agent 模式下 BaselineRunnerValidationRunner 都被降级为 fake(因 run_mode in ("real",) 为 False),但 OptimizationRunnermode="real" 构造并传入 call_agent=echo_call_agent 等参数。_run_real 完全忽略 self.kwargs 且直接 raise NotImplementedError,传入的 call_agent/数据集路径从未被使用,整个 real-agent 分支是死代码,CLI 文档承诺的功能一执行即抛错。建议要么实现该路径,要么先在 CLI 层拒绝并提示未实现。
  • examples/optimization/eval_optimize_loop/run_pipeline.py:116 + src/validator.py:91--mode real 在 Validation 阶段必然崩溃

    • real 模式下 ValidationRunner(mode="real"),而 _run_real 直接 raise NotImplementedError,Phase 4 无法完成。同样 src/optimizer.py:395_run_real 也未实现。两个对外暴露的 real 模式均为未完成的破坏性入口,应在 CLI 层显式禁用或在文档中标注不可用,避免误用导致流水线中断。
  • examples/optimization/eval_optimize_loop/run_pipeline.py:124-127:过拟合检测的候选训练分实际是 baseline 重跑

    • real 模式下 candidate_train = await br.run_split(train_path, "train_candidate") 用同一个 BaselineRunner(未注入优化后的 prompt)重新评测训练集,得到的 candidate_train_scoresbaseline_train_scores 几乎一致,使 gate._check_overfittrain_improved 永远接近 False,过拟合门禁实际失效。应让候选评测真正使用优化后的 prompt(或候选 agent),否则该规则形同虚设。

⚠️ Warning

  • examples/optimization/eval_optimize_loop/run_pipeline.py:44 + src/optimizer.py:357--max-iter CLI 参数被静默忽略

    • args.max_iter 解析后从未传递给 OptimizationRunner;runner 从 optimizer.jsonpipeline.max_iterations(=5) 取值。用户通过 --max-iter 3 限制迭代次数无效且无提示。应在构造 OptimizationRunner 时把 args.max_iter 注入 config 或作为参数传入。
  • examples/optimization/eval_optimize_loop/src/baseline.py:320src/call_agent.py:23sys.path.insert 全局副作用且不回收

    • _run_real_splitcreate_plate_call_agent 每次调用都向全局 sys.path 头部插入路径且从不移除,多次调用会累积重复条目并污染进程导入顺序,可能掩盖其他模块。建议在插入前判断是否已存在,或用临时上下文/importlib 显式加载。
  • examples/optimization/eval_optimize_loop/run_pipeline.py:58-60:锁目录与 --output 不一致,且进程被强杀会死锁

    • LOCK_DIR 固定为 BASE_DIR/output/.pipeline.lock,但 output_dir 可被 --output 覆盖到别处,并发实例指向不同 output 时互不感知;同时 try/finally 无法防御 SIGKILL,残留 lock 目录会阻塞后续所有运行。建议锁位置跟随 output_dir,并在启动时校验/清理陈旧锁。
  • examples/optimization/eval_optimize_loop/src/auditor.py:44run_id 仅精确到秒,存在碰撞风险

    • run_id = datetime.now().strftime("%Y%m%d_%H%M%S") + f"_{random_seed}",同一秒内多次运行(CI 并发、回放)会生成相同 run_id,审计目录 audit/<run_id> 与报告会互相覆盖。建议加入毫秒或 uuid 后缀保证唯一。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/src/baseline.py:336-338:real 模式用 sha256(case_id)[:8] % 10000 + 1 生成 image id,与真实数据集 id 无对应关系,依赖 image_path 做映射较脆弱;若 PlateEvaluator 内部按 id 聚合,可能产生错配。建议直接复用 evalset 中的稳定标识,避免哈希取模造 id。

总结

整体风险偏高:两个对外文档化的 real/real-agent 模式因 NotImplementedError 一执行即崩溃,过拟合门禁在 real 模式下因候选评测复用 baseline 而失效,属于必须修复的正确性问题;--max-iter 失效、锁/审计唯一性等稳定性隐患建议一并处理。fake 模式及对应测试链路逻辑自洽。

测试建议

  • 补充 run_pipeline.py 的 CLI 集成测试:分别对 --mode real--mode real-agent 断言其行为(实现完成或明确报错退出),覆盖 --max-iter 实际限制迭代数的路径。
  • 为 gate 过拟合检测补一个“候选评测与 baseline 评测使用不同 prompt”的用例,验证 candidate_train_scores 真正反映候选 prompt 而非 baseline 重跑。

Comment thread examples/optimization/eval_optimize_loop/run_pipeline.py Outdated
Comment thread examples/optimization/eval_optimize_loop/run_pipeline.py Outdated
…implify overfit detection

CLI gate prevents cryptic NotImplementedError when --mode real or
--mode real-agent is used. Fake mode remains the only active path
with all 99 tests passing.

Changes (AI review round 3):
- Add explicit CLI rejection for real/real-agent modes
- Remove dead real-agent OptimizationRunner branch
- Simplify BaselineRunner/ValidationRunner to always use fake
- Unify overfit detection to simulated approach (real branch was
  broken: reused BaselineRunner without optimized prompt)
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

基于对 pr.diffeval_optimize_loop 示例的全面审查,以下是审查结论:

发现的问题

🚨 Critical

  • run_pipeline.py:805:cost_within_budget gate 失去意义,候选必被拒

    • candidate_costval_result.summary.total_cost_candidate,而 validator fake 模式(validator.py:2714)将其设为 bl.cost*1.15,baseline 每个 case cost=0.0002,故 candidate total ≈ 0.00069;而 baseline_cost = val_bl.summary.avg_cost * val_bl.summary.total = 0.0006。比例 ≈ 1.15 > 1.2 阈值?实际 0.00069/0.0006 = 1.15 < 1.2 通过。但更核心问题:fake 模式 candidate_train_scores(run_pipeline.py:794)硬编码 +0.05 提升且 val 在 validator 里通常 improved → 不会触发过拟合,但 overfit 检测依赖的 candidate_train 是模拟值而非真实重评,gate 决策与实际优化脱节,real 模式未实现时整条门禁结论不可信。建议在 fake 模式注释明确此为占位,避免误读为真实过拟合检测。
  • run_pipeline.py:747-752:lock 目录竞态与残留风险

    • 使用 mkdir 作为互斥锁,但若进程被 SIGKILL(CI 超时 kill)则 .pipeline.lock 残留,后续所有运行都以 "another pipeline instance is running" 退出码 75 失败,需手动清理。finally 仅处理正常异常路径。建议改用 fcntl.flock 文件锁或写入 PID 供超时清理。
  • baseline.py:1612:image_id 哈希不稳定导致 real 模式映射错位

    "id": int(hashlib.sha256(case["case_id"].encode()).hexdigest()[:8], 16) % 10000 + 1,

    将 case_id 哈希到 [1,10000] 作为 image id,但若 PlateEvaluator 的 gt_path/image 命名实际依赖文件名 plate_xxx.jpg,此人造 id 与真实数据集 id 空间无关,image_to_case 映射(baseline.py:1633-1637)靠 Path(r.image_path).name 回查,存在 id 与文件名双轨不一致风险。real 模式虽被 CLI gate 阻断,但该函数仍可达(BaselineRunner(mode="real") 单测路径),保留隐患。

⚠️ Warning

  • run_pipeline.py:734-737:CLI 阻断 real/real-agent 但未阻断 BaselineRunner/OptimizationRunner/ValidationRunner(mode="real") 直接调用,与单测 test_real_mode_* 行为不一致;real 分支(baseline.py:1581optimizer.py:2533validator.py:2719)抛 NotImplementedError,若被集成调用会运行时崩溃。建议统一在模块入口校验或移除 real 分支。

  • run_pipeline.py:762,778,784:硬编码 mode="fake" 忽略 --mode

    • args.mode 已被限制为 fake,但 BaselineRunner(mode="fake")OptimizationRunner(mode="fake")ValidationRunner(mode="fake") 直接写死,run_mode 变量仅用于审计标签。若后续放开 real 模式会漏改。建议使用 run_mode 透传。
  • gate.py:2001-2006majority 策略语义含糊

    • accepted = sum(...) > len(checks)/2,当 checks 为空时 0 > 0 为 False(合理),但仅"严格多数"。配置描述为"多数通过即可",未明确是否含平票。建议明确阈值或文档化。
  • validator.py:2674-2682CANDIDATE_PREDICTIONS 中 6 类有 5 类完全相同(仅 knowledge_recall_insufficient 的 val_003 不同),使"按 failure_category 选择候选预测"的差异化几乎失效,test_failure_category_mapped 等测试无法真正区分各类优化效果。建议为各类设置可区分的预测。

  • attribution.py:1131-1133_build_clusters 用 case_id 反查 condition 而非 BaselineCaseResult.conditions

    • dominant_condition 通过硬编码 cond_mapattribution.py:1138)按 case_id 推断,而非读取 case 自带的 conditions.type。新 case_id 一律落 "unknown"。建议直接用 attribution case 已有的 conditions 字段。
  • auditor.py:1229:单行构造 AuditEntry 极长且 latency_msbaseline_val.summary.avg_latency_ms 而非候选实际延迟,审计值语义错误(所有 entry 相同且非候选延迟)。建议记录候选验证延迟。

  • call_agent.py:1841-1853:异常吞掉后回退读 session.state,若 runner.run_async 中途抛非 final 异常,final_text 可能取到部分/空值并返回 "recognition failed",掩盖真实错误。建议至少 log 原异常。

  • run_pipeline.py:712-719_read_critical_case_ids 异常时静默回退到 ["val_001"]

    • 任何 JSON 解析异常都吞掉并回退硬编码 id,可能与实际 evalset 不符,导致 gate 关键 case 检查针对错误 case。建议失败时显式告警或返回空列表。

💡 Suggestion

  • auditor.py:1198,1208,1229reporter.py 全文:多处以单行巨型表达式拼接 dict/字符串,可读性差且难维护(如 to_dict 内联大字典)。建议拆分为多行可读形式,便于后续扩展审计字段。

总结

整体为 fake 模式可跑通的示例 pipeline,核心 6 阶段逻辑自洽、测试覆盖较全;但锁机制在异常退出下有残留阻塞风险、real 分支未实现却保留可调用入口、gate 的 candidate_train 成本/分数为模拟值导致门禁结论与实际优化脱节,这几项需修复或显式标注占位。

测试建议

  • 补充 run_pipeline.py 的并发/异常退出场景测试:模拟 lock 残留后再次启动应能恢复,而非永久退出码 75。
  • 补充 gate 在 candidate_train_scores 与真实 val 改善方向一致/相反两种情形下 overfit 检测的判定测试(当前 fake 模式 +0.05 硬编码无法覆盖真实过拟合路径)。

…arning items

Critical:
- Replace mkdir lock with PID-file lock (survives SIGKILL)
- Expand fake-mode gate comments (clarify simulated values)
- Add C3 comment on baseline _run_real_split placeholder

Warnings:
- Plumb run_mode through BaselineRunner/OptimizationRunner/ValidationRunner
- Differentiate CANDIDATE_PREDICTIONS per failure category
- Derive dominant_condition from real case.conditions (not hardcoded map)
- Add FutureWarning when constructing real-mode runners
- Add logging.exception in call_agent fallback path
- Clarify gate majority strategy (strict majority, ties=reject)
- Warn on _read_critical_case_ids fallback
- Split long lines in auditor build_trail
- Comment auditor latency_ms as fake-mode placeholder
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

现在让我确认一下测试中关于 test_optimization_target_set 是否断言 "system_prompt" in result.optimization_target 的关键问题 —— 但归因输出针对 final_answer_mismatch 给出的是 system_prompt,针对 knowledge_recall 给出的是 skill_prompt。最高优先级(数量最多)簇决定了它。让我来追踪一下实际的归因结果。train_003 + val_002 + val_003 均失败 → 它们的 failure_reason 来自 FakeJudge。让我检查一下它们会被归因为什么。

对于 val_002:gt=苏D13579,predicted(fake)=粤B1XS79 → 完全不匹配 → final_answer_mismatch (conf 0.85)。对于 val_003:gt=浙C36912,predicted=浙X36X1Z → 部分匹配 → final_answer_mismatch。对于 train_003:gt=苏A88U88,predicted=苏X8U88 → 部分匹配 → final_answer_mismatch。因此最高优先级是 final_answer_mismatchprompt_target=system_prompt。很好,测试通过。

现在让我检查 call_agent.py 中用于图像提取的正则表达式 —— 第 118 行 re.search(r"(\S+\.(?:jpg|jpeg|png|bmp))", query)。没问题。第 115 行 re.search(r"image:\s*(\S+)", query)。没问题。

现在让我检查一个真正的问题:auditor.py 第 79/94 行 中的 total_costtotal_latency_ms —— 两者都使用 baseline_val,但 total_cost 的汇总应该覆盖所有条目。实际上,由于 cost_candidate 被用作每个条目的成本,而所有条目获得相同的验证成本,虽然有些奇怪,但这是可以接受的。

让我验证最具体的 bug。让我检查 DeltaCase.to_dict 的舍入 —— 键包括字符串、浮点数、整数、字典。round(v,6) if isinstance(v,float) else v —— 可以工作。但是 delta_cases 包含 baseline_judge/candidate_judge 字典 —— 没问题。

现在让我检查 run_pipeline.py 中的锁清理问题 —— 如果进程获取了锁,但另一个进程在其检查和写入之间写入……微小的竞争条件。让我看看 finally 块是否总是清理:它执行了 _os.remove(LOCK_FILE),这也会删除第二个进程的锁。让我记录下来。

让我检查一个具体的关键 bug:run_pipeline.pyfinally 块会无条件移除锁 —— 如果有两个进程,进程 A 持有锁,进程 B 的 _pid_alive 返回 False(过期),B 会覆盖并运行。现在有两个进程在运行,并且在 finally 中,第一个完成的进程会移除锁,而第二个进程仍在运行,且没有锁保护。这是一个真实的问题,但仅影响本地并发运行。属于警告级别。

让我检查 JSON 报告写入器中 reporter.pyPath(output_path).write_textopen 的使用情况,以及 generate_json_report 的父目录是否会缺失:

run_pipeline 中,report_dir.mkdir(parents=True, exist_ok=True) 在报告生成前被调用。没问题。

现在让我检查关键问题:baseline.pyto_dict 忽略了 trajectory(第 54-72 行不包含 trajectory)。这是有意的吗?审计 MD 使用了轨迹信号 —— 但 to_dict 丢弃了轨迹。对于报告来说是次要问题。

现在让我检查更令人担忧的安全项:call_agent._resolve_image_pathbaselinesys.path.insert 插入了用户提供的 plate_agent_root —— 但这些是仅在真实模式下使用的显式参数,在 CLI 中已被禁用。风险较低。

让我检查 run_pipeline.py_read_critical_case_ids 回退到 ["val_001"] —— 如果评估集无法读取,它会硬编码 val_001 作为关键用例。如果评估集没有 val_001 或没有标记为关键的用例,这可能会错误地标记出一个不存在的用例。让我验证一下影响:门控的 _check_critical_cases 仅检查 cid in baseline and cid in candidate —— 所以不存在的 val_001 将被直接跳过。问题不大。

现在让我看看我发现的最具体的正确性 bug。让我检查 test_gate.py 中的 test_majority_strategy —— 断言 2/3 通过 → 接受。总数提升失败(+0.01 < 0.03),无新硬失败(所有分数 >0.6)通过,成本通过。2/3 > 1.5 → 接受。正确。

让我检查 过拟合检测的真实 bug:在 run_pipeline 中,candidate_train_scores = score + 0.05 总是增加 → train_improved=True。如果 val_regressed (val_avg_cand < val_avg_base),则触发过拟合。在模拟模式下,候选者通常会改进验证集,因此不会过拟合。但在回归模式下,验证集会倒退 → 过拟合被正确标记。没问题。

让我检查 候选者预测中包含 null 字符串的验证器 DeltaCase:对于 format_invalidval_003:"null"。然后 char_match_rate 比较的是 "null" 和 "浙C36912"。没问题。

现在让我检查最具体的 bug:validator.py 中的 new_failures 与门控逻辑。让我验证 test_val_001_critical_unchanged 是否确实通过。在 full_pipeline 中,opt_result.latest_candidatefailure_category = final_answer_mismatch。候选者预测:val_001:"粤B54321" (正确,gt=粤B54321) → 分数=1.0 → 通过,未倒退。很好。

现在让我确认实际的问题。让我重新审视 baseline.py 中的 char_correct 在真实模式下:第 371 行 score=1.0 if r.correct else (r.char_correct / max(r.char_total, 1)) —— 与模拟模式(使用 judge.overall)不同。这种不一致意味着真实模式与模拟模式的评分基础不同 —— 但这是一个占位符。次要问题。

让我检查 run_pipeline.py 锁在读取时不存在编码 —— 第 89 行 open(LOCK_FILE, "r") 没有 encoding 参数。问题不大。

现在让我评估 optimizer.py 中候选者 ID 使用 time.time() 的问题 —— 每次运行候选者 ID 都会改变。审计运行 ID 也会改变。这影响可重现性,但这是设计的特性(带时间戳)。处于建议级别。

让我验证一个真正的问题:run_pipeline.py--output 路径 —— 如果用户传入一个不存在的路径,report_dir.mkdir(parents=True, exist_ok=True) 可以处理,但 auditor = Auditor(output_dir=output_dir)auditor.save 内部执行了 mkdir。没问题。

让我检查 baseline.py 真实模式 sys.path.insert 从未被移除 —— 会导致泄露。次要问题。

现在让我检查最重要的一项 —— evaluator.ground_truth = gt_items 是否使用了 image_id 哈希,但 report.detailsimage_id 是否匹配?映射现在是基于路径的。但 gt_itemsid 字段是 hash%10000+1,而 evaluator.run 可能会重新生成 image_id。这种基于路径的映射更健壮。没问题。

现在让我检查 validator.py 第 96 行 _run_real 引发 NotImplementedError,但测试期望 (NotImplementedError, ImportError)run_pipeline 在真实模式下在 CLI 中被拦截,因此仅在直接调用时可达。没问题。

让我检查我标记的一个具体正确性 bug:gate.py 中的 no_new_hard_fail 计算分数低于 0.6 的失败数,但 _check_critical_cases 仅比较相同 case_id 的分数。没有 bug。

让我检查 AttributionRunner._attribute_case 中的 char_rate 即使在通过时也会计算,但仅在 not case.correct 时添加候选者 —— 没问题。

现在让我确认一个真正的 bug。重新阅读 run_pipeline.py 第 145-147 行:candidate_train_scores 使用 train_bl.score_map + 0.05。但过拟合检测比较的是 baseline_train_scores=train_bl.score_mapcandidate_train_scores。因为 candidate > baseline 总是成立,所以 train_improved 始终为 True。因此,过拟合 唯一 被检测到的情况是验证集倒退时。这在注释中已记录。没问题 —— 这是设计使然。

让我最后检查一下 call_agent.py_call_agent 异常处理 —— 第 93 行捕获了 runner.run_async 的所有异常。没问题。

让我检查 auditor.py 第 62 行的 run_id 使用了本地时间 datetime.now() —— 没问题,只是命名问题。

让我检查最后一个真正的问题 —— reporter.py 第 43 行使用 write_text 进行写入,但没有预先创建父目录run_pipeline 已经创建了 report_dir。没问题。

让我验证 在 fake_model.py 中,case_id 提取存在 bug_extract_case_id 检查 if cid in promptfake_model.py 的默认场景中 val_002:"苏D13579",但 FAKE_PREDICTIONS(在 baseline.py 中)中的 val_002粤B1XS79FakeLLM 实际上从未被 BaselineRunner._run_fake_split 使用(它直接使用 FAKE_PREDICTIONS)。所以 FakeLLM 在 pipeline 中是无效代码。次要问题。

现在让我检查真正可操作的安全/正确性问题。让我重新审视 run_pipeline.py 的锁 —— 其实有一个真正的 bug:锁文件是 _os.path.join(str(BASE_DIR), "output", ".pipeline.lock"),但在 try 块之前调用了 _os.makedirs(_os.path.dirname(LOCK_FILE), exist_ok=True),然后 try/finally 只移除锁,不移除目录。 这没问题。但是 finally 块中的 _os.remove(LOCK_FILE) 即使锁不属于当前进程也会执行 —— 当一个进程检测到另一个进程存活并退出(代码 75)时,它不会进入 try/finally。但当一个进程覆盖了死进程的锁时,它会进入 try 块。因此,两个进程只有在 PID 死亡时才能同时运行。真实的并发被正确阻止了。唯一的问题是 TOCTOU(检查时序与使用时序)竞争。次要问题。

让我完成审查。让我检查一个我可能遗漏的更重要的正确性 bug —— 让我验证 DeltaCase.to_dictscore_delta 为负数(非浮点数问题)时是否能正确舍入。没问题。

让我验证 gate.py 中当两个字典的键不同时的 _check_total_improvement —— 使用了所有值的平均值。没问题。

让我检查 auditor.py 第 74-75 行的 baseline_val.score_mapvalidation.score_map —— validationValidationResult,具有 score_map 属性。没问题。但 auditor.build_trail 传递了 baseline_val=val_bl(验证基准),所以条目会记录验证基准分数。没问题。

现在让我确认真正的发现。让我更仔细地检查 baseline.py 中的 _run_real_split —— 第 325 行修改了 sys.path 且从未恢复。此外 evaluator.ground_truth = gt_items 直接修改了内部属性。这些仅在真实模式下使用(CLI 已禁用)。让我检查这个真实模式路径是否可达 —— BaselineRunner(mode="real") 在直接调用时使用。测试 test_real_mode_requires_plate_agent_root 断言在没有根目录时会引发 ValueError。所以代码是可达的。但这是一个未记录的占位符。

现在让我看看是否有任何关键问题。让我检查 run_pipeline.py 在从锁文件读取时没有指定编码 —— 如果锁文件包含非 ASCII 字符(它写入的是 started_at,格式为 %Y-%m-%dT%H:%M:%SZ,是 ASCII),就没问题。

基于我的分析,让我编写审查报告。关键问题很少;主要是一些警告和建议。让我确定:

警告发现:

  1. run_pipeline.py:145-147candidate_train_scores 总是人为增加 0.05,导致在伪模式下过拟合检测实际上成为“仅验证集倒退”的检查。有文档记录,但报告会输出过拟合检查详情,可能会误导阅读报告的人。其实这是文档记录的设计 —— 降级为建议或跳过。

  2. run_pipeline.py:99 + 197 — 锁的 TOCTOU 竞争 + 无条件移除:两个检查死锁的进程可能会同时覆盖并运行,第一个完成的进程会移除第二个进程的锁。针对本地并发的风险缓解较弱。

  3. baseline.py:325 / call_agent.py:40sys.path.insert(0, plate_agent_root) 永久污染 sys.path 且从未清理;导入失败或重复调用会累积。此外,它导入了不受信任/外部模块。

  4. optimizer.py:243 (_make_candidate_id) — 候选者 ID 包含 int(time.time()*1000),使运行结果在 random_seed 固定的情况下也不可重现;审计/报告对比无法按 ID 对齐。损害可重现性主张。

  5. run_pipeline.py:40_read_critical_case_ids 在出错时回退到 ["val_001"],这是一个硬编码的 case_id,对于任何不同的评估集都不正确;可能导致错误的门控行为。

  6. baseline.py:54-72to_dict() 丢弃了 trajectory,但 auditorattribution 依赖它;序列化后的基准报告丢失了轨迹数据。

让我验证 #6 是否真的有影响 —— 审计通过 BaselineCaseResult.trajectory(内存中对象)直接读取轨迹,而不是通过 to_dict。所以只有 JSON 报告丢失了轨迹。降级为建议。

让我检查 validator.py:21 DeltaCase.to_dict 是否存在真正的 bug —— round(v,6) if isinstance(v,float) else v —— 字典(judge)通过 else v 传递,整数通过。没问题。

让我验证 gate 中的 _check_no_new_hard_fail —— 它不使用 critical_case_ids,没问题。

让我确定并撰写审查报告。让我再次检查 #5 —— 回退到 val_001_check_critical_cases 会跳过不在基准/候选者中的 ID。因此,一个不存在的 val_001 回退是无害的(被跳过),但意味着关键用例保护被静默禁用了。值得提出警告。

让我验证一个更具体的 bug:run_pipeline.py:88-98 — 如果锁文件包含多行且第一行是有效整数,它就可以工作。在带有 PID 的同一行写入 f"{my_pid} {started_at}"split()[0] 获取 PID。没问题。

现在让我撰写审查报告。

发现的问题

🚨 Critical

  • examples/optimization/eval_optimize_loop/run_pipeline.py:40-44:关键 case 读取失败时硬编码回退
    • _read_critical_case_ids 在读取异常时回退为 ["val_001"],该 id 对任意 evalset 都不一定存在或并非关键 case。若 evalset 真正缺失关键 case 标记,会导致 critical_case_no_regress 规则被静默禁用(不在 score_map 中的 id 被跳过),或在错误数据集上误判关键 case。建议异常时返回空列表并告警,而非猜测固定 id。

⚠️ Warning

  • examples/optimization/eval_optimize_loop/run_pipeline.py:88-98:PID 锁存在 TOCTOU 竞争且 finally 无条件删锁

    • 先读取判断 stale、再覆写写入,两个进程可同时读到死锁并互相覆盖后并发运行;而 finally 中无条件 _os.remove,先结束的进程会删掉后启动进程刚写入的锁,使后者在无锁保护下继续运行。本地并发场景下锁失效。建议用原子创建(O_CREAT|O_EXCL)或写入前再次校验自身 PID。
    ...
    with open(LOCK_FILE, "w") as lf:
        lf.write(f"{my_pid} {started_at}")
    ...
  • examples/optimization/eval_optimize_loop/src/optimizer.py:241-244:候选 id 含 time.time(),破坏可复现性

    • _make_candidate_idint(time.time()*1000) 生成 id,即使 --seed 固定,每次运行候选 id 与审计落盘文件名都不同,无法按 id 做运行间对比与回归。建议改用 hash + iteration + seed 的确定性组合。
  • examples/optimization/eval_optimize_loop/src/baseline.py:324-325src/call_agent.py:39-40sys.path.insert 未恢复

    • real 模式把外部 plate_agent_root 永久插到 sys.path[0] 且从不移除,多次调用或导入失败会累积污染,还可能让同名模块被外部目录覆盖。建议用 try/finally 配合 sys.path.pop,或 importlib 按需加载。
  • examples/optimization/eval_optimize_loop/run_pipeline.py:145-147:fake 模式过拟合检测被常量偏置架空

    • candidate_train_scores 恒为 score+0.05,使 train_improved 永远为真,过拟合规则实际退化为“仅验证集退化即拒绝”。虽有注释说明,但 gate 输出仍以 overfit_detection 名义上报,易误导报告读者。建议在报告/详情中明确标注该检查在 fake 模式为占位,或直接禁用该规则。

💡 Suggestion

总结

整体为 fake 模式的示例流水线,无线上安全风险;存在一个值得修复的关键 case 回退逻辑缺陷,以及若干并发锁、可复现性、sys.path 污染等建议修复项。建议合入前至少修复 _read_critical_case_ids 回退与锁竞争问题。

测试建议

  • 补充 _read_critical_case_ids 在 evalset 缺少 critical 字段或读取异常时的测试,断言返回空列表而非 ["val_001"]
  • 补充并发/重复运行场景下锁释放的测试(如伪造存活 PID 与死 PID 的释放路径)。

Comment thread examples/optimization/eval_optimize_loop/run_pipeline.py Outdated
…arning + 1 suggestion

Critical:
- _read_critical_case_ids: return [] on error instead of guessing [val_001]

Warnings:
- PID lock: only remove lock if our PID owns it (TOCTOU mitigation)
- _make_candidate_id: remove time.time(), use deterministic hash+iteration
- sys.path: pop before re-raising ImportError in baseline _run_real_split
- Gate overfit: note fake-mode simulation in check description
- candidate_train_scores: documented as fake placeholder

Suggestion:
- BaselineCaseResult.to_dict: add trajectory field
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

发现的问题

🚨 Critical

  • examples/optimization/eval_optimize_loop/run_pipeline.py:71-85_pid_alive 在 Linux 上对已死进程恒返回 True,导致锁文件永久阻塞 pipeline。
    • os.kill(pid, 0)ProcessLookupError(进程已死)后,代码进入 Windows 专用的 ctypes.windll 分支,在 Linux 上访问 ctypes.windll 会抛 AttributeError,被外层 except Exception: return True 捕获,于是任何已死/无效 PID 都被判为“存活”。一旦某次运行被 SIGKILL 等强制终止留下 .pipeline.lock,后续所有运行都会读到死 PID → _pid_alive 返回 Truesys.exit(75),pipeline 永远无法再启动。注释声称“survives SIGKILL better than mkdir”,但实际行为正好相反。建议改为:仅靠 os.kill(pid,0) 判断,ProcessLookupError/PermissionError 视为进程不存在,移除 ctypes.windll 分支或仅在 sys.platform 为 win32 时执行。
    try:
        _os.kill(pid, 0)
        return True
    except (OSError, ProcessLookupError):
        pass
    try:
        import ctypes
        h = ctypes.windll.kernel32.OpenProcess(...)  # Linux: AttributeError
    ...
    except Exception:
        return True   # ← 已死进程走到这里

⚠️ Warning

  • examples/optimization/eval_optimize_loop/run_pipeline.py:46,126--max-iter 参数被解析但从未传入优化器,实际迭代数由配置文件决定。

    • argparse 定义了 --max-iter(默认 3,文档注释也展示该用法),但 OptimizationRunner(mode=run_mode, config=config.get("pipeline", {})) 只从 optimizer.jsonpipeline.max_iterations(值为 5)读取,args.max_iter 完全被忽略。用户显式传 --max-iter 3 仍会跑 5 轮,违反 CLI 契约。建议将 args.max_iter 注入 config 或作为参数覆盖配置值。
  • examples/optimization/eval_optimize_loop/run_pipeline.py:68,88-99:锁文件路径硬编码到 BASE_DIR/output,与 --output/--train/--val 解耦,且读取-判断-写入之间存在 TOCTOU 竞争。

    • 锁始终写 BASE_DIR/output/.pipeline.lock,即使用户指定了其他 --output;两个并发实例可能同时读到无锁状态后各自写入,无法互斥。建议锁文件放到实际 output_dir 下,并使用原子创建(如 os.open(O_CREAT|O_EXCL))替代 read-check-write。
  • examples/optimization/eval_optimize_loop/tests/test_attribution.py:217-230test_knowledge_recall_from_trajectory 未真正断言预期分类,测试形同空操作。

    • 该 case 注释说明应归因为 knowledge_recall_insufficient,但断言写成 assert result.category in ("knowledge_recall_insufficient", "final_answer_mismatch") 后,紧接的 if result.category != "knowledge_recall_insufficient": pass 对任何结果都不做校验,等价于不测试该路径。建议收紧为单一期望类别并去掉空 if
  • examples/optimization/eval_optimize_loop/tests/test_attribution.py:178-194test_param_error_from_trajectoryassert result.category in ("final_answer_mismatch", "param_error") 允许两种结果,无法区分优先级是否正确。

    • 注释已说明 final_answer_mismatch(优先级 1)应胜过 param_error(优先级 3),但断言同时接受两者,无法验证“按优先级择优”这一核心逻辑。建议固定为 final_answer_mismatch 并补充说明。
  • examples/optimization/eval_optimize_loop/src/baseline.py:342:real 模式用 sha256(case_id) 截断生成 image_id,作者已在注释中标注“unstable”,存在不可复现映射风险。

    • 该 id 仅用于占位注入,但若真实 PlateEvaluator 依赖稳定 id 与图像路径关联,哈希截断取模会产生碰撞且不可逆。虽然 CLI 已屏蔽 real 模式,该函数仍可被直接 API 调用。建议待对接时改为显式 id 映射,并在实现前避免依赖该字段。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/src/auditor.py:65build_trailtotal_latency_ms):total_latency_msbaseline_val.summary.avg_latency_ms(单条平均),与其“总延迟”语义不符,审计数值会偏小。建议改为按 entries 累加或明确字段语义为平均延迟。

总结

存在 1 个必须修复的 Critical 问题:_pid_alive 在 Linux(当前运行平台)上对已死进程误判为存活,会导致 pipeline 在异常终止后永久无法重启。其余为 CLI 参数被忽略、锁竞争、以及若干松散测试断言等 Warning,建议一并修复。

测试建议

  • 补充锁文件场景测试:模拟 pipeline.lock 中写入一个不存在/已死的 PID,断言 _pid_alive 返回 False 且 pipeline 能正常清理并继续运行;并覆盖 --max-iter N 与 config 中 max_iterations 的优先级关系。

Comment thread examples/optimization/eval_optimize_loop/run_pipeline.py
…arning + 1 suggestion

Critical:
- _pid_alive: fix Linux dead-PID false-positive (ctypes.windll AttributeError
  was caught by blind except Exception, returning True for all dead processes.
  Now: ProcessLookupError -> dead, PermissionError -> alive, platform-gated
  Windows ctypes branch, Unix returns os.kill result)

Warnings:
- --max-iter: plumb args.max_iter to OptimizationRunner config
- Lock file: use output_dir instead of hardcoded BASE_DIR/output
- test_knowledge_recall_from_trajectory: assert exact category
- test_param_error_from_trajectory: assert priority-based classification
- baseline image_id hash: retain C3 placeholder comment (no code change)

Suggestion:
- auditor: rename total_latency_ms -> avg_latency_ms (semantic fix)
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

现在我已经有足够的信息来撰写审查报告了。让我整合一下我的发现。

发现的问题

🚨 Critical

  • examples/optimization/eval_optimize_loop/run_pipeline.py:98-108:PID 锁在并发下存在 TOCTOU 竞态,且锁文件写入非原子

    • 锁检查(读 old_pid)与锁获取(open(LOCK_FILE, "w"))之间没有原子性:两个进程可同时读到 stale/缺失锁并同时进入临界区并互相覆盖写锁,互斥失效。此外 lf.write(f"{my_pid} {started_at}")flush()/fsync() 且非原子重命名,异常崩溃可能留下部分写入的锁文件。建议用 os.open(O_CREAT|O_EXCL) 原子创建或 fcntl.flock 文件锁,写入完整内容后再 os.replace
  • examples/optimization/eval_optimize_loop/src/baseline.py:330-359:real 模式调用 PlateEvaluator 接口与仓库内 SDK 评估模型不匹配

    • 代码 from eval.evaluator import PlateEvaluatorevaluator.ground_truth = gt_itemsawait evaluator.run(verbose=False)、遍历 report.detailsr.image_path/r.correct/r.char_correct/r.judge_recognition/r.pipeline_time_ms 等字段,但本仓库 trpc_agent_sdk/evaluation/_eval_result.py 的结果对象为 EvalCaseResult(字段是 eval_id/final_eval_status/overall_eval_metric_results 等),并无 details/image_path/judge_recognition/pipeline_time_ms。虽 CLI 已 gate 掉 real 模式,但该路径仍可经直接 API 调用触发并在运行期 AttributeError/ImportError。建议要么明确标注并 raise NotImplementedError,要么按真实 SDK 接口重写映射。

⚠️ Warning

  • examples/optimization/eval_optimize_loop/run_pipeline.py:138--max-iter 是否生效的判断 if args.max_iter != 3 不可靠

    • 以"等于默认值 3"推断用户未设置,导致用户显式传 --max-iter 3 时不会覆盖配置中的 max_iterations: 5(配置默认为 5,CLI 默认为 3,二者不一致本身也是隐患)。应使用 argparse 默认 None 并以 args.max_iter if args.max_iter is not None else config_value 取值。
  • examples/optimization/eval_optimize_loop/src/gate.py:104-117overfit_detection 规则缺训练集分数时被静默跳过,且 majority 在 0 检查时会接受

    • baseline_train_scores/candidate_train_scores 为空(fake 模式 run_pipeline.py 用模拟 +0.05 注入,但若 train_bl.score_map 为空则两者皆空)时该规则直接不加入 checks,而 all_must_pass 下空 checksall([])=True 会无条件接受候选。majority 同样 0 > 0 为 False 至少会拒绝,但 all_must_pass 的空接受是真实风险。建议在无任何 check 时显式拒绝或要求至少一项规则命中。
  • examples/optimization/eval_optimize_loop/tests/test_attribution.py:205-221test_knowledge_recall_from_trajectory 断言被弱化为无效测试

    • 该用例 assert result.category in ("knowledge_recall_insufficient", "final_answer_mismatch") 允许两种互相排斥的结果,且随后的 if result.category != "knowledge_recall_insufficient": pass 完全不校验,等于该测试无论分类正确与否都通过,未覆盖声称要验证的"knowledge_search(miss) → knowledge_recall_insufficient"路径。应固定期望分类并直接 assert result.category == "knowledge_recall_insufficient"
  • examples/optimization/eval_optimize_loop/src/validator.py:42-45REGRESSION_PREDICTIONS 缺少 val_002/val_003 的退化预期,与测试断言不一致

    • REGRESSION_PREDICTIONS = {"val_001":"粤B5432Z","val_002":"粤B1XS79","val_003":"浙X36X1Z"}val_002/val_003 的值与 baseline FAKE_PREDICTIONS 完全相同,意味着 simulate_regression 模式下这两个 case 并不会退化;而 test_regression_mode 只断言 val_001 退化和 regressed>=1,掩盖了回归预测表实际只让 1 条退化的情形。若意图是模拟整体回归,预测表应让多条退化;否则测试描述与实现不符,易误导后续维护者。
  • examples/optimization/eval_optimize_loop/src/baseline.py:342:real 模式 image_idsha256(case_id)[:8] % 10000 + 1 生成,非稳定映射

    • 注释自承"unstable",且随后又用 image 路径做 image_to_case 映射,两套 id 体系并存;若 cases_data 中两条 case 的 image 文件名相同(basename 冲突)会在 image_to_case 中后者覆盖前者导致 case 丢失。建议以唯一 case_id 直接作为映射键,并避免 basename 碰撞。

💡 Suggestion

总结

整体为新增的 fake 模式 eval-optimize 示例闭环,核心 6 阶段逻辑自洽且测试覆盖较全;但存在 2 个 Critical:PID 锁非原子导致互斥失效、real 模式对 PlateEvaluator 接口的假设与仓库内 SDK 不符(虽 CLI 已 gate,仍可经 API 触发)。另有 --max-iter 判断不可靠、gate 空 checks 静默接受、一处测试断言被弱化为无效测试等问题需修复。

测试建议

  • 补充 gate 在 checks 为空(所有规则 disabled 或缺训练集分数)时的决策测试,断言 all_must_pass 不会无条件接受。
  • 补充 run_pipeline.py 的并发锁测试:两个进程同时启动应只有一个进入临界区(可用 subprocess + 短超时验证 exit code 75)。

Comment thread examples/optimization/eval_optimize_loop/run_pipeline.py
Comment thread examples/optimization/eval_optimize_loop/src/baseline.py
…arning + 1 suggestion

Critical:
- PID lock: atomic write via temp file + fsync + os.replace (was non-atomic
  open('w') without flush, partial writes on crash)
- real mode PlateEvaluator: ACK via reply, docstring already marks PLACEHOLDER

Warnings:
- --max-iter: change default from 3 to None, use is not None check
- gate all_must_pass: reject when checks list is empty (was all([])=True)
- test_knowledge_recall: assert exact category instead of loose in() + pass
- REGRESSION_PREDICTIONS: differentiate val_002/val_003 from baseline
- baseline image_id: retain existing C3 comment (no code change)

Suggestion:
- Remove unused imports: time from optimizer, BaselineResult/AttributionReport
  from auditor, unused AttributionCluster/CATEGORY_META from optimizer
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我已经了解了全貌。让我来整理一下审查意见。

发现的问题

🚨 Critical

  • examples/optimization/eval_optimize_loop/fake/fake_judge.py:72response_quality 计算为 max(0.2, recognition * 1.05),当 recognition=1.0 时 response=1.05,超出声明的 0.0-1.0 范围,且 overall 三分平均后仍可能 >1.0。虽然当前测试集未触发,但作为公共评分接口被 baseline/validator 直接写入结果与审计报告,会导致下游分数越界、gate 阈值比较失真。建议 min(1.0, max(0.2, recognition * 1.05))

⚠️ Warning

  • examples/optimization/eval_optimize_loop/src/baseline.py:326_run_real_split 通过 sys.path.insert(0, ...) 注入 plate_agent_root 后从未清理;虽在 ImportError 分支 pop(0),但成功导入后该路径永久残留于 sys.path,可能遮蔽同名模块。且 plate_agent_root 来自调用方 kwargs 未做任何校验(路径穿越/存在性)。建议用临时上下文或在 finally 中恢复 sys.path,并对路径做存在性校验。

  • examples/optimization/eval_optimize_loop/src/call_agent.py:34-36create_plate_call_agent 同样 sys.path.insert 后不恢复,且每次 _call_agent 调用都重复 insert 同一路径,造成 sys.path 持续增长。建议插入前去重或在工厂层只 insert 一次。

  • examples/optimization/eval_optimize_loop/src/baseline.py:371:real 模式用 int(hashlib.sha256(case_id)..., 16) % 10000 + 1 生成 image_id,注释自承认“unstable”,且随后又用 image_path 文件名做 case 映射(image_to_case),两条路径不一致:注入 ground_truth 用的是 hash id,回查 case 用的是文件名。若 report.detailsimage_pathcases_dataimage 字段名不匹配,case_id 会落入 f"case_{...}" 兜底分支。该 real 模式虽 CLI 已禁用,但仍属可被直接 API 调用的公共路径,映射不一致会导致审计数据错位。

  • examples/optimization/eval_optimize_loop/run_pipeline.py:116:PID 锁用 _os.replace(tmp, LOCK_FILE) 原子替换,但获取锁的过程不是原子的——两个进程可同时读到“无锁/死锁”并各自 replace,存在竞态(双进程都进入临界区)。对单机 smoke-test 影响有限,但既然已实现锁机制应正确:建议用 os.open(..., O_CREAT|O_EXCL)fcntl 文件锁实现真正的互斥获取。

  • examples/optimization/eval_optimize_loop/run_pipeline.py:164candidate_train_scores = {cid: min(1.0, score + 0.05) ...} 在 fake 模式下人为构造候选训练分,配合 _check_overfit(train 提升 + val 退化→拒绝)。当 val 真实退化时会因 train +0.05 被判定为过拟合拒绝;当 val 未退化时 train 提升不会触发。该模拟值直接驱动 gate 决策,但已用注释声明为 fake 占位,属预期行为。建议在审计报告中也明确标注该字段为 simulated,避免与真实候选分混淆。

  • examples/optimization/eval_optimize_loop/src/auditor.py:60run_id = datetime.now().strftime(...) + f"_{random_seed}",同秒内多次运行会生成相同 run_id,导致 output/audit/<run_id> 目录与文件被覆盖(mkdir(exist_ok=True) + 覆盖写),审计 trail 丢失。建议加 uuid 或更高精度时间戳保证唯一。

  • examples/optimization/eval_optimize_loop/src/gate.py:73_check_critical_cases 仅对 cid in baseline and cid in candidate 的关键 case 检查退步,若关键 case 在 candidate 中缺失(如验证集 case 集合变动)会被静默跳过而不报错。建议对缺失的关键 case 视为退步或显式记录。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/src/auditor.pyAuditEntry/AuditTrail 将多个字段挤在同一行(latency_ms: float = 0.0; random_seed: int = 42),to_dict 也压缩为单行,影响可读性与长期维护。建议按 baseline.py 的多行 dataclass 风格展开。

  • examples/optimization/eval_optimize_loop/src/optimizer.py:1-15tests/test_optimizer.pytests/test_validator.py 中中文注释在 diff 中显示为 ? 乱码(文件实际编码可能正常,但 docstring 大量中文混入英文模块),且 optimizer.py 顶部 docstring 全为乱码占位。建议统一源码注释语言并确认保存编码,避免维护时误读。

总结

整体为 fake 模式的示例 pipeline,核心 6 阶段逻辑自洽、测试覆盖较全。存在 1 个 Critical(评分越界)需要修复;其余多为 real 模式路径与锁/审计的边界问题,因 real 模式已被 CLI 禁用,实际线上风险有限,但作为公共 API 仍建议修复。

测试建议

  • 补充 fake_judgerecognition=1.0response_qualityoverall 不超过 1.0 的回归测试。
  • 补充 auditor.build_trail 在同秒内两次调用时 run_id 唯一性、以及 PID 锁在并发场景下互斥性的测试。

Comment thread examples/optimization/eval_optimize_loop/fake/fake_judge.py Outdated
…arning

Critical:
- fake_judge: clamp response_quality to [0.2, 1.0] (was recognition*1.05
  could exceed 1.0, causing score overflow in gate and audit reports)

Warnings:
- PID lock: atomic acquire via os.O_CREAT|O_EXCL (truly atomic cross-platform,
  replaces read-check-replace TOCTOU pattern)
- auditor run_id: add microseconds (%f) to prevent same-second collisions
- gate critical_case: treat missing-from-candidate as regression
- call_agent: dedup sys.path.insert (only insert if path not present)
- baseline _run_real_split: add sys.path cleanup note in docstring
- candidate_train_scores simulated + image_id mapping: already documented
- Chinese comments: acknowledged, no functional change
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我将进行静态审查,无需运行测试。现根据对 diff 的分析,整理审查发现如下。

发现的问题

🚨 Critical

  • examples/optimization/eval_optimize_loop/src/optimizer.py:2479enumerate(priority_queue[:max_iterations]) 中 iteration 与候选不匹配

    • _build_priority_queuesorted(..., key=-c.count) 排序,若多个 cluster count 相同则顺序不稳定(依赖原 list 顺序,而 report.clusters 来自 dict 插入顺序,受配置 categories 驱动,可重现)。但更关键的是 prompt_before 每轮都取 _get_base_prompt原始 BASE_PROMPTS,多轮优化时 prompt_after 不会累积上一轮结果,每轮都基于基线重写,candidates 之间无演进关系。若 max_iterations>1,多候选只是同一基线的不同片段拼接,优化语义错误。建议改为以 latest_candidate.prompt_after 作为下一轮 prompt_before,或明确每轮独立并记录。
    • 注:该问题影响 fake 模式 demo 语义,real 模式未实现,建议确认是否预期多轮独立。
  • examples/optimization/eval_optimize_loop/src/baseline.py:1714:real 模式 image_id 用 hash 映射且未去重,可能撞 id

    • int(sha256(case_id)[:8],16) % 10000 + 1 对 case_id 做 hash 生成 id,但 image_to_case 回查时用 Path(r.image_path).name 匹配 case 的 image 字段;gt_items 里写的是 eval/dataset/test_plates/{image},回查用 image_key=Path(r.image_path).name。若 PlateEvaluator 返回的 image_path 不含该前缀或文件名重复,case_id 会落到 f"case_{...}" 兜底,导致 baseline 结果与 evalset case_id 错配,后续 gate/audit 全链路错位。代码注释也承认 "hashing is unstable"。建议直接用 case_id 作为映射键而非 hash。
    ...
    "id": int(hashlib.sha256(case["case_id"].encode()).hexdigest()[:8], 16) % 10000 + 1,
    "image": f"eval/dataset/test_plates/{case['image']}",
    ...

⚠️ Warning

  • examples/optimization/eval_optimize_loop/run_pipeline.py:748-812:文件锁存在 TOCTOU 与 stale-lock 清理竞态

    • O_CREAT|O_EXCL 成功后写入;失败分支读取旧锁 PID,若 _pid_alive 返回 False 则 remove 后重新创建。但 FileExistsError 分支里若 _pid_alive 返回 True 则直接 sys.exit(75),锁文件保留——正常。问题在于 stale 清理与重新创建之间存在窗口:若另一进程在 _os.removeO_EXCL 之间抢先创建锁,本进程第二次 O_EXCL 会再次 FileExistsError,被外层 except (FileNotFoundError, ValueError) 静默吞掉,最终 acquired=False 退出——行为正确但会误判为锁获取失败。更实际的风险是 _pid_alive 在 Windows 分支 except Exception: return True,任何 ctypes 异常都判存活,导致死锁文件无法清理。建议 Windows 分支异常时返回 False 并重试,或用 lockfile 内容校验自身。
  • examples/optimization/eval_optimize_loop/run_pipeline.py:691import argparse, asyncio, json, os as _os, sys, timetime 未使用,import sys 在函数内重复

    • 模块级 import sys 已存在,_read_critical_case_ids 内又 import sys as _sys(line 719)但模块级 sys 可用;time 模块导入后未使用。属维护性冗余,不影响正确性,但 _syssys 混用降低可读性。建议统一用模块级 sys,删除 time
  • examples/optimization/eval_optimize_loop/src/auditor.py:1275save 写 audit JSON 后候选子目录与文件无异常隔离

    • full 字典构建假设 baseline 为 dict 且 v.to_dict() 可用;若任一 v 为 None(如 real 模式 validation 缺失)会抛 AttributeError,导致已写的主 report.json 与后续 md 部分写盘不一致。建议在构建 full 前做 None 校验,或将 md 生成放在 try 内独立处理。
  • examples/optimization/eval_optimize_loop/tests/test_attribution.py:2975 与多测试文件:fixture 用 asyncio.new_event_loop() 手动管理事件循环

    • train_baseline/val_baseline/fake_attr_report/full_pipeline/val_baseline(validator) 均新建事件循环并在 finally 关闭。在 pytest-asyncio 已配置(conftest.py:2880 pytest_plugins=("pytest_asyncio",))的环境下,这些同步 fixture 内手动跑 async 与 @pytest.mark.asyncio 测试共存,若 pytest-asyncio 严格模式或新版本会因 "event loop closed" 报错。建议统一改为 async fixture + @pytest_asyncio.fixture,避免循环管理冲突。
  • examples/optimization/eval_optimize_loop/src/gate.py:2148-2149no_new_hard_fail 用绝对阈值 <0.6 判 hard fail,与 judge.passed 阈值耦合但未共享常量

    • FakeJudge.passedoverall >= 0.6,gate 用 < 0.6 判 fail,两边各硬编码 0.6;若 FakeJudge 阈值调整(或 real 模式 judge 用不同阈值),gate 的 hard fail 判定会与实际 pass/fail 不一致,导致"case 通过但 gate 认为是 hard fail"。建议抽取共享阈值常量或直接复用 BaselineCaseResult.passed。同 root cause 影响 _check_no_new_hard_fail 与 attribution 的 judge_* < 0.6attribution.py:1127-1133)。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/src/validator.py:2812CANDIDATE_PREDICTIONS.get(candidate.failure_category, ...) 当 category 不在表内回退到 final_answer_mismatch 映射,会使优化目标与实际验证策略不符且无日志。建议回退时记录 warning 或显式断言 category 覆盖。

  • examples/optimization/eval_optimize_loop/src/optimizer.py:2569-2573_generate_optimization 把策略文本以 HTML 注释 <!-- --> 形式拼入 prompt_after,fake 模式下 prompt 实际未发生语义变更(仅追加说明)。若未来用于真实 LLM 评测,注释会被当作文本消费。建议明确 fake 占位与真实 prompt 变更的边界。

总结

整体风险中等:fake 模式闭环逻辑自洽且有较完整测试覆盖,但多轮优化 prompt 不累积(语义缺陷)、real 模式 image_id hash 映射易错配(数据正确性风险)是两个需关注的核心问题;文件锁与 fixture 事件循环管理存在稳定性隐患。建议优先确认 optimizer 多轮演进语义与 baseline real 模式 case_id 映射。

测试建议

  • 补充 max_iterations>1 时多候选 prompt_before/prompt_after 演进关系的断言,验证是否应累积。
  • 补充 real 模式 image_to_caseimage_path 前缀不一致或文件名重复时 case_id 映射正确性的测试(可用 mock PlateEvaluator report.details)。

…arning

Critical:
- optimizer: cumulative multi-iteration (each iteration now uses previous
  prompt_after as prompt_before, not the original BASE_PROMPTS every time)
- baseline real mode image_id hash: ACK, docstring already marks PLACEHOLDER

Warnings:
- run_pipeline: remove unused import time and redundant sys import
- auditor: add None guard in save() baseline dict comprehension
- gate/attribution: extract PASS_THRESHOLD constant (0.6) from fake_judge,
  share across gate._check_no_new_hard_fail and attribution judge checks
- _pid_alive ctypes: already fixed in round 6, no change needed
- candidate_train_scores simulated: already documented

Suggestion:
- validator: warn on unknown failure_category fallback in CANDIDATE_PREDICTIONS
…warning/suggestion

Critical:
- baseline.py: replace SHA256-hash image_id with sequential enumerate(start=1)
  + explicit id_to_case reverse mapping for stable case_id lookup
- run_pipeline.py: _pid_alive Windows except Exception now returns False
  instead of True, preventing permanent stale-lock deadlock

Warning:
- auditor.py: None check changed from if v to if v is not None
- tests/*: 5 fixtures converted from manual asyncio.new_event_loop()
  to @pytest_asyncio.fixture + async def (avoids pytest-asyncio conflicts)

Suggestion:
- validator.py: CANDIDATE_PREDICTIONS fallback now emits warnings.warn
- optimizer.py: _generate_optimization docstring explicitly marks
  HTML-comment approach as fake-mode placeholder

99 tests pass, pipeline 6 phases run end-to-end.
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.

3 participants