Skip to content

Feat/eval optimization loop 91 - #179

Open
wzxsdf wants to merge 3 commits into
trpc-group:mainfrom
wzxsdf:feat/eval-optimization-loop-91
Open

Feat/eval optimization loop 91#179
wzxsdf wants to merge 3 commits into
trpc-group:mainfrom
wzxsdf:feat/eval-optimization-loop-91

Conversation

@wzxsdf

@wzxsdf wzxsdf commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Added examples/optimization/eval_optimize_loop/: a reproducible closed-loop pipeline of Evaluate → Attribute → Optimize → Validate → Gate → Audit. Runs without an API key in fake mode (< 1s).

A lightweight orchestration layer on top of the SDK's AgentEvaluator / AgentOptimizer: extracts candidates from OptimizeResult.rounds, independently re-evaluates them on train/validation sets (optimizer self-scores are not trusted), performs per-case diff comparison, and applies gating.

Domain: Library Catalog Agent (Novel/Science/History/FAQ classification + catalog tools + JSON). Six test cases cover three essential scenarios: optimizable-success, no-op, and overfitting-regression.

Three modes: fake / trace (deterministic, no LLM) and online (real AgentOptimizer).

Motivation

Issue #91 transforms prompt optimization into an auditable release decision. Aggregate scores cannot reveal validation-set regressions, new critical failures, or root causes. Therefore, optimization and release gating are decoupled: every candidate must pass independent re-evaluation before acceptance.

Design

  • Layered Attribution (pipeline/attribution.py): A rule engine attributes based on actual vs. expected trajectory differences (format / tool selection / parameters / knowledge / final response mismatch). Falls back to single-variable counterfactuals when rules miss. Counterfactuals run local pure-Python metrics (zero API cost), enabling ≥75% accuracy with ≤3 min runtime.
  • Triple Overfitting Detection (pipeline/gate.py): Explicit train↑ while val↓, generalization gap (only when val is unmet), and trend divergence — plus critical-case regression. Candidates with val-down / train-up are always rejected.
  • Deterministic + Auditable: fake/trace modes use pre-recorded variants and two LLM-free SDK evaluators (final_response_avg_score contains, tool_trajectory_avg_score exact). Features atomic writes + SHA256 digests; three-state cost.measurement where unknown costs are treated as failures (fail-closed).
  • Gating: Configurable AND rules with three-state decisions (accept / reject / needs_review) and corresponding exit codes (0 / 2 / 1).

Validation

  • pytest tests/ -q: 9 passed (three scenarios, overfitting detection, attribution coverage/accuracy, ≤3 min runtime, report fields, hidden samples, CLI exit codes, data quality).
  • fake/trace → exit 0, selects robust; overfit → rejected (overfitting + critical regression); ineffective → rejected (tie).
  • Attribution coverage: 100%, category accuracy: 100%.
  • flake8 + yapf + git diff --check all pass.

Deliverables

  • run_pipeline.py + 7 modules in pipeline/
  • offline/fixtures.py
  • agent/ (online LlmAgent with prompt hot-reload)
  • 3 + 6 test cases + gate.json + optimizer.json
  • sample_output/ (reports + audit logs)
  • README.md (~400-char design note) + DESIGN.md

Closes #91

wzxsdf added 2 commits July 13, 2026 21:28
- replay harness: 同一组轨迹驱动 InMemory/SQLite/Redis,比较事件/state/memory/summary
- 归一化(占位符,保留字段存在性)+ JSONPath 精确 allowed_diff + 覆盖率治理
- summary: SDK 确定性模型 + 三分比较 + loss/overwrite/affiliation 检测
- 检出验证: 快照层注入 + 端到端后端注入(改 SQL 行 / Redis key 重读)
- 实测发现 SQLite summary 持久化 drift,标 KNOWN_DRIFT 只报告不改
- 29 tests pass, 1 skipped (Redis 需 TRPC_REPLAY_REDIS_URL)
构建「评测→失败归因→prompt优化→验证集回归→接受决策→审计落盘」可复现闭环:
- 分层失败归因(规则快通道 + 反事实深归因,本地 metric 零成本)
- 过拟合三重检测(显式公式 + 泛化缺口 + 趋势背离)
- 可配置 gate(三态 accept/reject/needs_review)
- sha256 审计 + 三态成本 fail-closed
- fake/trace/online 三模式,fake 无 API key 可跑通(<1s),9 pytest 全过
@wzxsdf

wzxsdf commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

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

@codecov

codecov Bot commented Jul 14, 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        #179   +/-   ##
==========================================
  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.

解决 PR trpc-group#179 与 upstream/main 的冲突:

- 8 个 tests/sessions/replay 冲突文件(backends/harness/injectors/
  report/summary_checks + test_replay_consistency/injections/unit)
  统一采用 upstream/main 版本,即 PR trpc-group#178 合并后经 9 次 review
  修复(FPR 口径/Redis state 注入/删除未用 class 等)的最新版。
  PR trpc-group#179 分支携带的是 PR trpc-group#178 未经 review 的旧版,且 eval_optimize_loop
  不依赖 replay 模块(仅以字符串字面量 measured_from_replay 引用)。
- 保留 PR trpc-group#179 的 eval_optimize_loop example(32 文件)完整不动。
- 其余文件(.gitignore 等)自动合并。

测试: eval_optimize_loop 9 passed; replay 32 passed + 2 skipped(Redis).
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

设计文档仅显示了一个示例值。prompt_leakage_matches=0 的硬编码是故意的存根(stub),但 passed 仅取决于跨拆分(cross-split)。作为一种不完整的数据质量检查,值得提出警告。

现在让我看看 online 模式下的 command ——它从未在 run_online 中被使用。小问题。我还需要检查当没有任何内容被接受但存在 needs_review 时 report.selected_candidate_id 的情况——derive_status 返回 needs_review。很好。

让我验证一下测试 test_attribution_coverage_and_accuracy:它断言 fa.explained_failed_cases == fa.total_failed_cases(全部解释)。baseline 失败:train 3/3 失败,val 1/3 失败(membership)。总计 4 个失败。全部通过规则解释了吗?train_hours_format:baseline response 没有 "category" → format_violation。train_availability_args:baseline tool args BT-999 != BT-001 → tool_parameter_error。train_author_lookup:baseline 无工具但预期有 → knowledge_recall_insufficient。val_stable_membership:baseline response 没有 "category" → format_violation。全部已解释。✓。

现在,test_hidden_attribution_samples 示例 1:expected_response "category", actual "没有结构的纯文本"。规则:expected_tools 为空,跳过 tool。response:expected_resp "category" 不在 actual 中 → 检查 "category".lower() in _FORMAT_MARKERS → "category" 在集合中 → format_violation。✓。示例 4:expected_response "answer", actual "完全不同的内容"。"answer" 不在 _FORMAT_MARKERS 中 → final_response_mismatch。✓。

5 个全部正确 → 5/5 >= 0.75。很好。

现在让我考虑一个真正的问题:在 _counterfactual 中,cf_resp_repairs = base_tool_okcf_tool_repairs = base_resp_ok。逻辑检查了 "replacing response alone repairs" = tool side 已经 ok。这是合理的。但是对于 compound 情况,规则分类可能不一致,并被降级。让我追踪一下:如果规则返回一个类别但反事实(counterfactual)不一致,置信度(confidence)保持 0.85(没有降级)。这是一个小问题——可以给出高置信度但带有冲突信号。不值得标记。

让我再次检查 evaluate_gateoverfit_or_critical 包含 overfit,并且决策使用了它。对于 overfit=True 的情况,即使在技术上 safety_failed 为空,它也会强制设为 reject。没问题。

现在让我验证 compare 在评估中:base_map = {c.eval_id: c for c in base_split.cases}。对于每个候选用例,bc = base_map[cc.eval_id] —— 如果缺少 eval_id,KeyError 会导致崩溃。在 trace 中两者都包含所有内容。在 trace 中是安全的。

让我检查 run_fake 的基准 prompt 哈希:read_prompt("baseline") 读取 system.md。没问题。

我已经有足够的信息了。让我再仔细看看一个关注点:main() 的返回逻辑。对于 fake/trace 模式,run_fake 返回一个 report(永不为 None)。所以 report is None 对于 fake 模式意味着…… run_fake 总是返回 report。所以 return 0 if online else 1 —— fake 永远不会走这条路。注释说 "fake/trace None = error",但 run_fake 不会返回 None。无害。

让我验证一下是否存在凭证泄露:agent.py 中的 _create_model 将 api_key 传递给 OpenAIModel,未进行记录。审计快照不包含密钥。很好。environment.snapshot.json 记录了平台/python/sdk 版本——没有密钥。很好。

现在让我检查 command 字符串是否反映了实际调用的模式:command = f"python run_pipeline.py --mode {args.mode}" —— 没有包含 output-dir/seed。轻微的可复现性差距,但可接受。

让我确认关于 evaluator 中针对空 runsruns[0] 的担忧——我将把它标记为 Warning(如果 SDK 返回了用例 ID 但结果为空,会导致对缺少 runs 的防御性访问崩溃)。无法完全验证 SDK。我会谨慎措辞。

