Skip to content

feat: add Evaluation + Optimization closed-loop example (#91) - #263

Open
Ygrowly wants to merge 6 commits into
trpc-group:mainfrom
Ygrowly:feat/91-champion-challenger
Open

feat: add Evaluation + Optimization closed-loop example (#91)#263
Ygrowly wants to merge 6 commits into
trpc-group:mainfrom
Ygrowly:feat/91-champion-challenger

Conversation

@Ygrowly

@Ygrowly Ygrowly commented Jul 30, 2026

Copy link
Copy Markdown

feat: Evaluation + Optimization 自动回归与提示词优化闭环示例 (#91)

Refs #91

概述

新增 examples/optimization/eval_optimize_loop/,实现 Issue #91 要求的
"评测 → 失败归因 → prompt 优化 → 回归验证 → 产物审计" Champion–Challenger 闭环:
输入 train/val evalset + optimizer.json + prompt 源文件,输出
optimization_report.{json,md} 与是否接受候选的 Gate 决策;默认 dry-run,
仅 ACCEPT 且显式 --apply 时经 TargetPrompt 原子写回。

交付物(对照 Issue)

  • 6 阶段 pipeline:baseline 评测(真实 AgentEvaluator)、按 evaluator 证据的失败归因、
    AgentOptimizer.optimize(update_source=False) 原生优化、候选 val 回归逐 case 对比、
    G1–G7 可配置 Gate、审计落盘(唯一 run_id、frozen.json 哈希清单、逐轮候选、成本、耗时、种子)
  • 6 条评测 case(3 train + 3 val),覆盖可优化成功 / 优化无效 / 优化后退化三类场景
  • optimization_report.example.json 示例输出;DESIGN.md 为 300–500 字方案设计说明
  • fake mode 无 API Key 可跑通全流程(单场景 < 30 秒,远低于 3 分钟上限)

验收对照

验收项 结果
6 条样例 case 全部可运行并产出完整报告 ✅ 三场景实测:success→ACCEPT;no_effect→REJECT G1;overfit→REJECT G2(过拟合拒绝)
过拟合场景必须拒绝 ✅ G2 规则 + 真值表测试
失败归因可解释 ✅ 每个失败 case 输出 category + 理由 + 原始证据(actual/expected、metric reason、tool/param diff、trace 引用)
fake/trace mode ≤ 3 分钟
报告含 baseline/candidate/delta/gate/理由 ✅ schema v2.0,逐 case delta 与 transition
隐藏样本准确率 ⚠️ 官方隐藏集未提供,无法自证;提供公开标注归因 holdout(8/8)与三场景决策真值表,README 已明确边界

真实 API 端到端验证

已用 DeepSeek(OpenAI 兼容端点)跑通 --mode optimize:GEPA reflective 4 轮、
42 条模型调用全部审计落盘、Gate 正确 REJECT(无有效提升 + 成本证据缺失禁止自动写回)。
凭据经示例目录下被 gitignore 的 .env 读取,不进仓库。

SDK 修复说明(examples 之外的唯一改动)

trpc_agent_sdk/evaluation/_agent_evaluator.py"file.json:case_id" 冒号切分
与 Windows 盘符路径(如 C:\...\Temp\batch.evalset.json,GEPA adapter 的临时文件)冲突,
导致 Eval set file not found: C。最小修复:仅当完整路径不存在且冒号左侧为存在文件时
才按 case 选择器解析。tests/evaluation 全套回归通过。

测试

python -m pytest examples/optimization/eval_optimize_loop/tests -q   # 70 passed
python -m pytest tests/evaluation -q                                 # all passed
python -m compileall -q examples/optimization/eval_optimize_loop     # exit 0

Ygrowly added 2 commits July 30, 2026 14:39
- Fix Windows drive-colon parsing in _agent_evaluator eval set loader

- Load optimizer credentials from gitignored .env; lazy-import live_agent

- Remove internal notes and local tool config from the branch

- Rewrite DESIGN.md as the 300-500 word design note required by trpc-group#91
@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

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

Additional details and impacted files
@@            Coverage Diff             @@
##             main        #263   +/-   ##
==========================================
  Coverage        ?   87.87785%           
==========================================
  Files           ?         482           
  Lines           ?       45190           
  Branches        ?           0           
==========================================
  Hits            ?       39712           
  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.

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown

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

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

发现的问题

⚠️ Warning

  • examples/optimization/eval_optimize_loop/runner.py:2861-2870_run_evaluator 用裸 except AssertionError: pass 吞掉评测器所有断言错误,再回退到 result or EvaluateResult()
    • 该 except 不区分“case 失败”与配置/契约类断言错误;一旦 evaluate() 因配置错误、SDK bug 等抛 AssertionError,会被静默吞掉,get_result() 可能返回空结果,导致全部 case 落入 NOT_EVALUATEDinfrastructure_failure,并触发失真的 REJECT 报告。建议收窄捕获范围(仅捕获已知的 case-failure 断言)或至少记录异常类型与消息,避免把评测执行失败伪装成“基础设施失败”。
    ...
    try:
        await executer.evaluate()
    except AssertionError:
        pass
    result = executer.get_result()
    ...
    return result or EvaluateResult()

💡 Suggestion

  • examples/optimization/eval_optimize_loop/pipeline.py:1767-1774_build_repro_cmd 直接用 f"{flag}={value}" 拼接,对含空格或特殊 shell 字符的 --candidate-file / --optimizer-config 路径不做引用,生成的 repro_cmd 在复制执行时可能被 shell 错拆。可用 shlex.quote 包裹值以提升复现命令的健壮性。

总结

整体为新增示例管线 + 一处生产侧 _load_eval_set_from_file 的冒号选择器修复(Windows 盘符/含冒号文件名处理更健壮,向后兼容)。未发现阻塞级安全或逻辑错误;主要风险是 runner._run_evaluator 过宽地吞掉 AssertionError,可能掩盖真实评测错误并产生误导性 REJECT,建议收窄。

测试建议

  • 建议补一个用例:在 evaluate() 抛出非 case-failure 的 AssertionError(或 get_result() 返回 None)时,断言 _run_evaluator 的行为可观测(如错误被记录、或不再静默返回空 EvaluateResult),覆盖当前被吞掉的异常路径。

…ro cmd

- _run_evaluator: re-raise assertions that abort before results exist instead of masking them as empty NOT_EVALUATED outcomes

- _build_repro_cmd: shlex.quote each argument so paths with spaces stay intact

- Add tests covering both swallowed-assertion paths and repro cmd quoting
@Ygrowly

Ygrowly commented Jul 30, 2026

Copy link
Copy Markdown
Author

两点都已修复,见 d6085ff

  1. _run_evaluator 不再无条件吞 AssertionError。由于 _EvaluationCasesFailed 是 SDK 私有类,示例侧不便直接 import,改用行为判定:case 失败时 executer 内已有结构化结果,照常返回;get_result() 为 None 说明断言发生在结果生成之前(配置/契约类错误),此时带原始异常类型和消息重新抛出 RuntimeError,不会再落成空结果 + infrastructure_failure 的失真 REJECT。
  2. _build_repro_cmd 每个参数过 shlex.quote,含空格路径不会被拆。

按建议补了 tests/test_evaluator_error_handling.py:覆盖'断言且无结果必须抛出'、'case 失败保留结构化结果'、复现命令 shlex.split round-trip 三个用例。示例测试 73 项全部通过。

@Ygrowly

Ygrowly commented Jul 30, 2026

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 30, 2026
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

所有导出内容均存在。很好。现在让我来撰写最终的审查意见。

发现的问题

⚠️ Warning

  • examples/optimization/eval_optimize_loop/tests/test_agent_evaluator.py:130 (test_load_absolute_path_without_selector):该测试声明覆盖“含冒号的绝对路径按原文件加载(含 Windows 盘符)”,但 tmp_path 在 Linux CI 上为 /tmp/...,不含冒号,":" in eval_set_file 为假,冒号解析分支根本不会被执行,测试实际上无法验证它声称的 Windows 盘符回归保护。
    • 建议直接构造一个含冒号的路径字符串(例如显式拼一个 "set.evalset.json:case_a" 并断言走选择器分支,或 mock os.path.exists)使该分支在 POSIX CI 上也能被覆盖;否则该用例是空跑通过的,PR 的核心修复点缺乏有效回归保护。

💡 Suggestion

总结

整体风险较低:SDK 的冒号选择器修复逻辑正确(rpartition + os.path.exists 守卫能正确处理 Windows 盘符与 file.json:case_id),示例管线、归因、Gate、报告与 Prompt 恢复逻辑均有对应测试覆盖,未发现必须修复的阻塞问题。主要瑕疵是新增测试在 Linux CI 上未真正触达其声称覆盖的盘符分支,建议补一个确实进入冒号解析路径的用例。

测试建议

  • 补一条在 POSIX 下也能进入 _load_eval_set_from_file 冒号分支的用例:例如传入 "set.evalset.json:case_a" 形式的相对路径并断言 selected_case_id 生效,或 monkeypatch os.path.exists 模拟 Windows 盘符场景,确保 not os.path.exists(整体)os.path.exists(file_part) 的分支被实际执行。

- Add POSIX-only case loading an existing path that contains ':' so the drive-letter guard is covered on Linux CI as well

- Pass absolute paths to evaluator/optimizer now that the SDK guards the case selector, removing process-wide chdir and metric-config copying
@Ygrowly

Ygrowly commented Jul 30, 2026

Copy link
Copy Markdown
Author

两点都已处理,见 d99414f

  1. 测试覆盖问题属实:原 test_load_absolute_path_without_selector 只有在 Windows 上(tmp_path 自带盘符)才会触达冒号守卫,Linux CI 上是空跑。已补 test_load_existing_path_containing_colon:POSIX 下文件名允许冒号,直接创建一个名字含 : 的 evalset 文件并断言按原文件加载、不被切成选择器,保证 not os.path.exists(整体) 为假的守卫分支在 Linux CI 上真实执行(Windows 上该用例 skip,由原用例的盘符路径覆盖)。选择器分支本身由 test_load_with_case_selector 覆盖——完整串在两个平台都不存在于磁盘,rpartition 分支必然执行,docstring 已补充说明。
  2. chdir 绕路已一并移除:runner._run_evaluatorpipeline._run_optimize_for_candidate 改为直接传绝对路径,顺带删掉了 metric config 的本地拷贝逻辑。示例 73 项测试 + tests/evaluation 全套通过。

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

OpenAIModel 接受一个明确的 api_key 参数,live_agent 正确地传递了该参数。很好。没有泄露问题(model_info_from_env 仅记录布尔值的 base_url_configured)。

我已经审查完毕。该 PR 很稳健。让我写一下审查结论,指出几个值得注意的真正需要权衡的地方。

发现的问题

⚠️ Warning

  • examples/optimization/eval_optimize_loop/pipeline.py:140-143_case_contexts + _audited_call_agent 的 query 匹配):审计回调用 evalset 中 user_content.parts[].text 拼出的 query 作为 key 查 contexts,未匹配到就抛 KeyError。真实 AgentOptimizer 若对 query 做任何模板封装/trim/多 part 拼接,便会与登记的 key 不一致,导致整个 optimize 流程在调用 agent 阶段直接抛错;目前仅被 stub(直传原 query)测试覆盖。建议放宽匹配(如归一化空白、或用 eval_id 关联回调而非 query 文本),至少为 query 归一化加单测。

    matching_contexts = contexts.get(query)
    if not matching_contexts:
        raise KeyError(f"call_agent 收到未登记的评测 query:{query!r}")
  • examples/optimization/eval_optimize_loop/live_agent.py:74-77call_agent 逐事件拼接 part.text,依赖 part.thought 字段过滤思维链。该字段在非思维 part 上可能为 None(见 trpc_agent_sdk/planners/_planning_processor.py:154 设为 None),not None 虽为真可正常放行,但若某 provider 返回的 Part 完全不带 thought 属性,part.thought 会抛 AttributeError 使整个回归评测失败。建议用 getattr(part, "thought", None) 与 SDK 内部 _llm_agent.py:704 以外的健壮写法保持一致,降低对 provider 适配层实现细节的耦合。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/gates.py:163-166:G6 在 cost_status == "measured" 下用 total_tokens > cfg.budget_tokens 判预算,但 total_tokens 默认形参为 0 且 fake/trace 模式恒为 0——当用户把 budget_tokens 配成 0 或负值时会误判超预算。属示例配置边界,建议在 GateConfig 或文档里约束 budget_tokens > 0,避免误用。

总结

整体质量较高:核心 SDK 的盘符冒号守卫修复正确且有跨平台单测覆盖,示例管线的 prompt 写回/恢复、审计、gate 决策、归因均有较完整测试。未发现必须修复的 Critical 问题;两条 Warning 属于示例代码与真实优化器/provider 适配层耦合的健壮性隐患,建议按需加固。

测试建议

  • 补一条 _audited_call_agent 在 query 被首尾空格或模板包裹时仍能命中 context(或合理降级)的单测,覆盖真实优化器可能改写 query 的路径。
  • 补一条 live_agent/runner 在 Partthought 属性时不抛 AttributeError 的回归测试(可用 stub event 验证文本拼接逻辑)。

- Audit callback: normalize whitespace when matching queries and degrade to an unmatched-marked entry instead of raising KeyError mid-optimize

- live_agent: extract response_text_from_event using getattr so parts without thought/text attributes cannot break regression runs

- GateConfig: reject non-positive budget_tokens/budget_usd to keep G6 meaningful

- Add tests for trimmed-query matching, unknown-query degradation, thought-less parts and budget validation
@Ygrowly

Ygrowly commented Jul 31, 2026

Copy link
Copy Markdown
Author

三条意见都已在 3221408 处理:

query 匹配(pipeline.py):不再抛 KeyError。匹配前对 query 做空白归一化(strip + 折叠连续空白),仍未命中时降级为空上下文继续执行,并在审计条目里加 context_match: matched/unmatched 标记——审计是观察手段,不应中断优化流程。补了首尾空格仍命中、未登记 query 正常降级两条单测。

part.thought(live_agent.py):把文本拼接提取成 response_text_from_eventtext/thought 都改用 getattr 取值,Part 完全没有这两个属性也不会抛 AttributeError。用不带 thought 属性的 stub part 补了回归测试。

budget_tokens 边界(gates.py)GateConfig.__post_init__ 校验 budget_tokens > 0budget_usd(配置时)> 0,配错直接报 ValueError,避免 0/负预算让 G6 误判。

示例测试 78 passed,flake8 无告警。

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

基于我对 diff 和相关仓库上下文(CI 配置、pytest 配置、SDK 调用)的分析,以下是我的审查结果。

发现的问题

⚠️ Warning

  • examples/optimization/eval_optimize_loop/tests/: 整个示例测试套件(Issue 构建 Evaluation + Optimization 的自动回归与提示词优化闭环 #91 Milestone A 验收测试)不会在 CI 中运行

    • pyproject.toml:212 设置 testpaths = ["tests"],且 .github/workflows/ci.yml:78 仅运行 pytest ... tests/,而本 PR 新增的全部测试位于 examples/optimization/eval_optimize_loop/tests/,既不在 testpaths 内也不在 CI 调用路径中。test_truth_table.py 自述为"验收测试",但永远不会被执行,回归无法被捕获。建议在 CI 中显式追加 examples/optimization/eval_optimize_loop/tests/ 或在该目录增加 conftest/被 testpaths 覆盖。
  • examples/optimization/eval_optimize_loop/pipeline.py:140: _build_repro_cmdshlex.quote 生成 POSIX 风格引用,但 README 指向 PowerShell 用户

    • 在 Windows 上 shlex.quote("C:/tmp/my candidate.md") 产出 'C:/tmp/my candidate.md'(单引号),粘贴到 cmd/PowerShell 会失败;当前测试仅用 shlex.split 验证往返,未验证真实 shell 执行。建议对 Windows 路径用双引号或平台分支引用,以保证复现命令可直接执行。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/runner.py:283: _write_trace_evalset 把 metric 配置复制到 trace 目录,但 _run_evaluator 实际传入的是原始 metric_config_path 而非复制后的本地路径,复制产物未被使用。若意图是让 trace 目录自包含可复现,应将本地配置路径传给 evaluator;否则可删除该复制以减少误导。

总结

整体代码质量较高,归因、Gate 规则、Champion 还原、成本门禁与失败审计路径逻辑自洽,未发现安全漏洞或会导致核心功能失败的 Critical 问题。主要风险在于新增的验收测试套件未被 CI/pytest 实际收集执行,验收回归存在盲区,建议修复。

测试建议

  • 补充一个 CI 接入测试:验证 examples/optimization/eval_optimize_loop/tests/ 至少被某条 pytest 命令收集并执行(如 pytest examples/optimization/eval_optimize_loop/tests/ -q)。
  • 针对 _build_repro_cmd 在 Windows 路径含空格时的真实可执行性补充断言(而非仅 shlex.split 往返)。

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.

2 participants