Skip to content

feat: add eval-optimize-loop pipeline (Evaluation + Prompt Optimization) - #247

Open
Joannaxxx123 wants to merge 7 commits into
trpc-group:mainfrom
Joannaxxx123:feat/eval-optimize-loop
Open

feat: add eval-optimize-loop pipeline (Evaluation + Prompt Optimization)#247
Joannaxxx123 wants to merge 7 commits into
trpc-group:mainfrom
Joannaxxx123:feat/eval-optimize-loop

Conversation

@Joannaxxx123

Copy link
Copy Markdown

Summary

新增 examples/optimization/eval_optimize_loop/,实现"评测 → 失败归因 → Prompt 优化 → 回归验证 → 产物审计"的自动闭环 Pipeline。

Pipeline Flow

Stage 1: Baseline Evaluation → 评测 train + val set
Stage 2: Failure Attribution → 按类型聚类 (hallucination / tool_call_error / missing_information / overgeneralization)
Stage 3: Optimization → 基于训练集失败诊断, 定向修改 prompt
Stage 4: Candidate Validation → 用 optimized prompt 重新跑 val set
Stage 5: Delta Comparison + Gate → 5 项检查决定 ACCEPT / REJECT
Stage 6: Report Generation → JSON + Markdown 双格式输出

Features

  • Trace mode: 无需 API Key, < 1 秒跑通完整流程, 使用预录制轨迹
  • Live mode: 接入 DeepSeek API, LLM Judge 评测, 真实 GEPA 优化
  • 12 条测试用例: 覆盖可优化 / 优化无效 / 过拟合退化三类场景
  • Gate 决策: 5 项可配置检查 (min_improvement / no_hard_regression / key_cases / cost / per_metric_floor)
  • 完整审计: 每阶段输出 detail JSON + 结构化 MD 报告 + prompt 前后对比

Files Changed

  • examples/optimization/eval_optimize_loop/ — 全部新增

6-stage: Baseline -> Attribution -> Optimize -> Validate -> Gate -> Report. Trace mode (<1s, no API) + Live mode (DeepSeek API). 12 test cases covering optimizable/ineffective/overfitting. Output: JSON + MD with per-case trace and gate decision.
@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown

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

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

没有重载。已确认该 bug。get_agent_async 仅在 root_agent 为 None 时才会被使用,但此处定义了 root_agent,因此会使用该单例。如果 PR 作者将 root_agent = create_agent() 替换为 async def get_agent_async(),评估器会在每次评估时调用它,从而生成一个新的 agent 来读取磁盘。这就是修复方案。

现在我来撰写最终的审查报告。## 发现的问题

🚨 Critical

  • examples/optimization/eval_optimize_loop/agent/agent.py:145root_agent = create_agent() 是模块级单例,导致 live 模式候选验证使用的是 baseline 的旧指令

    • AgentEvaluator._get_agent_for_eval 通过 importlib.import_module("agent") 加载模块(命中缓存),并优先取模块级 root_agent(见 trpc_agent_sdk/evaluation/_agent_evaluator.py:607-624,仅在 root_agent 为 None 时才回退到 get_agent_async())。该单例在首次 import(Stage 1)时通过 _read_instruction() 读盘构建一次,之后 Stage 4 写盘的新 candidate prompt 不会反映到已加载 agent 的 instruction 里——_read_instruction 注释宣称"每次调用重新读盘"但实际并不会每次 eval 重新构建。结果是候选评估复用 baseline 同一个 agent,candidate_val ≈ baseline_val,delta≈0,Gate 永远因 min_improvement 被拒,整个 live 模式"优化→回归→过拟合拒绝"演示失效。
    • 修复方向:把 root_agent = create_agent() 改为 async def get_agent_async()(或 def get_agent())返回每次新建的 agent,使其每次 eval 都读盘;或在 orchestrator 每次评估前 importlib.reload(sys.modules["agent"])
  • examples/optimization/eval_optimize_loop/agent/optimizer.json:18examples/optimization/eval_optimize_loop/agent/optimizer.json:44examples/optimization/eval_optimize_loop/agent/test_config.json:13:硬编码真实 DeepSeek API Key

    • 三处 "api_key": "sk-b2df4854c5e14aa4baa0a8fb61310130" 直接提交进仓库,属于凭证泄露,进入 git 历史后即使删除也已被记录,需立即吊销并改用环境变量(如 TRPC_AGENT_API_KEY)注入。该 key 还会被 CI/他人本地运行时直接使用,存在计费与滥用风险。

