Skip to content

feat(examples): add evaluation + optimization closed-loop pipeline - #139

Open
coder-mtj wants to merge 43 commits into
trpc-group:mainfrom
coder-mtj:feat/issue-91-eval-optimize-loop
Open

feat(examples): add evaluation + optimization closed-loop pipeline#139
coder-mtj wants to merge 43 commits into
trpc-group:mainfrom
coder-mtj:feat/issue-91-eval-optimize-loop

Conversation

@coder-mtj

Copy link
Copy Markdown

Description | 描述

Reproducible Evaluation + Optimization closed-loop pipeline.

Implements: baseline evaluation → failure attribution (10 categories) → optimization → validation set comparison → multi-dimensional gate → JSON/MD report with audit trail.

Related Issue | 关联 Issue

Fix #91

Change Type | 修改类型

  • New feature | 新功能

Test Coverage | 测试覆盖

  • 35 tests covering config, baseline, attribution, gate, validation, report, integration
  • 3 train + 3 validation evalset cases with trace mode
  • Verified locally: python -m pytest examples/optimization/eval_optimize_loop/tests/ -v

Self-test Checklist | 自测清单

  • Verified locally | 本地验证通过
  • No existing features affected | 无影响现有功能
  • Full pipeline runs in fake mode and generates JSON+MD reports
  • Gate multi-dimensional checks all functional
  • Failure attribution correctly categorizes failures

@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

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

Additional details and impacted files
@@            Coverage Diff             @@
##             main        #139   +/-   ##
==========================================
  Coverage        ?   88.43489%           
==========================================
  Files           ?         491           
  Lines           ?       46035           
  Branches        ?           0           
==========================================
  Hits            ?       40711           
  Misses          ?        5324           
  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.

@coder-mtj

Copy link
Copy Markdown
Author

补充了设计文档和开发过程记录:

文件 说明
DESIGN.md 架构设计文档(7 阶段流水线、模块划分、5 维度 Gate、8 类失败归因)
README.md 使用说明 + 快速复现步骤 + CLI 参数表
ai-prompts.md 开发过程记录(4 轮:架构→优化→测试→修复)

测试覆盖:189 tests,14 测试文件,6 维度(单元/集成/大规模/边界/回归/性能)
Evalset 数据:62 cases(34 train + 16 val + 12 holdout),跨中/日/韩/Emoji 多语言
Pipeline:8 模块(config/baseline/attribution/optimize/validate/gate/report/tracing)

所有 CI checks 通过。如有遗漏或需要调整的地方请告知。

Implements trpc-group#91 — reproducible Evaluation + Optimization pipeline:
- Config loading (optimizer.json + evalsets validated)
- Baseline evaluation (fake mode with trace evalsets + SDK path)
- Failure attribution (10 categories: tool errors, rubric, format, etc.)
- Multi-dimensional gate (improvement threshold, critical cases, cost budget)
- Validation set comparison (new passes/failures, overfitting detection)
- JSON + Markdown report with full audit trail
- 6 train+val evalset cases (3 optimizable, 1 degrading, 1 format, 1 edge)
- 35 tests covering config, baseline, attribution, gate, validation, report, integration

Signed-off-by: coder-mtj <coder-mtj@users.noreply.github.com>
…overage

- Add pipeline/optimize.py: GEPA optimization wrapper (fake + live modes)
- Add pipeline/tracing.py: audit trail with seed/timing/cost/reproduce
- Add agent/ package: calculator agent for optimization testing
- Update run_pipeline.py: integrate new modules, AuditTracer, enhanced CLI
- Split monolithic test file into 14 focused test files
- Expand from 35 to 189 tests (5.4x increase)
- Add 6-dimensional test coverage: unit, integration, mock data,
  edge/boundary, regression, performance
- Enhance evalsets: 34 train + 16 val + 12 holdout cases
  (multi-domain: math, reasoning, tool calls, Chinese, CJK, format)
- Add DESIGN.md and README.md with architecture documentation
- All 189 tests passing, pipeline verified end-to-end in fake mode
…de no-op

- 新增 pipeline/comparator.py:分层评测规则(纯数字/contains/带单位/格式/工具)
- 修复 run_baseline_fake 空转:比较 conversation 期望 vs actual_conversation 实际
- 归因增强:直接读取 comparator 的 category/evidence
- 新增 23 个 comparator 单元测试,全量 212 tests 通过

Signed-off-by: popo <18682875253@163.com>
…valset data

- 新增 tests/test_gold_verdicts.py:84 条黄金判定表锁定归因精度(≥90%)
- 修复 train 数据标注错误:train_reasoning_002_fail / train_tool_002_fail 改为真正失败
- 新增 large_train.evalset.json(50 cases,17 个 _fail)
- comparator 增强:货币千分位、数字子集匹配
- 全量 299 tests 通过

Signed-off-by: popo <18682875253@163.com>
…te rejection

- 新增 --scenario CLI(fix_attributed/noop/overfit)演示三类验收场景
- validate.py: run_validation_trace 用 TraceMatcher 重评候选 actuals,带 per_case_results
- gate.py: 候选在验证集新增失败 → REJECT(过拟合检测真实生效)
- optimize.py: SCENARIOS 注册表 + candidate_strategy/fixed_categories
- 修复 Windows GBK 控制台 emoji print 崩溃
- 三类场景验证:fix_attributed=ACCEPT, noop=NEEDS_REVIEW, overfit=REJECT(CI 退出码 1)

Signed-off-by: popo <18682875253@163.com>
- JSON 报告新增 candidate 块(train/validation 评分 + 逐 case delta)
- MD 报告新增 Candidate vs Baseline 逐 case 对比表
- 归因条目补充 evidence 字段(可解释性)
- 修复 FailureCategory 枚举序列化

Signed-off-by: popo <18682875253@163.com>
- agent.py: 新增 build_call_agent()(确定性离线 CallAgent)
- baseline.py: run_baseline_sdk 变 async,用 AgentEvaluator.evaluate_eval_set;
  SDK 失败降级到 trace comparator
- optimize.py: run_optimize_live 正确 await AgentOptimizer.optimize(call_agent=...)
- optimizer.json: 补充 reflection_lm 配置
- run_pipeline.py: live 模式用 asyncio.run 隔离,项目根加入 sys.path
- 修复 SDK schema 不兼容时 live 模式崩溃问题

Signed-off-by: popo <18682875253@163.com>
… tests

- test_scenarios.py: 三场景端到端(fix_attributed=ACCEPT, noop=NEEDS_REVIEW, overfit=REJECT)
- test_attribution_accuracy.py: 归因准确率 ≥90%(验收标准 trpc-group#4)
- test_live_mode_import.py: live 模式健壮性 + fake 性能 <3s
- 全量 317 tests 通过

Signed-off-by: popo <18682875253@163.com>
…kage entry

- pipeline/__init__.py: 统一 re-export 全部核心符号
- 支持 from pipeline import PipelineConfig, run_baseline_fake, ...
- 清理 SDK live 运行产生的垃圾文件(baseline_prompts/ 等)
- 317 tests 保持全绿,零回归

Signed-off-by: popo <18682875253@163.com>
…gful sample report

- README: 三场景演示、工作原理、模块地图、CLI 参数、验收标准对照
- DESIGN: comparator/三场景/6 维度 gate/live 降级说明
- ai-prompts: 补充第 5 轮(trace 回放评测、三场景、过拟合拒绝)
- attribution: 修复 by_category 序列化(枚举 .value)
- sample_output: 有意义的默认报告(失败+归因+候选+gate ACCEPT)
- .gitignore: 忽略 SDK live 运行产物

Signed-off-by: popo <18682875253@163.com>
@coder-mtj
coder-mtj force-pushed the feat/issue-91-eval-optimize-loop branch from a4c5a20 to 41e84f8 Compare August 2, 2026 13:12
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我已经掌握了足够的信息。让我来整理最终的审查意见。

发现的问题

🚨 Critical

  • examples/optimization/eval_optimize_loop/pipeline/baseline.py:141-145:live 模式调用 AgentEvaluator.evaluate_eval_set 时未传必填的 eval_config

    • SDK 在 eval_config is None 时直接 raise ValueError("eval_config is required.")(见 _agent_evaluator.py:517),因此 live baseline 必然抛异常落入 except Exception 降级分支,所谓"真实评测"实际从未执行,永远走 trace 回放兜底。需构造 EvalConfig(或从 optimizer.json 的 evaluate 段加载)后传入。
  • examples/optimization/eval_optimize_loop/pipeline/baseline.py:155-166:读取 EvalCaseResult 用了不存在的属性,导致 live baseline 全部误判为失败

    • EvalCaseResult 只有 final_eval_statusEvalStatus 枚举)和 error_message,没有 passedfailure_reason(见 _eval_result.py:158-205)。getattr(cr, "passed", False) 恒为 Falsegetattr(cr, "failure_reason", "") 恒为空串,使 passed=0、所有 case 进 failed_case_ids,pass_rate 恒为 0。应改为 cr.final_eval_status == EvalStatus.PASSED
  • examples/optimization/eval_optimize_loop/pipeline/optimize.py:221-236:live 模式从 SDK OptimizeResult 读取的字段名几乎全部不匹配,结果被静默清零

    • SDK OptimizeResulttotal_llm_cost/total_rounds/best_prompts/status/pass_rate_improvement,无 total_cost/converged/total_iterations/optimized_fields/best_prompt(见 _optimize_result.py:167-281);RoundRecordround/validation_pass_rate,无 index/score/best_so_fargetattr 默认值掩盖了错误,使 live 优化的成本、轮次、best_prompt、rounds 评分全部为 0/空,报告失真。应按 SDK 实际字段映射。

⚠️ Warning

  • examples/optimization/eval_optimize_loop/tests/test_live_mode_import.py:22-43:live 模式测试断言过弱,未覆盖真实风险路径

    • 两条 live 测试仅断言"不抛异常 / 返回 OptimizeResult",而上面三个 Critical 恰恰会被 getattr 默认值和降级分支掩盖,测试全部通过却无法发现 live 模式完全失效。建议增加断言:baseline 传 eval_configresult.errors 为空且 passed_cases>0;optimize live 在 SDK 可用时校验 total_iterations/best_prompt 非默认零值。
  • examples/optimization/eval_optimize_loop/pipeline/baseline.py:137-139open(evalset_path, ...) 未用 with 关闭文件句柄

    • EvalSet.model_validate_json(open(evalset_path, encoding="utf-8").read()) 打开后未关闭,长期/批量 live 评测会泄漏句柄。改用 with open(...) as f:
  • examples/optimization/eval_optimize_loop/pipeline/optimize.py:186-192:在 import 期向 sys.path 插入路径并静默吞掉所有异常

    • 路径推算注释说"4 级",但 os.pardir 连乘 4 次在 pipeline/ 下得到的是项目根——逻辑虽对,但 except Exception: pass 会掩盖真实路径错误,且 sys.path.insert(0, ...) 是全局副作用、模块导入即执行,易污染其它测试。建议只在真正需要 live SDK 时执行,并记录 warning 而非静默。

💡 Suggestion

总结

fake/trace 模式逻辑完整且有较扎实测试覆盖;但 live 模式存在三处与 SDK 实际 API 不匹配的 Critical 缺陷(缺 eval_configEvalCaseResult/OptimizeResult 字段名错误),导致 live baseline 永远降级且全部误判失败、live optimize 结果被静默清零,必须修复。同时现有 live 测试断言过弱,未能拦截这些问题。

测试建议

  • 补充 live 模式"契约测试":用 mock 的 AgentEvaluator.evaluate_eval_set / AgentOptimizer.optimize 返回符合 SDK schema 的对象,断言 run_baseline_sdk/run_optimize_live 正确映射 final_eval_status→pass、total_llm_cost→cost、best_prompts→best_prompt、round→round_index 等字段。
  • 补充 eval_config 缺失场景的回归:确认 live baseline 在传入合法 EvalConfigerrors 为空,而非依赖降级分支通过测试。

open(evalset_path, encoding="utf-8").read()
)
# trace 模式离线评测:evaluate_eval_set 返回 per-case 结果
_, _, _, case_results = await AgentEvaluator.evaluate_eval_set(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

live baseline 调用 evaluate_eval_set 未传必填 eval_config

SDK 在 eval_config 为 None 时直接抛 ValueError,使 live baseline 必然落入 except 降级分支,真实评测从未执行、永远走 trace 回放兜底。需构造 EvalConfig(或从 optimizer.json 的 evaluate 段加载)后传入。

for case_id, results in (case_results or {}).items():
for cr in results:
total += 1
ok = getattr(cr, "passed", False)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

读取 EvalCaseResult 用了不存在的属性导致全部误判失败

EvalCaseResult 只有 final_eval_status 和 error_message,没有 passed/failure_reason;getattr 默认值使 passed 恒为 0、所有 case 进 failed_case_ids,pass_rate 恒为 0。应改为 cr.final_eval_status == EvalStatus.PASSED。

)

# Extract results
result.total_cost = getattr(opt_result, 'total_cost', 0.0)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

live optimize 读取 OptimizeResult 字段名不匹配被静默清零

SDK OptimizeResult/RoundRecord 字段名与代码读取的几乎全部不匹配,getattr 默认值掩盖错误,使成本、轮次、best_prompt、rounds 评分全部为 0/空,报告失真。应按 SDK 实际字段映射。

Critical fixes:
- baseline.py: pass required eval_config to evaluate_eval_set (was always falling back)
- baseline.py: use EvalCaseResult.final_eval_status instead of nonexistent 'passed'
- optimize.py: map SDK OptimizeResult fields correctly (total_llm_cost/total_rounds/best_prompts/rounds)

Other:
- config.py: add load_optimize_config() to build EvalConfig from optimizer.json
- baseline/optimize/config: extract sys.path setup into named helpers (no silent except)
- validate.py: move copy import to module top
- run_pipeline.py: load EvalConfig in live mode
- tests: add live-mode contract tests (mock SDK, verify field mapping)

319 tests pass

Signed-off-by: popo <18682875253@163.com>
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我已经收集了所有经过验证的发现,并附上了行号。让我来编写审查报告。

发现的问题

