|
| 1 | +# Evaluation + Optimization Loop |
| 2 | + |
| 3 | +This example demonstrates a reproducible pipeline that connects evaluation, |
| 4 | +failure attribution, prompt optimization, validation regression checks, gate |
| 5 | +decisions, and audit reports. |
| 6 | + |
| 7 | +It is intentionally different from `ci_integration/`: CI integration focuses on |
| 8 | +PR gates and nightly write-back, while this example focuses on the complete |
| 9 | +review loop before deciding whether a candidate prompt is worth accepting. |
| 10 | + |
| 11 | +## Run |
| 12 | + |
| 13 | +The default mode is deterministic and does not require an API key: |
| 14 | + |
| 15 | +```bash |
| 16 | +cd examples/optimization/eval_optimize_loop |
| 17 | +PYTHONPATH=../../.. python run_pipeline.py --mode fake |
| 18 | +``` |
| 19 | + |
| 20 | +If your environment only exposes `python3`, use: |
| 21 | + |
| 22 | +```bash |
| 23 | +PYTHONPATH=../../.. python3 run_pipeline.py --mode fake |
| 24 | +``` |
| 25 | + |
| 26 | +To refresh the committed sample reports: |
| 27 | + |
| 28 | +```bash |
| 29 | +PYTHONPATH=../../.. python run_pipeline.py --mode fake --update-sample-outputs |
| 30 | +``` |
| 31 | + |
| 32 | +The fake and trace modes support three deterministic scenarios: |
| 33 | + |
| 34 | +```bash |
| 35 | +PYTHONPATH=../../.. python run_pipeline.py --mode fake --scenario overfit |
| 36 | +PYTHONPATH=../../.. python run_pipeline.py --mode fake --scenario accepted |
| 37 | +PYTHONPATH=../../.. python run_pipeline.py --mode fake --scenario cost_exceeded |
| 38 | +``` |
| 39 | + |
| 40 | +- `overfit` is the default: train improves, validation does not improve, and a |
| 41 | + critical case regresses, so the gate rejects the candidate. |
| 42 | +- `accepted`: train and validation both improve with no new hard fail, so the |
| 43 | + gate accepts the candidate. |
| 44 | +- `cost_exceeded`: quality improves, but the estimated cost exceeds |
| 45 | + `gate.max_cost_usd`, so the gate rejects the candidate. |
| 46 | + |
| 47 | +For CI-style scripts, add `--ci-exit-code`. Normal demo runs always exit 0 after |
| 48 | +writing the report; CI mode exits 0 for accepted candidates and 1 for rejected |
| 49 | +candidates: |
| 50 | + |
| 51 | +```bash |
| 52 | +PYTHONPATH=../../.. python run_pipeline.py --mode fake --scenario accepted --ci-exit-code |
| 53 | +PYTHONPATH=../../.. python run_pipeline.py --mode fake --scenario overfit --ci-exit-code |
| 54 | +``` |
| 55 | + |
| 56 | +Trace replay mode also runs without model credentials. It first records fake |
| 57 | +outputs into generated `eval_mode: "trace"` evalsets, then evaluates those |
| 58 | +`actual_conversation` records without invoking `call_agent`: |
| 59 | + |
| 60 | +```bash |
| 61 | +PYTHONPATH=../../.. python run_pipeline.py --mode trace |
| 62 | +``` |
| 63 | + |
| 64 | +The optional real optimizer path delegates candidate search to |
| 65 | +`AgentOptimizer.optimize` and requires the normal optimization dependencies and |
| 66 | +model environment variables: |
| 67 | + |
| 68 | +```bash |
| 69 | +export TRPC_AGENT_API_KEY="<your-key>" |
| 70 | +export TRPC_AGENT_BASE_URL="<your-endpoint>" |
| 71 | +export TRPC_AGENT_MODEL_NAME="<your-model>" |
| 72 | +PYTHONPATH=../../.. python run_pipeline.py --mode optimizer |
| 73 | +``` |
| 74 | + |
| 75 | +## Verification Commands |
| 76 | + |
| 77 | +Use these commands to exercise the important review paths: |
| 78 | + |
| 79 | +```bash |
| 80 | +cd examples/optimization/eval_optimize_loop |
| 81 | +PYTHONPATH=../../.. python run_pipeline.py --mode fake --scenario overfit |
| 82 | +PYTHONPATH=../../.. python run_pipeline.py --mode fake --scenario accepted |
| 83 | +PYTHONPATH=../../.. python run_pipeline.py --mode fake --scenario cost_exceeded |
| 84 | +PYTHONPATH=../../.. python run_pipeline.py --mode trace --scenario overfit |
| 85 | +cd ../../.. |
| 86 | +pytest tests/evaluation/test_eval_optimize_loop_example.py -q |
| 87 | +``` |
| 88 | + |
| 89 | +## Inputs |
| 90 | + |
| 91 | +| File | Role | |
| 92 | +| --- | --- | |
| 93 | +| `train.evalset.json` | Training split used for baseline scoring and optimization feedback. | |
| 94 | +| `val.evalset.json` | Validation split used for regression and gate decisions. | |
| 95 | +| `optimizer.json` | Shared metric, optimizer, fake candidate, and gate configuration. | |
| 96 | +| `case_meta.json` | Public-case attribution labels used for local self-checking. | |
| 97 | +| `agent/prompts/router.md` | Router prompt source registered as `TargetPrompt("router_prompt")`. | |
| 98 | +| `agent/prompts/system.md` | System prompt source registered as `TargetPrompt("system_prompt")`. | |
| 99 | +| `agent/prompts/skill.md` | Skill prompt source registered as `TargetPrompt("skill_prompt")`. | |
| 100 | + |
| 101 | +The fake mode still uses `AgentEvaluator` for all train and validation scoring. |
| 102 | +Only candidate generation is deterministic, so the example can run in CI or on a |
| 103 | +laptop without model credentials. |
| 104 | + |
| 105 | +At startup, the pipeline validates that all required input files exist, train and |
| 106 | +validation evalsets are distinct and non-empty, gate keys are present, critical |
| 107 | +case IDs refer to validation cases, and evaluator parallelism is positive. |
| 108 | + |
| 109 | +Fake judge behavior is implemented by local exact-match final-response scoring |
| 110 | +(`final_response_avg_score`). The report records this as |
| 111 | +`run.judge_mode = "local_exact_match_fake_judge"`, so the evaluation path does |
| 112 | +not need an LLM judge or API key. |
| 113 | + |
| 114 | +For tool-only and rubric-only failure classes, fake judge mode uses |
| 115 | +deterministic attribution hints from `case_meta.json`. This lets the no-key |
| 116 | +example exercise the full attribution taxonomy without requiring real tool |
| 117 | +execution or an LLM rubric judge. |
| 118 | + |
| 119 | +## Outputs |
| 120 | + |
| 121 | +Each run writes to `runs/<mode>_<scenario>_<timestamp>/` by default: |
| 122 | + |
| 123 | +| File or directory | Contents | |
| 124 | +| --- | --- | |
| 125 | +| `optimization_report.json` | Machine-readable baseline, candidate, per-round prompts, delta, attribution, gate, and audit payload. | |
| 126 | +| `optimization_report.md` | Human-readable report for review. | |
| 127 | +| `candidate_prompts/` | Candidate prompt text per target field. | |
| 128 | +| `eval_*` | Raw `AgentEvaluator` result files for every phase. | |
| 129 | +| `config.snapshot.json` | Reproducible copy of the input config. | |
| 130 | +| `eval_metrics.snapshot.json` | Evaluator-compatible metric config extracted from `optimizer.json.evaluate`. | |
| 131 | + |
| 132 | +The report also includes `prompt_audit`, which records each target prompt's |
| 133 | +source path, baseline SHA-256, candidate SHA-256, character counts, and whether |
| 134 | +the candidate changed the prompt. Each prompt audit entry also includes a capped |
| 135 | +unified diff preview, so reviewers can see what changed without opening the |
| 136 | +candidate prompt files. This makes the score-to-prompt relationship auditable |
| 137 | +even when reports are copied outside the run directory. |
| 138 | + |
| 139 | +`input_audit` records stable SHA-256 hashes and byte counts for the train |
| 140 | +evalset, validation evalset, optimizer config, case metadata, and prompt source |
| 141 | +files. This makes a report reproducible even after it is detached from the |
| 142 | +working tree. |
| 143 | + |
| 144 | +`audit.cost` splits the total estimated spend into optimizer and evaluation |
| 145 | +components, then applies the same total to the configurable budget gate. In |
| 146 | +fake/trace mode both components are deterministic and normally zero except for |
| 147 | +the dedicated `cost_exceeded` scenario. |
| 148 | + |
| 149 | +`failure_attribution.self_check` compares attribution results against the public |
| 150 | +gold labels in `case_meta.json`. Gold labels live in `expected_failure_types`. |
| 151 | +For categories that cannot naturally arise in the no-key black-box path, such as |
| 152 | +tool-call and LLM-rubric failures, `fake_attribution_hints` provides an explicit |
| 153 | +fake-judge hint. The report marks those cases as `hint_assisted` and also reports |
| 154 | +rule-only accuracy separately. This is not a hidden-set proof, but it provides a |
| 155 | +local accuracy guard for the bundled examples and fails report validation if the |
| 156 | +labeled-case accuracy drops below 75%. |
| 157 | + |
| 158 | +Committed examples live in `sample_outputs/`. |
| 159 | + |
| 160 | +The JSON report includes both human reasons and structured gate checks: |
| 161 | + |
| 162 | +```json |
| 163 | +{ |
| 164 | + "gate": { |
| 165 | + "decision": "rejected", |
| 166 | + "reasons": ["..."], |
| 167 | + "checks": [ |
| 168 | + {"name": "min_validation_score_delta", "passed": false}, |
| 169 | + {"name": "no_new_hard_fail", "passed": false} |
| 170 | + ] |
| 171 | + } |
| 172 | +} |
| 173 | +``` |
| 174 | + |
| 175 | +## Gate Decision Contract |
| 176 | + |
| 177 | +A candidate is accepted only when every configured gate check passes: |
| 178 | + |
| 179 | +| Check | Accept condition | Reject condition | |
| 180 | +| --- | --- | --- | |
| 181 | +| `min_validation_score_delta` | Validation score delta is greater than or equal to `gate.min_validation_score_delta`. | Validation score delta is below the configured threshold. | |
| 182 | +| `no_new_hard_fail` | No validation case changes from pass to fail, unless `gate.allow_new_hard_fail` is true. | At least one validation case becomes a new hard fail while new hard fails are disallowed. | |
| 183 | +| `critical_case_regression` | No case in `gate.critical_case_ids` loses score. | Any critical validation case regresses. | |
| 184 | +| `overfitting_guard` | Validation improves, or training does not improve. | Training improves while validation does not improve. | |
| 185 | +| `max_cost_usd` | Estimated candidate cost is less than or equal to `gate.max_cost_usd`. | Estimated candidate cost exceeds the budget. | |
| 186 | + |
| 187 | +This contract is intentionally validation-first: training improvements alone |
| 188 | +never justify acceptance, and hidden samples are expected to follow the same |
| 189 | +structured decision rules. |
| 190 | + |
| 191 | +## Case Coverage |
| 192 | + |
| 193 | +The ten sample cases exceed the minimum requirement and cover the required outcomes: |
| 194 | + |
| 195 | +| Case | Split | Expected behavior | |
| 196 | +| --- | --- | --- | |
| 197 | +| `train_refund_double_charge` | train | Candidate fixes a baseline failure. | |
| 198 | +| `train_password_reset` | train | Already passing; candidate is unchanged. | |
| 199 | +| `train_legacy_sync` | train | Candidate does not help. | |
| 200 | +| `train_plan_question` | train | Stable non-target billing question remains passing. | |
| 201 | +| `train_policy_tool_missing` | train | Tool lookup failure attribution with an incomplete JSON response. | |
| 202 | +| `val_vip_refund` | validation | Candidate creates a new pass. | |
| 203 | +| `val_plan_question` | validation | Candidate has no effect. | |
| 204 | +| `val_checkout_outage` | validation | Candidate regresses a critical case. | |
| 205 | +| `val_mobile_crash` | validation | Stable technical troubleshooting case remains passing. | |
| 206 | +| `val_rubric_tone_fail` | validation | Deterministic rubric failure attribution remains failing. | |
| 207 | + |
| 208 | +The default fake candidate improves train score but does not improve validation |
| 209 | +overall because it also introduces a new hard fail. The gate therefore rejects |
| 210 | +the candidate and records the overfitting reason. |
| 211 | + |
| 212 | +## Requirement Mapping |
| 213 | + |
| 214 | +| #91 requirement | Implementation | |
| 215 | +| --- | --- | |
| 216 | +| Baseline train/validation evaluation | `run_pipeline.py::evaluate_dataset` uses `AgentEvaluator.get_executer(...).get_result()`. | |
| 217 | +| Per-case metric, pass/fail, reason, trace | Report cases include query, expected, actual, metric details, status, failure types, and reason. | |
| 218 | +| Failure attribution clustering | `attribute_failure` and `build_failure_stats`. | |
| 219 | +| Full attribution taxonomy | Public sample failures include `final_response_mismatch`, `tool_call_error`, `parameter_error`, `llm_rubric_not_met`, `knowledge_recall_insufficient`, and `format_violation`. | |
| 220 | +| Attribution accuracy guard | `case_meta.json` plus `failure_attribution.self_check` validates public labeled cases and marks fake hint-assisted cases. | |
| 221 | +| Prompt optimization target | `TargetPrompt` registers `router_prompt`, `system_prompt`, and `skill_prompt`. | |
| 222 | +| AgentOptimizer or equivalent extension | `--mode optimizer` delegates to `AgentOptimizer`; fake/trace modes use deterministic candidate generation for no-key CI. | |
| 223 | +| Candidate validation and per-case delta | `build_case_deltas` marks `new_pass`, `new_fail`, `score_improved`, `score_regressed`, and `unchanged`. | |
| 224 | +| Configurable gate | `optimizer.json.gate` plus `apply_gate`, including structured `gate.checks`. | |
| 225 | +| Overfitting rejection | Default `--scenario overfit` improves train but rejects because validation does not improve and a critical case regresses. | |
| 226 | +| Cost budget rejection | `--scenario cost_exceeded` improves quality but fails `max_cost_usd`. | |
| 227 | +| Audit artifacts | `input_audit`, `prompt_audit` with diff previews, `optimization_rounds`, `candidate_prompts/`, raw `eval_*` outputs, config snapshots, JSON report, and Markdown report. | |
| 228 | +| No API key mode | `--mode fake` and `--mode trace`. | |
| 229 | +| 6 public cases | `train.evalset.json` has 5 cases and `val.evalset.json` has 5 cases. | |
| 230 | + |
| 231 | +## Design Notes |
| 232 | + |
| 233 | +本示例把 `AgentEvaluator.get_executer(...).get_result()` 作为唯一评测事实来源,分别对训练集和验证集生成逐 case 记录,包含输入、期望输出、实际输出、metric 分数、pass/fail、失败类型和原因。失败归因采用可复现规则:JSON 解析失败或缺字段归为格式不符合要求,category 不一致归为知识召回不足,priority 不一致归为参数错误,action 不一致归为最终回复不匹配;对必须依赖真实工具轨迹或 LLM rubric judge 的类别,fake mode 通过 `case_meta.json` 的 `fake_attribution_hints` 标记 `tool_call_error` 和 `llm_rubric_not_met`,并在 self-check 中区分 hint-assisted 与 rule-only 准确率,避免把公开标签直接当成黑盒判断结果。 |
| 234 | + |
| 235 | +候选 prompt 不会直接写回源文件,而是先在验证集回归,并和 baseline 做逐 case delta,标记新增通过、新增失败、分数提升、分数下降和无变化。gate 由 `optimizer.json` 配置,检查验证集总分提升阈值、是否新增 hard fail、关键 case 是否退化以及成本预算;额外防过拟合规则是训练集提升但验证集没有提升时拒绝候选。这样即使优化器记住训练失败样例,只要验证集没有真实收益,或者牺牲了关键 case,也不会进入可接受状态。 |
| 236 | + |
| 237 | +审计产物包括配置快照、随机种子、候选 prompt 文件、`optimization_rounds`、原始 evaluator 输出、JSON 报告和 Markdown 报告,便于完整复现实验配置,也便于后续 CI 读取结构化字段。`prompt_audit` 记录源路径、baseline/candidate 哈希、字符数和是否变更,reviewer 可以把分数变化追溯到具体 prompt。trace mode 会生成 `eval_mode: "trace"` 数据并回放 `actual_conversation`,保证无 API key 环境也能复现 baseline、归因、优化、验证、gate 和审计闭环。 |
0 commit comments