⚠️ Warning

  • examples/optimization/eval_optimize_loop/pipeline/orchestrator.py:6462:trace 模式引用的 trace_train_candidate.evalset.json 在本 PR 中并不存在

    • Stage 4 在 trace 分支会尝试加载该文件,必然抛异常被 except Exception 吞掉并回退到 ctx.baseline_train,使得 delta_report_train.delta == 0,过拟合 Gate 的 train_improved 判定永不成立。要么补充该 trace 文件,要么显式跳过 train 候选评估并在报告中标注,而不是依赖被吞掉的异常静默回退。
  • examples/optimization/eval_optimize_loop/README.md(目录结构图)与 examples/optimization/eval_optimize_loop/run_pipeline.py:91:输出目录文档/实现不一致

    • README 描述输出落在 output/trace_mode/output/live_mode/,但 run_pipeline.py 实际写入 output/<timestamp>/datetime.now().strftime(...))。当前已提交的 output/trace_modeoutput/live_mode 目录与代码运行结果对不上,运行后会新增大量时间戳目录而非覆盖既有目录,易误导复用者。建议统一为固定子目录或更新文档。
  • examples/optimization/eval_optimize_loop/README.md:21:Live mode 示例使用 set VAR=value 语法

    • README 在通用说明里用 set TRPC_AGENT_API_KEY=sk-xxx(Windows CMD 语法),而同块又给出 bash 风格的多行命令,macOS/Linux 用户照抄会失败。建议统一为 export 或在 docstring(run_pipeline.py:13 已是 bash 风格)一致的写法。

💡 Suggestion

总结

存在两处必须修复的问题:live 模式因 agent 单例缓存导致候选验证实际复用 baseline agent(核心功能失效),以及三处硬编码真实 API Key(凭证泄露)。修复前不建议合入。

测试建议

  • 增加 live 模式端到端测试:在 Stage 4 前后断言 _read_instruction() 读到的盘上内容与 ctx.optimize_result.best_prompts 一致,并验证 candidate agent 的 instruction 确实为优化后 prompt(而非 baseline),以锁定单例缓存回归。
  • 补充一个最小 live 冒烟测试(mock LLM)验证 candidate pass_rate 与 baseline pass_rate 可区分,避免"候选=基准"的静默退化。

Comment thread examples/optimization/eval_optimize_loop/agent/agent.py Outdated
@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.00000% with 1 line in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@6a2f7f9). Learn more about missing BASE report.

Files with missing lines Patch % Lines
trpc_agent_sdk/evaluation/_agent_evaluator.py 90.00000% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main        #247   +/-   ##
==========================================
  Coverage        ?   87.87114%           
==========================================
  Files           ?         482           
  Lines           ?       45165           
  Branches        ?           0           
==========================================
  Hits            ?       39687           
  Misses          ?        5478           
  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.

@taoisnie

Copy link
Copy Markdown
Contributor

注意api-key等凭证信息,一定要脱敏,防止泄漏

…y; replace hardcoded API keys with env vars; fix trace train candidate silent fallback
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

已确认:报告生成器(reporter)中的 _build_per_case_delta 仅针对 newly_failing 情况设置 failure_typefailure_reason。val_003 为 newly_failing,其 failure_type 为 "overgeneralization",failure_reason 为一段叙述。当前的报告生成器代码(第 6734-6747 行)确实为过度泛化(overgeneralization)子情况生成 failure_type "overgeneralization",并带有特定的消息文本 "过度泛化:Prompt 导致 Agent 在仅需..."。让我核对一下——已提交的报告显示为 "过度泛化:候选prompt导致模型在仅需库存查询时额外调用了价格工具。这是典型的过拟合——...",这与当前报告生成器第 6741-6744 行的文本("过度泛化:Prompt 导致 Agent 在仅需 {sorted(exp_tools)} 的场景下额外调用了 {extra}。模型过度学习了训练集的模式,在不需要时也补充了额外信息。")并不匹配。所以确实是不同步的——叙述文本不同。