⚠️ Warning

  • examples/optimization/eval_optimize_loop/pipeline/optimize.py:232:live 模式 converged 判定错误,且被 mock 测试固化

    • SDK OptimizeResult.status 取值为 "SUCCEEDED"/"FAILED"/"CANCELED"(见 trpc_agent_sdk/evaluation/_optimize_result.py:19),而此处比较 == 'accepted',导致 live 模式即使成功也恒为 converged=Falsetests/test_live_mode_import.pytest_optimize_maps_sdk_fieldsstatus="accepted" 的 mock 值断言 converged is True,反而锁死了这个错误映射。应改为 == "SUCCEEDED",并修正该 mock 用真实 SDK 状态值。
  • examples/optimization/eval_optimize_loop/pipeline/baseline.py:163 与数据 schema 不兼容,live baseline 始终降级

    • EvalSet 及其 IntermediateData.tool_uses(类型为 list[FunctionCall],字段为 name/args) 配置了 extra="forbid"trpc_agent_sdk/evaluation/_common.py:37),而 evalset 数据里 tool_uses 用的是 {"tool_name", "arguments"}tool_responses{"result"}(如 data/train.evalset.json:251),model_validate_json 必然抛 ValidationErrorexcept Exception 吞掉并降级到 trace comparator。结果是 live 模式 baseline 实际从不走真实 SDK 评测,errors 里只留一条降级提示。建议将数据字段对齐 SDK schema(name/argsresponse),或在降级时显式记录 schema 失败原因。
  • examples/optimization/eval_optimize_loop/pipeline/comparator.py:215-260:工具结果校验对真实数据是死代码,工具类归因永不触发

    • _tool_result_text 只读 tool 字典的 result/output/response,但真实 tool_uses 条目只有 tool_name/arguments,结果存在独立的 tool_responses 字段(comparator.py 完全未读取)。因此 _compare_tools 的结果比较与 _tool_result_vs_answer 在真实 evalset 上恒返回通过,所有工具类失败最终都被归因为 final_response_mismatchtest_gold_verdicts.py 的黄金表也印证了 train_tool_*_fail 全部落入 final_response_mismatch)。tests/test_comparator.pytest_tool_result_vs_answer/test_wrong_tool_selected 用自造的 {"name","result"} 字典跑通,给出了“工具归因有效”的假象。建议 comparator 解析 tool_responses 或按 SDK FunctionCall/FunctionResponse 字段取值,并让测试基于真实数据结构。
  • examples/optimization/eval_optimize_loop/run_pipeline.py:238:live 模式候选验证仍走 fake 场景,未真正评估优化后的 prompt

    • 不论 --mode 为何,Stage 5 始终调用 run_validation_trace,它按 scenario 用期望/扰动重写 actual_conversation 来“模拟”候选(pipeline/validate.py:139-176),从不使用 optimize_result.best_prompt 对验证集做真实重评。因此 live 模式下产出的优化 prompt 与最终 gate 决策之间没有真实联系,gate 的 train 提升与 val 回归都是模拟值。若 live 模式意在演示真实闭环,应在 live 分支用优化后 prompt 重新评测。
  • examples/optimization/eval_optimize_loop/run_pipeline.py:89-93pipeline/validate.py:172-176overfit 场景未传 --val-regression-cases 时会误 ACCEPT

    • _apply_scenariooverfit + not is_train 时仅扰动 val_regression_cases 中的 case;该列表默认为空(config.py:54),导致候选 val 与 baseline 完全一致、new_failures=0,而 train 又被“记住”产生正提升,gate 会 ACCEPT 一个本应表示过拟合的候选。建议在 overfit 场景下未提供回归 case 时给出告警或自动选取若干 case 扰动。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/tests/test_gold_verdicts.py:黄金表由 comparator 自身判定生成(注释亦自述“作为回归锁”),test_attribution_accuracy 断言 ≥90% 实质是“代码与自己一致”,只能防漂移、不能验证归因正确性。若验收标准 feature: support a2a #4 要求真实归因准确率,应保留一份人工标注的黄金表而非自动生成。

总结

整体无安全或致命逻辑缺陷,fake 模式闭环自洽;但 live 模式存在多处与 SDK 实际 schema/状态值不匹配的问题(converged 恒假、EvalSet 校验必失败而降级、工具结果校验为死代码),且相关 mock 测试用错误期望值固化了这些 bug,属于建议修复的兼容性与测试有效性问题。

测试建议

  • 补充一个用真实 SDK OptimizeResultstatus="SUCCEEDED"tool_usesFunctionCall schema)驱动 run_optimize_live/run_baseline_sdk 字段映射的契约测试,替换当前用 "accepted"/自造 tool 字典的 mock,确保 live 映射对真实 SDK 成立。
  • --scenario overfit 不带 --val-regression-cases 的情形补一个断言(应告警或拒绝),覆盖当前会被误 ACCEPT 的路径。

…erfit default (AI review round 2)

- optimize.py: converged now checks SDK status == 'SUCCEEDED' (not 'accepted')
- comparator.py: _compare_tools reads tool_responses (real evalset structure); numeric comparison with rounding tolerance
- validate.py: overfit scenario auto-perturbs 2 val cases when --val-regression-cases empty (was mis-ACCEPT)
- test_gold_verdicts: train_tool_002_fail now correctly attributed to tool_call_error
- tests: mock SDK status updated to 'SUCCEEDED'; tool test cases use real data structure

319 tests pass; three scenarios give ACCEPT/NEEDS_REVIEW/REJECT

Signed-off-by: popo <18682875253@163.com>
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

发现的问题

🚨 Critical

未发现明确阻塞问题。代码逻辑、过拟合检测、SDK 字段映射均与仓库上下文(trpc_agent_sdk/evaluation/_agent_optimizer.py_optimize_result.py_agent_evaluator.py)一致,三类场景(fix_attributed/noop/overfit)的状态流转与 gate 决策正确。

⚠️ Warning

  • examples/optimization/eval_optimize_loop/run_pipeline.py:238-246:live 模式下验证阶段未使用真实优化后的 prompt

    • run_optimize_live 拿到的 optimize_result.best_prompt(GEPA 真实产物)在后续从未被使用;run_validation_trace 在 fake/live 两种模式下都用 scenario 模拟生成候选 actuals,而非回放真实优化 prompt。结果是 live 模式的 gate 决策(improvement/overfitting)基于模拟候选,不反映实际优化效果,可能与真实优化结论相悖。建议 live 模式下用 best_prompt 驱动 agent 重评 val/train,或在文档/CLI 明确标注 live 验证仍为模拟。
  • examples/optimization/eval_optimize_loop/run_pipeline.py:77-78, 112--holdout-evalset 被接收但从不评分

    • 参数 help 写 "Holdout set (optional, scored in report)",但 holdout_evalset 仅存入 PipelineConfig,pipeline 全程未加载/评测/写入报告,用户会被 help 文案误导。建议要么真正加载并评分 holdout,要么删除该参数并修正 help。
  • examples/optimization/eval_optimize_loop/pipeline/validate.py:125-171_apply_scenariofixed_categories 参数为死参数

    • 函数签名接收并在 run_validation_trace 中透传 fixed_categories,但函数体仅按 scenario/candidate_conversation 分支处理,从未读取该参数;fix_attributed 实际是"修复全部 train 失败 case"而非"仅修复归因类别"。这会误导维护者以为候选只修复指定类别,也使 optimize_result.fixed_categories 的语义与验证行为不一致。建议要么真正用 fixed_categories 过滤要修复的 case,要么移除该参数。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/run_pipeline.py:158-165, 209-211:live 模式多次独立调用 asyncio.run(两次 baseline + 一次 optimize),且 build_call_agent() 被实例化两次。可合并为单个 async 入口顺序 await,复用同一 call_agent,避免重复构造与多事件循环开销;不影响正确性。

总结

整体风险较低,无必须修复的阻塞问题;fake/trace 闭环与三类场景逻辑正确且与 SDK API 对齐。主要遗留集中在 live 集成路径:真实优化 prompt 未参与验证/gate、holdout 参数空转、fixed_categories 死参数,建议在 live 模式正式启用前处理。

测试建议

  • 补充 live 模式验证路径测试:用 mock SDK 注入带 best_promptsOptimizeResult,断言验证/gate 基于真实 prompt 而非 scenario 模拟(覆盖上述第一条 Warning)。
  • 若保留 holdout 参数,补一条端到端测试断言 holdout 在报告中出现评分;否则建议删除参数。

… param (AI review round 3)

- run_pipeline.py: load and score --holdout-evalset via comparator; write to report audit
- validate.py: remove unused fixed_categories param from _apply_scenario
- three scenarios still give ACCEPT/NEEDS_REVIEW/REJECT; 319 tests pass

Signed-off-by: popo <18682875253@163.com>
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

关键细节:to_dict() 会调用 finalize(),而 run_pipeline.py:314 调用了 to_dict(),随后在 :360 处又调用了 finalize()finalize() 会从 time.monotonic() 重新计算 total_duration_s。因此,第二次调用会得到一个略大的持续时间值。这没有危害(幂等性)。但需要注意:to_dict():314 处冻结了 reproduce_command 等信息到 audit_dict 中,然后 :319 处的 audit_dict.update(...) 又用同一个字段覆盖了 reproduce_command。不会导致重复计算。没问题。

此外,finalize() 中的 reproduce_command 只包含 --mode 和(非默认时的)--seed,而忽略了 --scenario--max-iterations 等。因此,对于非默认场景,复现命令是不完整的——通过该命令无法复现非默认场景的运行结果。这是一个真正的复现审计缺陷。让我确认一下:

确实,finalize() 只输出 --mode {mode} 和条件性的 --seed。像 --scenario overfit--max-iterations 5--val-regression-cases 等参数丢失了。因此,“复现命令”无法真正复现运行结果。这是一个真实的复现性缺陷,值得给出一个 Warning。

让我给出最终的审查意见。

发现的问题

🚨 Critical

无。

⚠️ Warning

  • examples/optimization/eval_optimize_loop/pipeline/tracing.py:163-167:复现命令缺失关键参数,无法真正复现非默认配置的运行。

    • finalize() 只拼了 --mode 和非默认 --seed,遗漏 --scenario--max-iterations--val-regression-cases--train/val-evalset 等影响结果的参数。审计字段名为 reproduce_command 且会写入报告,但用该命令无法复现 overfit/noop 等场景,与“完整可复现审计”的目标不符。建议把 load_pipeline_config 用到的所有非默认参数都纳入拼装。
  • examples/optimization/eval_optimize_loop/run_pipeline.py:372:CI 模式只在 REJECT 时退出非零,NEEDS_REVIEW 被当作成功。

    • --ci 的语义是“gate 不通过则失败”,但 NEEDS_REVIEW(改进未达阈值 / 引入新失败但未到拒绝)同样意味着候选不可直接采纳,CI 仍返回 0,可能放行有风险的候选。建议明确 NEEDS_REVIEW 在 CI 下是否也应非零退出,或在文档/help 中说明 CI 仅拦截 REJECT。
  • examples/optimization/eval_optimize_loop/pipeline/gate.py:82-96critical_case_ids 保护只作用于 train 集新增失败,无法覆盖仅 val 集回归的关键 case。

    • newly_failed = set(candidate_failed) - set(baseline_failed) 用的是 train 失败 id,而关键 case 通常定义在验证集上;若某关键 case 仅在 val 上退化,不会触发 critical 拒绝(只会被 validation_new_failures 统一拒绝,丢失“关键 case”这一维度信息)。建议将关键 case 检查同时作用于验证集 delta 的 new_fail

💡 Suggestion

  • examples/optimization/eval_optimize_loop/pipeline/optimize.py:174converged = total_iterations < max_iterations 把“迭代未达上限”等同于“收敛”,语义偏弱;在 noop(0 迭代)下 converged=False 而零失败时 converged=True,二者口径不一致。若该字段对外暴露,建议改为基于 best_score 是否稳定的更明确判定,或在文档注释中限定其含义。

总结

整体实现成熟、测试覆盖充分(gold verdict 锁、live 模式 SDK 字段映射契约测试、三场景端到端验证),live 模式对 AgentOptimizer.optimize / evaluate_eval_set 的调用签名与 SDK 源码一致。未发现阻塞级正确性或安全问题;主要风险集中在审计复现命令不完整与 CI 门控语义偏宽松两点,建议修复。

测试建议

  • 增加用例验证 finalize() 生成的 reproduce_command 能在 overfit/noop/自定义 --max-iterations 场景下复现相同 gate 决策。
  • 增加 CI 模式下 NEEDS_REVIEW 退出码的断言,固化预期 CI 行为。

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

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

- tracing: keep injected reproduce_command covering all non-default CLI args
  instead of overwriting with the minimal mode/seed fallback
- run_pipeline: CI mode exits 2 on NEEDS_REVIEW (REJECT stays 1)
- gate: extend critical-case protection to validation-set regressions and
  wire --critical-cases CLI arg so the protection is reachable
@coder-mtj
coder-mtj force-pushed the feat/issue-91-eval-optimize-loop branch from 6c63b87 to f0eb2fa Compare August 3, 2026 01:40
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

测试方面没有安全问题。我已经充分验证了代码评审结果。现在让我整理最终报告。

发现的问题

🚨 Critical

  • examples/optimization/eval_optimize_loop/pipeline/validate.py:277-321run_pipeline.py:311:live 模式下验证阶段使用模拟候选而非真实优化后 prompt
    • 无论 --mode fake 还是 liverun_pipeline.py:311 都调用 run_validation_trace,它按 scenario 合成候选 actual_conversationvalidate.py:148-167_apply_scenario),而非基于 optimize_result.best_prompt 真实重跑 Agent。结果:live 模式下的过拟合检测、新失败统计和最终 gate 决策都来自模拟数据,不代表真实优化效果,可能让不合格 prompt 通过或拒绝合格 prompt。建议在 live 模式用优化后 prompt 真实重评 train/val(复用 run_baseline_sdk/trace 回放),否则明确标记 live 验证为不支持并跳过 gate。

⚠️ Warning

  • examples/optimization/eval_optimize_loop/pipeline/gate.py:76-81:improvement<0 时提前 REJECT,导致 checks 3-6 未追加到审计

    • improvement < 0 立即返回,此时 checks 仅含 improvement_threshold,critical_cases / new_failures / overfitting / cost_budget 检查从未执行。报告会静默丢失同次退化运行中是否还伴随关键 case 回归、过拟合等信息,破坏审计完整性。建议先计算全部 check 再按结果分支,或在提前返回前补齐其余 check。
  • examples/optimization/eval_optimize_loop/pipeline/optimize.py:174converged 用迭代轮数代理收敛,语义错误

    • result.converged = result.total_iterations < config.max_iterations。当 len(categories_to_fix) == max_iterations 时所有类别已修复却报 converged=False;反之一个无效优化器在仅 1 个失败类别时跑满 max_rounds=1(<max_iterations=3)却报 converged=True。该标志被写入报告并影响验收判断。建议由分数稳定度或归因覆盖率(如 len(fixed_categories) >= len(categories_to_fix))推导,而非仅看轮数。
  • examples/optimization/eval_optimize_loop/pipeline/tracing.py:159-166finalize() 非幂等,报告写入的 total_duration_s 与终端打印不一致

    • run_pipeline.py:375to_dict()(内部 finalize(),把当时时长写入报告 JSON),随后 run_pipeline.py:421 再次调 finalize() 用更晚的时间重算 total_duration_s 供打印。两处时长会有微小差异,且报告里的是较早值。建议 finalize() 增加幂等保护(已 finalize 则不重算)或复用同一对象。
  • examples/optimization/eval_optimize_loop/pipeline/comparator.py:525-530:长度不一致失败用硬编码优先级 99,与 _CATEGORY_PRIORITY 不一致

    • 期望 invocation 多于实际时插入 (99, MISSING_EXPECTED_OUTPUT, ...),但表中该类别优先级为 5(数字越小越优先)。当与其它失败并存时,硬编码 99 会让"缺失输出"根因被覆盖掉。应改用 _CATEGORY_PRIORITY.get(FailureCategory.MISSING_EXPECTED_OUTPUT, 99),或在注释明确此处刻意最低优先级。
  • examples/optimization/eval_optimize_loop/pipeline/comparator.py:13pipeline/attribution.py:12:两套同名 FailureCategory 并存且都被导出

    • comparator 中为 class FailureCategory(str)(类属性值本身是 str 而非实例),attribution 中为 class FailureCategory(str, Enum)__init__.py:20 只导出 comparator 版本,而 AttributionEntry.category 用 attribution 的 Enum。当前因字符串值一致、归因侧用 _CATEGORY_MAP 做字符串映射未出错,但跨模块 isinstance/相等性语义不一致,维护者易误判类型。建议统一为单一来源。

