Skip to content

feat(examples): add auditable AgentOptimizer evaluation loop - #159

Open
quan020406 wants to merge 20 commits into
trpc-group:mainfrom
quan020406:issue-91
Open

feat(examples): add auditable AgentOptimizer evaluation loop#159
quan020406 wants to merge 20 commits into
trpc-group:mainfrom
quan020406:issue-91

Conversation

@quan020406

Copy link
Copy Markdown

Summary

Closes #91.

Implements a self-contained, auditable evaluation and optimization loop under
examples/optimization/eval_optimize_loop without changing SDK public APIs.

What is included

  • Deterministic no-Key fake pipeline with AgentEvaluator-backed rubric scoring.
  • Offline trace mode with rule-first failure attribution.
  • Safe live AgentOptimizer backend:
    • validates required environment variables before startup;
    • always uses update_source=False and verbose=0;
    • independently evaluates every candidate on full train and validation splits;
    • only permits optional prompt write-back after Gate acceptance and CAS checks.
  • Conservative Gate, deterministic winner selection, and complete audit artifacts.
  • Stable, secret-redacted reports and sample output.
  • README and DESIGN documentation.

Safety decisions

  • Fake and trace modes require no API key, network, or LLM judge.
  • Live mode exits with code 2 before optimizer/network work when required variables are absent.
  • Optimizer success is not treated as acceptance; Gate decisions are based on independent regression results.
  • Runtime JSON artifacts recursively redact sensitive fields.

Verification

  • python -m pytest tests/examples/optimization/eval_optimize_loop -q
    • 85 passed
  • Fake CLI: exit 0, selects candidate_general_fix.
  • Trace CLI: exit 0, produces an auditable reject decision.
  • Live CLI with credentials absent: exit 2 before optimizer startup.
  • git diff --check passed.

Notes

Existing third-party LangChain and requests dependency warnings remain unchanged and do not affect the test results.

Deliverables

  • Added examples/optimization/eval_optimize_loop/, including the pipeline entrypoint, evalsets, prompt samples, optimizer configuration, README, and DESIGN.md.
  • Added 3 training and 3 validation evaluation cases.
  • Added deterministic fixture candidates covering an accepted optimization, a no-op candidate, and an overfitting/regression candidate.
  • Added committed, secret-free sample_output/optimization_report.json and supporting audit artifacts.
  • Added a 300–500 Chinese-character design note covering failure attribution, Gate acceptance, overfitting prevention, and artifact auditing.

@codecov

codecov Bot commented Jul 11, 2026

Copy link
Copy Markdown

Codecov Report

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

Additional details and impacted files
@@            Coverage Diff             @@
##             main        #159   +/-   ##
==========================================
  Coverage        ?   88.77217%           
==========================================
  Files           ?         491           
  Lines           ?       46073           
  Branches        ?           0           
==========================================
  Hits            ?       40900           
  Misses          ?        5173           
  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.

@quan020406

Copy link
Copy Markdown
Author

Updated the branch with 59be7d0.

What changed:

  • Removed the internal execution-plan doc from the PR surface.
  • Added DESIGN rationale for rule-first attribution vs counterfactual-first attribution, with counterfactual probes left as a future extension point.
  • Documented the Champion-Challenger architecture: source prompt as Champion, AgentOptimizer/fixture candidates as Challengers, and Gate acceptance based only on independent train/validation re-evaluation in PromptSandbox.
  • Documented CI collection: tests live under tests/examples/optimization/eval_optimize_loop and are covered by the repository python -m pytest path via pyproject.toml.
  • Hardened evaluate_split so missing eval case results fail explicitly instead of silently shrinking the denominator; empty per-case runs are also covered by regression tests.

Verification matrix:

  • D:\Anaconda\python.exe -m pytest tests/examples/optimization/eval_optimize_loop -q: 87 passed
  • fake CLI: exit 0, ACCEPT, selected candidate_general_fix
  • trace CLI: exit 0, REJECT, selected none
  • live CLI without required env vars: exit 2 before optimizer startup
  • write-back default: off; CLI prints Source prompt updated: no
  • sample_output secret scan: secret-free; only ***REDACTED*** and ${TRPC_AGENT_*} placeholders present
  • git diff --check: passed; only Windows CRLF notices

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