这证实了已提交的报告来自于不同版本的报告生成器。因此,这种不同步是确实存在的,且涉及 gate 和 reporter 两个部分。

现在我已经有了完整的概念。让我来撰写审查报告。

发现的问题

⚠️ Warning

  • examples/optimization/eval_optimize_loop/output/live_mode/optimization_report.json:869-881:提交的 live 报告与当前 gate.py 逻辑不一致,属于无法复现的误导性产物

    • 该报告里 gate_decision.reason 为一段自定义叙述("存在硬回归: val_003(PASS->FAIL)。候选prompt导致模型在纯库存查询时过度泛化…")并带有 "warnings": ["val_003 overgeneralization: …"],但当前 pipeline/gate.py:59 生成的是 "Gate 拒绝: 存在硬回归 (PASS→FAIL): ['val_003']" 格式、且从不输出该 warning;同时 val delta=+0.67 时当前 gate.py:97no_overfit 应为 true,报告却为 false。同样 output/trace_mode/optimization_report.json:477-488 的 checks 缺少 no_overfit、baseline prompt 也与当前 system.md/BASELINE_PRESETS 不符。这些产物由旧版代码生成,会误导阅读者,建议要么用当前代码重新生成覆盖,要么从仓库中删除。
  • examples/optimization/eval_optimize_loop/pipeline/orchestrator.py:112-113:6487-6489:在已提交的源文件 agent/prompts/system.md/skill.md 上原地改写再恢复,存在源文件被污染的风险

    • Stage 0 把 defective preset 直接 write_text 覆盖到 agent/prompts/system.md,Stage 4 再把候选 prompt 覆盖上去,依赖 finally 中的 _restore_original_prompts_if_needed() 还原。若进程被 SIGKILL/断电或在 finally 之前硬中断, defective/候选 prompt 会残留在被 git 跟踪的源文件里。建议改为写到临时副本或独立的 candidate prompt 路径,由 get_agent() 读取该路径,避免改写已提交源文件。
  • examples/optimization/eval_optimize_loop/:归因与 Gate 逻辑无任何测试覆盖

    • attributor.py(684 行,含大量正则/启发式兜底)、gate.py(6 项 check 含过拟合判定)、comparator.py 均为高风险纯逻辑且无单测;同仓库近期为 examples/evaluation 新增了 pytest 配置却未覆盖本示例。建议至少为 _classify_case(各 failure_type 分支)和 gate.decide(min_improvement / no_hard_regression / no_overfit 三类拒绝路径)补充纯函数单测。

💡 Suggestion

总结

整体无安全/命令注入等 Critical 阻塞问题,pipeline 结构完整、prompt 还原逻辑基本正确。主要风险是提交的输出产物与当前代码已脱节(误导性产物)、原地改写已提交源文件的中断污染风险,以及核心归因/Gate 逻辑缺少测试。

测试建议

  • attributor._classify_case 补充单测:覆盖 tool_call_error/missing_information/overgeneralization/hallucination/format_error 及 case-level 兜底分支,确保 FAILED case 不会落到空 failure_types
  • gate.decide 补充单测:覆盖 min_improvement 不足、no_hard_regression(newly_failing)、no_overfit(train↑ val↓) 三类拒绝路径以及全通过时 accepted=True

@Joannaxxx123

Copy link
Copy Markdown
Author

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

Rook1ex added a commit to trpc-group/cla-database that referenced this pull request Jul 28, 2026
@Joannaxxx123 Joannaxxx123 reopened this Jul 29, 2026
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

确认。现在让我来编写审查报告。我还需要更仔细地验证一下 trace-mode 下 trace_val_candidate 路径的存在性检查问题——这里没有进行存在性检查,与训练集不同。如果有人在不包含该文件的 agent 目录下运行,run_evaluation 会调用 AgentEvaluator.get_executer,进而调用 _load_eval_set_from_fileFileNotFoundError。这会被明确抛出。尚可接受,但值得注意:测试套件依赖于已提交的文件。

