Skip to content

Commit 230246e

Browse files
committed
feat(examples): add eval optimization loop
1 parent 73655ab commit 230246e

15 files changed

Lines changed: 3675 additions & 0 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
runs/
2+
__pycache__/
3+
*.pyc
4+
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
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 和审计闭环。
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
"""Agent entry points for the eval-optimize-loop example."""
2+

0 commit comments

Comments
 (0)