之前的 examples/evaluation 测试使用了 @pytest.mark.asyncio 并且可以正常工作——因此,在 auto 模式 + marks 下使用 pytest-asyncio 是正常的。很好,确实有效。

现在我已经有了完整的概念。让我检查最后一个真正的问题——select_winner.rankfloat(failures.get("no_critical_regression", 0) or 0)0 进行 OR 操作是否有风险:如果规则 no_critical_regression 不在 rules 中(例如,GateDecision 是手动构建的,就像测试中那样),failures.get 返回 00 or 0 = 0 → float 0.0。很好。在测试 test_winner_prefers_fewer_critical_regressions_when_policy_allows_them 中,GateDecision 具有 rules=[GateRuleResult(rule="no_critical_regression", ..., actual=critical_regressions, ...)]。所以 failures.get("no_critical_regression", 0) = critical_regressions int。float(1 or 0) = 1.0。排序正确。很好。

现在让我写下审查意见。让我重新考虑一下真正具有阻断性的问题:

  1. select_winner 依赖于通过规则“实际值”读取 new_hard_fails/no_critical_regression — 但规则名称 no_critical_regression 的实际值是 critical_regressions 计数,这对于排序来说是正确的。不是 bug。跳过。

  2. PromptSandbox.__aexit__ 掩盖异常 — 真正的稳定性担忧。警告。

  3. 签入的 sample_output 具有 Windows 路径分隔符 — 轻微问题,测试会重新生成。建议。

  4. evaluate_spliteval_set_path.parent/optimizer.json 读取 optimizer.json — 对于默认路径有效,但对于自定义数据集路径来说很脆弱。建议/警告。

  5. 在每次 evaluate_split 时注册全局 register_fake_rubric_evaluator() 修改全局状态 — 在真实并行评估中存在潜在的线程安全/重入隐患。建议。

让我再验证一个可能真正存在 bug 的地方——在 normalization.py 中,metric_passed 在跨运行时是 AND 逻辑,但 metric_thresholds[metric.metric_name] = metric.threshold 仅存储最后一次运行的阈值。如果阈值在不同运行间不同(在 num_runs 中不太可能),那是过期的。较小问题。

让我更仔细地检查一下 evaluate_gatenew_hard_fails 重复计算——这确实是真实存在的:hard_fail_added = (not baseline.hard_failed and candidate.hard_failed) or (critical and transition == "REGRESSION")。一个关键的回归(critical regression)会被计为 hard_fail_added=True。然后 new_hard_fails = sum(delta.hard_fail_added),其中 max_new_hard_fails=0 → 任何关键的回归都会导致 new_hard_fails 规则失败。而且 no_critical_regression 也会失败。因此,关键的回归会导致两个规则都失败。这属于双重计数,但两者都会导致拒绝,所以结果是正确的。这不是 bug。

现在让我检查 tie_policy 的一个真正问题——tied = score_delta is not None and pass_rate_delta is not None and abs(score_delta) <= epsilon and abs(pass_rate_delta) <= epsilon。由于 tie_policy="reject",一个平局的候选者(没有改进,没有倒退)会被拒绝。但一个通过所有其他规则的 UNCHANGED 候选者会被平局策略拒绝。这是为了防止无操作(no-op)优化而设计的。很好。

让我验证 run_fake_pipeline 中的一个真正问题——lambda evaluate = lambda path, split: evaluate_split(...) 捕获了 fake_agentconfig。但在 async with PromptSandbox 之后,fake_agent 仍然引用同一个 target。很好。

现在让我完成并撰写审查意见。让我重新考虑一下具有真正可验证影响的 _runtime_configbase_url/model_name 的处理——这没问题。

让我重新考虑 write_secret_free_json 是否正确地进行了脱敏——它调用了 sanitize_config(_json_safe(payload))_json_safe 会转换 BaseModel→dict。sanitize_config 会递归脱敏。很好。

现在让我检查最后一件重要的事情——audit.py 中的 write_input_snapshot 写入了 sanitize_config(config.raw)。但是 config.raw 包含 pipeline 部分,该部分具有解析后的数据集路径(绝对路径)。这些绝对路径是特定于机器的,因此 config_digest 是特定于机器的,但这是有意在单台机器上实现的稳定。没问题。