让我再次确认关于检测带有 .json: 的 Windows 路径的 _split_eval_set_selector —— 有一个真正值得关注的问题:如果 evalset 路径是一个不包含 .json 的 Windows 驱动器路径,例如 C:\data\evalset:case1(没有 .json 后缀),新代码会返回整个字符串且 selected_case_id=None,然后 os.path.exists("C:\data\evalset:case1") 执行失败。但 ADK 的约定是 file.json:case。并不是真正的回归问题。

现在进行总结。

发现的问题

🚨 Critical

  • examples/optimization/eval_optimize_loop/pipeline/orchestrator.py:11047-11059:trace 模式下 trace_val_candidate.evalset.json 未做存在性校验即直接进入评测。
    • 与 train 候选轨迹不同(train 候选有 candidate_train_path.exists() 守卫并在缺失时回退 baseline),val 候选直接 await run_evaluation(evalset_path=candidate_val_path, ...)。一旦该文件缺失(例如其他开发者复用本示例裁剪了候选轨迹),_load_eval_set_from_file 会抛 FileNotFoundError 使整条 pipeline 崩溃,而不是回退或给出可读提示。建议同样加 .exists() 守卫并明确报错原因,保持与 train 候选一致的容错策略。

⚠️ Warning

  • examples/optimization/eval_optimize_loop/pipeline/attributor.py:9718-9729:兜底归因中的"工具返回与回答矛盾"检测为死代码,永远不会触发。

    • evaluator._extract_tool_call_dicts 只产出 {"name","args"} 的字典(见 evaluator.py:27),不存在 raw_return 键,因此 actual_tool_returns_text 恒为空串,下方针对 available:false/status:紧张 的矛盾判定分支永远不执行。这使 README 宣称的 contradictory_information 归因在该兜底路径上实际失效。建议改为从 case 中提取工具返回值(如使用 get_all_tool_responses)后再做矛盾比对,或显式标注该路径暂不支持矛盾检测。
  • examples/optimization/eval_optimize_loop/pipeline/evaluator.py:10341-10352:多 num_runs 场景下 case 通过状态与指标聚合口径不一致。

    • total_cases 每个 case_id 仅计 1 次,但 all_passfor ecr in run_results 内被反复覆盖,最终只反映最后一条 run 的 final_eval_status;同时 metric_scores 会把多次 run 的分数都计入平均。当前 test_config.json 固定 num_runs=1,不会暴露问题,但一旦提高 num_runs,pass_rate 与 metric_breakdown 会基于不同的 run 子集计算,产生不一致统计。建议显式聚合多次 run(如全部 run 均 PASSED 才算该 case 通过),或在 num_runs>1 时明确语义。
  • examples/optimization/eval_optimize_loop/pipeline/orchestrator.py:11054-11057:trace 模式候选 train 轨迹缺失时注释声称"overfit Gate disabled",但实际并未禁用。

    • 该分支仅将 candidate_train = baseline_train,随后 delta_report_train = compare(baseline_train, baseline_train) 得到 train_delta=0,过拟合检查因不满足 train_delta >= 0.01 自然不触发,而非被显式关闭。注释与行为不符,易误导后续维护者误以为存在专门的禁用逻辑。建议修正注释,或在确实希望跳过时显式置 reject_overfit/传 delta_report_train=None

💡 Suggestion

  • examples/optimization/eval_optimize_loop/agent/optimizer.json:423test_config.json:513llm_rubric_response 阈值分别为 0.850.5,且 optimizer.json 定义了一套与 test_config.json 不同的 rubric(r1/r2/r3 vs completeness/no_redundancy/no_extra)。该 optimizer.json 未被本 pipeline 实际使用(pipeline 走 analysis-driven 路径),但文件并存易让维护者误以为 GEPA 优化器会运行并产生归因所依赖的 rubric id。建议在 README 或文件头注明 optimizer.json 为 SDK GEPA 优化器的遗留/备选配置,归因实际依赖 test_config.json 的 rubric id。