💡 Suggestion

总结

存在 1 个 Critical:live 模式验证/gate 基于模拟候选而非真实优化后 prompt,使 live 模式的核心验收结论不可信,建议优先修复。其余为审计完整性(gate 提前返回、finalize 非幂等)、语义错误(converged)与根因优先级不一致等 Warning,可在示例范围内逐步改进。

测试建议

  • 补充 live 模式端到端测试:断言 live 模式下 run_validation_trace 的候选评估基于 optimize_result.best_prompt 而非 scenario 合成数据(可用 mock AgentEvaluator 验证调用次数与输入 prompt)。
  • 补充 converged 边界用例:len(categories_to_fix) == max_iterations 时应判定为已收敛,以及失败类别少于 max_iterationsconverged 的预期值,锁定语义。

…roup#139

- gate: compute all 6 checks before branching so audit detail survives
  early REJECT paths (no_degradation/critical_cases/new_failures/
  overfitting/cost_budget always recorded)
- optimize: converged by attribution coverage, not iteration-cap proxy
- tracing: make finalize() idempotent so report and terminal duration match
- comparator: use _CATEGORY_PRIORITY for MISSING_EXPECTED_OUTPUT instead of
  hardcoded 99 priority
- run_pipeline: explicitly warn when live-mode validation/gate runs on
  scenario-simulated candidates (honest labeling per review Critical)
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

README 声称 "Live mode: 真实 SDK AgentOptimizer (需配置 TRPC_AGENT_API_KEY)",但 build_call_agent 总是使用 AgentConfig() 且 model_name="fake",且 _live_run 未实现。因此,live 模式的 agent 实际上从未调用真实的 LLM —— 优化器的反射 LM 在 optimizer.json 中配置为 "fake" 模型。因此,live 模式实际上并没有运行“真实 SDK + 真实 LLM”。不过 run_pipeline.py 中的注释确实承认了这一点:“live 模式:验证/门控使用的是场景模拟的候选者,而非真实的优化提示词”。README 在这一点上有些误导,但代码本身对这一局限性是坦诚的。这顶多算是一个文档/建议层面的问题。

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

关键发现总结:

  1. 基于时间的性能测试断言 (test_performance.py) —— 可能会导致 CI 不稳定(flaky)。属于警告(Warning)级别。
  2. 实时模式(live-mode)的 holdout 使用了 run_baseline_fake 而非 SDK —— 存在不一致,但影响较小。属于建议(Suggestion)级别。
  3. README 对 live 模式的描述不够准确。属于建议(Suggestion)级别。
  4. _STRIP_CHARS 是死代码。由于过于琐碎,不予提及。

让我再仔细检查一个潜在的真正 Bug:在 gate.py 中,validation_new_failed 被用于 critical_val_regressed,但在 run_pipeline.py 中,validation_new_failed 被计算为 [d.eval_id for d in validation.deltas if d.change == "new_fail"]。这些是验证集(val set)的新失败案例。关键案例保护机制覆盖了训练集和验证集。没问题。

让我核实一下是否存在可能导致除以零的 pass_rate 格式化问题:baseline_train.pass_rate:.1% —— pass_rate 是浮点数,没问题。但如果 total_cases 为 0,baseline_train.passed_cases/total_cases —— 在 run_baseline_fake 中由 passed / total if total > 0 else 0.0 防护。没问题。

让我检查最后一个真正值得关注的问题:run_pipeline.py 的实时模式在配置加载阶段没有使用 try/except 对 load_evalset 进行防护 —— 等等,它在配置阶段确实有 try/except (FileNotFoundError, ValueError)。但 load_pipeline_config 本身不会进行文件读取。没问题。

让我核实一下测试中的 test_optimize_maps_sdk_fields —— 它对 trpc_agent_sdk.evaluation._optimize_config 进行了 monkeypatch,但 run_optimize_live 并没有导入该模块(它导入的是 trpc_agent_sdk.evaluation)。所以这个 monkeypatch 是无害的。实时优化路径使用了 from trpc_agent_sdk.evaluation import AgentOptimizer, TargetPrompt。模拟模块(fake module)提供了这些。没问题。但是 run_optimize_live 首先调用了 _ensure_import_paths(),它会将仓库根目录插入到 sys.path 中 —— 如果安装了真正的 SDK,trpc_agent_sdk 可能已经缓存了真实的模块。monkeypatch.setitem(sys.modules, "trpc_agent_sdk.evaluation", _FakeEvalModule) 替换了它。但 trpc_agent_sdk.evaluation._optimize_config 可能没有被预加载。run_optimize_live 内部的导入只会导入 AgentOptimizerTargetPrompt,而不是 _optimize_config。所以没问题。

等等 —— 在 run_optimize_live 中,在 try 代码块内部,它执行了 from trpc_agent_sdk.evaluation import AgentOptimizer, TargetPrompt。由于 monkeypatch 已经设置了 sys.modules["trpc_agent_sdk.evaluation"],此导入会返回模拟模块。很好。但 trpc_agent_sdk 本身呢(父包)?如果之前没有导入过,from trpc_agent_sdk.evaluation import ... 会需要 trpc_agent_sdk 在 sys.modules 中。在测试中,只对 trpc_agent_sdk.evaluation 进行了 monkeypatch。如果 trpc_agent_sdk 没有被加载,导入会失败。但在实践中,conftest 导入了 pipeline.config,后者调用了 _ensure_repo_root_in_path 但并没有导入 trpc_agent_sdk。然而,之前的测试 test_baseline_maps_eval_status_to_pass 对其进行了 monkeypatch,而在 load_pipeline_config 中并没有 trpc_agent_sdk 的导入。嗯,这可能会导致测试顺序问题。但这属于测试代码的健壮性,而非 PR 代码本身。除非确认了,否则不值得标记。

让我通过检查测试是否实际能通过导入来验证一下 —— 实际上我无法运行。测试 _FakeEvalModuleAgentOptimizer 具有一个异步的 optimize 静态方法。run_optimize_live 执行了 await AgentOptimizer.optimize(config_path=..., call_agent=..., target_prompt=..., ...)。模拟模块返回了模拟结果。然后它映射字段。应该可以正常工作。关于 trpc_agent_sdk 父包的问题:monkeypatch.setitem(sys.modules, "trpc_agent_sdk.evaluation", _FakeEvalModule) —— 但要使 from trpc_agent_sdk.evaluation import X 起作用,Python 需要绑定 trpc_agent_sdk。如果 trpc_agent_sdk 不在 sys.modules 中,Python 会尝试导入它。由于源码包位于仓库根目录(通过 _ensure_import_paths 加入 sys.path),导入真实的 trpc_agent_sdk 会成功,然后访问 trpc_agent_sdk.evaluation 会找到被 monkeypatch 过的模块。所以这可以工作。很好。

我现在对我的发现充满信心。让我再仔细检查一下 _check_format 中关于 "markdown" 的情况以及仅限 JSON 输出的需求 —— 但数据中没有这些情况。没问题。

让我最后检查一下 report.pygenerate_json_reportoptimization_result 参数是否与 optimization_info 字典匹配 —— run_pipeline 传入了 optimization_info。没问题。

让我核实一下是否存在 gate.details["checks"] 的 KeyError 问题 —— gate.details 总是包含 "checks",因为每个返回路径都包含了它。没问题。

现在让我再核实一个真正的 bug 候选:在 evaluate_gate 中,newly_failed = set(candidate_failed) - set(baseline_failed)。在 run_pipeline.py 中,传入的 baseline_failed=baseline_train.failed_case_idscandidate_failed=candidate_train.failed_case_ids。对于 fix_attributed 场景,候选者修复了失败(减少了失败),因此 newly_failed 为空 → 可接受。很好。

我已经完成了审查。让我来写出审查结论。

发现的问题

⚠️ Warning

  • examples/optimization/eval_optimize_loop/tests/test_performance.py:160-318:性能测试使用硬编码墙钟断言(50 case ≤5s、100 case e2e ≤15s、report ≤2s)做 assert elapsed < N,在繁忙/共享 CI runner 上极易 flaky 失败。这些断言锁的是“纯 Python 评测快”,但阈值偏紧且与机器负载强相关;建议改为更宽松的预算或仅做 smoke 断言(如 < 60s),或用 pytest.mark.slow 隔离避免阻塞主 CI。

  • examples/optimization/eval_optimize_loop/run_pipeline.py:5916(附近,holdout 评分块):live 模式下 holdout 仍走 run_baseline_fake,与同模式下 train/val 走 run_baseline_sdk 不一致,导致 live 报告里 holdout 与 baseline 语义不可比。建议在 live 模式下对 holdout 同样使用 run_baseline_sdk(或显式标注 holdout 为 trace-comparator 评分),避免审计数据误导。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/README.md(live mode 说明附近行):README 宣称 live 模式为“真实 SDK AgentOptimizer(需配置 TRPC_AGENT_API_KEY)”,但 agent/agent.pybuild_call_agent 始终用 AgentConfig(model_name="fake")_live_run 未实现,且 optimizer.json 的 reflection_lm 为 fake,因此 live 模式实际不会跑真实 LLM。代码内已有诚实标注(run_pipeline.py 的 warning),建议同步在 README 注明 live 模式当前为“SDK 接线 + 离线确定性 agent”的占位实现,避免使用者误以为可端到端真实优化。

总结

整体实现完整、测试覆盖扎实,核心评测/归因/门控逻辑与 SDK 字段映射均与仓库上下文一致,未发现安全或明确逻辑错误。主要风险是性能测试的紧墙钟断言可能在 CI 上 flaky,以及 live 模式 holdout 评分路径与 baseline 不一致——均非阻塞性问题。

测试建议

暂无额外测试建议;现有 test_gold_verdicts.pytest_scenarios.pytest_live_mode_import.py 已较好覆盖判定准确性、三类场景与 live 降级。若采纳上述 Warning,建议补一条“holdout 在 live 模式下与 train/val 同源评分”的一致性断言。

The live graceful-degradation blocks did 'from pipeline.baseline import
run_baseline_fake' / 'from pipeline.optimize import OptimizeResult' inside
main(). Python treats a from-import as a local binding for the whole
function, so the fake-mode path referenced run_baseline_fake before it was
assigned → UnboundLocalError crash. Both names are already imported at
module top; removed the inner imports. Verified fake (ACCEPT) and live
(degrade) both exit 0.
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

测试执行需要批准;我将跳过它,并依靠静态审查。我已经彻底检查了代码。现在开始撰写审查结论。

基于我的全面审查,代码质量很高且经过了充分测试。我发现了一些值得标记的微小问题。让我来最终确定审查结论。

发现的问题

⚠️ Warning

  • examples/optimization/eval_optimize_loop/pipeline/baseline.py:170-195run_baseline_sdk 在统计 total_cases/passed_cases 时按 EvalCaseResult(per-run)累加,而 failed_case_ids 按 case_id 追加。

    • num_runs > 1 或同一 case 有多个 result 时,total_cases 会等于"运行数"而非"case 数",且 failed_case_ids 会出现重复 id,导致 pass_ratetotal_cases 语义不一致、failed_case_ids 含重复项。当前 SDK 默认 NUM_RUNS=1 故不触发,但一旦调用方传入 num_runs>1 或 SDK 行为变化会产生错误统计。建议按 case_id 去重统计,或显式以 case 为单位聚合。
  • examples/optimization/eval_optimize_loop/run_pipeline.py:241-248:live 模式下连续两次 asyncio.run(run_baseline_sdk(...)) 分别跑 train/val。

    • 每个 asyncio.run 创建并销毁独立事件循环,效率低且若 run_baseline_sdk 内部注册了循环绑定的资源(如未关闭的 async client)可能在第二次 asyncio.run 时触发 "Event loop is closed" / 资源告警。建议合并为一次 asyncio.runasyncio.gather 并发跑两个 evalset,既规避循环复用问题也提速。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/pipeline/optimize.py:113-118:overfit 场景的 max_rounds = min(max_rounds + 1, len(categories_to_fix))max_iterations >= len(categories) 时退化为 len(categories)(+1 被 min 截断),与注释"可多修一轮"不一致;且收敛判定 fixed_categories 覆盖全部类别时 overfit 候选仍报 converged=True,语义上略误导。属 fake 模拟,不影响门控正确性,可按注释意图修正或澄清。

总结

整体代码质量较高、边界处理与降级路径覆盖完善、live 模式字段映射与 SDK 签名均与仓库上下文一致,未发现 Critical 级阻塞问题。两处 Warning(SDK 评测多 run 统计口径、连续 asyncio.run 事件循环复用)在默认参数下不触发,但建议修复以提升鲁棒性。

测试建议

  • 建议补充 run_baseline_sdknum_runs>1(mock SDK 返回多 run 结果)下的 total_cases/failed_case_ids 去重断言。
  • 建议补充 live 模式连续两次 asyncio.run 调用 baseline 的集成冒烟测试(可用 mock SDK),确认无事件循环/资源告警。

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我现在已经有了所有已确认的行号。让我来撰写最终的审查意见。

发现的问题