其实,更大的担忧是 results_by_id 中缺失的用例被静默丢弃,导致 n 变小且 pass_rate 分母错误。但使用 num_runs=1 的 trace 模式会评估所有用例。我将其合并到评估器(evaluator)的鲁棒性警告中。

让我完成最终评审。我还要检查一下 session_memory_summary_diff_report.json 和文档——这些不是代码。跳过。

让我验证一个关于 ineffective 的真正问题:测试断言 decisions["ineffective"] == "reject"。根据 tie_policy=reject,overfit=False,is_tie=True(delta≈0)。tie_policy 检查失败(安全性)→ reject。✓。但是等等——对于 ineffective,validation_score_improved 也会失败(delta 0 < 0.05)→ 那是 needs_review,不是安全性。但 tie_policy 安全性失败 → reject。✓。

一切一致。让我写下评审意见。

发现的问题

🚨 Critical

无。

⚠️ Warning

  • examples/optimization/eval_optimize_loop/pipeline/gate.py:88-94max_score_regression_per_case 配置被加载但未生效

    • GateConfiggate.json 读入 max_score_regression_per_caseconfig.py:19,35),但 no_case_regression 检查硬编码为 len(delta.buckets.regressed) == 0,从不使用该阈值。当前默认值 0.0 恰好与硬编码零回退一致,故现状不报错;但一旦使用者调高该值期望放宽回归容忍,gate 仍会按零容忍 reject,行为与配置语义不符。建议用该字段判定 len(regressed) <= 0 的等价逻辑或显式删除该配置项以免误用。
  • examples/optimization/eval_optimize_loop/pipeline/evaluator.py:33-51:评估结果缺失/空时的健壮性不足

    • cr = runs[0] 假设每个 eval_id 至少有一条 run,若 SDK 返回空 run 列表会抛 IndexError;同时若某 eval_idresults_by_id 中缺失,则被 c["eval_id"] in snapshots_by_id 静默过滤,n 变小导致 pass_rate/average_score 分母错误而决策仍照常进行。建议对缺失 case 显式记为 0 分失败或抛错,避免静默漂移。

💡 Suggestion

总结

整体逻辑自洽,三类场景决策、过拟合检测、归因覆盖与 CLI 退出码均与 fixtures/测试一致,未发现阻塞级问题。主要风险是 max_score_regression_per_case 配置失效与 evaluator 对缺失/空结果的静默处理,建议修复后再合并。

测试建议

  • 补充用例:构造某 case 在 results_by_id 中缺失或 runs 为空的 eval_config,验证 evaluate_split 不会静默吞掉或抛 IndexError。
  • 补充用例:将 gate.jsonmax_score_regression_per_case 调为 >0 并构造一个仅有轻微 score 回归(passed 不变)的候选,断言 gate 行为与配置阈值一致。"};;
    null;
    delivering the review.

🚨 Critical

无。

⚠️ Warning

  • examples/optimization/eval_optimize_loop/pipeline/gate.py:88-94max_score_regression_per_case 配置被加载但未生效

    • GateConfiggate.json 读入 max_score_regression_per_caseconfig.py:19,35),但 no_case_regression 检查硬编码为 len(delta.buckets.regressed) == 0,从不使用该阈值。当前默认值 0.0 恰好与硬编码零回退一致,故现状不报错;但一旦使用者调高该值期望放宽回归容忍,gate 仍会按零容忍 reject,行为与配置语义不符。建议用该字段判定 len(regressed) <= 0 的等价逻辑或显式删除该配置项以免误用。
  • examples/optimization/eval_optimize_loop/pipeline/evaluator.py:33-51:评估结果缺失/空时的健壮性不足

    • cr = runs[0] 假设每个 eval_id 至少有一条 run,若 SDK 返回空 run 列表会抛 IndexError;同时若某 eval_idresults_by_id 中缺失,则被 c["eval_id"] in snapshots_by_id 静默过滤,n 变小导致 pass_rate/average_score 分母错误而决策仍照常进行。建议对缺失 case 显式记为 0 分失败或抛错,避免静默漂移。

💡 Suggestion

总结

整体逻辑自洽,三类场景决策、过拟合检测、归因覆盖与 CLI 退出码均与 fixtures/测试一致,未发现阻塞级问题。主要风险是 max_score_regression_per_case 配置失效与 evaluator 对缺失/空结果的静默处理,建议修复后再合并。

测试建议

  • 补充用例:构造某 case 在 results_by_id 中缺失或 runs 为空的 eval_config,验证 evaluate_split 不会静默吞掉或抛 IndexError。
  • 补充用例:将 gate.jsonmax_score_regression_per_case 调为 >0 并构造一个仅有轻微 score 回归(passed 不变)的候选,断言 gate 行为与配置阈值一致。

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