总结

SDK 层两处改动(_split_eval_set_selector 修复 Windows 路径误切、单模型聚合保留 rubric_scores)正确且有测试覆盖,无阻塞问题。主要风险集中在示例 pipeline:trace 模式 val 候选轨迹缺存在性校验(建议修),以及 attributor 兜底矛盾检测因工具字典缺字段而是死代码(建议修)。

测试建议

  • 补充 trace 模式 trace_val_candidate.evalset.json 缺失场景的测试,验证行为符合预期(报错或回退),与 train 候选缺失路径对称。
  • 为 attributor 兜底矛盾检测补充一条用例:当候选回答声称"可以配送/库存充足"但工具返回相反结果时,应能产出 contradictory_information(当前会失败,可据此驱动修复 raw_return 取值来源)。

Comment thread examples/optimization/eval_optimize_loop/pipeline/orchestrator.py Outdated
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我现在完全理解了。让我来整理一下评审意见。我会先验证几个候选问题。

问题 A — per_metric_floor 检查使用了 d.candidate_scores,当候选用例是 NOT_EVALUATED 时可能缺失。_validate_evaluation_completed 会拒绝 NOT_EVALUATED 指标,因此所有指标得分都存在。然而,comparator 中的 candidate_scores 是由 cc.metrics.values() 构建的 —— 只有当指标存在于候选结果中时才包含。如果候选用例有不同的指标……在实践中是一样的配置。影响较小。不属于明确的 Bug。

问题 B — 对比器 delta = candidate.pass_rate - baseline.pass_rate 验证候选和验证基准使用相同的基础值。没问题。

让我验证一个真正的问题:orchestrator trace 模式 Stage 4 候选验证调用了 run_evaluation(...),但没有包含 **eval_kwargs(为空)也没传入 agent_module —— 这对于仅追踪(trace-only)来说是正确的。但是,追踪候选 evalset 可能包含需要 call_agent/agent_module 的用例。 由于追踪固件(trace fixtures)是仅追踪(trace-only)的(预记录),因此不需要 agent。很好。

现在,问题 C(真正的 Bug):在实时模式下,当 ctx.baseline_train.pass_rate >= 1.0 时,Stage 3 会执行 else: diagnosis = "训练集全部通过,无需优化。" 分支,但 optimized_system = baseline_system(没有变化),并且 optimized_skill = baseline_skill。然后创建了 ctx.optimize_result,其 best_prompts = {system: baseline_system, skill: baseline_skill}。由于 best_prompts.get("system_prompt") 为真,Stage 4 会将候选物化为相同的提示词并重新评估。这会浪费一次评估资源,但不是 Bug。

我打算重点关注的实际问题,我先仔细检查一下 agent/config.py 的路径。实时模式是有效的。但是,在追踪模式下,get_model_config 会抛出 ValueError。代理模块 agent/agent.py 是否会在追踪模式下被导入?在追踪模式下,agent_module=None,因此 SDK 不会导入 agent 模块。但是 run_pipeline.py 导入了 from pipeline.orchestrator import run_pipeline,而 orchestrator 并不导入 agent。很好 —— 在追踪模式下不会调用 get_model_config

然而,pipeline/evaluator.py 顶部有 from trpc_agent_sdk.evaluation import AgentEvaluator, EvalSet, EvalConfig。而在追踪模式下它仍然有效。

现在让我再找一个我想验证的真正的正确性 Bug:gate 的 key_cases 检查。key_failed 会遍历 delta.per_case(验证集),如果 d.case_id in gate_config.key_casesd.candidate_status == "FAILED"。配置中设置了 key_cases: ["val_002"]。没问题。

让我再检查一下 _classify_case 中 dict.fromkeys 去重后的 failure_typesexplanations 的顺序不匹配是否会导致报告解释信息不匹配。解释信息只是拼接在一起的,没有按类型索引。没问题。