⚠️ Warning

  • examples/optimization/eval_optimize_loop/pipeline/baseline.py:147-153:SDK baseline 回退加载了硬编码的默认 data/optimizer.json,而非用户传入的 --optimizer-config

    • run_baseline_sdkeval_config is None 时,从模块文件相对路径拼出 data/optimizer.json 加载 EvalConfig。但 run_pipeline.py:585 处是先尝试 load_optimize_config(cfg.optimizer_config) 失败才传 None 进来——即用户自定义 optimizer 配置加载失败后,baseline 会静默改用默认配置的 metrics/阈值评分,而不是报错或继续用用户配置。建议将 optimizer_config_path 透传进 run_baseline_sdk,从用户指定的路径加载,或加载失败时显式记错而非替换默认配置。
  • examples/optimization/eval_optimize_loop/pipeline/validate.py:189_perturb_casefinal_responseNone 的 invocation 会 AttributeError 崩溃

    • inv.get("final_response", {}).get("parts", []) 仅在 key 缺失时返回 {};当 key 存在但值为 None(部分 evalset schema 允许)时 .get("parts", [])AttributeError。而 run_pipeline.pyrun_validation_trace 只捕获 ValueError,该异常会直接中断整条 pipeline。comparator._get_invocation_parts 已用 invocation.get("final_response") or {} 防护,此处应保持一致:
      parts = (inv.get("final_response") or {}).get("parts", [])
  • examples/optimization/eval_optimize_loop/pipeline/optimize.py:303-305run_optimize_live 末尾 except Exception):宽泛捕获把 pipeline 自身 bug 伪装成“优化失败”

    • run_baseline_sdk(仅捕获 (ValueError, KeyError, TypeError)、其余向上抛)不同,run_optimize_liveexcept Exception as e 吞掉所有异常(含 AttributeError/TypeError 等 SDK 字段映射错误),仅记入 result.errors 并返回空 OptimizeResult,pipeline 照常出报告。这会让 live 集成缺陷对用户完全不可见。建议收窄捕获范围或对非预期异常类型重新抛出。
  • examples/optimization/eval_optimize_loop/tests/test_large_scale.py:8856-8858:mock evalset 对乘除法算错了期望答案

    • expected = str(a + b) if "Multiply" not in template else str(a * b) 用大小写敏感的子串 "Multiply" 判断,而模板是 "What is {a} * {b}?""Divide {a} by {b}",都不含 "Multiply",于是乘法/除法题的期望都被算成 a+b。当前断言只查 passed+failed==total、未校验 comparator 对这些 case 的判定正确性,所以不会失败,但 mock 数据语义错误、且让乘除法 case 永远不真正测试数值比较。建议按运算符判断("*" in template / "/" in template)。
  • examples/optimization/eval_optimize_loop/tests/test_pipeline_overfit.py:10489-10510test_train_up_val_down_rejected 名不副实,实际得到 NEEDS_REVIEW 而非 REJECT

    • 用例注释声称“train 提升但 val 退化应 REJECT”,但传入的 candidate_failed=["val_critical_001"]、未传 validation_new_failures、未设 critical_case_ids,按 gate 逻辑走 newly_failed 分支只会得到 NEEDS_REVIEW;断言 != ACCEPT 仍通过,掩盖了 overfitting-REJECT 路径未被覆盖。建议补 validation_new_failures=1(或 critical_case_ids=["val_critical_001"])以真正触发 REJECT,否则改名为 _needs_review
  • examples/optimization/eval_optimize_loop/tests/test_baseline.py:8110-8127test_sdk_falls_back_to_trace_comparator 依赖 SDK 校验失败才回退,但该数据可被 SDK 正常接受

    • 测试数据为 eval_mode="trace" 且同时含 conversation+actual_conversation,符合 SDK EvalCase 校验(trace 合法 shape),evaluate_eval_set 会真正跑通(actual 与 expected 一致 → PASSED,total_cases==1)。也就是说断言通过并非因为“降级回退”,而是 SDK 正常成功;一旦 SDK 行为变化或不可用,断言即失效。建议 monkeypatch EvalSet.model_validate_json 强制抛错以确定性走回退分支,或改为断言 result.errors/回退证据。

💡 Suggestion

总结

整体未发现 Critical 阻塞问题;代码结构清晰、fake/trace 模式与 SDK 集成降级路径考虑较周全。主要风险集中在 live 模式回退用错配置(baseline.py:147)、_perturb_caseNone 防护缺失导致 pipeline 崩溃(validate.py:189),以及若干测试有效性问题(mock 算错答案、命名与实际行为不符、依赖环境)。前两条建议在合并前修复。

测试建议

  • 补一个 case:overfit 场景下 val case 的 final_response 显式为 None,验证 _perturb_case 不崩溃且 run_validation_traceValueError 分支而非 AttributeError
  • 补 live 模式回退测试:当用户 --optimizer-config 的 EvalConfig 加载失败时,断言 baseline 报错或仍使用用户配置,而非静默切换到默认 data/optimizer.json

…ent loop

- baseline.run_baseline_sdk: aggregate results per case_id (dedupe) instead of
  per-run, so total_cases/passed/failed are consistent and failed_case_ids has
  no duplicates when the SDK returns multiple runs per case (NUM_RUNS>1)
- run_pipeline: run train/val live baselines in one asyncio.run(asyncio.gather)
  instead of two separate event loops (avoids loop-reuse/resource warnings)
asyncio.run only accepts a coroutine; passing the gather future raised
'ValueError: a coroutine was expected', which made live baseline always
fall back to fake. Wrapped gather in an inner async function. Verified live
mode now degrades only for the genuine SDK schema mismatch.
Also adds a num_runs>1 per-case aggregation test.
…urb + narrow except

- baseline: run_baseline_sdk now uses the user's --optimizer-config via
  optimizer_config_path instead of silently falling back to default
  data/optimizer.json when eval_config is missing
- validate: _perturb_case guards final_response=None (was AttributeError crash)
- optimize: run_optimize_live narrows its catch to (ValueError/KeyError/TypeError)
  so pipeline bugs propagate instead of being masked as 'optimization failed'
- tests: fix math expected-answer operator detection; make overfit REJECT test
  actually reject (validation_new_failures=1)
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

这些是有效的回归测试。很好。

现在让我最后检查一下 run_pipeline.py 中的一个真正令人担忧的区域——holdout 是在 live 模式下通过 run_baseline_fake 进行评分的,但 train/val 使用的是 SDK。这一点已经被标记过了。没问题。

让我再检查一下 _apply_scenario overfit train 路径是否真的提高了 train 的通过率。对于 overfit,train 的情况会将 actual_conversation = conversation(预期结果)-> 所有 train 情况都通过 -> 100%。很好。而 val 回归情况则受到干扰 -> 失败。很好。

现在,一个真正值得关注的问题:在 run_validation_trace 中,overfit 场景会自动选择 val_cases[:2] 作为回归用例。但它使用的是 str(c.get("eval_id", ""))。如果 eval_id 缺失,就会变成 ""。然后 _apply_scenario 会检查 case_id in val_regression_cases。问题不大。

让我最后检查一下 compare_case 得分为 0 的情况——当对话为空但实际存在时。第 486-494 行:返回 passed=Falsecategory=MISSING_EXPECTED_OUTPUT。很好。

我已经完成了审查。让我整理一下最终的发现。真正实质性的问题:

  1. Warning — test_pipeline_overfit.py:15-28: test_train_up_val_down_rejected 测试名称/注释声称拒绝,但 gate 返回的是 NEEDS_REVIEW;断言较弱,实际上并没有测试 REJECT 路径。

  2. Warning — baseline.py:187-209 (live SDK 路径): 如果所有 SDK EvalCaseResults 都是 NOT_EVALUATED,baseline 会静默返回 total=0,pass_rate=0.0,且没有错误——pipeline 会继续执行,将一个空的 baseline 视为合法,可能导致错误的 gate 决策,且没有降级警告。

让我再重新考虑一下第 2 点是否真的可达/令人担忧。evaluate_eval_set 对于仅 trace 的情况应该会产生 PASSED/FAILED。NOT_EVALUATED 是针对缺少 trace 数据的情况。由于示例中的 evalsets 都有 actual_conversation,它们应该会被评估。所以这需要一个格式错误的 evalset。将其视为 Warning(稳定性)是合理的。

让我再重新考虑一下是否还有其他 Critical 问题。让我检查一下 _perturb_case 的返回值是否会被错误调用。第 167 行:new_case["actual_conversation"] = _perturb_case(case)_perturb_case 返回一个列表。没问题。

让我验证一下 tracing.pyrecord_input_file 的一个潜在问题——如果路径是相对路径且 CWD(当前工作目录)发生变化,open(path, "rb") 可能会失败。它捕获了 OSError。没问题。

现在让我再重新考虑一下 is_output_dir_allowed 是否存在真正的绕过漏洞。_out_abs.startswith(_root_abs + os.sep)。如果 _root_abs/repo_out_abs/repo-evil/x -> startswith("/repo/") 为 False -> 被拒绝。很好。+ os.sep 防止了前缀混淆。很稳健。

让我再检查一下 build_reproduce_command —— 它没有包含 --mode 的引号,但 args.mode 仅限于 ["fake","live"],所以是安全的。其他字符串使用 shlex.quote 是正确的。很好。

我已经完成了审查。让我撰写最终总结。让我再仔细检查一下 MD 报告的一个细节,当 audit 为空字典时,audit.get('improvement', 0.0)——但 audit 总是由 audit_dict 填充。没问题。

让我再验证一个潜在问题:run_pipeline.py 第 502 行 "errors": errors —— errors 列表包含了验证场景错误(第 399 行)和优化错误(第 350 行)。但是 tracer.to_dict() 已经输出了它自己的 errors(来自 add_error)。覆盖操作将 tracer 的错误替换为了本地的 errors 列表。由于优化错误同时存在于两者中,所以没问题。但是验证场景错误(str(_ve))仅存在于本地的 errors 中。因此它们出现在 audit_dict["errors"] 中,但没有出现在 tracer 的内部审计中。由于 audit_dict 是报告的内容,因此它是正确的。不是 Bug。

现在让我做最后的总结。让我重新考虑一下关于测试有效性的发现是否也适用于其他测试。查看 test_pipeline_overfit.py::test_train_up_val_down_rejected —— 这是最明显的例子。值得作为一个 Warning 提出。

让我写出审查结果。## 发现的问题

⚠️ Warning

  • examples/optimization/eval_optimize_loop/tests/test_pipeline_overfit.py:15-28test_train_up_val_down_rejected 名义上验证"train 提升 + val 退化 → REJECT",但实际未传 validation_new_failures,只传了 candidate_failed=["val_critical_001"]。按 gate.py:155-160 的优先级,train 侧 newly_failed 命中只会走 NEEDS_REVIEW,断言却用 != GateDecision.ACCEPT 这种弱条件通过,并未真正覆盖 REJECT 路径。

    • 建议改为断言 == GateDecision.REJECT,并通过 validation_new_failures=1 + validation_new_failed=["val_critical_001"] 触发过拟合 REJECT,使测试与验收标准 chore: 修改新增 example 安装命令 #3 真实对应。
  • examples/optimization/eval_optimize_loop/pipeline/baseline.py:187-209:live SDK 评测路径在所有 EvalCaseResult 均为 NOT_EVALUATED 时会跳过全部 case,返回 total=0 / pass_rate=0.0 / errors=[]。由于 run_pipeline.py:274 仅在 baseline_train.errors 非空时才打印降级提示,这种"全量未评测"的空结果会被当作合法的 0% baseline 静默推进到 gate(可能被误判为退化 REJECT 或 NEEDS_REVIEW),且无任何 warning。

    • 建议在 total == 0 且无 error 时追加一条 result.errors(如"all cases NOT_EVALUATED"),让 live 模式空评分能触发既有降级提示路径。
  • examples/optimization/eval_optimize_loop/run_pipeline.py:259-260:live baseline 异常降级后用 baseline_train.errors = [_msg] 直接覆盖,会丢弃 run_baseline_fake 自身返回的 errors(如 evalset 文件缺失/解析失败时本应上浮的原始错误),使降级原因之外的失败信号丢失。

    • 建议改为 baseline_train.errors = [_msg] + baseline_train.errorsbaseline_train.errors.append(_msg),保留 fake 回退路径的真实错误。

总结

整体逻辑稳健,fake/trace 模式闭环、过拟合检测、路径安全防护与审计追踪实现完整且有测试覆盖;未发现 Critical 级别问题。上述 Warning 主要集中在 live 模式空评分的静默风险、降级时原始错误丢失,以及一个名义验证 REJECT 实则只验证 NEEDS_REVIEW 的弱测试,建议修复以避免误判与信号丢失。

测试建议

  • 补充一个 live SDK 评测"全部 NOT_EVALUATED"场景的测试(monkeypatch 注入全 NOT_EVALUATED 的 case_results),断言 run_baseline_sdk 返回非空 errors,避免空评分静默通过。
  • test_train_up_val_down_rejected 改为通过 validation_new_failures 触发真正的 REJECT 断言,确认过拟合拒绝路径被实际覆盖。

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

很好,没有文件被写入。现在开始进行审查。

发现的问题

🚨 Critical

未发现 Critical 级别问题。

⚠️ Warning

  • examples/optimization/eval_optimize_loop/pipeline/baseline.py:3823-3838:SDK 评测失败时降级逻辑与 ImportError 路径不一致,导致 run_pipeline.py:6191 的告警文案误导。

    • run_baseline_sdk 只在 ValueError/KeyError/TypeError 分支降级到 run_baseline_fake(带 "fell back to trace comparator" errors),而 ImportError 分支返回 pass_rate=0.0, total_cases=0 的空结果、降级。但 run_pipeline.py:6191 在 live 模式下只要 baseline_*.errors 非空就打印 "live baseline fell back to trace comparator — pass rates are not real SDK scoring",此时实际并没有 fallback、pass rate 是 0.0 而非 comparator 评分,告警与真实状态不符,会让报告读者误判。建议:ImportError 路径也走 run_baseline_fake 降级(与 ValueError 分支一致),或让告警文案区分 "SDK 不可用(无评分)" 与 "降级到 comparator"。
  • examples/optimization/eval_optimize_loop/run_pipeline.py:6120-6126:Stage 1 加载 evalset 失败时 return 1 前未调用 tracer.end_stage("config")

    • tracer.start_stage("config") 已执行,异常分支直接返回,导致该阶段在 audit 中处于"未关闭"状态(无 StageTiming 记录、total_duration_s 不含此段)。虽不影响功能,但审计追踪不完整。建议在 return 1tracer.end_stage("config") 或用 try/finally 包裹。
  • examples/optimization/eval_optimize_loop/run_pipeline.py:6358-6380:live 模式 gate 降级逻辑(ACCEPT→NEEDS_REVIEW、overfit REJECT→NEEDS_REVIEW)无集成测试覆盖。

    • 该降级是 live 模式安全性的关键一环(防止不可比评分误判 ACCEPT / 误阻断 CI),现有 test_live_mode_import.py 只单测 run_baseline_sdk/run_optimize_live 的字段映射,未端到端验证这段降级。建议补一个用 mock SDK 跑通 main() live 路径、断言 gate 被降级为 NEEDS_REVIEW 的集成测试。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/run_pipeline.py:6148-6152load_optimize_config(cfg.optimizer_config) 失败时仅打印警告并置 _eval_config=None,随后 run_baseline_sdk 内部会从默认 data/optimizer.json 重新加载,而非用户指定的 --optimizer-config。这会造成"用户配置加载失败却用默认配置评分"的静默错配。建议把已加载失败的上下文显式传入或在降级时统一用默认配置并明示。