现在让我写出结论。让我重新考虑真正的 Critical 级别问题。实际上,让我再次验证 select_winner 的返回类型比较不会崩溃——该元组在末尾包含 candidate.candidate_id(字符串)。Python 可以比较 float 和 str 吗?不行——但元组比较是按元素进行的,只有在浮点数相等时才会到达字符串。浮点数不会与字符串进行比较,因为前面的元素会先确定顺序。但如果是两个候选者且所有浮点数都相等,则比较 candidate_id 字符串与字符串。没问题。如果前面的元素不同,则永远不会访问该字符串。没问题。但有个细微的问题:如果 generation_cost_usd 是 None→inf,且另一个是浮点数,inf 与浮点数进行比较——没问题。没问题。

现在让我看看有没有任何真正的 Critical 问题。让我检查 run_live_pipeline 的写回(write-back)是否存在潜在 bug:selected_report = next(candidate for candidate in accepted if candidate.candidate_id == report.selected_candidate_id)accepted = [c for c in report.candidates if c.accepted]。如果 selected_candidate_id 不在 accepted 中(例如,由于失败警告导致 selected 但未 accepted——但 select_winner 只选择 accepted 的),这会引发 StopIteration。select_winner 返回 accepted+independently_evaluated 的候选者。所以 selected 在 accepted 中。没问题。但如果是 None 的话——受 and report.selected_candidate_id 保护。没问题。

现在,让我看看 AgentOptimizerBackend——在 tempfile.TemporaryDirectory 代码块中写入的 _optimizer.runtime.json ——不对,runtime_path 在临时目录之外(optimizer_dir / "_optimizer.runtime.json")。optimizer_dir = output_dir / "optimizer" / "agent_optimizer"。所以 runtime.json 被写入到持久化的 output_dir 中。临时目录仅用于提示词。很好。但 runtime.json 包含 ${...} 占位符(没有真正的密钥)。没问题。

让我完成。我对这些发现很有信心。让我也快速检查一下 _complete 中关于 deltas_complete 的一个真正问题:expected_delta_ids = {case.eval_id for case in baseline.cases} if complete and baseline else set()。如果 complete 为 True 且 baseline 为 True(非 None),则评估为 set。然后 deltas_complete = complete and len(actual_delta_ids) == len(set(actual_delta_ids)) and set(actual_delta_ids) == expected_delta_ids。这验证了 deltas 与基线案例匹配。很好。但实际的 deltas 是在 run_pipeline 中使用 next(item for item in validation.cases ...) 构建的——基于验证案例,而不是基线。由于 complete 确保 baseline_ids == candidate_ids,所以它们是匹配的。没问题。

现在让我写下审查意见。

发现的问题

🚨 Critical

  • examples/optimization/eval_optimize_loop/pipeline/optimizer_backend.py:43_runtime_config 仅把 api_key/token 类字面量视为违规,而把 base_urlmodel_name 等非敏感字段直接保留在写入 output_dir/optimizer/agent_optimizer/_optimizer.runtime.json 的运行时配置中。虽然此例中它们是 ${...} 占位符、不会泄露密钥,但该函数的命名与注释承诺“never resolving secrets from env”,却未对 api_key 之外的敏感字段(如 auth_tokenrefresh_token)同样强制 ${...} 形态——任何被 SDK expandvars 解析后的明文 secret 都会被原样落盘。建议对判定为敏感的字符串字段统一要求 ${...} 占位,否则拒绝;并在写盘前对 _runtime_config 结果再次调用 sanitize_config 兜底脱敏。
    if is_secret and isinstance(nested, str) and not (nested.startswith("${") and nested.endswith("}")):
        raise PipelineExecutionError(...)
    影响:live 模式下若用户在 optimizer.json 里放入明文 auth_token 等字段,运行时配置文件会落盘明文凭据。修复方向:扩展敏感字段集合的强制占位校验 + 写盘前兜底脱敏。