现在让我敲定真正的问题。让我重新检查评估器的 pass_rate_validate_evaluation_completed —— 有一个微妙的问题:_validate_evaluation_completed 只检查每个 case_result(运行结果)的 overall_eval_metric_results。但评估器的主循环使用 ecr.final_eval_status 来判断 all_pass。这些是不同的。如果指标未评估,验证会先抛出异常。没问题。

现在让我再重新考虑一下 orchestrator 中 trace 模式的 duration_seconds=0.0started_at=finished_at —— 对于 trace 模式,duration_seconds=0.0(硬编码,没有使用 time.time() - t0),而 live 模式使用了 round(time.time()-t0,1)。轻微的不一致,不是 Bug。

我现在已经有了足够的验证。让我写下最终确定的发现。

让我再仔细检查一下:comparator 中第 36 行左右的 _classify_change —— 当两个状态都不是通过/失败(例如 NOT_EVALUATED)时,它会进入最终的 return "unchanged"。没问题。

现在,关于 candidate_validation_error/trace-val-missing 与 min_improvement 的 gate 交互,这里有一个微妙但实际的问题。当缺少 trace val 候选固件时,ctx.candidate_val = ctx.baseline_val,因此 delta.delta = 0,并且 candidate_validation_error 被设置 → gate 拒绝。记录在案且是有意的。

现在让我确认一下我想要标记的最重要的实际问题。让我重新读一下评估器对 metric_scoresmetric_breakdown 的处理:当某个指标在所有用例中都没有失败时,但仍然有分数,metric_scores.setdefault(name, []).append(emr.score)。这会针对每个用例追加每个指标。但如果某个用例缺少该指标(指标仅存在于某些用例中),那么平均值就只基于存在的用例计算。这是合理的。

现在让我重新考虑我认为最有说服力的 Bug:在 gate.py 中,检查 per_metric_floor 会针对每个超出下限的用例+指标追加原因,但不会将用例信息包含得很好。这不是 Bug。

让我重新思考一下,是否存在任何 Critical 问题。让我重新检查 _split_eval_set_selector —— 这是一个实际的修复(之前破坏了 Windows 路径)。新代码:marker_index = eval_set_file.lower().rfind(".json:")。对于 D:\work\trace_train.evalset.json,没有 .json: 后缀 → 返回完整路径,None。很好。对于 .json:case_001 → 正确拆分。这个 SDK 修改是正确的。但是等等 —— trace_train.evalset.json:case_001 怎么办?标记是 .json: —— evalset.json:case_001 包含 .json:marker_index.evalset.json 中找到 .json……实际上 rfind(".json:") 找到的是子字符串 .json:。在 trace_train.evalset.json:case_001 中,子字符串 .json:.evalset.json: 中的 .json: 位置开始。path_end = marker_index + 5selected = eval_set_file[path_end+1:] = case_001actual = eval_set_file[:path_end] = trace_train.evalset.json。正确。

测试涵盖了 Windows 驱动器路径、带用例的 Windows 路径以及 unix 路径。很好。SDK 修改很可靠。

现在让我确保没有遗漏安全性问题。optimizer.json 使用了 ${TRPC_AGENT_API_KEY} 插值 —— api key 是通过环境变量引用的,而不是硬编码的。很好。没有泄露。

现在,关于示例 agent get_product_price 的默认回退prices.get((city, product), {"price": 5, ...}) 返回一个默认值。这是一个示例工具,不是安全问题。

我现在已经有了充分的了解。让我最终确定。我会将 attributor 中死代码/失效启发式的问题标记为 Warning(可达但无用的矛盾检测),以及评估器 pass_rate 边界(ZeroDivision 已防护)。坦率地说,这些都不是 Critical 问题。

让我重新考虑最值得标记的事项。让我看看 test_sample_reports.py 中是否存在测试有效性差距 —— 它断言 test_public_trace_cases_cover_required_optimization_outcomes 要求包含 newly_failing。这锁定了提交的报告。这是一个防护测试,没问题。

让我重新考虑 _validate_evaluation_completed 的测试_result(status="FAILED", score=0.0) —— 指标有 eval_status.name="FAILED",score=0.0。验证:overall_eval_metric_results 非空,对于每个指标,status != NOT_EVALUATED 且 score 不为 None → 通过。很好。