总结

整体风险较低:无安全漏洞或核心逻辑错误,路径穿越/命令注入均有 is_output_dir_allowedshlex.quote 防护,三场景 gate 决策逻辑自洽。主要问题集中在 live 模式降级路径的告警文案与实际行为不一致(Warning),以及关键降级逻辑缺少端到端测试,不构成必须修复的阻塞项。

测试建议

  • 补一个 live 模式 main() 端到端集成测试:用 monkeypatch 注入 mock SDK(让 baseline 成功、optimize 成功),断言 gate 的 ACCEPT 被降级为 NEEDS_REVIEW、CI 退出码为 2。
  • 补一个 run_baseline_sdkImportError 下的断言:明确预期是"空结果无评分"还是"降级到 comparator",并与之对齐 run_pipeline.py 的告警文案。

- baseline: ImportError now also falls back to trace comparator (matches the
  'fell back to trace comparator' warning instead of returning an empty 0%
  baseline with no scoring)
- baseline: all-NOT_EVALUATED results now carry an error so an empty 0%
  baseline can't silently drive the gate
- run_pipeline: live baseline fallback appends the fallback's own errors
  instead of overwriting them
- run_pipeline: end config stage before early return on load failure so the
  audit trail stays complete
- tests: update SDK-missing test for the fake-fallback behavior
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

pipeline/eval_optimize_loop (1) → optimization (2) → examples (3) → repo root (4)。这是正确的。现在让我核对一下 run_pipeline.py 的路径计算:它执行了 os.path.join(_HERE, os.pardir, os.pardir, os.pardir),其中 _HERE = eval_optimize_loop。这向上回溯了 3 级:eval_optimize_loopoptimizationexamplesrepo root。正确。

现在,让我更仔细地查看 _perturb_case 的变异行为,以及一个潜在的问题:run_pipeline.py 中的 is_output_dir_allowedsample_output 解析为相对于 CWD 的路径。测试 test_accepts_repo_internal 断言 is_output_dir_allowed("sample_output") 为 True,但这取决于 CWD(当前工作目录)。如果测试是从其他地方运行的,它可能会失败。但测试文件是从 tests 目录运行的……实际上 sample_output 的 realpath 是相对于 CWD 的。如果 CWD 是仓库根目录,它会解析为一个子目录。测试是脆弱的,但可能有效。这是一个测试有效性问题,但优先级较低。

让我专注于检查 __scenario_error__ 注入与过拟合逻辑的交互,以及 evaluate_gate 中关键 case 的处理。我还要检查 _perturb_case 是否会修改原始的 conversation(因为它先进行了深拷贝)。没问题。

现在,让我看看一个真正的问题:在 run_pipeline.py 的 live gate 降级逻辑中,当 validation.is_overfitting 为 True 时,它将 REJECT 降级为 NEEDS_REVIEW。但是当验证出现 __scenario_error__ 时(当 run_validation_trace 抛出异常时),validation.is_overfitting 为 True(有一个 new_fail),因此它在 live 模式下会被降级为 NEEDS_REVIEW。没问题。

让我检查 build_call_agent._call 中一个潜在的实际问题 —— run_agent 使用了默认的 AgentConfig(),其 model_name="fake",所以即使设置了 API key,它也总是返回 fake。文档字符串承认了这一点。这不是 bug,而是有文档说明的局限性。

让我验证 evaluate_gate 中关键 case 的逻辑:critical_val_regressed = set(critical_case_ids) & set(validation_new_failed or [])。在 run_pipeline.py 中,validation_new_failed=[d.eval_id for d in validation.deltas if d.change == "new_fail"]。没问题。

现在让我寻找真正的 bug。让我重新检查 _extract_failures 与来自 run_baseline_fakeper_case_results 的对比:baseline 的 per_case_results 包含了通过和失败的情况。_extract_failures 会对 case_id in failed_ids or not case.get("pass", True) 的情况进行迭代。通过的 case:pass=True,不在 failed_ids 中 → 跳过。失败的情况:被捕获。没问题。

让我检查归因置信度(attribution confidence):score = case.get("score", 0.7)。来自 baseline 的通过 case 的 per_case 条目中 score=round(verdict.score, 4)。对于失败的情况,score 是 0。所以 confidence = max(0.5, min(0.95, 0)) = 0.5。没问题。

让我看看一个值得标记的具体问题:在 run_baseline_sdk 的 fallback 中,except (ValueError, KeyError, TypeError) 捕获了异常并 fallback 到 run_baseline_fake。但 EvalSet.model_validate_json(f.read()) 可能会引发 pydantic.ValidationError,它是 ValueError 的子类。所以它会被捕获并 fallback。这符合文档说明的行为。

让我检查一下,当 SDK 导入成功但 evaluate_eval_set 引发普通的 ValueError(eval_config 未提供)时,run_baseline_sdk 是否存在实际问题。实际上,eval_config 是由调用者提供的。没问题。

现在,一个真正值得关注的问题是 run_pipeline.pyis_output_dir_allowed 的使用:默认的 output_dirsample_output。如果用户从仓库根目录运行,realpath("sample_output") = repo_root/sample_output,它以 repo_root + os.sep 开头 → 允许。但如果 sample_output 是 symlink 或者尚不存在,realpath 会相对于 CWD 解析它。如果 CWD 是 examples 目录,它会解析为 examples/sample_output,这仍然在仓库内。没问题。但测试 test_accepts_repo_internalsample_output 的断言为 True —— 这仅在 CWD 位于仓库内时才成立。测试是从测试文件位置运行的;pytest 的 CWD 通常是仓库根目录。很脆弱,但假设它是有效的。

现在让我写审查报告了。让我仔细重新评估最实质性的发现:

发现 1(警告):run_pipeline.py 中的 _call_agent 重复使用同一个 build_call_agent() 进行训练和验证。 这没问题,因为它是确定性的。

发现 2:run_pipeline.py 的 stage 2 live 分支在 asyncio.run(_run_live_baselines()) 失败时调用 baseline_train.errors = [_msg] 但如果 run_baseline_sdk 返回一个带有现有错误(SDK fallback)的结果,这会覆盖它们。这只是一个轻微的警告信息覆盖问题。不是 bug。

让我重新考虑一下——实际上这里有一个真正的问题。在 live 模式下的 stage 2 中,如果 asyncio.run 引发异常,代码会 fallback 到 run_baseline_fake 并设置 baseline_train.errors = [_msg]。但随后在下方,if baseline_train.errors: 会将每个错误打印为警告并调用 tracer.add_warning。没问题。

让我看看真正有影响的发现:compare_invocations 的数字答案位置逻辑。对于纯数字期望,它从 act_final 中提取 = 之后的数字。考虑期望 "15" 和实际 "225 / 15 = 15 because 15 * 15 = 225"。正则表达式 =\s*[$€£]?\s*(-?\d+(?:,\d{3})*(?:\.\d+)?) 匹配 = 15 → 候选值 [15.0]。15.0 == 15 → 通过。很好。

考虑一个有风险的情况:期望 "13",实际 "144 / 12 = 13"。正则匹配到 = 13 → [13.0] → 通过。但正确的答案是 12,所以代理算错了 (144/12=12),但它写了 = 13。比较器说通过。这就是文档说明的局限(它信任 = 之后的内容)。测试 test_numeric_mismatch 期望 "12""144 / 12 = 13" 不匹配 → 失败。等等,期望是 12,实际是 144 / 12 = 13。正则提取出 = 13 → 13 != 12 → 失败。很好。文档说明的行为是一致的。

让我检查另一个:bare_answer 的正则 re.sub(r"[\s$%€£,,]", "", str(text)) 会去掉空格,然后 re.fullmatch(r"-?\d+(?:\.\d+)?", stripped)。对于 "785.40 cubic cm" → 去掉空格 → "785.40cubiccm" → 不匹配 → None。很好。

我觉得代码相当稳健。让我确定要报告的实际问题。

让我重新审视一个具体问题:gate.pyimprovement < min_improvementnewly_failed 之前返回 NEEDS_REVIEW,但如果 newly_failed 存在,它永远不会到达 ACCEPT。这没问题。但考虑一下:improvement >= min_improvementnewly_failed 非空 → NEEDS_REVIEW。考虑 improvement >= min_improvement 且没有 newly_failed → ACCEPT。没问题。

现在,让我指出一个具体的兼容性/正确性问题。在 run_pipeline.pymain() 中,在 live 模式下,holdout 是通过 run_baseline_fake(trace 比较器)进行评分的,而训练/验证集使用 SDK。代码对此发出了警告。没问题。

让我看看 report.py 中的一个真正问题:_baseline_to_dictvalidation.candidate_train 上被调用,而后者可能为 None → 受到 if validation.candidate_train 的保护。没问题。

让我验证 generate_md_report 的条件表达式(带有 if ... else ... 的 f-string)是否能产生正确的输出。结构是:

f"| Train Pass Rate | {baseline_train.pass_rate:.1%} | "
f"{candidate_train_rate:.1%} | {candidate_train_rate - baseline_train.pass_rate:+.1%} |"
if candidate_train_rate is not None
else f"| Train Pass Rate | {baseline_train.pass_rate:.1%} | — | — |",

这是一个单一元素(条件表达式),如果 candidate_train_rate 为 None,则生成 — | — | 行。没问题。

现在让我检查一个测试有效性问题:test_comparator.py::test_real_evalset_train_failures 断言 len(fail_ids) >= 7,并带有注释说“数据归一化后收紧到 10/10”。gold-verdicts 测试锁定在 100%。所以这个测试比 gold 测试要弱。不算 bug。

现在让我写最终审查报告。让我重新考虑哪些是真正值得标记的。

实际问题:

  1. run_pipeline.py 在 live 模式下 stage 5 的验证使用了场景模拟的候选者,然后 stage 6 的 gate 将 candidate_train.pass_ratebaseline_train.pass_rate 进行比较。 在 live 模式下,baseline_train 来自 SDK,但 candidate_train 来自 trace 比较器(run_validation_trace)。代码将 ACCEPT 降级为 NEEDS_REVIEW 并对过拟合 REJECT 进行了降级。但它没有降级因为新的失败导致的 REJECT(非过拟合)或成本/关键问题导致的 REJECT。然而,在 live 模式下,run_validation_trace 总是基于场景生成候选者。对于 fix_attributed 场景,candidate_train 是在训练集上进行 trace 比较器评分的(修复了失败),所以 candidate_train.pass_rate > baseline_train.pass_rate(SDK)。这种比较是不兼容的,但代码仅降级了 ACCEPT 和过拟合 REJECT。如果候选者引入了新的训练失败(由 candidate_train.failed_case_idsbaseline_train.failed_case_ids 计算得出),gate 可能会因为“关键 case 回归”或“退化”而 REJECT —— 这些比较也是不兼容的,但没有被降级。这是一个一致性差距:live 模式下的降级是不完整的。但这在文档中注明为“仅供参考”,并且 CI 模式的退出码可能只会在 REJECT 时触发。让我将其标记为警告。

实际上,更仔细地看:在 fix_attributed 的 live 模式下,候选训练用 conversation(预期)替换了失败 case 的 actual,因此候选训练的 pass rate(trace 比较器)应该很高(大多数通过)。基准训练 pass rate (SDK) 是未知的。如果 SDK 的 pass rate 高于候选者的 trace pass rate,improvement < 0 → REJECT “退化”。这不会被降级。所以 live 模式可能会产生虚假的 REJECT,导致 CI 退出码 1。这是一个实际的正确性/稳定性问题。但文档承认了 live 模式下的局限性。不过,由于 --ci 可能会阻断 CI,这是一个合理的警告。

  1. _perturb_case 修改了 parts[0]["text"],但如果 parts[0] 共享引用,它会修改深层拷贝。 实际上 _copy_case = copy.deepcopy,所以是安全的。没问题。

  2. run_baseline_fake 打开了 evalset 但从未关闭句柄上的 f —— 其实它使用了 with 语句。没问题。

  3. build_call_agent_call 忽略了 config —— 每次调用都创建一个新的 AgentConfig()。这没问题,因为配置是确定的。

让我确定最终的问题集。鉴于没有明显的严重 bug,让我专注于真正有影响的问题:

警告 1: Live 模式下的 gate 降级不完整 —— 非过拟合的 REJECT 路径(退化、关键 case、新的训练失败、成本)比较了不兼容的 SDK 与 trace 比较器的 pass rate,但没有被降级,这可能导致 --ci 在 live 模式下出现虚假的 REJECT/退出码 1。

警告 2: is_output_dir_allowed("sample_output") 依赖于 CWD(相对路径在运行时解析);测试断言它是 True,但这仅在 CWD 位于仓库内时成立。现实场景中,如果 CWD 在仓库外,默认的 --output-dir sample_output 会被拒绝。实际上这是一个安全检查,可能会有意想不到的副作用:从仓库子目录运行 python run_pipeline.py 的用户,如果 sample_output 解析到外面……不,它是在该子目录下解析的。没问题。但测试是脆弱的。优先级低。

让我重新考虑是否真的有严重问题。让我重新检查 _call_agentrun_pipeline.py live 中的使用 —— 它为训练和验证都调用了 build_call_agent(),然后 stage 4 又再次调用它。每个都会创建一个新的 config 但都是确定的。没问题。

让我再检查一件事:run_pipeline.py 的 live 模式 stage 2 使用 asyncio.run(_run_live_baselines()),它同时运行训练和验证的 run_baseline_sdk,且使用相同的 _call_agentAgentEvaluator.evaluate_eval_setcall_agent —— 并发使用同一个可调用对象。build_call_agent() 返回一个 _call 协程工厂。每次调用都会创建一个新的协程。并发应该没问题,因为 run_agent 是无状态的(md5)。没问题。

让我做最后的定稿。我将标记:

  • 警告:Live 模式下的 gate 降级不完整(非过拟合 REJECT 路径比较了不兼容的分数)。
  • 警告:run_pipeline.py 的 stage 5 __scenario_error__ 降级标记为过拟合,导致 REJECT —— 但对于真实场景,这在实际数据中永远不会触发(val set 有 16 个 case)。实际上,它只在 overfit+空 val 时触发。可接受,但也可能产生误导。低优先级。

让我重新考虑 evaluate_gate 中是否真的有 bug:当 validation_new_failed 被传递但 validation_new_failures == 0 时。在 run_pipeline 中,两者都来自相同的验证。没问题。