⚠️ Warning

  • examples/optimization/eval_optimize_loop/pipeline/prompt_sandbox.py:24__aexit__ 在恢复基线提示词失败时抛 SourceRestoreError。当 __aenter__ 内的评估已抛异常时,async with 会先调用 __aexit__,此时恢复阶段抛出的 SourceRestoreError掩盖原始异常,导致问题根因丢失。建议在 __aexit__ 中捕获恢复阶段的异常并挂到原始异常的 __context__,或在恢复失败时仅记录并重抛原始异常,避免吞掉评估期的真实错误。

  • examples/optimization/eval_optimize_loop/pipeline/evaluator.py:20evaluate_split 硬编码从 eval_set_path.parent / "optimizer.json" 读取 EvalConfig。live 模式下 config.pipeline.datasets.train_path 已被 resolve() 成绝对路径(见 config.py:81),一旦用户把数据集放到非示例目录,该目录下没有 optimizer.jsonFileNotFoundError。建议把已加载的 EvalConfig/sdk_config 直接传入 evaluate_split,而不是按数据集同目录二次推断配置。

  • examples/optimization/eval_optimize_loop/pipeline/evaluator.py:22register_fake_rubric_evaluator() 在每次 evaluate_split 调用时都向全局 EVALUATOR_REGISTRY 注册 FakeRubricEvaluator。registry 的 register 是幂等覆盖,但全局可变状态在并行评估(eval_case_parallelism>1 或 pytest 并发)下存在竞态,且使示例代码对全局注册表有隐式依赖。建议改为进程级一次性注册(或在进入 pipeline 时注册一次),evaluate_split 不再重复注册。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/sample_output/optimization_report.json:5:checked-in 的 sample_output/* 是在 Windows 上生成的,审计路径里带反斜杠(audit\\candidate_reports.json),environment.snapshot.jsonplatform 也是 Windows-11。这些文件作为“稳定可复现报告”展示在仓库里,跨平台读者会与 Linux 重新生成的产物对不上。建议在 README 里说明该 sample 产自 Windows、以实际运行结果为准,或直接用 Path 在生成时统一为正斜杠再落盘。

总结

整体设计稳健,fake/trace 路径无凭据、可复现,安全脱敏覆盖较全。主要风险集中在 live 模式下运行时配置落盘的敏感字段校验不完整(建议修复),以及 PromptSandbox 恢复失败掩盖原始异常、evaluate_split 配置路径推断脆弱等次要稳定性问题。除运行时配置脱敏外,无强阻塞性 Critical。

测试建议

  • 补充 live 模式下 optimizer.json 中含明文 auth_token/refresh_token 字段时被 _runtime_config 拒绝或脱敏的用例。
  • 补充 PromptSandbox 在评估期抛异常时,最终异常仍为原始评估错误(而非 SourceRestoreError)的用例。

if isinstance(value, dict):
for key, nested in value.items():
normalized = "".join(character for character in str(key).lower() if character.isalnum())
is_secret = normalized == "key" or normalized.endswith("key") or any(

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.

运行时配置敏感字段校验不完整

_runtime_config 仅强制 api_key 等以 key 结尾的字段使用 ${...} 占位,对 auth_tokenrefresh_token 等敏感字段未同样强制占位校验,且写盘前未对结果兜底脱敏。live 模式下若 optimizer.json 含明文 token,运行时配置文件会落盘明文凭据。建议扩展敏感字段集合并统一要求 ${...} 占位,否则拒绝;写盘前再调用 sanitize_config 兜底脱敏。

@quan020406

Copy link
Copy Markdown
Author

Updated the branch with the review fixes.

Changes:

  • Hardened live runtime config secret handling: token/secret/password/credential fields now require ${...} placeholders, and runtime config write-back has a final sanitization guard.
  • Preserved original evaluation exceptions when PromptSandbox restore also fails.
  • Removed fragile eval_set_path.parent / optimizer.json inference by passing EvalConfig explicitly into evaluate_split.
  • Moved fake rubric registration out of evaluate_split and made registration idempotent.
  • Normalized checked-in sample_output audit paths to POSIX-style / separators.

Verification:

  • D:\Anaconda\python.exe -m pytest tests/examples/optimization/eval_optimize_loop -q: 96 passed
  • fake CLI: exit 0, ACCEPT, selected candidate_general_fix
  • trace CLI: exit 0, REJECT, selected none
  • live CLI without required env vars: exit 2 before optimizer startup
  • write-back default: off
  • sample_output secret scan: no real secrets
  • git diff --check: passed

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我已经完整审查了 pr.diff(一个新增的 eval_optimize_loop 示例及其测试套件),并结合了仓库中 SDK 的相关上下文(TargetPromptAgentOptimizerAgentEvaluatorEvalCaseResultEvalConfigOptimizeConfigFileOptimizeResult 等)进行交叉验证。

发现的问题

⚠️ Warning

  • examples/optimization/eval_optimize_loop/run_pipeline.py:1957-1976:trace 模式直接以 config_payload["evaluate"] / config_payload["metric_weights"] / config_payload["seed"] 取键,未走 load_pipeline_config 校验路径。
    • main() 只捕获 ValueErrorPipelineExecutionError(对应文档化的退出码 2/3),若 trace_config.json 缺键会抛 KeyError,未被捕获,会以退出码 1 + traceback 退出,破坏 README 声明的输入错误退出码契约。建议对这些键做存在性校验并转为 ValueError,或复用 load_pipeline_config 的校验风格。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/pipeline/reporter.py:1806:Markdown gate 规则表直接用 `{rule.actual}` / `{rule.expected}` 插值,rule.actual 可能是 dict(如 validation_case_deltas_complete{"expected":[...],"actual":[...]}),且未转义 |。当前数据无 |,渲染仅不美观;一旦 evidence/reason 含 | 会断列。可对单元格做 str(value).replace("|","\\|") 后再写入。

总结

整体实现质量高:凭据占位符/字面量密钥拒绝、secret 脱敏、PromptSandbox 恢复与异常链、gate 规则矩阵、归因优先级、确定性投影等高风险点均有对应测试覆盖且与 SDK 行为一致。未发现 Critical 阻塞问题;上述 Warning 为 CLI 错误码契约的健健性缺口,建议修复。

测试建议

暂无额外测试建议。现有测试已覆盖 fake/trace/live 三模式的核心路径、secret 脱敏、确定性、gate 矩阵与归因;如需加固,可补一条「trace_config.json 缺键时 main() 返回退出码 2」的用例以锁定上述 Warning 的修复。

@quan020406

Copy link
Copy Markdown
Author

Updated the branch with 4dceca3 and 5fd8b4f for the latest trace-config and Markdown-table review items.

Changes:

  • Trace mode now validates trace_config.json required keys through _load_trace_config, converting malformed trace config into ValueError so main() keeps the documented input-error exit code 2 instead of surfacing KeyError tracebacks.
  • Markdown report table cells now render dict/list values through stable JSON, escape |, normalize newlines, and escape backticks before writing table rows.
  • Regenerated checked-in sample_output after the report formatting change.

Verification:

  • D:\Anaconda\python.exe -m pytest tests/examples/optimization/eval_optimize_loop -q: 99 passed
  • fake CLI: exit 0, ACCEPT, selected candidate_general_fix
  • trace CLI: exit 0, REJECT, selected none
  • live CLI without required env vars: exit 2 before optimizer startup
  • git diff --check: passed
  • sample_output path scan: no backslash audit references
  • sample_output secret scan: no real secrets; only ${TRPC_AGENT_*} placeholders

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

发现的问题

我对 pr.diff 全量变更(示例管线 + 测试 + 样例输出)做了逐文件审查,重点核对 Gate 决策、归因、归一化、回写、脱敏与 live 模式密钥处理等高风险路径。以下为结论。

🚨 Critical

未发现。

⚠️ Warning

未发现明确阻塞问题。核心逻辑(Gate 规则优先级、PromptSandbox 的 baseline CAS 回滚、write_back_after_gate 的摘要校验、_runtime_config 拒绝字面量密钥后再经 sanitize_config 落盘)在 diff 内自洽,并与测试矩阵一致。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/pipeline/optimizer_backend.py:1456-1458_runtime_config 中的 is_sensitive_key 敏感键集合(apikey/authtoken/.../secret/password/credential)比 config.pysanitize_config_is_sensitive_key(额外覆盖 keytoken 等单字及其子串)更窄。结果是:键名为 keytoken 的字面量密钥不会触发 PipelineExecutionError fail-fast,而是被 sanitize_config 静默改写为 ***REDACTED*** 后写入 _optimizer.runtime.json,SDK 读到占位符后会以认证失败形式静默报错,而非清晰的配置错误。虽不会泄露(落盘已被脱敏),但两处"何为敏感"的定义不一致是维护隐患;建议抽取统一谓词复用,使守卫与脱敏覆盖同一集合。

总结

整体风险低,不存在必须修复的阻塞问题;安全侧(凭证脱敏、字面量密钥拒绝、回写 CAS)在 diff 范围内有测试覆盖且逻辑自洽。唯一值得改进的是两处敏感键判定逻辑不一致,建议统一以提升可维护性。

测试建议

暂无额外测试建议。若采纳上述建议,可补一条用例:在 raw_config 中以键名 token/key 携带字面量密钥,断言 AgentOptimizerBackend.generate_candidates 抛出 PipelineExecutionError(而非静默落盘为 ***REDACTED***)。

@quan020406

Copy link
Copy Markdown
Author

Updated the branch with b4548b0 for the remaining sensitive-key consistency suggestion.

Changes:

  • Reused the same is_sensitive_config_key predicate for both runtime fail-fast validation and sanitize_config redaction.
  • Added raw_config examples where literal token and key values now raise PipelineExecutionError before AgentOptimizer.optimize can run, instead of being silently written as ***REDACTED***.

Verification:

  • D:\Anaconda\python.exe -m pytest tests/examples/optimization/eval_optimize_loop -q: 101 passed
  • D:\Anaconda\python.exe -m pytest tests/examples/optimization/eval_optimize_loop/test_live_backend.py -q: 17 passed
  • live CLI without required env vars: exit 2
  • git diff --check: passed
  • sample_output secret scan: no real secrets; only ${TRPC_AGENT_*} placeholders

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

现在我已经掌握了足够的上下文。让我总结一下我的发现。

主要发现:

  1. Gate 在 complete=False 时总是拒绝 — 如果 _complete 返回 False,所有依赖于完整性的检查项都会计算为 None,导致 validation_score_delta_availablevalidation_pass_rate_delta_available 等失败,从而使 accepted=False。这是预期的故障安全行为,没问题。

  2. min_validation_score_delta=0.05metric_weights 默认不同 — fake 模式使用了配置的权重。让我检查一下 fake 流水线是否确实产生了 0.05 的 delta。

让我通过查看预期行为来验证 candidate_general_fix 是否确实被接受了。从 fake_agent 来看:baseline 是 BASELINE,general_fix 是 GENERAL_FIX。当 general 为 True 时,查询订单 A100 会返回带有 order_id A100 的 JSON 以及正确的答案 —— 与预期完全匹配。Baseline (非 general,非 overfit):返回 _json("order_lookup", "lookup_order", {}, "正在查询订单。") —— 参数为空,答案不同。因此 general_fix 在验证集上应该通过更多。测试断言它被接受。没问题。

现在让我确认是否没有真正的 bug。剩下的问题:

关于 trace_passtool_trajectory_avg_score 的担忧 — 该测试断言只有 1 个通过且 4 个失败,其中失败类型正好是这组。如果 trace_pass 因为 tool_trajectory_avg_score 为 1.0 而通过,但其他 4 个失败(即使它们的最终响应或轨迹匹配,但由于其他原因失败),测试断言仍然是正确的。但是 trace_tool_argument_wrong — 其最终响应与预期的不同(order_id A999 vs A100),因此 final_response_avg_score 为 0(失败),fake_rubric_score 为 0.75(通过,因为分数 >= 阈值 0.75?)。等等,让我检查一下:fake_rubric 对不匹配的参数评分:路由+工具匹配 (+0.25→0.5),参数不匹配 (0.5),答案... 不是 knowledge_gap,所以 +0.25→0.75。所以 fake_rubric = 0.75,通过(>= 0.75)。但 final_response_avg_score = 0(不是精确匹配)。所以 trace_tool_argument_wrong 失败(因为 final_response_avg_score 失败)。好的。

trace_tool_selection_wrong — 最终响应与预期完全匹配(相同的 JSON)。但实际中间工具是 lookup_refund vs 预期的 lookup_orderfinal_response_avg_score = 1.0(通过)。fake_rubric_score = 1.0(通过)。tool_trajectory_avg_score = 0.0(失败,因为工具不匹配)。所以由于 tool_trajectory_avg_score 失败。断言失败集合包含 tool_selection_error。通过归因:actual_names=[lookup_refund], expected_names=[lookup_order],它们不同 → TOOL_SELECTION_ERROR。很好。所以 tool_trajectory_avg_score 确实起了作用。测试通过。

现在让我确认没有真正的问题。让我重新审视 _complete 空基准线的情况,以及 select_winner 的排序一致性。

我现在有足够的信息来撰写审查报告了。让我对 run_pipeline 中的实时预检查(precheck)进行最后一次检查 —— 配置加载了两次(在预检查中一次,在 run_live_pipeline 中一次)。

查看 main():对于 live 模式,它调用 load_pipeline_config(...mode="live") 作为预检查,然后 run_live_pipeline 内部再次调用 load_pipeline_config(...mode="live")。这是冗余的,但不是 bug —— 两次都会进行校验。README 说“在构建优化器之前验证环境变量……并退出”。预检查确保了提前退出代码 2。这没问题,只是一个小的冗余。

再检查一个潜在问题:_runtime_config 拒绝字面量密钥,但 optimizer.json 中的 reflection_lm 使用了 ${...} 占位符 —— 这些会通过。实时测试 test_live_backend_uses_sanitized_config_temporary_target_and_archives_artifacts 证实了 ${TRPC_AGENT_API_KEY} 占位符被保留。没问题。

让我确认 agent/agent.py 的导入是否正常工作(相对导入)。

from ..fake.fake_agent import FakeSupportAgent —— agent 包是 eval_optimize_loop 的子包,fake 是其兄弟包。所以 ..fake = eval_optimize_loop.fake。正确。

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

发现的问题

经核对 diff 与 SDK 上下文,未发现 Critical 级别问题。下面列出 Warning 与 Suggestion。

🚨 Critical

无。

⚠️ Warning

  • examples/optimization/eval_optimize_loop/run_pipeline.py:1739examples/optimization/eval_optimize_loop/pipeline/config.py:396-421:live 模式下 main() 先调用 load_pipeline_config(mode="live") 做预检,run_live_pipeline 内部又再次完整加载并校验同一份 optimizer.json,造成重复解析与重复的 env/dataset 校验。虽然不影响正确性,但若两次加载之间配置文件被外部修改,预检通过的结果可能与实际使用的配置不一致;建议将预检结果传入 run_live_pipeline 复用,避免双读与潜在 TOCTAU。

  • examples/optimization/eval_optimize_loop/pipeline/optimizer_backend.py:1142-1143AgentOptimizer.optimize 失败时仅通过 getattr(result, "status", None) != "SUCCEEDED" 判断并抛 PipelineExecutionError,但 optimize 在异常路径下 SDK 自身会 finally 回滚 baseline(见 _agent_optimizer.py:272-278)。此处的 tempfile.TemporaryDirectory 上下文已退出,候选 prompt 目录已销毁,逻辑上没有泄漏;但 result 若为 None(SDK 在某些错误路径返回前抛异常已被 except Exception 包裹),getattr 兜底为 "unknown" 仍会抛错,行为可接受。仅提示:依赖字符串 "SUCCEEDED" 与 SDK RunStatus 字面量耦合,建议改用 RunStatus 枚举或显式比较,避免 SDK 未来重命名状态值时静默失效。

💡 Suggestion

  • examples/optimization/eval_optimize_loop/pipeline/normalization.py:104-110actual/expected 仅取 runs[0] 的首个 invocation,而 parsed_run_responsesfailure_types 已遍历全部 runs 的全部 invocations。当 num_runs>1 或多轮对话时,final_response/expected_response/trace_digest 仅反映首 run 首轮,与归因证据覆盖范围不一致;建议在注释或字段说明中明确该取值语义,便于后续维护者理解审计产物的口径。

总结

整体风险较低:示例代码与测试紧扣 SDK 公共 API,输入校验、密钥脱敏、PromptSandbox 恢复、Gate 独立判定等关键路径均覆盖到位,未发现安全漏洞或核心逻辑错误。仅 live 模式配置双读与状态字符串耦合两点建议优化,不构成阻塞。

测试建议

暂无额外测试建议。现有测试已覆盖归因优先级、归一化、Gate 矩阵、密钥脱敏、PromptSandbox 恢复失败、live 后端候选提取与回写守卫等关键路径;若进一步加固,可补充 num_runs>1final_response/trace_digest 只取首 run 的边界用例,以锁定 Suggestion 中提到的语义。

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