现在,一个真正的担忧:_validate_evaluation_completed 检查 if not case_result.overall_eval_metric_results,但构造的测试 SimpleNamespace 具有 overall_eval_metric_results=[metric] 且没有 eval_metric_result_per_invocation 属性。在 NOT_EVALUATED 路径中,代码执行 getattr(case_result, "eval_metric_result_per_invocation", []) → 默认为 []。没问题。很好。

好的,让我最终确定。我认为坦率地说没有 Critical 问题。让我重新考虑是否有一个值得 Warning 的问题。

实际上,让我重新检查 orchestrator 的环境变量竞态条件,将其视为影响维护性/稳定性的一个真正问题:流水线设置了进程全局的 EVAL_OPT_SYSTEM_PROMPT_PATH。如果两个 run_pipeline 在同一个进程中并发执行(例如在测试中或未来的并行化),它们会相互覆盖对方的提示词环境变量,导致候选验证在错误的提示词下运行。finally 中的恢复操作也会在退出时恢复到原始状态。鉴于这是单运行入口,这是一个稳定性/并发风险。我将把它标记为 Warning。

还有 candidate_train_path 回退 ctx.candidate_train = ctx.baseline_train 会导致 delta_report_train.delta = 0,从而静默禁用过拟合检测 —— 但这是有文档记录且有意为之的。我最多只会提一下,但不会标记,因为它已经有文档记录。

让我写下最终评审。考虑到限制条件,我会保持精确。

发现的问题

⚠️ Warning

  • examples/optimization/eval_optimize_loop/pipeline/orchestrator.py:88-94run_pipeline_SYSTEM_PROMPT_ENV/_SKILL_PROMPT_ENV 的设置与恢复):候选 prompt 通过进程级 os.environ 注入,Agent 在 agent.py:307_read_instruction 中读取这两个变量。run_pipeline 期间全程占用进程级环境变量,若同一进程内并发执行(如测试或后续并行化),两次运行的 baseline/candidate 环境变量会互相覆盖,导致候选验证实际加载到错误的 prompt 快照而结果失真且无报错。建议改为通过参数/上下文显式传递 prompt 路径,或将整段流程限定为进程内单运行并加注释说明该约束。

  • examples/optimization/eval_optimize_loop/pipeline/orchestrator.py:424(trace 模式缺失 trace_train_candidate.evalset.jsonctx.candidate_train = ctx.baseline_train):此时 delta_report_train.delta 恒为 0,Gate 的过拟合检测 (no_overfit) 会被静默跳过,过拟合拒绝路径在该场景下不会触发。虽 README 有“train delta=0; no train-side overfit evidence”说明,但建议在 optimizer_detail.json/报告中显式标注“train-side overfit check skipped due to missing candidate train fixture”,避免审计时误判为“未过拟合”。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/pipeline/attributor.py:665-676:兜底矛盾检测读取 t.get("raw_return", ...),而 _extract_tool_call_dicts 仅产出 {"name","args"},不含 raw_return,因此该段 actual_tool_returns_text 恒为空、两条矛盾判定永远不会命中,属于失效启发式。建议从工具返回结构中提取真实返回值,或删除该不可达分支以避免误导后续维护。

总结

未发现必须修复的 Critical 问题:SDK 侧 _split_eval_set_selector 的 Windows 路径修复与单模型 rubric_scores 透传均正确且有测试覆盖;pipeline 各阶段逻辑自洽,样例报告通过自洽性测试。主要风险是候选 prompt 经进程级 os.environ 注入带来的并发安全隐患,以及 trace 模式缺失 candidate train fixture 时过拟合检测被静默跳过的可审计性问题,建议修复但不阻塞合入。

测试建议

  • 建议补一个用例覆盖:同一进程内并发两次 run_pipeline(或 mock get_agent),断言候选验证阶段读取到的 prompt 与各自 run 的 candidate 快照一致,以暴露环境变量串用风险。
  • 建议补一个 trace 模式缺少 trace_train_candidate.evalset.json 的场景测试,断言报告中明确出现“overfit check skipped”类标注,避免审计误读。

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