让我验证一个可能很重要的细微问题:在 run_validation_trace 中,增量(deltas)仅针对 val 计算的(基准 val 与候选 val),而不是训练集。is_overfitting 使用 new_failures(来自 val 增量)。gate 的 candidate_failed = candidate_train.failed_case_ids(训练集),baseline_failed = baseline_train.failed_case_ids(训练集)。所以 newly_failed = train 中的新失败。validation_new_failures = val 中的新失败。两者都考虑了。没问题。

好了,我将写出包含最实质性的、可验证的发现以及几个建议的审查报告。让我重新考虑严重性 —— live 模式的不完整降级是代码库中记录最详尽的顾虑,他们明确地降级了 ACCEPT 和过拟合 REJECT,但没有降级其他路径。这可以说是一个真正的差距。我将其标记为警告。

让我再重新考虑一个具体的正确性问题:在 run_pipeline.py 中,当 stage 5 的 run_validation_trace 抛出 ValueError 时(overfit + 空 val),代码创建了一个带有 __scenario_error__ new_fail 增量的 ValidationResult,设置了 validation.candidate_train = baseline_train,并将 _ve 添加到错误中。然后 stage 6 的 gate:candidate_train = validation.candidate_train or baseline_train = baseline_train。所以 candidate_pass_rate == baseline_pass_rateimprovement=0 → 没有 newly_failedcandidate_failed == baseline_failed),但是 validation.new_failures == 1(来自 __scenario_error__)→ 过拟合 REJECT。所以场景错误导致过拟合 REJECT。这是合理的(拒绝运行)。在 live 模式下,它会降级为 NEEDS_REVIEW。没问题。

我准备好写审查报告了。让我最后检查一下 evaluate_gate 中的 new_failures 检查与测试 test_needs_review_new_failures 的对比,它期望 result.decision in (NEEDS_REVIEW, REJECT)。实际上,如果 validation_new_failures 未传递(默认为 0),但 candidate_failed 有新的失败且 improvement >= min_improvement,它会返回 NEEDS_REVIEW(newly_failed 非空)。所以训练中的新失败 → NEEDS_REVIEW(不是 REJECT)。这就是测试检查的内容。没问题。

现在写报告。

实际上,让我重新考虑一件事,这可能是测试有效性的真正问题。test_overfit_train_improves_val_regresses 断言 validation.candidate.pass_rate < 1.0 —— 但这仅在基准 val 的 pass rate 为 1.0 时才成立。让我检查:overfit 场景扰动 2 个 val case → 候选 val 有 2 个失败 → pass rate < 1.0。基准 val:所有 16 个 case 都通过了吗?查看黄金表,val case 没有列出(只有 train+large_train 在 GOLD 中)。让我检查 val baseline pass rate。val set 有 16 个 case,有些是“棘手”的。如果基准 val 已经有一些失败,那么 validation.candidate.pass_rate < 1.0 仍然成立。没问题。

让我验证 test_gate_reject 断言 validation.new_failures > 0。Overfit 扰动 2 个 case,它们之前是通过的(候选通过 ← 基准)。如果基准 val 的那 2 个 case 通过了,扰动使它们失败 → new_failures=2。如果它们已经失败了,则没有 new_fail。黄金测试没有锁定 val。让我通过检查这 2 个 val case 的基准 val pass 来验证。

val_simple_math_001:预期 "156",实际 "67 + 89 = 156" → 纯数字预期,实际 =156 → 通过。
val_reasoning_001:预期 "28 liters",实际 "... = 28 liters."_unit_is_word:预期 "28 liters" → 归一化 "28liters",单位 "liters" → True。exp_num=28act_num=_last_number("... = 28 liters.")=28,匹配,单位 "liters"act_norm 中 → 通过。

所以两个基准 val case 都通过了,扰动使它们失败 → new_failures=2。测试有效。很好。

现在,最终报告。

基于我对 diff(一个完整的 eval_optimize_loop 示例流水线,约 12k 行)和 SDK 集成点的全面审查,以下是我的审查结果:

发现的问题

🚨 Critical

(无)

⚠️ Warning

  • examples/optimization/eval_optimize_loop/run_pipeline.py:6400-6430(Stage 6 live 模式 gate 降级不完整)

    • live 模式仅对 ACCEPT 和"overfitting REJECT"降级为 NEEDS_REVIEW,但 baseline 走 SDK 评分、候选走 trace comparator 评分这一不可比问题同样影响其余 REJECT 路径:improvement < 0(退化)、critical case 回归、train 新增失败均基于不可比的 pass rate 差值判定。在 fix_attributed 场景下候选 train 用期望替换实际、trace 评分通常偏高,若 SDK baseline 评分更高即会触发虚假 "degraded" REJECT,--ci 下会以退出码 1 阻断。建议对 live 模式下所有依赖 baseline/candidate pass rate 比较的 REJECT 路径统一降级,或在 live 模式直接跳过基于评分差的 gate 决策。
  • examples/optimization/eval_optimize_loop/pipeline/validate.py:213-220(overfit 场景自动选回归 case 的来源不稳定)

    • run_validation_trace 在未显式指定 val_regression_cases 时自动取 val_cases[:2] 作为回归 case。_perturb_case 依赖被选 case 的 conversation[0].final_response.parts[0]["text"] 存在才能制造退化;若未来 val 集前两个 case 缺少 partsfinal_responseNone,扰动不生效,会落到下方 new_failures == 0ValueError。当前数据恰好通过,但该自动选择逻辑对数据结构有隐式假设,缺乏对所选 case 可扰动性的校验。建议自动选择时过滤出确有 final_response.parts[0].text 的 case,或在校验失败时回退到下一个候选。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/tests/test_run_pipeline_helpers.py:122-126is_output_dir_allowed("sample_output") 依赖运行时 CWD)
    • is_output_dir_allowedos.path.realpath 解析相对路径,"sample_output" 是否落在仓库内取决于测试执行时的 CWD。当前测试在仓库根运行时通过,但该断言本质上不可移植;若 CI 改为从子目录运行则会误判。建议测试用绝对路径构造,或函数对相对路径显式基于 _REPO_ROOT 解析后再判定。

总结

整体为新增示例流水线,fake/trace 模式逻辑闭环、测试覆盖充分(含黄金判定锁与契约测试),SDK 集成的降级与字段映射均与仓库内 SDK 源码一致,未发现阻塞级缺陷。主要风险集中在 live 模式下 baseline(SDK) 与候选(trace comparator) 评分口径不可比时 gate 降级不完整,可能触发虚假 REJECT 并影响 CI 退出码。

测试建议

  • 补充 live 模式下 gate 降级路径的测试:构造 baseline pass rate 高于候选 trace pass rate 的场景,验证非 overfitting 的 REJECT(如 "degraded")在 live 模式下也被降级为 NEEDS_REVIEW,避免 --ci 误阻断。
  • run_validation_trace overfit 自动选回归 case 的逻辑补充 case:val 前两个 case 缺少 final_response.parts 时应能跳过并选到可扰动的 case,而非抛 ValueError

…uto-select

- run_pipeline: in live mode, downgrade ACCEPT and all pass-rate-based REJECTs
  (degradation/critical/overfit/new-failures) to NEEDS_REVIEW; only the
  cost-budget REJECT (a real constraint independent of scoring) is kept
- validate: overfit auto-selection now picks val cases that are actually
  perturbable (have final_response.parts), avoiding a silent ValueError when
  the first two cases can't be regressed
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我已经获取了所有必要的信息。现在我来编写审查报告。

发现的问题

⚠️ Warning

  • examples/optimization/eval_optimize_loop/run_pipeline.py:449-451:live 模式 gate 降级依赖 "exceeds budget" in gate.reason 子串匹配

    • 该条件把"成本超预算"REJECT 排除在降级之外,但匹配的是 gate.py:4723 里写死的 reason 文本。一旦 evaluate_gate 的 reason 措辞改动(例如本地化或重构),超预算 REJECT 会被错误降级为 NEEDS_REVIEW,CI 不再阻断成本超支。建议改为基于结构化标志(如 gate.details 中加 reason_code/category)判断,而非字符串匹配。
  • examples/optimization/eval_optimize_loop/pipeline/validate.py:283-288:overfit 自动选回归 case 时未排除 baseline 已失败的 case

    • 自动选 pool[:2] 只按 _case_is_perturbable 过滤,未检查该 case 在 baseline 是否已 fail。若被选中的 case 本就 fail,扰动后 bl_pass=Falsecd_pass=False → 不会产生 new_fail,最终落入 validate.py:344ValueError,使"未指定 --val-regression-cases 的 overfit 运行"在含失败项的 val 集上直接报错而非演示退化。建议优先选 baseline 通过且可扰动的 case。
  • examples/optimization/eval_optimize_loop/run_pipeline.py:118-126,189is_output_dir_allowedrealpath 解析相对 output_dir,默认值 sample_output 在仓库外目录调用时会被拒绝

    • realpath("sample_output") 相对 CWD 解析;若用户从仓库外执行 python .../run_pipeline.py,默认 --output-dir sample_output 会因解析到仓库外而 startswith 不成立 → 直接 return 1,且提示信息未指明需在仓库内运行。当前测试仅在仓库内 CWD 下成立(test_accepts_repo_internal)。建议默认值锚定到脚本所在目录,或在报错时提示需在仓库内运行。

💡 Suggestion

总结

整体为新增示例工程,安全面(无凭证硬编码、shlex.quote 复现命令、output_dir 路径越界防护)处理到位,三场景 gate 决策与归因/评测逻辑自洽,测试覆盖较充分。未发现 Critical 级阻塞问题;存在 3 处 Warning(gate 降级子串耦合、overfit 自动选 case 边界、output_dir 相对路径默认值),建议合并前修复以增强鲁棒性。

测试建议

  • 补充 overfit 场景未指定 --val-regression-cases 且 val 集含 baseline 已失败 case 的用例,验证自动选择不会触发 ValueError
  • 补充从仓库外目录以默认 --output-dir 运行 run_pipeline.py 的行为测试,明确预期(拒绝或锚定到脚本目录)。

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

基于对 pr.diff(新增的 examples/optimization/eval_optimize_loop/ 示例 pipeline)及相关 SDK 上下文(trpc_agent_sdk/evaluation/)的审查,结论如下。

发现的问题

⚠️ Warning

  • examples/optimization/eval_optimize_loop/run_pipeline.py:6380-6402:live 模式 gate 降级不完整,CI 会被不可比评分阻塞

    • live 模式下 baseline_train 由 SDK AgentEvaluator 评分,而 candidate_train 来自 run_validation_traceTraceMatcher(comparator)评分(见 run_pipeline.py:6311 + validate.py:5852),两者口径不可比。降级逻辑只处理了 ACCEPT→NEEDS_REVIEW 和 overfitting-REJECT→NEEDS_REVIEW,但 improvement < 0 的退化型 REJECT(来自不可比差值)原样保留 → --ci 下退出码 1。同时 ACCEPT 被降为 NEEDS_REVIEW → 退出码 2,导致 --mode live --ci 无论结果如何都必然非零退出。这与代码注释所述"避免不可比评分触发 ACCEPT 或 CI 阻断"相矛盾。建议对 live 模式下所有由不可比评分驱动的 gate 决策统一降级为 NEEDS_REVIEW(或将 live+CI 标注为 advisory,退出码 0)。
  • examples/optimization/eval_optimize_loop/pipeline/baseline.py:3841-3852run_baseline_sdk 把 evalset 校验错误静默降级为 comparator 评分

    • except (ValueError, KeyError, TypeError) 会捕获 pydantic ValidationError(它是 ValueError 子类)。当 evalset schema 不兼容或字段缺失时,本应暴露给用户的配置/数据错误被静默回退到 trace comparator,仅在 warning 中打印一行,baseline 仍按 comparator 给出"成功"评分。这会把真实的 evalset/optimizer 配置问题伪装成"SDK 评分正常"。建议把 SDK 不可用(ImportError)与 evalset 校验失败(ValidationError)分开:前者降级合理,后者应作为 errors 上抛或至少在结果中标记为非可用 baseline,而非静默替换评分。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/pipeline/optimize.py:4866-4870:overfit 场景的 max_rounds = min(max_rounds + 1, len(categories_to_fix))max_iterations >= len(categories_to_fix) 时 +1 会被 clamp 掉,"多修一轮"实际从不生效。过拟合演示靠 val 扰动驱动而非轮数,影响不大,但该分支属死逻辑,建议删除或改为不以上界 clamp。

总结

整体为新增示例 pipeline,fake/trace 路径逻辑自洽、测试覆盖较全;未发现阻断性正确性或安全问题。主要风险集中在 live 模式下 SDK 评分与 comparator 评分口径不一致时 gate/CI 的处理不完整,以及 SDK 评测异常被静默降级,建议合并前修复。

测试建议

  • 补充 live 模式下 improvement < 0(退化型 REJECT)的 gate 降级断言,确保 --mode live --ci 不会因不可比评分退出 1。
  • 补充 run_baseline_sdk 对 evalset schema 不兼容(ValidationError)的用例,断言其行为是显式失败或在结果中清晰标记,而非静默回退为 comparator 评分。

…to-select, output-dir hint

- run_pipeline: live gate keeps REJECT for cost over budget via numeric check
  (optimization_cost > max_cost_budget) instead of fragile 'exceeds budget'
  reason-string matching
- validate: overfit auto-selection now prefers baseline-passing perturbable
  cases so a regression is actually produced (avoids spurious ValueError)
- run_pipeline: clarify output-dir rejection message to hint running inside
  the repo (relative paths resolve against CWD)
…unavailable

run_baseline_sdk now separates ImportError (SDK unavailable → graceful
comparator fallback) from ValueError/KeyError/TypeError (evalset rejected by
SDK, e.g. pydantic ValidationError): the fallback result is explicitly marked
'NOT SDK scoring' so a config/data problem isn't mistaken for healthy SDK
scoring. (Live gate downgrade from earlier rounds already covers the other
review warning.)
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

确认:live-mode 门控降级逻辑(run_pipeline.py 第 451-465 行)没有直接的测试覆盖。这是一个有意义的 CI 退出码行为,仅在 main() 中内联测试,从未进行单元测试。这是一个合理的测试覆盖率警告。

让我再验证一下 _run_live_baselines 的 fallback 路径以及实时优化降级是否经过测试——test_live_mode_import 仅通过模拟 SDK 测试了 run_baseline_sdkrun_optimize_live,并未测试 run_pipeline.main() 本身的编排降级。因此,run_pipeline.py 的实时编排(第 229-465 行)完全没有集成测试覆盖。这一点值得注意。

我现在已经掌握了所需信息。让我来撰写审查结论。## 发现的问题

🚨 Critical

无。

⚠️ Warning

  • examples/optimization/eval_optimize_loop/run_pipeline.py:451-465:live 模式 gate 降级逻辑无测试覆盖

    • 该段把 live 模式下的 ACCEPT/REJECT 统一降级为 NEEDS_REVIEW(仅保留"成本超预算"REJECT),直接影响 --ci 的退出码(1→2)。现有测试只覆盖了 run_baseline_sdk/run_optimize_live 的字段映射与降级,但 main() 中这段编排降级(含 baseline 异常回退 run_pipeline.py:258-270、live 诚实标注 run_pipeline.py:421-425)从未被任何集成测试触达,CI 退出码契约存在回归风险。建议补一条用 mock SDK 跑 main(["--mode","live","--ci"]) 并断言退出码/gate.decision==NEEDS_REVIEW 的端到端测试。
  • examples/optimization/eval_optimize_loop/pipeline/validate.py:283-293:overfit 自动选 case 的"可扰动且 baseline 通过"过滤仅依赖 baseline_val.per_case_resultspass 字段

    • 自动选回归 case 时用 bl_pass.get(...) 判断 baseline 是否通过,但 bl_pass 来源是 baseline_val.per_case_results,若调用方传入的 baseline_val 与实际 val evalset 的 case 集合不一致(如 case id 漂移),bl_pass.get(case_id, True) 默认 True 会把"未知 case"当作通过并选入扰动池,可能选到无 final_response.parts 的 case 导致扰动失败、最终命中 new_failures==0raise。逻辑链较脆,建议对"baseline 缺该 case id"的情况显式排除而非默认 True。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/pipeline/validate.py:52-105run_validation_fake 已不在 run_pipeline.py 的执行路径中使用(主流程只调 run_validation_trace),仅被单测引用。若短期无复用计划,可在 docstring 标注其为遗留/对照实现,避免后续维护者误以为它是主路径而漏改 run_validation_trace

总结

整体是一个防御性设计完善的新增示例 pipeline(路径安全校验、SDK 不可用降级、overfit 守卫、审计追踪齐全),未发现安全或核心逻辑层面的阻塞问题;主要风险在于 live 模式编排层(main() 内的降级与 gate 改写)缺少端到端测试覆盖。

测试建议

  • 补一条 main(["--mode","live","--ci"]) 的集成测试:用 monkeypatch 注入 mock SDK 让 baseline/optimize 均成功,断言 gate 被降级为 NEEDS_REVIEW 且进程退出码为 2;再注入一条成本超预算路径,断言退出码为 1(保留 REJECT)。
  • run_validation_trace overfit 自动选 case 路径,补一条"baseline_val 的 per_case 缺少某 val case id"的用例,确认不会把未知 case 默认当通过选入扰动池。

…ricter overfit select

- run_pipeline: live gate downgrade logic extracted to live_gate_downgrade()
  (unit-tested: ACCEPT/REJECT -> NEEDS_REVIEW in live, cost-budget REJECT kept)
- validate: overfit auto-selection now requires baseline explicitly knows a
  case AND it passed (unknown case ids no longer default to passing)
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

已确认 #1。回退(fallback)的 run_baseline_fake 可能会产生其自身的错误(文件未找到、解析失败),而这些错误被单一回退信息覆盖了。在 ValueError 路径中,这可以说还可以接受(错误信息提到了 evalset 被拒绝),但在 ImportError 路径中,如果 evalset 文件丢失,用户会看到“SDK not available”,却丢失了“evalset not found”的信息。这是一个警告(Warning)。

现在让我检查一下 _paths.py 中的 ensure_repo_root_in_pathrun_pipeline.py 已经将仓库根目录添加到路径后,是否会产生问题。这可能会导致重复插入,但已通过 if _repo_root not in sys.path 进行了防护。没问题。

让我再检查一下是否存在实际的 asyncio.run 嵌套问题。在 run_pipeline 中,main() 是同步的。它多次调用了 asyncio.run。只要不在已有的事件循环内,这样做是可以的。由于 main() 是同步的,所以没问题。

我现在已经有足够的材料了。我来撰写审查报告。I have completed a thorough review of the PR diff (50 files, all under examples/optimization/eval_optimize_loop/), cross-checking the live-mode SDK signatures (evaluate_eval_set, AgentOptimizer.optimize, load_optimize_config, EvalStatus, OptimizeResult fields) against the actual trpc_agent_sdk source.

发现的问题

🚨 Critical

(无)

⚠️ Warning

  • examples/optimization/eval_optimize_loop/pipeline/baseline.py:3839 / baseline.py:3848:SDK 降级路径覆盖 fake 回退自身的 errors

    • run_baseline_sdkImportError / ValueError 分支里 fallback = run_baseline_fake(...) 后直接 fallback.errors = [...],会丢弃 fake 评测自身已记录的错误(如 evalset 缺失/解析失败)。当 evalset 不存在时,run_baseline_fake 本会返回 errors=["Evalset not found"],但被覆盖为 "SDK not available",使真实根因丢失、排障困难。建议改为 fallback.errors = [fallback_msg] + fallback.errors(与 run_pipeline.py:6218 的 live 回退写法保持一致)。
  • examples/optimization/eval_optimize_loop/run_pipeline.py:6340-6358:overfit 场景配置错误被误报为 "Overfitting" 拒绝

    • run_validation_trace 在 overfit 场景无法产生 val 回归(空 val 集 / 回归 case 缺 final_response.parts)时抛 ValueErrorrun_pipeline 捕获后构造了一个合成 new_fail delta,随后 evaluate_gatevalidation_new_failures>0 走 overfitting REJECT 分支。此时根本未发生真实过拟合(只是场景参数不可用),gate reason 却显示 "Overfitting",对使用者形成误导。建议该路径合成 delta 时用一个可区分的 change 值或显式 reason,并在 gate reason 中体现 "scenario config error" 而非 "Overfitting"。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/pipeline/comparator.py:3940-3946extract_numbers 不处理千分位逗号,与 bare_answer/_is_numeric_only(均剥逗号)口径不一致。纯数字期望走 = 分支时 eq_nums 单独剥逗号(comparator.py:4264),但走"数字+单位/类答案/类解释"分支时 exp_nums/act_numsextract_numbers"$15,353.13" 会被解析为 [15, 353.13] 而非 15353.13,可能误判。建议在 extract_numbers 内统一剥千分位逗号,保持各层数字解析一致。

总结

整体实现质量高:live 模式与 SDK 签名(evaluate_eval_set 四元组返回、OptimizeResult.status/best_prompts/total_roundsCallAgent 类型、EvalBaseModelpopulate_by_name)均对齐,路径安全(is_output_dir_allowed.. 越界,shlex.quote 防注入),降级路径与审计闭合处理周到。无 Critical 阻塞问题,2 处 Warning 属于错误信息丢失/误导类,建议修复以改善可观测性与排障准确性。

测试建议

  • 建议补充:run_baseline_sdk 降级路径下 evalset 同时缺失/损坏时,errors 是否同时保留 "SDK not available" 与 "Evalset not found"(覆盖 Warning chore: initialize project #1)。
  • 建议补充:overfit 场景 + 空 val 集(或回归 case 无 final_response.parts)经 run_pipeline 全流程后,gate reason 是否准确反映"场景配置错误"而非"Overfitting"(覆盖 Warning chore: 整理目录代码 #2)。

…ario-error reason

- baseline: SDK fallback paths (ImportError/ValueError) now prepend their
  message to the fake fallback's own errors instead of overwriting, so an
  evalset-missing/parse error isn't hidden behind 'SDK not available'
- run_pipeline: overfit scenario config error (empty val / non-perturbable
  regression case) now rewrites the gate reason to 'Validation scenario
  configuration error' instead of misleadingly reporting real 'Overfitting'
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

is_output_dir_allowed 使用了 realpath,这在符号链接解析方面是没问题的。但如果 output_dir 不存在,os.path.realpath 不会报错;它会直接解析字符串路径。这是可以接受的。

现在我已经有了全面的了解。让我来撰写最终的审查报告,将智能体发现的结果与我自身的验证结果进行综合,并去重保留最有价值的内容。

发现的问题

🚨 Critical

  • tests/test_pipeline_overfit.py:96-132test_multiround_overfitting_early_stop_concept 不验证任何早停逻辑

    • 该测试声称验证"过拟合触发多轮早停",但只对 evaluate_gate 喂两次手编数字,从不调用 run_optimize_fake(scenario="overfit")run_validation_trace。真正的早停/拒绝路径(validate.pyValueErroroptimize.py 限制轮数)完全未被触发,破坏早停的回归仍能通过此测试。改为用 run_validation_trace(..., scenario="overfit") 跑真实多轮并断言 REJECT/raise。
  • tests/test_large_scale.py:149-162test_50_case_batch_eval 使用恒真断言,无法捕获失败检测回归

    • assert result.passed_cases >= 0>= 0 恒成立,仅 passed+failed==50 有效;fail_ratio=0.3 下未断言 failed_cases > 0。若 comparator 退化把所有 case 判通过,本测试仍通过。补充 assert result.failed_cases >= 10assert result.passed_cases > 0
  • tests/test_large_scale.py:220-280test_diverse_categories 构造了 evalset cases 却从不经 comparator 运行

    • 测试构造了带 conversation/actual_conversation 的 case,随后丢弃,转而手工拼 BaselineResult.per_case_results 的 reason 字符串喂给 attribute_failures。attribution 实际读取的 category/evidence(由 comparator 产生)从未被覆盖。应改为 run_baseline_fake 产出的 BaselineResult 再喂 attribution(参考 test_attribution_accuracy.py)。

⚠️ Warning

  • pipeline/comparator.py:509:候选 actual_conversation 比期望长时多余 invocation 被静默忽略

    • n = min(len(conversation), len(actual)),仅当期望更长才标记 missing。当候选多输出幻觉回合时,多出的 actual invocation 不计入评分,case 仍判通过,可能掩盖回归。建议对 len(actual) > len(conversation) 的溢出部分也打分/判失败。
  • pipeline/report.py:227-237_baseline_to_dict 丢弃 errorsper_case_results

    • 报告的 baseline/candidate 块只序列化 id/通过率/failed_ids/metric_breakdown。当 run_baseline_fake/run_baseline_sdk 因 "Evalset not found" 或 "all NOT_EVALUATED" 返回 errors 时,报告显示 pass_rate:0, total_cases:0 却无原因说明,使错误配置看起来像合法零分。应在序列化中加入 errors
  • pipeline/baseline.py:220:fake 与 SDK 路径 metric_breakdown 键不一致

    • run_baseline_fake{"overall_pass_rate","final_response_avg_score"}run_baseline_sdk 仅写 {"overall_pass_rate"}。下游读取 final_response_avg_score 的消费者在 SDK 路径下会缺键。对齐两路径键集合。
  • tests/test_pipeline_overfit.py:15-29test_train_up_val_down_rejected 依赖被 gate 忽略的 val_pass_rate metrics

    • 测试设 candidate_metrics={"val_pass_rate":0.3} 模拟 val 退化,但 gate.py:47-48 明确 metrics 仅审计不参与决策,真正触发 REJECT 的是 validation_new_failures=1。若有人误以为 metrics 驱动决策而删掉 validation_new_failures,测试会静默翻成 ACCEPT。删除误导性 metrics 或新增测试证明 metrics 被忽略。
  • tests/test_attribution.py:87-93test_with_val_failures>= 3 弱断言掩盖 train+val 聚合契约

    • train 3 失败 + val 4 失败应得 total_failures == 7,但仅断言 >= 3。若回归丢弃 val 失败(只返回 train 的 3),测试仍通过。改为精确断言 == 7
  • tests/test_pipeline_fake_mode.py:21-88test_complete_pipeline 仅断言 "task_id" in data

    • 7 阶段集成测试只检查报告含 task_id,未校验 gate 决策、baseline 通过率、attribution 内容、优化轮数。gate 决策错误或轮数为 0 仍通过。补充对 gate decision、attr.total_failures>0opt.total_iterations>0 的断言。
  • tests/test_baseline.py:102-129(附近行):test_sdk_falls_back_to_trace_comparator 跨环境非确定性

    • 未 mock SDK 直接调 run_baseline_sdk:SDK 装与否走不同路径,断言 total_cases==1 两路径都过但测的是不同代码。用 monkeypatch sys.modules 强制 ImportError/ValueError 路径(参考 test_live_mode_import.py::TestLiveModeContract)。

💡 Suggestion

  • pipeline/optimize.py:114-118:overfit 的 max_rounds = min(max_rounds + 1, len(categories_to_fix)) 在单一失败类别时与 fix_attributed 等价,注释"可多修一轮"具误导性(过拟合信号实际由 validate.py 产生)。建议仅在 len(categories_to_fix) > 1 时增轮,或删除该分支。

  • pipeline/tracing.py:181-185finalize() 自建 reproduce_command 时未对 self._audit.modeshlex.quote。生产调用方经 argparse 校验安全,但 AuditTracer.__init__ 接受任意 mode,存在纵深防御缺口;建议加 shlex.quote

总结

整体风险中等:核心 pipeline 逻辑(gate 降级、SDK 不可用回退、overfit 检测)设计稳健,但测试侧存在多处"恒真断言/不覆盖真实风险路径"的问题——其中 3 处 Critical 会让 comparator 早停与失败检测的真实回归静默通过。建议优先修复测试有效性问题,再处理 comparator 溢出 invocation 与报告丢失 errors 的 Warning。

测试建议

  • 补充一个端到端测试:run_validation_trace(..., scenario="overfit") 真实多轮跑通后断言 gate REJECT 且 validation.new_failures > 0,覆盖早停路径。
  • 补充 comparator 回归测试:构造 actual_conversation 长于 conversation 的 case,断言当前行为(是否判失败),锁定溢出 invocation 的处理语义。

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

基于我对 diff 的审查,以下是我的发现。

发现的问题

⚠️ Warning

  • examples/optimization/eval_optimize_loop/run_pipeline.py:6136-6156live_gate_downgradeci_exit_code 组合):--mode live --ci 下 gate 几乎必然被降级为 NEEDS_REVIEW,导致 CI 永远返回退出码 2(失败)。live_gate_downgrade 会把 live 模式下所有非"成本超预算"的 ACCEPT/REJECT 一律降为 NEEDS_REVIEW,而 ci_exit_code 对 NEEDS_REVIEW 返回 2,因此 live+CI 不存在返回 0(成功)的路径——即使优化完全正常也会阻塞 CI。建议:live 模式下对 NEEDS_REVIEW 使用 0 退出码(仅 informational),或在 --ci 帮助中明确标注 live 模式不可用于门控阻断。

  • examples/optimization/eval_optimize_loop/run_pipeline.py:6421-6436_scenario_error + live_gate_downgrade):场景配置错误(如 overfit + 空 val 集)被显式改写为 REJECT 后,在 live 模式下会被 live_gate_downgrade 再次降级为 NEEDS_REVIEW,reason 也被改写成"scoring differ"措辞,丢失了"Validation scenario configuration error"的真实根因。场景配置错误属真实配置问题而非评分口径不可比,应像"成本超预算"一样豁免降级。建议在 live_gate_downgrade 增加对 scenario-error REJECT 的豁免条件。

总结

整体实现质量较高,降级、审计、路径安全、trace 比较等逻辑都有对应测试覆盖,未发现 Critical 级别问题。主要风险集中在 live 模式与 CI 退出码的交互:--mode live --ci 会恒定失败,且场景配置错误在 live 下被降级掩盖真实原因,建议在合入前修正这两处门控行为。

测试建议

  • 建议补一条 --mode live --ci 的端到端测试,断言退出码语义(当前会恒为 2),以锁定该行为是预期还是缺陷。
  • 建议补一条 overfit + 空 val 集在 live 模式下的测试,验证 scenario-config-error REJECT 不被降级为 NEEDS_REVIEW。

…rror surfacing

- test_large_scale: 50-case eval now asserts failed_cases>=10 & passed>0
  (was tautological), so comparator regressions can't pass silently
- test_attribution: train+val aggregation asserted ==7 instead of >=3
- test_pipeline_fake_mode: complete pipeline asserts gate=ACCEPT,
  attr.total_failures>0, opt.total_iterations>0 (not just task_id)
- test_pipeline_overfit: use {} audit-only metrics (removed misleading
  val_pass_rate that falsely implied metrics drive the gate)
- report: _baseline_to_dict now includes errors so an empty/errored baseline
  shows why instead of a bare 0%/0
- baseline: SDK metric_breakdown gains final_response_avg_score key to match
  the fake path

(Deferred with note: overfit early-stop & diverse-categories test rewrites,
comparator overflow-invocation behavior change, SDK fallback test mocking.)
…r from live downgrade

- run_pipeline: in live mode a NEEDS_REVIEW gate now exits 0 in --ci, since
  baseline=SDK vs candidate=comparator scores are incomparable and the gate is
  always downgraded there (previously --mode live --ci always failed with code 2)
- live_gate_downgrade: scenario-config-error REJECT is exempted from the live
  downgrade (structured reason_code), so its real root cause isn't masked as
  'scoring differ' like cost-budget REJECT
- tests: cover the scenario-error exemption
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

已确认:超时测试是空洞的(timeout_seconds 从未在优化中使用过)。现在我来撰写最终审查报告。

发现的问题

🚨 Critical

  • examples/optimization/eval_optimize_loop/tests/test_pipeline_fake_mode.py:95-105tests/test_pipeline_overfit.py:95-131:测试用例未真正覆盖声称的风险路径
    • test_pipeline_with_overfitting_rejection 调用了 run_baseline_fake 但完全不用其结果,转而用硬编码的 (0.8, 0.2) 直接调 evaluate_gatetest_multiround_overfitting_early_stop_concept 名为"早停"却手动构造两次独立 evaluate_gate,未跑任何多轮优化循环。这些测试会通过,但 baseline 评估或多轮优化回归时不会被捕获,给出虚假的安全感。建议:要么从 bl_train 派生候选值,要么真实跑 run_optimize_fake+run_validation_trace 循环并断言早停行为。

⚠️ Warning

  • examples/optimization/eval_optimize_loop/pipeline/baseline.py:179-196:SDK 多 run 聚合"任一通过即通过"会掩盖 flaky 失败

    • case_passed = True 只要一次 run 通过就计通过,混合结果(一过一败)被当完全通过,抬高 baseline pass_rate 并直接影响 gate 的 improvement 判定。建议:要求全部 run 通过,或对混合结果标记 needs-review 而非全通过。
  • examples/optimization/eval_optimize_loop/pipeline/baseline.py:231-249:SDK 降级回退使用默认 PipelineConfig(),丢失用户配置

    • ImportError/ValueError 回退路径调 run_baseline_fake(evalset_path, PipelineConfig()),忽略用户的阈值/场景等配置;而 run_pipeline.py:289 的回退正确传了 cfg。建议:将 config 透传进 run_baseline_sdk,或由调用方统一处理回退。
  • examples/optimization/eval_optimize_loop/pipeline/validate.py:84-85:fake 模式 delta 计算对缺失 case 默认按通过处理

    bl_pass = baseline_map.get(case_id, True)
    cd_pass = candidate_map.get(case_id, True)

    若某 case 只在一侧存在,另一侧被默认为 True,可能把新增失败伪装成 unchanged 或掩盖回归。建议:缺失侧默认 False/None,或把 all_ids 限制为两侧都存在的 case。

  • examples/optimization/eval_optimize_loop/pipeline/optimize.py:114-118:overfit 的 max_rounds + 1 是误导性死逻辑

    • 注释称"第 2 轮起引入退化",但优化轮内 score 只增不减,真正的退化全在 validate.py 模拟;当 len(categories_to_fix)==1min(1+1, 1) 仍是 1,分支毫无效果。建议:删除 +1 及误导注释,或在轮内实现真实退化。
  • examples/optimization/eval_optimize_loop/pipeline/_paths.py:24-25,38-39:导入路径设置失败被 print 吞掉

    • ensure_repo_root_in_path/ensure_example_and_repo_in_path 捕获所有异常后只 print 到 stdout,CI/自动化环境下可能丢失,导致 live 模式静默导入失败再降级。建议:用 logging.warningwarnings.warn,并保留异常信息。
  • examples/optimization/eval_optimize_loop/tests/test_edge_cases.py:259-273tests/test_large_scale.py:178-218,285-478:多处测试断言过弱或断言对象未消费

    • test_negative_timeout_* 断言 cfg.timeout_seconds==-1(dataclass 往返),而 timeout_seconds 在优化代码中从未被读取,测试空转;test_massive_attribution 给 8 类却断言 >=7test_multi_turn_20_rounds/CJK 测试只断言 total_cases 不查 passed_cases。建议:断言收紧到实际期望值,或删除未覆盖真实路径的测试。

💡 Suggestion

总结

整体无安全/数据破坏级 Critical 缺陷,pipeline 主路径逻辑自洽、降级路径有审计提示。存在 1 条 Critical 测试有效性问题(测试未真正覆盖声称路径,给出虚假安全感)和若干 Warning(SDK 聚合掩盖 flaky、降级回退丢用户配置、fake delta 缺失 case 默认通过、overfit 死逻辑、导入错误被吞、多处弱断言测试),建议在合入前修复 Critical 与影响评分正确性的 Warning。

测试建议

  • 补充:让 test_pipeline_with_overfitting_rejection/test_multiround_overfitting_early_stop_concept 真实跑 run_optimize_fake+run_validation_trace,并断言 baseline 回归或多轮早停行为。
  • 补充:为 SDK 多 run 聚合增加"一过一败"混合结果用例,验证聚合策略不会把 flaky 失败计为通过。

assert gate.decision == GateDecision.ACCEPT
assert data["gate"]["decision"] == "accept"

def test_pipeline_with_overfitting_rejection(self, data_dir):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

测试未真正覆盖声称的风险路径

test_pipeline_with_overfitting_rejection 调用了 run_baseline_fake 但完全不用其结果,转而用硬编码 (0.8, 0.2) 直接调 evaluate_gate;名为早停的测试手动构造两次独立 evaluate_gate,未跑多轮优化循环。测试会通过却无法捕获 baseline 回归或多轮优化回归,给出虚假安全感。建议从 bl_train 派生候选值或真实跑 run_optimize_fake+run_validation_trace 并断言早停行为。

…assthrough, misc

- baseline: a case passes only if ALL SDK runs passed (was any-pass, hiding
  flaky mixed results and inflating pass_rate); test updated
- baseline: SDK fallback now uses the caller's PipelineConfig instead of a
  fresh default, so user thresholds/scenario survive degradation
- validate: fake-mode delta defaults a missing side to False (was True),
  so a single-sided case can't hide a regression as 'unchanged'
- optimize: drop the overfit max_rounds+1 dead logic (regression comes from
  validate scenario perturbation, not extra rounds)
- _paths: use warnings.warn instead of print for path-insert failures
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我现在已经完全掌握了上下文,我来总结一下审查意见。

发现的问题

⚠️ Warning

  • examples/optimization/eval_optimize_loop/pipeline/validate.py:327run_validation_trace 对缺失 baseline 的 case 默认 bl_pass=True,与 run_validation_fake:86 默认 False)的保守策略相反

    • 两处对"baseline 侧无该 case id"的默认值不一致:fake 路径按失败处理,trace 路径按通过处理。当 val 集 case id 与 baseline_val 不完全对齐(如 id 漂移、新增 case)时,两条路径的 new_failures/is_overfitting 判定会分叉,trace 路径可能把"baseline 未评测但候选通过"的 case 记为 unchanged 而漏掉回归信号。建议统一为保守的 False(与 :86 注释一致),或显式要求两侧 id 对齐。
    • # run_validation_trace(:327)默认 True
      bl_pass = baseline_map.get(case_id, True)
      # run_validation_fake(:86)默认 False(保守)
      bl_pass = baseline_map.get(case_id, False)
  • examples/optimization/eval_optimize_loop/pipeline/validate.py:142-144:overfit 场景下,case 若携带 candidate_conversation 会直接回放并 return,跳过 _perturb_case 的 val 回归扰动

    • 当 evalset 既用 candidate_conversation 做隐藏样本回放、又选了 overfit 场景时,被选中的回归 case 不会真正退化,最终落到 :353new_failures==0 分支抛 ValueError,而非产生预期的 val 回归。当前测试数据未触发该组合,但属真实可复用路径。建议在 overfit 场景下对回归 case 不走 candidate_conversation 快捷分支,或在扰动后覆盖其 actual_conversation

💡 Suggestion

  • examples/optimization/eval_optimize_loop/pipeline/comparator.py:509compare_case 只对 min(len(conversation), len(actual)) 条 invocation 评分,len(actual) > len(conversation) 的多余实际回复被完全忽略。当前不构成判错,但候选多吐内容时不会被任何层捕获,长期看可考虑对超出长度的 actual 追加一个 format_not_as_required 证据,避免冗余输出静默通过。

总结

整体为新增的示例 pipeline,逻辑自洽、异常降级与审计路径覆盖充分、关键 helper(路径安全、live 降级、CI 退出码)均有单测。未发现 Critical 级阻塞问题;两处 Warning 集中在 validate 模块对"缺失 case"和"candidate_conversation + overfit 组合"的边界处理一致性,建议修复以避免过拟合检测在异常数据下失真。

测试建议

  • 补充一组测试:val 集 case id 与 baseline_val.per_case_results 存在差异(漂移/新增)时,run_validation_tracerun_validation_fakenew_failures/is_overfitting 行为是否一致。
  • 补充 overfit + 回归 case 携带 candidate_conversation 的组合用例,确认扰动是否生效或是否按预期报错。

…candidate_conversation

- validate: run_validation_trace now defaults a baseline-missing case to
  False like run_validation_fake (was True), so id drift/new cases can't hide
  a regression as unchanged
- validate: overfit val-regression targets bypass the candidate_conversation
  replay shortcut so they are actually perturbed (hidden-sample replay would
  otherwise skip the regression and hit the new_failures==0 ValueError)
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

没有硬编码的密钥。很好。现在让我完成评审。

发现的问题

🚨 Critical

无。

⚠️ Warning

  • examples/optimization/eval_optimize_loop/pipeline/comparator.py:505-510compare_casen = min(len(conversation), len(actual)) 附近):当 actual_conversationconversation 更长(实际多产出了 invocation)时,多出的实际 invocation 被静默忽略,既不计入 score 也不产生失败。仅 len(conversation) > len(actual) 方向被处理为 MISSING_EXPECTED_OUTPUT。这会让"实际多回答/跑偏"的 case 被误判通过;建议对 len(actual) > len(conversation) 也计 0.0 分并归类(如 final_response_mismatch/unknown)。

  • examples/optimization/eval_optimize_loop/pipeline/optimize.py:481-487run_optimize_livetarget.add_path(..., os.path.join(prompt_dir, fname)) 附近):prompt_dir 取自 config.prompt_dir,默认 "data/prompts"(相对路径)。run_pipeline.py 要求从仓库内运行但并不保证 CWD 为 example 目录,非 example 目录下运行 --mode live 时该相对路径解析失败,os.listdir 抛异常 → 整个 live 优化降级为空结果。建议基于 __file__/example 目录解析为绝对路径,或与 optimizer_config 同样接受绝对路径入参。

  • examples/optimization/eval_optimize_loop/tests/test_baseline.py:113-129test_sdk_falls_back_to_trace_comparator):测试名声称验证"SDK 失败→降级到 trace comparator",但未 monkeypatch SDK,而仓库实际存在 trpc_agent_sdk 源码包且 ensure_repo_root_in_path() 会将其加入 sys.path,因此测试走的是真实 SDK 评测路径而非降级路径。结果依赖 SDK 对临时 trace case 的实际评分,既不稳定也并未验证降级契约;建议显式 monkeypatch sys.modules["trpc_agent_sdk.evaluation"] 强制走降级分支后再断言。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/run_pipeline.py:644-648(live baseline 加载 EvalConfig 附近):_eval_config 加载失败时设为 None 传入 run_baseline_sdk,后者内部又会再次尝试 load_optimize_config(同一已失败的加载),属于重复尝试。可在 main 侧加载成功时直接传入、失败时显式跳过 SDK 路径,避免重复加载与日志噪音。

总结

整体实现稳健,SDK 字段映射、降级路径、过拟合 gate、审计追踪与路径安全校验均经验证正确,未发现安全或核心逻辑层面的 Critical 问题。存在两处边界正确性缺口(多余 actual invocation 被忽略、live 模式相对 prompt_dir)和一处测试有效性问题(降级测试未真正走降级路径),建议修复但不阻塞合入。

测试建议

  • 补充 compare_caselen(actual_conversation) > len(conversation) 时的断言(应判失败而非通过)。
  • test_sdk_falls_back_to_trace_comparator 增加 monkeypatch SDK 模块的变体,真正覆盖降级分支。

…r, deterministic fallback test

- comparator: when actual_conversation has MORE invocations than expected,
  the overflow is now scored 0.0 and classified (final_response_mismatch)
  instead of being silently ignored (was only handling the missing-output side)
- optimize: live-mode prompt_dir (relative 'data/prompts') is anchored to the
  example dir so running from another CWD no longer crashes live optimization
- test_baseline: force the SDK-fallback branch deterministically via
  monkeypatch (repo ships trpc_agent_sdk, so it wasn't actually testing the
  fallback path) and assert the fallback errors
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.

构建 Evaluation + Optimization 的自动回归与提示词优化闭环

2 participants