narcolepticchicken commited on
Commit
30117ba
·
verified ·
1 Parent(s): efd2832

Delete training/train_router_v[1-7]*.py, training/train_v10_fixed.py, training/repro*.py, training/router_v[5-8]*.py, training/integration_real.py, training/debug_sprout*.py, training/check_bundle*.py, training/execution_feedback_loop.py, training/tune_feedback.py, training/verify_frontier.py, training/router_integration_benchmark.py, docs/combined_final_report.md, docs/bert_*.md, docs/final_report*.md, docs/FINAL_CORRECTED_REPORT.md, standalone_eval*.py, run_eval.py, run_benchmark.py, train_router_gen.py, train_router.py

Browse files
docs/FINAL_CORRECTED_REPORT.md DELETED
@@ -1,60 +0,0 @@
1
- # ACO Final Results — Corrected
2
-
3
- ## The Simple Strategy That Beats Everything
4
-
5
- **Tier 1 → Tier 2 → Tier 4**: Route to cheapest model first. If it fails, try mid-tier. If that fails, use frontier.
6
-
7
- | Metric | Value |
8
- |--------|-------|
9
- | Success rate | **83.2%** (416/500) |
10
- | Avg cost | **$0.173/task** |
11
- | vs Frontier | **45.5% cheaper** AND **5.0pp more successful** |
12
- | vs Oracle | 3.8pp gap (83.2% vs 87.0%) |
13
-
14
- This requires **zero ML** — just a static escalation policy. No feature extraction, no training, no model loading.
15
-
16
- ## Why It Works
17
-
18
- | Tier | Model | Cost | Marginal Contribution |
19
- |------|-------|------|-----------------------|
20
- | 1 | deepseek-v4-flash | $0.014 | Solves 316 tasks (63.2%) |
21
- | 2 | gpt-5-mini | $0.033 | Solves 26 more (5.2%) |
22
- | 4 | claude-opus-4.7 | $0.317 | Solves 74 more (14.8%) |
23
- | **Combined** | — | $0.173 | **416 tasks (83.2%)** |
24
-
25
- The key is that tier 2 is cheap ($0.033) and catches meaningful marginal wins before escalating to the expensive tier 4.
26
-
27
- ## What About ML-Based Routing?
28
-
29
- The v10 XGBoost router with feedback achieves 85.2% at $0.443/task — 2.0pp more success at **2.56× the cost**. The additional 10 resolves cost $135 each vs T1→T2→T4.
30
-
31
- ### Why ML Routing Doesn't Help Here
32
-
33
- The tier-1 success rate is 63.2%. The router's job is to predict which 63.2% to route to tier 1. But:
34
- - False negatives (routing a tier-1-solvable task to tier 4) waste $0.303
35
- - False positives (routing an unsolvable task to tier 1) waste $0.014 + escalation
36
-
37
- The simple T1→T2→T4 strategy already achieves 83.2% — there are only 19 more tasks the oracle can solve (87.0%). The improvement ceiling is tight. An ML router would need near-perfect accuracy on the hardest <4% of tasks to beat the simple strategy.
38
-
39
- ## Updated Pareto Frontier
40
-
41
- ```
42
- 87% │ ★ Oracle
43
-
44
- 85% │ ★ All tiers (85.2%, $0.443)
45
-
46
- 83% │ ★ T1→T2→T4 (83.2%, $0.173) ← BEST REAL STRATEGY
47
-
48
- 78% │ ★ Frontier (78.2%, $0.317)
49
-
50
- 63% │ ★ Always cheap (63.2%, $0.014)
51
- └─────────────────────────────────────
52
- $0.05 $0.10 $0.15 $0.20 $0.25 $0.30 $0.35 $0.40 $0.45
53
- ```
54
-
55
- ## Recommendations
56
-
57
- 1. **Deploy T1→T2→T4 immediately.** It's free, simple, and beats everything.
58
- 2. **Don't build an ML router for SWE-bench.** The simple strategy already captures most of the oracle's gains.
59
- 3. **Only build ML routing if** the task distribution has a much wider tier-1 success range (e.g., 20-80% across task types) where smart routing can meaningfully outperform the static cascade.
60
- 4. **The ACO modules (verifier, doom detector, etc.)** should be tested on real agent traces, not simulated ones — the simulation can't capture the complex failure modes these modules are designed to handle.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/bert_5class_final_report.md DELETED
@@ -1,96 +0,0 @@
1
- # BERT 5-Class Router — Final Report
2
-
3
- ## What We Did
4
-
5
- ### Task 1: Fix SPROUT Parsing ✅
6
-
7
- The original `train_bert_5class.py` had broken SPROUT parsing:
8
- - It did naive `if m_lower in col.lower()` string matching on column names
9
- - It checked `isinstance(val, (int, float))` for model columns
10
- - But SPROUT model columns are dicts: `{'judge_response': '{"correctness_score": 0.8, ...}'}`
11
-
12
- **V3 fix** (`training/train_bert_5class_v3.py`):
13
- - Robust `extract_correctness_score()` handles all SPROUT formats
14
- - Handles escaped single quotes in JSON strings (`Model\\'s → Model's`)
15
- - Regex fallback for truly broken JSON
16
- - Result: **0 parse errors** on 30,968 rows (vs 11,610 errors in V2)
17
-
18
- ### Task 2: Retrain BERT as 5-Class Router ✅
19
-
20
- Training on SPROUT (30,968 samples, 13 models → 5 ACO tiers):
21
-
22
- | Metric | Epoch 1 | Epoch 2 |
23
- |--------|---------|---------|
24
- | eval_accuracy | 76.85% | 76.95% |
25
- | eval_acc_tier1 | 99.96% | 99.92% |
26
- | eval_acc_tier2 | 0.84% | 1.26% |
27
- | eval_acc_tier3 | 0% | 0% |
28
- | eval_acc_tier4 | 0% | 1.74% |
29
- | eval_acc_tier5 | 0% | 0% |
30
-
31
- **Finding: BERT trained on SPROUT QA data collapses to predicting tier 1 for everything.** This is because 77.4% of SPROUT tasks are solvable by tier 1 models with correctness_score ≥ 0.7.
32
-
33
- ### Task 3: Execution Feedback Loop on SWE-Bench ✅
34
-
35
- Evaluated BERT 5-class router on SWE-Router (500 coding tasks, 8 models):
36
-
37
- | Policy | Success | AvgCost | CostRed |
38
- |--------|---------|---------|---------|
39
- | oracle | 87.0% | $0.062 | 80.3% |
40
- | bert_direct | 54.8% | $0.042 | 86.8% |
41
- | bert_feedback | 84.0% | $0.578 | -82.5% |
42
- | bert_cascade | 70.4% | $0.783 | -147.1% |
43
- | frontier | 78.2% | $0.317 | baseline |
44
- | always_cheap | 63.2% | $0.014 | 95.5% |
45
-
46
- **BERT tier predictions on SWE-bench (untrained weights):**
47
- - Tier 1: 0.2%, Tier 2: 96.0%, Tier 3: 3.8%, Tier 4: 0%, Tier 5: 0%
48
-
49
- **Finding: Untrained BERT always picks tier 2** → bert_feedback escalates to frontier on failures → costs MORE than frontier (-82.5% cost reduction). The feedback escalation mechanism doesn't help when the initial routing is wrong.
50
-
51
- ## Key Findings
52
-
53
- ### 1. SPROUT ≠ SWE-bench
54
-
55
- SPROUT is a QA benchmark. The BERT 5-class router trained on SPROUT learns to predict the same tier for everything. Using SPROUT data for a coding-agent cost router doesn't work because:
56
- - QA task difficulty ≠ coding task difficulty
57
- - SPROUT correctness scores (judge LLM evaluations) ≠ SWE-bench resolution rates
58
- - The class distribution is fundamentally different
59
-
60
- ### 2. BERT for Tier Routing is Fundamentally Broken
61
-
62
- Both the binary BERT and the 5-class BERT collapse to predicting a single class. The binary version predicts P(success) ≈ 89.5% for ALL tiers. The 5-class version predicts tier 1 for everything (99.96% accuracy on tier 1, near-zero on all others).
63
-
64
- ### 3. What Actually Works
65
-
66
- The **XGBoost v10 router trained on real SWE-Router execution data** remains the best approach:
67
- - 500 tasks × 8 models = 4,000 real outcomes
68
- - Feature engineering from problem statements
69
- - Per-tier success prediction with calibration
70
- - Direct optimal-tier prediction
71
- - Achieves 76-85% success with 36-41% cost reduction
72
-
73
- ### 4. Execution Feedback Works Better as Post-Hoc Optimization
74
-
75
- The "execution feedback" approach (route → execute → observe → escalate) is conceptually right, but only works when:
76
- - The initial routing is reasonably good (not random)
77
- - The feedback mechanism is fast (ideally, within the same run)
78
- - The escalation cost doesn't overwhelm the savings
79
-
80
- ## Recommendation
81
-
82
- For the ACO project going forward:
83
-
84
- 1. **For BERT**: Use as a feature extractor for XGBoost (Option C from bert_eval_report), not as a standalone router
85
- 2. **For SPROUT**: Continue using it but with better class balancing (oversampling, class weights)
86
- 3. **For routing**: Hybrid heuristic + XGBoost (v7_s0.25_d0.85) is the current best approach
87
- 4. **For execution feedback**: Focus on per-step routing within a single agent run, not per-task routing with SWE-Router
88
-
89
- ## Files Created
90
-
91
- - `training/train_bert_5class_v3.py` — Robust SPROUT parser + 5-class BERT training
92
- - `training/execution_feedback_loop.py` — Route → Execute → Feedback evaluation
93
- - `training/debug_sprout_parsing.py` — SPROUT format inspection
94
- - `router_models/bert_5class/` — Trained BERT model (on Hub)
95
- - `eval/bert_feedback_results.json` — SWE-bench evaluation results
96
- - `docs/bert_5class_final_report.md` — This report
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/bert_eval_report.md DELETED
@@ -1,60 +0,0 @@
1
- # BERT Router Evaluation Results
2
-
3
- ## Setup
4
- - BERT: DistilBERTForSequenceClassification (num_labels=2, binary)
5
- - Trained on SPROUT (31K rows, 13 models) for binary success/fail prediction
6
- - Evaluated on SWE-Router (500 tasks, 8 models)
7
-
8
- ## Results
9
-
10
- | Policy | Success | CostRed |
11
- |--------|---------|---------|
12
- | Oracle | 87.0% | 80.3% |
13
- | v10+feedback | 81.4% | -35.6% |
14
- | bert+feedback | 81.0% | -35.3% |
15
- | Frontier | 78.2% | baseline |
16
- | v10 XGBoost | 56.2% | -7.4% |
17
- | BERT | 49.2% | -0.4% |
18
- | Always cheap | 63.2% | 95.5% |
19
-
20
- ## Diagnosis: BERT Router is Broken
21
-
22
- **Root cause**: BERT is a binary classifier (num_labels=2) trained to predict success/fail on SPROUT. When used for per-tier routing by prepending `[Tier X]` to the input, it ignores the tier prefix and predicts P(success) ≈ 89.5% for ALL tiers.
23
-
24
- **Evidence**:
25
- - All 100 sampled tasks routed to Tier 1
26
- - P(success) is nearly identical across all tiers: 89.5% ± 0.001
27
- - The tier prefix `[Tier X]` has no effect on BERT's predictions
28
-
29
- **Why**: BERT was trained on SPROUT data where the input was just the problem statement, not `[Tier X] problem_statement`. The model never saw the tier prefix during training, so it ignores it.
30
-
31
- ## Fix Required
32
-
33
- To make BERT work for tier routing, we need one of:
34
-
35
- ### Option A: Retrain as 5-class model
36
- - Change num_labels from 2 to 5
37
- - Labels: optimal tier (1-5) for each task
38
- - This directly predicts the best tier from the problem statement
39
-
40
- ### Option B: Retrain with tier-prefixed inputs
41
- - Keep binary classification
42
- - Augment training data: for each task, create 5 examples `[Tier 1] problem`, `[Tier 2] problem`, etc.
43
- - Label = success/fail at that tier
44
- - This teaches the model to condition on the tier prefix
45
-
46
- ### Option C: Use BERT for feature extraction only
47
- - Use BERT's [CLS] embedding as features for the XGBoost router
48
- - This replaces hand-crafted keyword features with learned representations
49
- - No need to change BERT's training
50
-
51
- **Recommended**: Option C — it's the least risky and provides immediate value by upgrading the XGBoost feature extraction.
52
-
53
- ## v10 XGBoost Performance Issue
54
-
55
- The v10_fixed model also underperforms expectations here (56.2% direct vs 76.6% in previous eval). This is likely because:
56
- 1. The v10_fixed model has only 14 features (fewer than the full model)
57
- 2. The threshold of 0.65 may not be well-calibrated for this model
58
- 3. The safety floor enforcement may be too weak
59
-
60
- This needs investigation — the original v10 eval used a different model bundle.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/combined_final_report.md DELETED
@@ -1,121 +0,0 @@
1
- # ACO Execution Feedback & BERT Router — Final Report
2
-
3
- ## Session Summary
4
-
5
- This session tackled two core challenges in the Agent Cost Optimizer:
6
-
7
- 1. **Fix BERT 5-class router training** (broken SPROUT parsing)
8
- 2. **Implement real execution feedback** on SWE-bench
9
-
10
- ## Task 1: BERT 5-Class Tier Router ✅
11
-
12
- ### Problem
13
- The old `train_bert_5class.py` had zero usable training data because SPROUT model columns contain nested dicts like `{'judge_response': '{"correctness_score": 0.8, ...}'}`, and the old code checked for `isinstance(val, (int, float))`.
14
-
15
- ### Fix
16
- `training/train_bert_5class_v3.py` — robust `extract_correctness_score()`:
17
- - Handles dicts, strings, escaped single quotes (`Model\'s → Model's`)
18
- - Regex fallback for broken JSON
19
- - **0 parse errors** on 30,968 SPROUT rows (vs 11,610 in V2)
20
-
21
- ### Results
22
- Trained 5-class DistilBERT on SPROUT (30K rows, 13 models → 5 ACO tiers):
23
-
24
- | Epoch | Accuracy | Tier 1 Acc | Tier 2 Acc | Tier 3-5 Acc |
25
- |-------|----------|-----------|-----------|-------------|
26
- | 1 | 76.85% | 99.96% | 0.84% | 0% |
27
- | 2 | 76.95% | 99.92% | 1.26% | 0-1.7% |
28
- | 3 | 76.56% | 95.54% | 19.12% | 0-7.8% |
29
- | 4 | 75.78% | 93.61% | 24.58% | 0-4.3% |
30
- | 5 | 75.52% | 91.75% | 31.30% | 0-7.8% |
31
-
32
- **Finding: By epoch 5, tier 2 accuracy improves to 31.3% and tier 1 drops to 91.8% — the model is slowly learning minority classes. With more epochs, class weighting, or data balancing, this could improve. But for practical use, the model on SWE-bench predicts a single tier for everything.**
33
-
34
- Model uploaded to: https://huggingface.co/narcolepticchicken/agent-cost-optimizer/tree/main/router_models/bert_5class
35
-
36
- ## Task 2: Execution Feedback Loop ✅
37
-
38
- ### Pipeline
39
- 1. BERT 5-class router predicts optimal tier from problem statement
40
- 2. Map tier → actual SWE-Router model
41
- 3. If cheap model fails: escalate to next tier (execution feedback)
42
- 4. Compare: BERT, BERT+feedback, frontier, oracle
43
-
44
- ### SWE-Bench Results (500 tasks)
45
-
46
- | Policy | Success | AvgCost | CostRed |
47
- |--------|---------|---------|---------|
48
- | oracle | 87.0% | $0.062 | 80.3% |
49
- | always_cheap | 63.2% | $0.014 | 95.5% |
50
- | frontier | 78.2% | $0.317 | baseline |
51
- | bert_direct | 54.8% | $0.042 | 86.8% |
52
- | bert_feedback | 84.0% | $0.578 | **-82.5%** |
53
- | bert_cascade | 70.4% | $0.783 | -147.1% |
54
-
55
- **Finding: BERT+feedback achieves 84.0% success (above frontier's 78.2%) but at -82.5% cost — worse than just using the frontier model always.** The feedback escalation mechanism adds cost faster than it adds success, because the initial routing is wrong (BERT predicts tier 2 for 96% of tasks).
56
-
57
- ## Task 3: BAAR-Style Router (BERT [CLS] + XGBoost) 🔄
58
-
59
- Inspired by BAAR (Budget-Aware Adaptive Routing, Shnitzer et al., 2025):
60
- - Use BERT's [CLS] embedding as semantic features for XGBoost
61
- - Combine with classic keyword features
62
- - Route to cheapest tier with P(success) ≥ threshold
63
- - Job ID: 69fe8b91317220dbbd1a6fda — **scheduled, awaiting execution**
64
-
65
- ## Overall Findings
66
-
67
- ### What Works
68
-
69
- | Approach | Cost Reduction | Notes |
70
- |----------|---------------|-------|
71
- | XGBoost v10+feedback (real SWE data) | 36.4% | Best practical result |
72
- | Always cheap model | 95.5% | Only 63.2% success |
73
- | Oracle | 80.3% | 87% success, upper bound |
74
-
75
- ### What Doesn't Work
76
-
77
- | Approach | Issue |
78
- |----------|-------|
79
- | BERT 5-class on SPROUT → SWE-bench | Domain mismatch: QA vs coding |
80
- | BERT binary success predictor | Predicts 89.5% for ALL tiers |
81
- | BERT+feedback escalation | Costs more than frontier |
82
-
83
- ### Why BERT Fails
84
-
85
- 1. **SPROUT is QA data, SWE-bench is coding data.** Task difficulty signals don't transfer.
86
- 2. **77.4% of SPROUT tasks are solvable by tier 1 models.** The classifier collapses to majority.
87
- 3. **BERT's [CLS] token encodes holistic semantics, not task-difficulty-specific features.** Without domain-specific fine-tuning (on coding tasks with real execution labels), BERT can't distinguish easy from hard coding tasks.
88
-
89
- ### What Would Actually Work (Recommendations)
90
-
91
- 1. **Train BERT directly on SWE-bench problem statements** with execution outcome labels — don't use SPROUT at all for coding tasks
92
- 2. **BAAR-style: use BERT [CLS] as features for XGBoost** (currently evaluating)
93
- 3. **Per-step routing**: route model choice at each agent step, not once per task
94
- 4. **Confidence-based escalation**: use model logprobs/entropy as routing features
95
-
96
- ## Files Created This Session
97
-
98
- | File | Purpose |
99
- |------|---------|
100
- | `training/train_bert_5class_v3.py` | Robust SPROUT parser + 5-class BERT training |
101
- | `training/execution_feedback_loop.py` | Route → Execute → Feedback evaluation |
102
- | `training/debug_sprout_parsing.py` | SPROUT format inspection |
103
- | `training/train_baar_router.py` | BAAR-style BERT+XGBoost router |
104
- | `router_models/bert_5class/` | Trained BERT model (5 epochs, on Hub) |
105
- | `docs/bert_5class_final_report.md` | Detailed BERT analysis |
106
- | `docs/combined_final_report.md` | This file |
107
-
108
- ## Pareto Frontier (Session Summary)
109
-
110
- Comparing all routing policies evaluated across this session:
111
-
112
- ```
113
- Frontier: 78.2% success @ baseline cost
114
- v10+feedback: 84.8% success @ 36.4% cost reduction (PARETO-OPTIMAL)
115
- Always cheap: 63.2% success @ 95.5% cost reduction
116
- BERT direct: 54.8% success @ 86.8% cost reduction
117
- BERT feedback: 84.0% success @ -82.5% cost (dominated by frontier)
118
- Oracle: 87.0% success @ 80.3% cost reduction
119
- ```
120
-
121
- Only **v10+feedback** and **oracle** sit on the Pareto frontier. Everything else is dominated.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/final_report.md DELETED
@@ -1,126 +0,0 @@
1
- # ACO Final Report: Agent Cost Optimizer
2
-
3
- ## Executive Summary
4
-
5
- ACO is a universal control layer that reduces autonomous agent cost while preserving task quality. After 11 iterations of router design, training on synthetic data, real execution data, and combined datasets, the key finding is:
6
-
7
- **Training on real execution data is the single most important lever.** The router trained on synthetic data actually *increased* cost by 11.6% on real tasks. The router trained on real SWE-Router data achieved 36.9% cost reduction at comparable quality — a 34.9 percentage point swing from one change.
8
-
9
- ## Required Answers
10
-
11
- ### How much cost was saved at iso-quality?
12
-
13
- On real SWE-bench tasks (500 coding tasks, 8 models):
14
-
15
- | Comparison | Cost Reduction | Quality Delta |
16
- |-----------|---------------|---------------|
17
- | v11 feedback vs always-frontier | 36.9% | -3.4pp (74.8% vs 78.2%) |
18
- | v11 cascade (thr=0.65) vs frontier | 62.5% | -10.8pp (67.4% vs 78.2%) |
19
- | v9 feedback (synthetic) vs frontier | 2.1% | +0.1pp (90.0% vs 90.0%) |
20
-
21
- On synthetic benchmarks, v9 with execution feedback matches frontier quality exactly (90.0% vs 90.0%) at 2.1% cost reduction. On real data, the quality gap is wider because real agent tasks have longer horizons and more failure modes.
22
-
23
- ### Which module saved the most?
24
-
25
- **Ablation results (SWE-bench, 500 tasks):**
26
-
27
- | Module Removed | Success Delta | Cost Delta | Impact |
28
- |---------------|---------------|------------|--------|
29
- | Feedback escalation | -8.6pp | -$0.0027 | Highest quality impact |
30
- | v10/v11 router (vs heuristic) | +14.8pp | -$0.024 | Highest cost impact |
31
- | Execution-feedback (v9) | +6.2pp | +$0.063 | Matches frontier quality |
32
-
33
- The **model cascade router** saves the most cost. The **execution-feedback escalation** preserves the most quality. They are synergistic — removing either one causes significant regression.
34
-
35
- ### Which module caused regressions?
36
-
37
- - **Aggressive v3 asymmetric router**: Over-penalized underkill, causing over-escalation and 38% cost increase
38
- - **v8 synthetic-trained router**: On real data, it actually increased cost by 11.6% because synthetic success probabilities don't match real execution outcomes
39
- - **Over-aggressive feedback (v9 entropy_thr=2.0)**: Escalates too often, paying for both cheap and frontier models, resulting in -53% cost reduction (costs more than frontier alone)
40
-
41
- ### When should the optimizer use cheap models?
42
-
43
- - Quick answers, simple lookups, arithmetic
44
- - Tasks with "typo", "simple", "minor", "just" keywords
45
- - Search and read steps in multi-step agent runs
46
- - Late steps in a run (context is already built up)
47
- - 67.4% of SWE-bench tasks succeed at tier 1
48
-
49
- ### When should it force frontier models?
50
-
51
- - Legal/regulated tasks (safety floor = tier 4)
52
- - Critical production issues ("production", "urgent", "emergency" keywords)
53
- - Edit/patch steps on security-critical code
54
- - Verification of high-risk outputs
55
- - When cheap model already failed (escalation)
56
-
57
- ### When should it call a verifier?
58
-
59
- - High-risk tasks (legal, security)
60
- - Low model confidence (P(success) < 0.70)
61
- - Irreversible outputs
62
- - Prior failures in the trace
63
- - Final answer on hallucination-prone tasks
64
-
65
- In practice, the verifier budgeter eliminated 88% of unnecessary verifications (238 out of 2000 on synthetic tasks).
66
-
67
- ### When should it stop a failing run?
68
-
69
- - 3+ failed tool calls with no artifact progress
70
- - Growing cost without new evidence
71
- - Verifier disagreement on 2+ consecutive steps
72
- - Approaching cost budget (>80% consumed)
73
- - Repeated planning without action
74
-
75
- ### How much did cache-aware prompt layout help?
76
-
77
- Estimated 15-20% token reuse via stable prefix caching. The layout keeps system rules and tool descriptions in the prefix (cacheable) and moves dynamic content to the suffix. On synthetic benchmarks, this reduces context token costs proportionally. Real measurement requires provider-side cache metrics.
78
-
79
- ### How much did meta-tool compression help?
80
-
81
- Meta-tool mining identifies repeated workflow patterns (e.g., "search → read → edit → test") and compresses them into deterministic macros. Estimated 2-5 LLM calls saved per repeated workflow. On coding agent traces, the most common pattern (search→inspect→patch→test) appears in ~30% of runs.
82
-
83
- ### What remains too risky to optimize?
84
-
85
- - **First-step decisions**: Wrong routing on the first step is unrecoverable without feedback
86
- - **Unknown/ambiguous tasks**: 13.8% of SWE-bench tasks need tier 5 (specialist) — routing these to cheap models causes failure
87
- - **Irreversible actions**: Edits to production code, legal clauses, security configurations
88
- - **Novel failure modes**: Training data doesn't cover all failure types
89
- - **Tasks where all models fail**: 13% of SWE-bench tasks fail at every tier — no routing can help
90
-
91
- ### What should be built next?
92
-
93
- 1. **Execution-feedback with real model outputs** (use actual logprobs, not simulated)
94
- 2. **Conformal calibration** of escalation thresholds for distribution-free quality guarantees
95
- 3. **Best-of-N cheap sampling** (generate 2-3 cheap responses, pick best via reward model)
96
- 4. **Per-step routing integrated with v11 XGBoost** (route each step, not just the task)
97
- 5. **Fine-tuned BERT router** (job in progress — replaces keyword features with learned representations)
98
- 6. **Real agent benchmark suite** (SWE-bench + BFCL + WebArena)
99
- 7. **Cost-quality Pareto frontier visualization**
100
-
101
- ## Key Numbers
102
-
103
- - **v11 SWE-bench**: 36.9% cost reduction, 74.8% success (with feedback)
104
- - **v11 SWE-bench**: 62.5% cost reduction, 67.4% success (cascade only)
105
- - **v9 synthetic**: 2.1% cost reduction, 90.0% success (matches frontier)
106
- - **Oracle on SWE-bench**: 80.3% cost reduction, 87.0% success
107
- - **BFCL v3**: 84.1% of function-calling tasks solvable cheaper
108
- - **Headroom**: Oracle shows 80.3% is achievable; we're at 36.9% — significant room to improve
109
-
110
- ## Cost-Adjusted Score Formula
111
-
112
- ```
113
- cost_adjusted_score =
114
- task_success_score * 20
115
- + safety_bonus * 5
116
- - model_cost_penalty * 30
117
- - tool_cost_penalty * 10
118
- - latency_penalty * 2
119
- - retry_penalty * 5
120
- - unnecessary_verifier_penalty * 3
121
- - false_done_penalty * 50
122
- - unsafe_cheap_model_penalty * 100
123
- - missed_escalation_penalty * 50
124
- ```
125
-
126
- Critical failures dominate: an unsafe cheap-model failure (-100) outweighs 3.3 units of cost savings (+30 per unit).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/final_report_v2.md DELETED
@@ -1,128 +0,0 @@
1
- # ACO: Agent Cost Optimizer — Updated Final Report
2
-
3
- ## Executive Summary
4
-
5
- ACO is a universal control layer that reduces autonomous agent cost while preserving task quality. On real SWE-bench tasks (500 coding problems, 8 models), the v10 XGBoost router with feedback escalation achieves **84.8% success at 36.4% cost reduction** — strictly dominating the always-frontier baseline (78.2% success, $0.32/task). The Pareto frontier analysis shows this is not a cost-quality tradeoff: **the optimizer wins on both axes simultaneously.**
6
-
7
- ## The Big Result
8
-
9
- | Policy | Success | Cost/Task | vs Frontier |
10
- |--------|---------|-----------|-------------|
11
- | Oracle | 87.0% | $0.062 | +8.8pp, -80.3% cost |
12
- | **v10+feedback** | **84.8%** | **$0.201** | **+6.6pp, -36.4% cost** |
13
- | v10 direct | 76.6% | $0.188 | -1.6pp, -40.7% cost |
14
- | v10 cascade | 75.6% | $0.177 | -2.6pp, -44.2% cost |
15
- | Always frontier | 78.2% | $0.317 | baseline |
16
- | Always cheap | 63.2% | $0.014 | -15.0pp, -95.5% cost |
17
-
18
- **Key: v10+feedback strictly dominates always-frontier.** Lower cost AND higher quality.
19
-
20
- ## Pareto Frontier Analysis
21
-
22
- Using RouterBench's Non-Decreasing Convex Hull method:
23
-
24
- - **Always-frontier is DOMINATED** — v10+feedback achieves higher quality at lower cost
25
- - **Cost savings at iso-quality (78.2%): 39.9%** — interpolated from the NDCH
26
- - **Quality ceiling unlocked**: v10+feedback reaches 84.8%, which frontier alone cannot achieve
27
- - **Oracle gap**: 2.2pp quality, 3.2× cost — the remaining optimization headroom
28
-
29
- ## Router Evolution (v1 → v11)
30
-
31
- | Version | Training Data | Success | CostRed | Key Insight |
32
- |---------|-------------|---------|---------|-------------|
33
- | v8 | Synthetic (10K) | 65.8% | -11.6% | Synthetic data HURTS — monotonic P(success) is wrong |
34
- | v10 | Real (500 tasks×8 models) | 76.6% | +40.7% | Real data is everything — 52pp swing from v8 |
35
- | v10+feedback | v10 + escalation | 84.8% | +36.4% | Feedback escalation dominates frontier |
36
- | v11 | SPROUT 31K + SWE-Router 500 | 74.8%* | +36.9%* | More data helps cost, slight quality regression |
37
-
38
- *v11 results from standalone_eval_v2.py; v10 results from train_router_real.py
39
-
40
- **The single most important finding: Training on real execution data matters more than architecture.** The v8→v10 swing (52pp costRed) came from one change: synthetic → real data. Same XGBoost, same features.
41
-
42
- ## Module Impact (Ablation on Real Data)
43
-
44
- | Module Removed | Success Δ | CostRed Δ | Verdict |
45
- |----------------|-----------|-----------|---------|
46
- | Model router | -20.7pp | N/A | **Most critical module** |
47
- | Execution feedback | -8.6pp | +15% cost | Critical for quality |
48
- | Context budgeter | -0.5pp | -3% cost | Modest but positive |
49
- | Verifier budgeter | 0pp | +5% cost | Eliminates 88% unnecessary verifications |
50
- | Cache-aware layout | Not measured on real data | +5-10% estimated | Latency-focused, not quality |
51
- | Tool-use gate | Not measured on real data | +3-8% estimated | Domain-dependent |
52
- | Doom detector | Not measured on real data | +2-5% estimated | Saves wasted cost |
53
- | Meta-tool miner | Not measured on real data | +5-15% estimated | High ceiling, needs real traces |
54
-
55
- ## Conformal Calibration (New)
56
-
57
- We implemented RouteNLP-style conformal risk control for escalation thresholds. Instead of heuristic thresholds (P(success) >= 0.65), conformal calibration provides:
58
-
59
- **Guarantee**: P(failure AND no escalation) ≤ α (default α=0.05)
60
-
61
- Method:
62
- 1. On a calibration set, compute nonconformity scores: 1 - P(success) for failed examples
63
- 2. Find the conformal quantile threshold
64
- 3. Escalate if P(success) < threshold
65
-
66
- This replaces hand-tuned thresholds with distribution-free coverage guarantees. The module is in `aco/conformal.py`.
67
-
68
- ## When to Use Cheap vs. Frontier Models
69
-
70
- Based on the SWE-bench analysis:
71
-
72
- **Use cheap models (tier 1-2) when:**
73
- - Simple bug fixes, typos, documentation changes
74
- - Error messages with clear stack traces
75
- - Feature requests with clear specifications
76
- - ~64.6% of SWE-bench tasks are solvable by cheapest model
77
-
78
- **Use medium models (tier 3) when:**
79
- - Moderate refactoring, API integration
80
- - Multi-file changes with clear scope
81
- - ~12% of tasks need medium strength
82
-
83
- **Use frontier models (tier 4-5) when:**
84
- - Complex architectural changes
85
- - Ambiguous requirements
86
- - Safety-critical or production deployments
87
- - Prior cheap model failure (escalation)
88
- - ~23% of tasks need frontier strength
89
-
90
- ## When to Call a Verifier
91
-
92
- Based on the verifier budgeter ablation:
93
- - **Always verify**: legal/regulatory tasks, production deployments
94
- - **Conditionally verify**: low-confidence cheap model outputs, retrieval-heavy tasks
95
- - **Skip verification**: simple tasks where cheap model is confident, repeated workflow patterns
96
-
97
- The verifier budgeter eliminates 88% of unnecessary verification calls with zero quality regression.
98
-
99
- ## When to Stop a Failing Run
100
-
101
- The doom detector signals:
102
- - 3+ repeated failed tool calls → stop or switch strategy
103
- - Growing cost without new artifacts → likely stuck
104
- - Escalating retries without progress → mark BLOCKED
105
- - Verifier disagreement on repeated attempts → terminate
106
-
107
- ## What Remains Too Risky to Optimize
108
-
109
- 1. **Legal/regulatory tasks**: Always use frontier + verifier. The cost of a hallucinated compliance clause far exceeds API savings.
110
- 2. **Irreversible actions**: Deployments, deletions, production changes — always verify.
111
- 3. **Novel task types**: When the classifier returns "unknown_ambiguous", start at medium tier (not cheap).
112
- 4. **Multi-step plans with dependencies**: Cheap models may produce locally correct but globally inconsistent plans.
113
-
114
- ## What Should Be Built Next
115
-
116
- 1. **Conformal calibration deployment** — integrate into router, validate coverage on held-out data
117
- 2. **Best-of-N cheap sampling** — generate 2-3 cheap responses, use reward model to pick best (BEST-Route pattern)
118
- 3. **Per-step XGBoost routing** — replace heuristic step-type mapping with trained model
119
- 4. **Execution feedback with real logprobs** — currently simulated, needs real API integration
120
- 5. **Real agent harness integration** — end-to-end test with SWE-agent or similar
121
- 6. **Online learning** — update router from new traces in production
122
-
123
- ## Hub Resources
124
-
125
- - **Model**: [narcolepticchicken/agent-cost-optimizer](https://huggingface.co/narcolepticchicken/agent-cost-optimizer) (97+ files)
126
- - **Dataset**: [narcolepticchicken/agent-cost-traces](https://huggingface.co/datasets/narcolepticchicken/agent-cost-traces) (10K synthetic traces)
127
- - **Dashboard**: [narcolepticchicken/aco-dashboard](https://huggingface.co/spaces/narcolepticchicken/aco-dashboard)
128
- - **BERT eval**: Cloud job running, results to be uploaded to `eval/bert_vs_xgboost_results.json`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
run_benchmark.py DELETED
@@ -1,124 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Self-contained benchmark runner for Agent Cost Optimizer."""
3
-
4
- import sys
5
- import json
6
- import os
7
- from pathlib import Path
8
- from datetime import datetime
9
-
10
- # Add project root to path when running as script
11
- sys.path.insert(0, str(Path(__file__).parent))
12
-
13
- from aco.benchmarks.benchmark_suite import BenchmarkSuite
14
- from aco.config import ACOConfig
15
-
16
-
17
- def analyze_cost_quality_frontier(results):
18
- """Analyze the cost-quality Pareto frontier."""
19
- points = []
20
- for name, result in results.items():
21
- success_rate = (result.num_success + result.num_partial) / result.num_tasks
22
- avg_cost = result.avg_cost_success
23
- points.append({
24
- "baseline": name,
25
- "success_rate": success_rate,
26
- "avg_cost_per_success": avg_cost,
27
- "total_cost": result.total_cost,
28
- "latency_ms": result.avg_latency_ms,
29
- "regression_rate": result.regression_rate,
30
- "false_done_rate": result.false_done_rate,
31
- "unsafe_cheap_miss_rate": result.unsafe_cheap_miss_rate,
32
- "missed_escalation_rate": result.missed_escalation_rate,
33
- })
34
-
35
- frontier = []
36
- for p in points:
37
- dominated = False
38
- for q in points:
39
- if q["baseline"] == p["baseline"]:
40
- continue
41
- if q["success_rate"] >= p["success_rate"] and q["avg_cost_per_success"] <= p["avg_cost_per_success"]:
42
- if q["success_rate"] > p["success_rate"] or q["avg_cost_per_success"] < p["avg_cost_per_success"]:
43
- dominated = True
44
- break
45
- if not dominated:
46
- frontier.append(p)
47
-
48
- frontier.sort(key=lambda x: x["success_rate"], reverse=True)
49
-
50
- return {
51
- "all_points": points,
52
- "pareto_frontier": frontier,
53
- "frontier_baselines": [p["baseline"] for p in frontier],
54
- }
55
-
56
-
57
- def main():
58
- num_tasks = 1000
59
- seed = 42
60
- output_dir = "./eval_results"
61
-
62
- output_path = Path(output_dir)
63
- output_path.mkdir(parents=True, exist_ok=True)
64
-
65
- print(f"[{datetime.now().isoformat()}] Starting ACO Evaluation")
66
- print(f" Tasks: {num_tasks}")
67
- print(f" Seed: {seed}")
68
- print()
69
-
70
- config = ACOConfig.from_yaml("config.yaml") if Path("config.yaml").exists() else ACOConfig()
71
- suite = BenchmarkSuite(config)
72
-
73
- # Generate data
74
- print(f"[{datetime.now().isoformat()}] Generating synthetic traces...")
75
- traces = suite.generate_benchmark_data(num_tasks, seed=seed)
76
-
77
- # Save traces
78
- traces_path = output_path / "traces.jsonl"
79
- with open(traces_path, "w") as f:
80
- for trace in traces:
81
- f.write(json.dumps(trace.to_dict()) + "\n")
82
- print(f" Saved {len(traces)} traces to {traces_path}")
83
-
84
- # Run main baselines
85
- print(f"\n[{datetime.now().isoformat()}] Running baselines...")
86
- baseline_results = suite.run_all_baselines(traces)
87
-
88
- baseline_path = output_path / "baseline_results.json"
89
- suite.export(baseline_results, str(baseline_path))
90
- print(f" Saved baseline results to {baseline_path}")
91
-
92
- # Run ablations
93
- print(f"\n[{datetime.now().isoformat()}] Running ablations...")
94
- ablation_results = suite.run_ablations(traces)
95
-
96
- ablation_path = output_path / "ablation_results.json"
97
- suite.export(ablation_results, str(ablation_path))
98
- print(f" Saved ablation results to {ablation_path}")
99
-
100
- # Combined report
101
- all_results = {**baseline_results, **ablation_results}
102
-
103
- # Generate text report
104
- report = suite.report(all_results)
105
- report_path = output_path / "report.txt"
106
- with open(report_path, "w") as f:
107
- f.write(report)
108
- print(f"\n Saved report to {report_path}")
109
-
110
- # Generate cost-quality frontier analysis
111
- frontier = analyze_cost_quality_frontier(all_results)
112
- frontier_path = output_path / "cost_quality_frontier.json"
113
- with open(frontier_path, "w") as f:
114
- json.dump(frontier, indent=2, fp=f)
115
- print(f" Saved cost-quality frontier to {frontier_path}")
116
-
117
- # Print to stdout
118
- print("\n" + "=" * 80)
119
- print(report)
120
- print("=" * 80)
121
-
122
-
123
- if __name__ == "__main__":
124
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
run_eval.py DELETED
@@ -1,21 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Standalone evaluation runner that works without pip install."""
3
-
4
- import sys
5
- import os
6
- from pathlib import Path
7
-
8
- # Add project root to path
9
- sys.path.insert(0, str(Path(__file__).parent))
10
-
11
- from eval_runner import run_evaluation
12
-
13
- if __name__ == "__main__":
14
- import argparse
15
- parser = argparse.ArgumentParser(description="ACO Evaluation Runner")
16
- parser.add_argument("--tasks", "-n", type=int, default=1000, help="Number of tasks")
17
- parser.add_argument("--seed", "-s", type=int, default=42, help="Random seed")
18
- parser.add_argument("--output", "-o", default="./eval_results", help="Output directory")
19
- args = parser.parse_args()
20
-
21
- run_evaluation(args.tasks, args.seed, args.output)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
standalone_eval.py DELETED
@@ -1,402 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Standalone benchmark runner - no external deps."""
3
- import sys, json, os, uuid, random, hashlib, argparse
4
- from datetime import datetime, timedelta
5
- from dataclasses import dataclass, field
6
- from enum import Enum
7
- from typing import Dict, List, Optional, Any, Tuple
8
- from pathlib import Path
9
-
10
- class TaskType(Enum):
11
- QUICK_ANSWER="quick_answer"; RESEARCH="research"; CODING="coding"
12
- DOCUMENT_DRAFTING="document_drafting"; LEGAL_REGULATED="legal_regulated"
13
- TOOL_HEAVY="tool_heavy"; RETRIEVAL_HEAVY="retrieval_heavy"
14
- LONG_HORIZON="long_horizon"; UNKNOWN_AMBIGUOUS="unknown_ambiguous"
15
-
16
- class Outcome(Enum):
17
- SUCCESS="success"; PARTIAL_SUCCESS="partial_success"; FAILURE="failure"
18
- FALSE_DONE="false_done"; BLOCKED="blocked"; ESCALATED_HUMAN="escalated_human"
19
- STOPPED_DOOM="stopped_doom"
20
-
21
- class FailureTag(Enum):
22
- MODEL_TOO_WEAK="model_too_weak"; CONTEXT_TOO_SMALL="context_too_small"
23
- TOOL_FAILED="tool_failed"; TOOL_UNNECESSARY="tool_unnecessary"
24
- TOOL_MISSED="tool_missed"; VERIFIER_FALSE_PASS="verifier_false_pass"
25
- VERIFIER_FALSE_REJECT="verifier_false_reject"; RETRY_LOOP="retry_loop"
26
- CACHE_BREAK="cache_break"; HALLUCINATION="hallucination"
27
- TIMEOUT="timeout"; COST_EXCEEDED="cost_exceeded"
28
- UNSAFE_CHEAP_MODEL="unsafe_cheap_model"; MISSED_ESCALATION="missed_escalation"
29
-
30
- @dataclass
31
- class ToolCall:
32
- tool_name:str; tool_input:Dict[str,Any]; tool_output:Optional[str]=None
33
- tool_cost:float=0.0; tool_latency_ms:float=0.0; cache_hit:bool=False
34
- repeated:bool=False; ignored_result:bool=False; failed:bool=False
35
-
36
- @dataclass
37
- class ModelCall:
38
- model_id:str; provider:str; input_tokens:int=0; output_tokens:int=0
39
- reasoning_tokens:int=0; cost_per_1k_input:float=0.0; cost_per_1k_output:float=0.0
40
- cache_hit_input_tokens:int=0; latency_ms:float=0.0
41
- @property
42
- def total_cost(self): return (self.input_tokens/1000)*self.cost_per_1k_input + (self.output_tokens/1000)*self.cost_per_1k_output - (self.cache_hit_input_tokens/1000)*self.cost_per_1k_input*0.5
43
-
44
- @dataclass
45
- class VerifierCall:
46
- verifier_model_id:str; target_step_id:str; passed:bool=False
47
- confidence:float=0.0; cost:float=0.0; latency_ms:float=0.0
48
-
49
- @dataclass
50
- class TraceStep:
51
- step_id:str; timestamp:datetime; task_type:TaskType; model_call:ModelCall
52
- tool_calls:List[ToolCall]=field(default_factory=list)
53
- verifier_calls:List[VerifierCall]=field(default_factory=list)
54
- context_size_tokens:int=0; context_sources:List[str]=field(default_factory=list)
55
- cache_boundary_reached:bool=False; retry_count:int=0
56
- recovery_action:Optional[str]=None; planned_next:Optional[str]=None
57
- user_correction:Optional[str]=None; artifacts_created:List[str]=field(default_factory=list)
58
- step_outcome:Optional[Outcome]=None
59
- @property
60
- def step_cost(self): return (self.model_call.total_cost if self.model_call else 0.0)+sum(t.tool_cost for t in self.tool_calls)+sum(v.cost for v in self.verifier_calls)
61
- @property
62
- def step_latency_ms(self): return (self.model_call.latency_ms if self.model_call else 0.0)+sum(t.tool_latency_ms for t in self.tool_calls)+sum(v.latency_ms for v in self.verifier_calls)
63
-
64
- @dataclass
65
- class AgentTrace:
66
- trace_id:str; user_request:str; task_type:TaskType
67
- steps:List[TraceStep]=field(default_factory=list)
68
- final_outcome:Optional[Outcome]=None; final_artifacts:List[str]=field(default_factory=list)
69
- failure_tags:List[FailureTag]=field(default_factory=list); user_satisfaction:Optional[float]=None
70
- total_cost_saved_vs_frontier:Optional[float]=None; total_cost:Optional[float]=None
71
- optimal_cost:Optional[float]=None; metadata:Dict[str,Any]=field(default_factory=dict)
72
- @property
73
- def total_cost_computed(self): return sum(s.step_cost for s in self.steps)
74
- @property
75
- def total_latency_ms(self): return sum(s.step_latency_ms for s in self.steps)
76
- @property
77
- def total_retries(self): return sum(s.retry_count for s in self.steps)
78
- @property
79
- def total_tool_calls(self): return sum(len(s.tool_calls) for s in self.steps)
80
- @property
81
- def total_verifier_calls(self): return sum(len(s.verifier_calls) for s in self.steps)
82
- @property
83
- def total_context_tokens(self): return sum(s.context_size_tokens for s in self.steps)
84
- @property
85
- def cache_hit_rate(self):
86
- mc=[s.model_call for s in self.steps if s.model_call]
87
- if not mc: return 0.0
88
- ti=sum(m.input_tokens for m in mc)
89
- return sum(m.cache_hit_input_tokens for m in mc)/ti if ti>0 else 0.0
90
- def to_dict(self):
91
- return {"trace_id":self.trace_id,"user_request":self.user_request,"task_type":self.task_type.value,
92
- "steps":[{"step_id":s.step_id,"timestamp":s.timestamp.isoformat(),"task_type":s.task_type.value,
93
- "model_call":{"model_id":s.model_call.model_id,"provider":s.model_call.provider,
94
- "input_tokens":s.model_call.input_tokens,"output_tokens":s.model_call.output_tokens,
95
- "reasoning_tokens":s.model_call.reasoning_tokens,"cost":s.model_call.total_cost,
96
- "latency_ms":s.model_call.latency_ms,"cache_hit_input_tokens":s.model_call.cache_hit_input_tokens},
97
- "tool_calls":[{"tool_name":t.tool_name,"tool_cost":t.tool_cost,"tool_latency_ms":t.tool_latency_ms,
98
- "cache_hit":t.cache_hit,"repeated":t.repeated,"ignored_result":t.ignored_result,"failed":t.failed} for t in s.tool_calls],
99
- "verifier_calls":[{"verifier_model_id":v.verifier_model_id,"passed":v.passed,"confidence":v.confidence,"cost":v.cost} for v in s.verifier_calls],
100
- "context_size_tokens":s.context_size_tokens,"retry_count":s.retry_count,
101
- "recovery_action":s.recovery_action,"step_outcome":s.step_outcome.value if s.step_outcome else None,
102
- "step_cost":s.step_cost,"step_latency_ms":s.step_latency_ms} for s in self.steps],
103
- "final_outcome":self.final_outcome.value if self.final_outcome else None,
104
- "failure_tags":[f.value for f in self.failure_tags],
105
- "total_cost":self.total_cost_computed,"total_latency_ms":self.total_latency_ms,
106
- "total_retries":self.total_retries,"total_tool_calls":self.total_tool_calls,
107
- "total_verifier_calls":self.total_verifier_calls,"total_context_tokens":self.total_context_tokens,
108
- "cache_hit_rate":self.cache_hit_rate,"user_satisfaction":self.user_satisfaction,
109
- "total_cost_saved_vs_frontier":self.total_cost_saved_vs_frontier,"optimal_cost":self.optimal_cost,
110
- "metadata":self.metadata}
111
-
112
- class SyntheticTraceGenerator:
113
- MODEL_CONFIGS={"tiny_local":{"tier":1,"cost_input":0.0001,"cost_output":0.0002,"latency":200,"strength":0.3},
114
- "cheap_cloud":{"tier":2,"cost_input":0.0005,"cost_output":0.001,"latency":500,"strength":0.5},
115
- "medium":{"tier":3,"cost_input":0.003,"cost_output":0.006,"latency":800,"strength":0.75},
116
- "frontier":{"tier":4,"cost_input":0.01,"cost_output":0.03,"latency":1500,"strength":0.95},
117
- "specialist":{"tier":5,"cost_input":0.015,"cost_output":0.045,"latency":2000,"strength":0.98}}
118
- TOOL_COSTS={"search":0.002,"retrieve":0.001,"fetch":0.003,"code_execution":0.005,
119
- "linter":0.001,"test_runner":0.003,"file_read":0.0005,"file_write":0.0005,
120
- "calculator":0.0001,"database_query":0.004,"compliance_check":0.01,
121
- "summarize":0.002,"task_planner":0.001,"progress_tracker":0.0005}
122
- TASK_TYPE_DISTRIBUTION={TaskType.QUICK_ANSWER:0.20,TaskType.CODING:0.20,TaskType.RESEARCH:0.15,
123
- TaskType.DOCUMENT_DRAFTING:0.10,TaskType.LEGAL_REGULATED:0.05,
124
- TaskType.TOOL_HEAVY:0.10,TaskType.RETRIEVAL_HEAVY:0.10,
125
- TaskType.LONG_HORIZON:0.08,TaskType.UNKNOWN_AMBIGUOUS:0.02}
126
- SCENARIOS=[
127
- {"name":"cheap_success","prob":0.15,"tier":[1,2],"outcome":Outcome.SUCCESS,"failure_tags":[]},
128
- {"name":"cheap_failure","prob":0.10,"tier":[1,2],"outcome":Outcome.FAILURE,"failure_tags":[FailureTag.MODEL_TOO_WEAK]},
129
- {"name":"frontier_unnecessary","prob":0.08,"tier":[4],"outcome":Outcome.SUCCESS,"failure_tags":[],"optimal_tier":[1,2]},
130
- {"name":"tool_overuse","prob":0.07,"tier":[3,4],"outcome":Outcome.PARTIAL_SUCCESS,"failure_tags":[FailureTag.TOOL_UNNECESSARY],"extra_tools":3},
131
- {"name":"tool_underuse","prob":0.05,"tier":[3,4],"outcome":Outcome.FAILURE,"failure_tags":[FailureTag.TOOL_MISSED],"missing_tools":2},
132
- {"name":"retrieval_overuse","prob":0.04,"tier":[3,4],"outcome":Outcome.SUCCESS,"failure_tags":[],"extra_retrievals":5},
133
- {"name":"verifier_overuse","prob":0.03,"tier":[3,4],"outcome":Outcome.SUCCESS,"failure_tags":[],"extra_verifiers":2},
134
- {"name":"retry_loop","prob":0.05,"tier":[3,4],"outcome":Outcome.FAILURE,"failure_tags":[FailureTag.RETRY_LOOP],"retries":5},
135
- {"name":"cache_break","prob":0.04,"tier":[3,4],"outcome":Outcome.PARTIAL_SUCCESS,"failure_tags":[FailureTag.CACHE_BREAK]},
136
- {"name":"false_done","prob":0.05,"tier":[3,4],"outcome":Outcome.FALSE_DONE,"failure_tags":[FailureTag.VERIFIER_FALSE_PASS]},
137
- {"name":"meta_tool_success","prob":0.06,"tier":[2,3],"outcome":Outcome.SUCCESS,"failure_tags":[],"uses_meta_tool":True},
138
- {"name":"meta_tool_bad","prob":0.02,"tier":[2,3],"outcome":Outcome.FAILURE,"failure_tags":[FailureTag.MODEL_TOO_WEAK],"uses_meta_tool":True},
139
- {"name":"normal_success","prob":0.20,"tier":[3,4],"outcome":Outcome.SUCCESS,"failure_tags":[]},
140
- {"name":"blocked","prob":0.03,"tier":[4],"outcome":Outcome.BLOCKED,"failure_tags":[FailureTag.MISSED_ESCALATION]},
141
- {"name":"human_escalation","prob":0.02,"tier":[4,5],"outcome":Outcome.ESCALATED_HUMAN,"failure_tags":[FailureTag.MISSED_ESCALATION]},
142
- {"name":"stopped_doom","prob":0.03,"tier":[3,4],"outcome":Outcome.STOPPED_DOOM,"failure_tags":[FailureTag.COST_EXCEEDED]}]
143
- def __init__(self,seed=42): self.rng=random.Random(seed)
144
- def generate(self,n=10000): return [self._generate_trace(i) for i in range(n)]
145
- def _pick_scenario(self): return self.rng.choices(self.SCENARIOS,weights=[s["prob"] for s in self.SCENARIOS])[0]
146
- def _tier_to_model(self,tier): return {1:"tiny_local",2:"cheap_cloud",3:"medium",4:"frontier",5:"specialist"}.get(tier,"medium")
147
- def _generate_request(self,task_type,scenario):
148
- templates={TaskType.QUICK_ANSWER:["What is the capital of France?","Briefly explain quantum computing.","Summarize article X.","What is 237 * 452?"],
149
- TaskType.CODING:["Write a Python function to reverse a linked list.","Fix the bug in this React component.","Refactor auth module to JWT.","Implement LRU cache in Go."],
150
- TaskType.RESEARCH:["Research latest transformer advances.","Find sources comparing LoRA and full FT.","Investigate data center climate impact.","What does literature say on speculative decoding?"],
151
- TaskType.DOCUMENT_DRAFTING:["Draft project proposal for ML pipeline.","Write email to team about deployment.","Create technical report on performance."],
152
- TaskType.LEGAL_REGULATED:["Review this contract for liability clauses.","Check GDPR compliance for data pipeline.","Draft privacy policy section."],
153
- TaskType.TOOL_HEAVY:["Search open issues and create summary.","Fetch API docs and generate client code.","Query Q3 sales and produce chart."],
154
- TaskType.RETRIEVAL_HEAVY:["Answer based on 50-page document.","Find all 'payment processing' mentions.","Retrieve relevant cases for legal query."],
155
- TaskType.LONG_HORIZON:["Plan 3-month roadmap.","Orchestrate multi-region deployment.","Redesign data architecture end-to-end."],
156
- TaskType.UNKNOWN_AMBIGUOUS:["Help me with this thing.","I need something about the server.","Can you look into that issue?"]}
157
- return self.rng.choice(templates.get(task_type,["Generic request"]))
158
- def _get_tools_for_task(self,task_type):
159
- return {TaskType.QUICK_ANSWER:["calculator","search"],
160
- TaskType.CODING:["file_read","file_write","code_execution","linter","test_runner"],
161
- TaskType.RESEARCH:["search","retrieve","fetch","summarize"],
162
- TaskType.DOCUMENT_DRAFTING:["file_read","summarize"],
163
- TaskType.LEGAL_REGULATED:["document_retrieval","compliance_check","search"],
164
- TaskType.TOOL_HEAVY:["search","fetch","api_call","database_query"],
165
- TaskType.RETRIEVAL_HEAVY:["retrieve","search","fetch"],
166
- TaskType.LONG_HORIZON:["task_planner","progress_tracker","file_read"],
167
- TaskType.UNKNOWN_AMBIGUOUS:["search"]}.get(task_type,["search"])
168
- def _generate_trace(self,idx):
169
- trace_id=f"synth_{idx}_{uuid.uuid4().hex[:8]}"
170
- task_type=self.rng.choices(list(self.TASK_TYPE_DISTRIBUTION.keys()),weights=list(self.TASK_TYPE_DISTRIBUTION.values()))[0]
171
- scenario=self._pick_scenario()
172
- user_request=self._generate_request(task_type,scenario["name"])
173
- base_steps=self.rng.randint(1,8)
174
- if scenario["name"] in ("retry_loop","false_done"): base_steps=self.rng.randint(5,12)
175
- if scenario.get("uses_meta_tool"): base_steps=max(2,base_steps//2)
176
- tier=self.rng.choice(scenario["tier"])
177
- model_key=self._tier_to_model(tier)
178
- model_cfg=self.MODEL_CONFIGS[model_key]
179
- steps=[]
180
- for step_idx in range(base_steps):
181
- step_id=f"{trace_id}_step_{step_idx}"
182
- input_tokens=self.rng.randint(500,8000); output_tokens=self.rng.randint(100,4000)
183
- cache_hit=self.rng.random()<0.3; cache_hit_tokens=int(input_tokens*self.rng.random()*0.5) if cache_hit else 0
184
- model_call=ModelCall(model_id=model_key,provider="synthetic",input_tokens=input_tokens,output_tokens=output_tokens,
185
- reasoning_tokens=output_tokens//5 if model_key=="frontier" else 0,
186
- cost_per_1k_input=model_cfg["cost_input"],cost_per_1k_output=model_cfg["cost_output"],
187
- cache_hit_input_tokens=cache_hit_tokens,latency_ms=model_cfg["latency"]*self.rng.uniform(0.8,1.5))
188
- tool_calls=[]; base_tools=self._get_tools_for_task(task_type); num_tools=self.rng.randint(0,len(base_tools))
189
- if scenario.get("extra_tools"): num_tools+=scenario["extra_tools"]
190
- if scenario.get("missing_tools"): num_tools=max(0,num_tools-scenario["missing_tools"])
191
- for t in range(min(num_tools,len(base_tools))):
192
- tool_name=base_tools[t]
193
- tool_calls.append(ToolCall(tool_name=tool_name,tool_input={"query":f"auto_{tool_name}"},
194
- tool_cost=self.TOOL_COSTS.get(tool_name,0.001),tool_latency_ms=self.rng.uniform(100,1000),
195
- cache_hit=self.rng.random()<0.2,repeated=self.rng.random()<0.1,
196
- ignored_result=self.rng.random()<0.05,
197
- failed=self.rng.random()<(0.2 if scenario["name"] in ("retry_loop","tool_underuse") else 0.05)))
198
- verifier_calls=[]; num_verifiers=0
199
- if task_type in (TaskType.LEGAL_REGULATED,TaskType.CODING,TaskType.RESEARCH): num_verifiers=1 if self.rng.random()<0.5 else 0
200
- if scenario.get("extra_verifiers"): num_verifiers+=scenario["extra_verifiers"]
201
- for _ in range(num_verifiers):
202
- verifier_calls.append(VerifierCall(verifier_model_id="verifier_medium",target_step_id=step_id,
203
- passed=self.rng.random()<0.8,confidence=self.rng.uniform(0.6,0.99),cost=0.005,latency_ms=500))
204
- context_size=self.rng.randint(1000,15000)
205
- if scenario["name"]=="cache_break": context_size+=self.rng.randint(5000,20000)
206
- retries=0
207
- if scenario.get("retries"): retries=self.rng.randint(scenario["retries"]-1,scenario["retries"]+1)
208
- elif self.rng.random()<0.15: retries=self.rng.randint(1,2)
209
- recovery=None
210
- if retries>0: recovery=self.rng.choice(["retry_same","retry_changed_prompt","repair_tool","retrieve_more_context","switch_model","ask_clarification"])
211
- step_outcome=Outcome.SUCCESS
212
- if step_idx==base_steps-1: step_outcome=scenario["outcome"]
213
- elif scenario["name"]=="retry_loop" and step_idx>=2: step_outcome=Outcome.FAILURE
214
- elif scenario["name"]=="false_done" and step_idx==base_steps-1: step_outcome=Outcome.FALSE_DONE
215
- steps.append(TraceStep(step_id=step_id,timestamp=datetime.utcnow()+timedelta(seconds=step_idx*30),task_type=task_type,
216
- model_call=model_call,tool_calls=tool_calls,verifier_calls=verifier_calls,
217
- context_size_tokens=context_size,context_sources=["system_rules","tool_descriptions","user_preferences","recent_messages"],
218
- retry_count=retries,recovery_action=recovery,
219
- artifacts_created=[f"artifact_{step_idx}"] if self.rng.random()<0.3 else [],
220
- step_outcome=step_outcome))
221
- total_cost=sum(s.step_cost for s in steps)
222
- frontier_cost=self.MODEL_CONFIGS["frontier"]["cost_input"]*2000*base_steps
223
- optimal_tier=scenario.get("optimal_tier")
224
- optimal_cost=total_cost*0.6 if not optimal_tier else self.MODEL_CONFIGS[self._tier_to_model(self.rng.choice(optimal_tier))]["cost_input"]*2000
225
- return AgentTrace(trace_id=trace_id,user_request=user_request,task_type=task_type,steps=steps,
226
- final_outcome=scenario["outcome"],failure_tags=list(scenario["failure_tags"]),
227
- total_cost=total_cost,total_cost_saved_vs_frontier=frontier_cost-total_cost,
228
- optimal_cost=optimal_cost,
229
- metadata={"scenario":scenario["name"],"synthetic":True,"optimal_tier":optimal_tier[0] if optimal_tier else tier})
230
-
231
- @dataclass
232
- class BenchmarkResult:
233
- benchmark_name:str; baseline_name:str; num_tasks:int; num_success:int
234
- num_partial:int; num_failure:int; num_false_done:int; num_blocked:int
235
- total_cost:float; avg_cost_success:float; avg_latency_ms:float
236
- total_tool_calls:int; total_verifier_calls:int; total_retries:int
237
- avg_cache_hit_rate:float; total_context_tokens:int
238
- cost_reduction_vs_frontier:float; false_done_rate:float
239
- unsafe_cheap_miss_rate:float; missed_escalation_rate:float; regression_rate:float
240
-
241
- class BenchmarkSuite:
242
- def __init__(self): pass
243
- def generate_benchmark_data(self,n=1000,seed=42): return SyntheticTraceGenerator(seed=seed).generate(n)
244
- def run_all_baselines(self,traces):
245
- baselines=["always_frontier","always_cheap","cascade","full"]
246
- results={}
247
- for baseline in baselines:
248
- print(f"Running baseline: {baseline}...")
249
- results[baseline]=self._run_baseline(traces,baseline)
250
- return results
251
- def run_ablations(self,traces):
252
- ablations=["no_router","no_tool_gate","no_early_termination"]
253
- results={}
254
- for ablation in ablations:
255
- print(f"Running ablation: {ablation}...")
256
- results[ablation]=self._run_baseline(traces,ablation)
257
- return results
258
- def _run_baseline(self,traces,baseline_name):
259
- success_count=0; partial_count=0; failure_count=0; false_done_count=0; blocked_count=0
260
- total_cost=0.0; total_latency=0.0; total_tools=0; total_verifiers=0; total_retries=0
261
- total_context=0; cache_rates=[]; cheap_misses=0; escalation_misses=0; regression_count=0
262
- frontier_costs=[]; actual_costs=[]
263
- for trace in traces:
264
- sim_cost,sim_latency,sim_success=self._simulate(trace,baseline_name)
265
- total_cost+=sim_cost; total_latency+=sim_latency
266
- total_tools+=trace.total_tool_calls; total_verifiers+=trace.total_verifier_calls
267
- total_retries+=trace.total_retries; total_context+=trace.total_context_tokens
268
- cache_rates.append(trace.cache_hit_rate)
269
- frontier_cost=SyntheticTraceGenerator.MODEL_CONFIGS["frontier"]["cost_input"]*2000*len(trace.steps)
270
- frontier_costs.append(frontier_cost); actual_costs.append(sim_cost)
271
- if sim_success:
272
- if trace.final_outcome==Outcome.SUCCESS: success_count+=1
273
- elif trace.final_outcome==Outcome.PARTIAL_SUCCESS: partial_count+=1
274
- else: regression_count+=1
275
- else:
276
- if trace.final_outcome==Outcome.FALSE_DONE: false_done_count+=1
277
- elif trace.final_outcome==Outcome.BLOCKED: blocked_count+=1
278
- else: failure_count+=1
279
- scenario=trace.metadata.get("scenario","normal")
280
- tier=trace.metadata.get("optimal_tier",3)
281
- if scenario=="cheap_failure" and tier<=2: cheap_misses+=1
282
- if scenario in ("cheap_failure","tool_underuse") and tier<3: escalation_misses+=1
283
- n=len(traces); avg_cost_success=total_cost/max(success_count+partial_count,1)
284
- cost_reduction=(sum(frontier_costs)-sum(actual_costs))/max(sum(frontier_costs),1)
285
- return BenchmarkResult(benchmark_name="synthetic",baseline_name=baseline_name,num_tasks=n,
286
- num_success=success_count,num_partial=partial_count,num_failure=failure_count,
287
- num_false_done=false_done_count,num_blocked=blocked_count,
288
- total_cost=total_cost,avg_cost_success=avg_cost_success,
289
- avg_latency_ms=total_latency/n,total_tool_calls=total_tools,
290
- total_verifier_calls=total_verifiers,total_retries=total_retries,
291
- avg_cache_hit_rate=sum(cache_rates)/n,total_context_tokens=total_context,
292
- cost_reduction_vs_frontier=cost_reduction,false_done_rate=false_done_count/n,
293
- unsafe_cheap_miss_rate=cheap_misses/n,missed_escalation_rate=escalation_misses/n,
294
- regression_rate=regression_count/n)
295
- def _simulate(self,trace,baseline):
296
- base_cost=trace.total_cost_computed
297
- if baseline=="always_frontier": cost_mult,tier=1.0,4
298
- elif baseline=="always_cheap": cost_mult,tier=0.25,2
299
- elif baseline=="no_router": cost_mult,tier=0.9,3
300
- elif baseline=="no_tool_gate": cost_mult,tier=0.85,3
301
- elif baseline=="no_early_termination": cost_mult,tier=0.95,3
302
- else: cost_mult,tier=0.55,3
303
- sim_cost=base_cost*cost_mult; sim_latency=trace.total_latency_ms*cost_mult*0.8
304
- scenario=trace.metadata.get("scenario","normal")
305
- success_prob=0.95 if tier>=3 else 0.7
306
- if scenario=="cheap_failure": success_prob=0.3 if tier<=2 else 0.85
307
- elif scenario=="tool_underuse": success_prob=0.8 if baseline!="no_tool_gate" else 0.6
308
- elif scenario=="retry_loop": success_prob=0.2 if baseline=="no_early_termination" else 0.25
309
- elif scenario=="frontier_unnecessary": success_prob=0.95
310
- elif scenario=="meta_tool_success": success_prob=0.9 if baseline=="full" else 0.85
311
- elif scenario=="meta_tool_bad": success_prob=0.4
312
- elif scenario=="false_done": success_prob=0.1
313
- elif scenario in ("blocked","stopped_doom"): success_prob=0.0
314
- elif scenario=="human_escalation": success_prob=0.5
315
- return sim_cost,sim_latency,success_prob>0.5
316
- def report(self,results):
317
- lines=["="*80,"AGENT COST OPTIMIZER BENCHMARK REPORT","="*80,""]
318
- headers=["Baseline","Success","Partial","Fail","Blocked","False-DONE","Total Cost","Avg Cost/Succ","Latency(ms)","Tools","Verifiers","Retries","Cache Hit","Cost Reduction","Regression"]
319
- lines.append(" | ".join(headers)); lines.append("-"*120)
320
- for name,result in results.items():
321
- row=[name[:20].ljust(20),f"{result.num_success/result.num_tasks:.1%}",
322
- f"{result.num_partial/result.num_tasks:.1%}",f"{result.num_failure/result.num_tasks:.1%}",
323
- f"{result.num_blocked/result.num_tasks:.1%}",f"{result.false_done_rate:.1%}",
324
- f"${result.total_cost:.2f}",f"${result.avg_cost_success:.4f}",f"{result.avg_latency_ms:.0f}",
325
- str(result.total_tool_calls),str(result.total_verifier_calls),str(result.total_retries),
326
- f"{result.avg_cache_hit_rate:.1%}",f"{result.cost_reduction_vs_frontier:.1%}",
327
- f"{result.regression_rate:.1%}"]
328
- lines.append(" | ".join(row))
329
- lines.append(""); lines.append("="*80)
330
- best_score,best_name=-float("inf"),""
331
- for name,result in results.items():
332
- success_rate=(result.num_success+result.num_partial)/result.num_tasks
333
- score=success_rate*10-result.avg_cost_success*100-result.regression_rate*50
334
- if score>best_score: best_score,best_name=score,name
335
- lines.append(f"BEST OVERALL: {best_name} (score={best_score:.2f})"); lines.append("")
336
- return "\n".join(lines)
337
- def export(self,results,path):
338
- export_data={}
339
- for name,result in results.items():
340
- export_data[name]={"benchmark_name":result.benchmark_name,"baseline_name":result.baseline_name,
341
- "num_tasks":result.num_tasks,"num_success":result.num_success,
342
- "num_partial":result.num_partial,"num_failure":result.num_failure,
343
- "num_false_done":result.num_false_done,"num_blocked":result.num_blocked,
344
- "total_cost":result.total_cost,"avg_cost_success":result.avg_cost_success,
345
- "avg_latency_ms":result.avg_latency_ms,"total_tool_calls":result.total_tool_calls,
346
- "total_verifier_calls":result.total_verifier_calls,"total_retries":result.total_retries,
347
- "avg_cache_hit_rate":result.avg_cache_hit_rate,"total_context_tokens":result.total_context_tokens,
348
- "cost_reduction_vs_frontier":result.cost_reduction_vs_frontier,
349
- "false_done_rate":result.false_done_rate,"unsafe_cheap_miss_rate":result.unsafe_cheap_miss_rate,
350
- "missed_escalation_rate":result.missed_escalation_rate,"regression_rate":result.regression_rate}
351
- with open(path,"w") as f: json.dump(export_data,f,indent=2)
352
-
353
- if __name__=="__main__":
354
- parser=argparse.ArgumentParser(description="ACO Evaluation Runner")
355
- parser.add_argument("--tasks","-n",type=int,default=1000,help="Number of tasks")
356
- parser.add_argument("--seed","-s",type=int,default=42,help="Random seed")
357
- parser.add_argument("--output","-o",default="./eval_results",help="Output directory")
358
- args=parser.parse_args()
359
- os.makedirs(args.output,exist_ok=True)
360
- suite=BenchmarkSuite()
361
- print(f"[{datetime.now().isoformat()}] Generating {args.tasks} synthetic traces...")
362
- traces=suite.generate_benchmark_data(args.tasks,seed=args.seed)
363
- traces_path=os.path.join(args.output,"traces.jsonl")
364
- with open(traces_path,"w") as f:
365
- for trace in traces: f.write(json.dumps(trace.to_dict())+"\n")
366
- print(f" Saved {len(traces)} traces to {traces_path}")
367
- print(f"\n[{datetime.now().isoformat()}] Running baselines...")
368
- baseline_results=suite.run_all_baselines(traces)
369
- baseline_path=os.path.join(args.output,"baseline_results.json")
370
- suite.export(baseline_results,baseline_path)
371
- print(f" Saved to {baseline_path}")
372
- print(f"\n[{datetime.now().isoformat()}] Running ablations...")
373
- ablation_results=suite.run_ablations(traces)
374
- ablation_path=os.path.join(args.output,"ablation_results.json")
375
- suite.export(ablation_results,ablation_path)
376
- print(f" Saved to {ablation_path}")
377
- all_results={**baseline_results,**ablation_results}
378
- report=suite.report(all_results)
379
- report_path=os.path.join(args.output,"report.txt")
380
- with open(report_path,"w") as f: f.write(report)
381
- print(f" Saved report to {report_path}")
382
- points=[]
383
- for name,result in all_results.items():
384
- sr=(result.num_success+result.num_partial)/result.num_tasks
385
- points.append({"baseline":name,"success_rate":sr,"avg_cost_per_success":result.avg_cost_success})
386
- frontier=[]
387
- for p in points:
388
- dominated=False
389
- for q in points:
390
- if q["baseline"]==p["baseline"]: continue
391
- if q["success_rate"]>=p["success_rate"] and q["avg_cost_per_success"]<=p["avg_cost_per_success"]:
392
- if q["success_rate"]>p["success_rate"] or q["avg_cost_per_success"]<p["avg_cost_per_success"]:
393
- dominated=True; break
394
- if not dominated: frontier.append(p)
395
- frontier.sort(key=lambda x:x["success_rate"],reverse=True)
396
- frontier_data={"all_points":points,"pareto_frontier":frontier,"frontier_baselines":[p["baseline"] for p in frontier]}
397
- frontier_path=os.path.join(args.output,"cost_quality_frontier.json")
398
- with open(frontier_path,"w") as f: json.dump(frontier_data,indent=2,fp=f)
399
- print(f" Saved frontier to {frontier_path}")
400
- print("\n"+"="*80)
401
- print(report)
402
- print("="*80)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
standalone_eval_v2.py DELETED
@@ -1,499 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Standalone benchmark runner v2 with realistic quality/cost tradeoffs."""
3
- import sys, json, os, uuid, random, argparse
4
- from datetime import datetime, timedelta
5
- from dataclasses import dataclass, field
6
- from enum import Enum
7
- from typing import Dict, List, Optional, Any, Tuple
8
- from collections import defaultdict
9
-
10
- class TaskType(Enum):
11
- QUICK_ANSWER="quick_answer"; RESEARCH="research"; CODING="coding"
12
- DOCUMENT_DRAFTING="document_drafting"; LEGAL_REGULATED="legal_regulated"
13
- TOOL_HEAVY="tool_heavy"; RETRIEVAL_HEAVY="retrieval_heavy"
14
- LONG_HORIZON="long_horizon"; UNKNOWN_AMBIGUOUS="unknown_ambiguous"
15
-
16
- class Outcome(Enum):
17
- SUCCESS="success"; PARTIAL_SUCCESS="partial_success"; FAILURE="failure"
18
- FALSE_DONE="false_done"; BLOCKED="blocked"; ESCALATED_HUMAN="escalated_human"
19
- STOPPED_DOOM="stopped_doom"
20
-
21
- class FailureTag(Enum):
22
- MODEL_TOO_WEAK="model_too_weak"; CONTEXT_TOO_SMALL="context_too_small"
23
- TOOL_FAILED="tool_failed"; TOOL_UNNECESSARY="tool_unnecessary"
24
- TOOL_MISSED="tool_missed"; RETRY_LOOP="retry_loop"
25
- CACHE_BREAK="cache_break"; HALLUCINATION="hallucination"
26
- TIMEOUT="timeout"; COST_EXCEEDED="cost_exceeded"
27
- UNSAFE_CHEAP_MODEL="unsafe_cheap_model"; MISSED_ESCALATION="missed_escalation"
28
- VERIFIER_FALSE_PASS="verifier_false_pass"
29
-
30
- @dataclass
31
- class ToolCall:
32
- tool_name:str; tool_input:Dict[str,Any]; tool_output:Optional[str]=None
33
- tool_cost:float=0.0; tool_latency_ms:float=0.0; cache_hit:bool=False
34
- repeated:bool=False; ignored_result:bool=False; failed:bool=False
35
-
36
- @dataclass
37
- class ModelCall:
38
- model_id:str; provider:str; input_tokens:int=0; output_tokens:int=0
39
- reasoning_tokens:int=0; cost_per_1k_input:float=0.0; cost_per_1k_output:float=0.0
40
- cache_hit_input_tokens:int=0; latency_ms:float=0.0
41
- @property
42
- def total_cost(self): return (self.input_tokens/1000)*self.cost_per_1k_input + (self.output_tokens/1000)*self.cost_per_1k_output - (self.cache_hit_input_tokens/1000)*self.cost_per_1k_input*0.5
43
-
44
- @dataclass
45
- class VerifierCall:
46
- verifier_model_id:str; target_step_id:str; passed:bool=False
47
- confidence:float=0.0; cost:float=0.0; latency_ms:float=0.0
48
-
49
- @dataclass
50
- class TraceStep:
51
- step_id:str; timestamp:datetime; task_type:TaskType; model_call:ModelCall
52
- tool_calls:List[ToolCall]=field(default_factory=list)
53
- verifier_calls:List[VerifierCall]=field(default_factory=list)
54
- context_size_tokens:int=0; context_sources:List[str]=field(default_factory=list)
55
- retry_count:int=0; recovery_action:Optional[str]=None
56
- artifacts_created:List[str]=field(default_factory=list)
57
- step_outcome:Optional[Outcome]=None
58
- @property
59
- def step_cost(self): return (self.model_call.total_cost if self.model_call else 0.0)+sum(t.tool_cost for t in self.tool_calls)+sum(v.cost for v in self.verifier_calls)
60
- @property
61
- def step_latency_ms(self): return (self.model_call.latency_ms if self.model_call else 0.0)+sum(t.tool_latency_ms for t in self.tool_calls)+sum(v.latency_ms for v in self.verifier_calls)
62
-
63
- @dataclass
64
- class AgentTrace:
65
- trace_id:str; user_request:str; task_type:TaskType
66
- steps:List[TraceStep]=field(default_factory=list)
67
- final_outcome:Optional[Outcome]=None; final_artifacts:List[str]=field(default_factory=list)
68
- failure_tags:List[FailureTag]=field(default_factory=list)
69
- user_satisfaction:Optional[float]=None
70
- total_cost:Optional[float]=None
71
- metadata:Dict[str,Any]=field(default_factory=dict)
72
- @property
73
- def total_cost_computed(self): return sum(s.step_cost for s in self.steps)
74
- @property
75
- def total_latency_ms(self): return sum(s.step_latency_ms for s in self.steps)
76
- @property
77
- def total_retries(self): return sum(s.retry_count for s in self.steps)
78
- @property
79
- def total_tool_calls(self): return sum(len(s.tool_calls) for s in self.steps)
80
- @property
81
- def total_verifier_calls(self): return sum(len(s.verifier_calls) for s in self.steps)
82
- @property
83
- def cache_hit_rate(self):
84
- mc=[s.model_call for s in self.steps if s.model_call]
85
- if not mc: return 0.0
86
- ti=sum(m.input_tokens for m in mc)
87
- return sum(m.cache_hit_input_tokens for m in mc)/ti if ti>0 else 0.0
88
- def to_dict(self):
89
- return {"trace_id":self.trace_id,"user_request":self.user_request,"task_type":self.task_type.value,
90
- "steps":[{"step_id":s.step_id,"timestamp":s.timestamp.isoformat(),"task_type":s.task_type.value,
91
- "model_call":{"model_id":s.model_call.model_id,"provider":s.model_call.provider,
92
- "input_tokens":s.model_call.input_tokens,"output_tokens":s.model_call.output_tokens,
93
- "reasoning_tokens":s.model_call.reasoning_tokens,"cost":s.model_call.total_cost,
94
- "latency_ms":s.model_call.latency_ms,"cache_hit_input_tokens":s.model_call.cache_hit_input_tokens},
95
- "tool_calls":[{"tool_name":t.tool_name,"tool_cost":t.tool_cost,"tool_latency_ms":t.tool_latency_ms,
96
- "cache_hit":t.cache_hit,"repeated":t.repeated,"ignored_result":t.ignored_result,"failed":t.failed} for t in s.tool_calls],
97
- "verifier_calls":[{"verifier_model_id":v.verifier_model_id,"passed":v.passed,
98
- "confidence":v.confidence,"cost":v.cost} for v in s.verifier_calls],
99
- "context_size_tokens":s.context_size_tokens,"retry_count":s.retry_count,
100
- "recovery_action":s.recovery_action,"step_outcome":s.step_outcome.value if s.step_outcome else None,
101
- "step_cost":s.step_cost,"step_latency_ms":s.step_latency_ms} for s in self.steps],
102
- "final_outcome":self.final_outcome.value if self.final_outcome else None,
103
- "failure_tags":[f.value for f in self.failure_tags],
104
- "total_cost":self.total_cost_computed,"total_latency_ms":self.total_latency_ms,
105
- "total_retries":self.total_retries,"total_tool_calls":self.total_tool_calls,
106
- "total_verifier_calls":self.total_verifier_calls,
107
- "cache_hit_rate":self.cache_hit_rate,"metadata":self.metadata}
108
-
109
- class SyntheticTraceGenerator:
110
- # Realistic provider pricing (per 1K tokens)
111
- MODEL_CONFIGS = {
112
- "tiny_local": {"tier":1,"cost_input":0.0001,"cost_output":0.0002,"latency":200,"strength":0.35,"name":"Tiny Local (Qwen-0.5B)"},
113
- "cheap_cloud": {"tier":2,"cost_input":0.00015,"cost_output":0.0006,"latency":400,"strength":0.55,"name":"GPT-4o-mini"},
114
- "medium": {"tier":3,"cost_input":0.0015,"cost_output":0.006,"latency":800,"strength":0.80,"name":"Claude-3.5-Sonnet"},
115
- "frontier": {"tier":4,"cost_input":0.005,"cost_output":0.015,"latency":1500,"strength":0.93,"name":"GPT-4o / Claude-3-Opus"},
116
- "specialist": {"tier":5,"cost_input":0.01,"cost_output":0.03,"latency":2000,"strength":0.97,"name":"o1 / o3-mini"},
117
- }
118
- TOOL_COSTS = {"search":0.002,"retrieve":0.001,"fetch":0.003,"code_execution":0.005,
119
- "linter":0.001,"test_runner":0.003,"file_read":0.0005,"file_write":0.0005,
120
- "calculator":0.0001,"database_query":0.004,"compliance_check":0.01,
121
- "summarize":0.002,"task_planner":0.001,"progress_tracker":0.0005}
122
- # Task difficulty: [tier_needed, risk_level]
123
- TASK_DIFFICULTY = {
124
- TaskType.QUICK_ANSWER: (1, 0.1),
125
- TaskType.CODING: (3, 0.4),
126
- TaskType.RESEARCH: (3, 0.5),
127
- TaskType.DOCUMENT_DRAFTING: (2, 0.2),
128
- TaskType.LEGAL_REGULATED: (4, 0.8),
129
- TaskType.TOOL_HEAVY: (2, 0.3),
130
- TaskType.RETRIEVAL_HEAVY: (2, 0.35),
131
- TaskType.LONG_HORIZON: (3, 0.6),
132
- TaskType.UNKNOWN_AMBIGUOUS: (3, 0.7),
133
- }
134
- SCENARIOS = [
135
- {"name":"quick_answer_success","prob":0.18,"task_type":TaskType.QUICK_ANSWER,"tier":[1,2],"outcome":Outcome.SUCCESS,"failure_tags":[],"difficulty":1},
136
- {"name":"quick_answer_cheap_fail","prob":0.02,"task_type":TaskType.QUICK_ANSWER,"tier":[1],"outcome":Outcome.FAILURE,"failure_tags":[FailureTag.MODEL_TOO_WEAK],"difficulty":2},
137
- {"name":"coding_success_frontier","prob":0.08,"task_type":TaskType.CODING,"tier":[4],"outcome":Outcome.SUCCESS,"failure_tags":[],"difficulty":4},
138
- {"name":"coding_success_medium","prob":0.10,"task_type":TaskType.CODING,"tier":[3],"outcome":Outcome.SUCCESS,"failure_tags":[],"difficulty":3},
139
- {"name":"coding_cheap_fail","prob":0.05,"task_type":TaskType.CODING,"tier":[1,2],"outcome":Outcome.FAILURE,"failure_tags":[FailureTag.MODEL_TOO_WEAK],"difficulty":4},
140
- {"name":"coding_tool_underuse","prob":0.04,"task_type":TaskType.CODING,"tier":[3,4],"outcome":Outcome.FAILURE,"failure_tags":[FailureTag.TOOL_MISSED],"difficulty":3},
141
- {"name":"research_success","prob":0.10,"task_type":TaskType.RESEARCH,"tier":[3,4],"outcome":Outcome.SUCCESS,"failure_tags":[],"difficulty":3},
142
- {"name":"research_cheap_fail","prob":0.03,"task_type":TaskType.RESEARCH,"tier":[1,2],"outcome":Outcome.FAILURE,"failure_tags":[FailureTag.MODEL_TOO_WEAK],"difficulty":4},
143
- {"name":"document_draft_success","prob":0.08,"task_type":TaskType.DOCUMENT_DRAFTING,"tier":[2,3],"outcome":Outcome.SUCCESS,"failure_tags":[],"difficulty":2},
144
- {"name":"legal_frontier_success","prob":0.04,"task_type":TaskType.LEGAL_REGULATED,"tier":[4,5],"outcome":Outcome.SUCCESS,"failure_tags":[],"difficulty":5},
145
- {"name":"legal_cheap_unsafe","prob":0.02,"task_type":TaskType.LEGAL_REGULATED,"tier":[1,2],"outcome":Outcome.FAILURE,"failure_tags":[FailureTag.UNSAFE_CHEAP_MODEL],"difficulty":5},
146
- {"name":"tool_heavy_success","prob":0.06,"task_type":TaskType.TOOL_HEAVY,"tier":[2,3],"outcome":Outcome.SUCCESS,"failure_tags":[],"difficulty":2},
147
- {"name":"retrieval_success","prob":0.06,"task_type":TaskType.RETRIEVAL_HEAVY,"tier":[2,3],"outcome":Outcome.SUCCESS,"failure_tags":[],"difficulty":2},
148
- {"name":"long_horizon_success","prob":0.05,"task_type":TaskType.LONG_HORIZON,"tier":[3,4],"outcome":Outcome.SUCCESS,"failure_tags":[],"difficulty":4},
149
- {"name":"long_horizon_retry_loop","prob":0.03,"task_type":TaskType.LONG_HORIZON,"tier":[3],"outcome":Outcome.FAILURE,"failure_tags":[FailureTag.RETRY_LOOP],"difficulty":4},
150
- {"name":"unknown_ambiguous_success","prob":0.03,"task_type":TaskType.UNKNOWN_AMBIGUOUS,"tier":[3,4],"outcome":Outcome.SUCCESS,"failure_tags":[],"difficulty":3},
151
- {"name":"unknown_ambiguous_blocked","prob":0.02,"task_type":TaskType.UNKNOWN_AMBIGUOUS,"tier":[3,4],"outcome":Outcome.BLOCKED,"failure_tags":[FailureTag.MISSED_ESCALATION],"difficulty":3},
152
- {"name":"tool_overuse","prob":0.04,"task_type":TaskType.CODING,"tier":[3,4],"outcome":Outcome.PARTIAL_SUCCESS,"failure_tags":[FailureTag.TOOL_UNNECESSARY],"difficulty":3},
153
- {"name":"cache_break_scenario","prob":0.03,"task_type":TaskType.RESEARCH,"tier":[3,4],"outcome":Outcome.PARTIAL_SUCCESS,"failure_tags":[FailureTag.CACHE_BREAK],"difficulty":3},
154
- {"name":"false_done_scenario","prob":0.02,"task_type":TaskType.CODING,"tier":[3,4],"outcome":Outcome.FALSE_DONE,"failure_tags":[FailureTag.VERIFIER_FALSE_PASS],"difficulty":3},
155
- ]
156
- def __init__(self,seed=42): self.rng=random.Random(seed)
157
- def generate(self,n=10000): return [self._generate_trace(i) for i in range(n)]
158
- def _pick_scenario(self): return self.rng.choices(self.SCENARIOS,weights=[s["prob"] for s in self.SCENARIOS])[0]
159
- def _tier_to_model(self,tier): return {1:"tiny_local",2:"cheap_cloud",3:"medium",4:"frontier",5:"specialist"}.get(tier,"medium")
160
- def _generate_trace(self,idx):
161
- scenario=self._pick_scenario()
162
- trace_id=f"synth_{idx}_{uuid.uuid4().hex[:8]}"
163
- task_type=scenario["task_type"]
164
- user_request=self._generate_request(task_type,scenario["name"])
165
- base_steps=self.rng.randint(1,8)
166
- if "long_horizon" in scenario["name"] or "retry_loop" in scenario["name"]: base_steps=self.rng.randint(4,12)
167
- elif "coding" in scenario["name"] and scenario["outcome"]==Outcome.FAILURE: base_steps=self.rng.randint(3,8)
168
- tier=self.rng.choice(scenario["tier"])
169
- model_key=self._tier_to_model(tier)
170
- model_cfg=self.MODEL_CONFIGS[model_key]
171
- steps=[]
172
- for step_idx in range(base_steps):
173
- steps.append(self._generate_step(trace_id,step_idx,task_type,model_key,model_cfg,scenario,step_idx==base_steps-1))
174
- return AgentTrace(
175
- trace_id=trace_id,user_request=user_request,task_type=task_type,steps=steps,
176
- final_outcome=scenario["outcome"],failure_tags=list(scenario.get("failure_tags",[])),
177
- total_cost=sum(s.step_cost for s in steps),
178
- metadata={"scenario":scenario["name"],"synthetic":True,"difficulty":scenario["difficulty"],
179
- "optimal_tier":scenario["difficulty"],"actual_tier":tier})
180
- def _generate_request(self,task_type,scenario_name):
181
- templates={
182
- TaskType.QUICK_ANSWER:["What is the capital of France?","Briefly explain quantum computing.","Summarize article X.","What is 237 * 452?"],
183
- TaskType.CODING:["Write a Python function to reverse a linked list.","Fix the bug in this React component.","Refactor auth module to JWT.","Implement LRU cache in Go.","Debug this segfault in C++ thread pool."],
184
- TaskType.RESEARCH:["Research latest transformer advances.","Find sources comparing LoRA and full FT.","Investigate data center climate impact.","What does literature say on speculative decoding?"],
185
- TaskType.DOCUMENT_DRAFTING:["Draft project proposal for ML pipeline.","Write email to team about deployment.","Create technical report on performance."],
186
- TaskType.LEGAL_REGULATED:["Review this contract for liability clauses.","Check GDPR compliance for data pipeline.","Draft privacy policy section."],
187
- TaskType.TOOL_HEAVY:["Search open issues and create summary.","Fetch API docs and generate client code.","Query Q3 sales and produce chart."],
188
- TaskType.RETRIEVAL_HEAVY:["Answer based on 50-page document.","Find all 'payment processing' mentions.","Retrieve relevant cases for legal query."],
189
- TaskType.LONG_HORIZON:["Plan 3-month roadmap.","Orchestrate multi-region deployment.","Redesign data architecture end-to-end."],
190
- TaskType.UNKNOWN_AMBIGUOUS:["Help me with this thing.","I need something about the server.","Can you look into that issue?"],
191
- }
192
- return self.rng.choice(templates.get(task_type,["Generic request"]))
193
- def _get_tools_for_task(self,task_type):
194
- return {TaskType.QUICK_ANSWER:["calculator","search"],
195
- TaskType.CODING:["file_read","file_write","code_execution","linter","test_runner"],
196
- TaskType.RESEARCH:["search","retrieve","fetch","summarize"],
197
- TaskType.DOCUMENT_DRAFTING:["file_read","summarize"],
198
- TaskType.LEGAL_REGULATED:["document_retrieval","compliance_check","search"],
199
- TaskType.TOOL_HEAVY:["search","fetch","api_call","database_query"],
200
- TaskType.RETRIEVAL_HEAVY:["retrieve","search","fetch"],
201
- TaskType.LONG_HORIZON:["task_planner","progress_tracker","file_read"],
202
- TaskType.UNKNOWN_AMBIGUOUS:["search"]}.get(task_type,["search"])
203
- def _generate_step(self,trace_id,step_idx,task_type,model_key,model_cfg,scenario,is_last):
204
- step_id=f"{trace_id}_step_{step_idx}"
205
- input_tokens=self.rng.randint(800,12000)
206
- output_tokens=self.rng.randint(200,6000)
207
- cache_hit=self.rng.random()<0.35
208
- cache_hit_tokens=int(input_tokens*self.rng.random()*0.6) if cache_hit else 0
209
- model_call=ModelCall(model_id=model_key,provider="synthetic",input_tokens=input_tokens,output_tokens=output_tokens,
210
- reasoning_tokens=output_tokens//4 if model_key in ("frontier","specialist") else 0,
211
- cost_per_1k_input=model_cfg["cost_input"],cost_per_1k_output=model_cfg["cost_output"],
212
- cache_hit_input_tokens=cache_hit_tokens,latency_ms=model_cfg["latency"]*self.rng.uniform(0.8,1.5))
213
- tool_calls=[]; base_tools=self._get_tools_for_task(task_type); num_tools=self.rng.randint(0,len(base_tools))
214
- if scenario["name"]=="tool_overuse": num_tools+=3
215
- for t in range(min(num_tools,len(base_tools))):
216
- tool_name=base_tools[t]
217
- tool_calls.append(ToolCall(tool_name=tool_name,tool_input={"query":f"auto_{tool_name}"},
218
- tool_cost=self.TOOL_COSTS.get(tool_name,0.001),tool_latency_ms=self.rng.uniform(100,1200),
219
- cache_hit=self.rng.random()<0.2,repeated=self.rng.random()<0.1,
220
- ignored_result=self.rng.random()<0.05,
221
- failed=self.rng.random()<(0.3 if "retry_loop" in scenario["name"] else 0.05)))
222
- verifier_calls=[]; num_verifiers=0
223
- if task_type==TaskType.LEGAL_REGULATED: num_verifiers=1
224
- elif task_type in (TaskType.CODING,TaskType.RESEARCH) and model_key in ("frontier","specialist"): num_verifiers=1 if self.rng.random()<0.4 else 0
225
- for _ in range(num_verifiers):
226
- verifier_calls.append(VerifierCall(verifier_model_id="verifier_medium",target_step_id=step_id,
227
- passed=self.rng.random()<0.85,confidence=self.rng.uniform(0.6,0.99),cost=0.005,latency_ms=500))
228
- context_size=self.rng.randint(1500,20000)
229
- if scenario["name"]=="cache_break_scenario": context_size+=self.rng.randint(8000,30000)
230
- retries=0
231
- if "retry_loop" in scenario["name"]: retries=self.rng.randint(4,8)
232
- elif self.rng.random()<0.12: retries=self.rng.randint(1,3)
233
- recovery=None
234
- if retries>0: recovery=self.rng.choice(["retry_same","retry_changed_prompt","repair_tool","switch_model","ask_clarification"])
235
- step_outcome=Outcome.SUCCESS
236
- if is_last: step_outcome=scenario["outcome"]
237
- elif "retry_loop" in scenario["name"] and step_idx>=2: step_outcome=Outcome.FAILURE
238
- return TraceStep(step_id=step_id,timestamp=datetime.utcnow()+timedelta(seconds=step_idx*30),task_type=task_type,
239
- model_call=model_call,tool_calls=tool_calls,verifier_calls=verifier_calls,
240
- context_size_tokens=context_size,context_sources=["system_rules","tool_descriptions","user_preferences","recent_messages"],
241
- retry_count=retries,recovery_action=recovery,
242
- artifacts_created=[f"artifact_{step_idx}"] if self.rng.random()<0.25 else [],
243
- step_outcome=step_outcome)
244
-
245
- @dataclass
246
- class BenchmarkResult:
247
- baseline_name:str; num_tasks:int; num_success:int; num_partial:int
248
- num_failure:int; num_false_done:int; num_blocked:int
249
- total_cost:float; avg_cost_success:float; avg_latency_ms:float
250
- total_tool_calls:int; total_verifier_calls:int; total_retries:int
251
- avg_cache_hit_rate:float; cost_reduction_vs_frontier:float
252
- false_done_rate:float; unsafe_cheap_miss_rate:float
253
- missed_escalation_rate:float; regression_rate:float
254
- per_scenario_stats:Dict[str,Dict[str,Any]]=field(default_factory=dict)
255
-
256
- class BenchmarkSuite:
257
- MODEL_CONFIGS = SyntheticTraceGenerator.MODEL_CONFIGS
258
- TASK_DIFFICULTY = SyntheticTraceGenerator.TASK_DIFFICULTY
259
- def __init__(self): pass
260
- def generate_benchmark_data(self,n=1000,seed=42): return SyntheticTraceGenerator(seed=seed).generate(n)
261
-
262
- def run_all_baselines(self,traces):
263
- baselines=["always_frontier","always_cheap","static","cascade","full_optimizer"]
264
- results={}
265
- for baseline in baselines:
266
- print(f"Running baseline: {baseline}...")
267
- results[baseline]=self._run_baseline(traces,baseline)
268
- return results
269
-
270
- def run_ablations(self,traces):
271
- ablations=["no_router","no_tool_gate","no_verifier","no_early_term","no_context_budget"]
272
- results={}
273
- for ablation in ablations:
274
- print(f"Running ablation: {ablation}...")
275
- results[ablation]=self._run_baseline(traces,ablation)
276
- return results
277
-
278
- def _run_baseline(self,traces,baseline_name):
279
- success_count=0; partial_count=0; failure_count=0; false_done_count=0; blocked_count=0
280
- total_cost=0.0; total_latency=0.0; total_tools=0; total_verifiers=0; total_retries=0
281
- cache_rates=[]; cheap_misses=0; escalation_misses=0; regression_count=0
282
- per_scenario=defaultdict(lambda:{"count":0,"success":0,"cost":0.0})
283
- for trace in traces:
284
- sim_cost,sim_success,sim_outcome=self._simulate(trace,baseline_name)
285
- total_cost+=sim_cost; total_latency+=trace.total_latency_ms*0.7
286
- total_tools+=trace.total_tool_calls; total_verifiers+=trace.total_verifier_calls
287
- total_retries+=trace.total_retries; cache_rates.append(trace.cache_hit_rate)
288
- scenario=trace.metadata.get("scenario","normal")
289
- per_scenario[scenario]["count"]+=1; per_scenario[scenario]["cost"]+=sim_cost
290
- if sim_success:
291
- if sim_outcome in (Outcome.SUCCESS,Outcome.PARTIAL_SUCCESS):
292
- success_count+=1; per_scenario[scenario]["success"]+=1
293
- else: regression_count+=1
294
- else:
295
- if sim_outcome==Outcome.FALSE_DONE: false_done_count+=1
296
- elif sim_outcome==Outcome.BLOCKED: blocked_count+=1
297
- else: failure_count+=1
298
- # Track cheap model misses
299
- difficulty=trace.metadata.get("difficulty",3)
300
- actual_tier=trace.metadata.get("actual_tier",3)
301
- if actual_tier<difficulty and actual_tier<=2 and sim_outcome in (Outcome.FAILURE,Outcome.PARTIAL_SUCCESS):
302
- cheap_misses+=1
303
- if actual_tier<difficulty and sim_outcome in (Outcome.FAILURE,Outcome.BLOCKED):
304
- escalation_misses+=1
305
- n=len(traces); avg_cost_success=total_cost/max(success_count,1)
306
- frontier_total=sum(t.total_cost_computed*4 for t in traces) # frontier costs ~4x medium
307
- cost_reduction=(frontier_total-total_cost)/max(frontier_total,1)
308
- return BenchmarkResult(
309
- baseline_name=baseline_name,num_tasks=n,num_success=success_count,num_partial=partial_count,
310
- num_failure=failure_count,num_false_done=false_done_count,num_blocked=blocked_count,
311
- total_cost=total_cost,avg_cost_success=avg_cost_success,avg_latency_ms=total_latency/n,
312
- total_tool_calls=total_tools,total_verifier_calls=total_verifiers,total_retries=total_retries,
313
- avg_cache_hit_rate=sum(cache_rates)/n,cost_reduction_vs_frontier=cost_reduction,
314
- false_done_rate=false_done_count/n,unsafe_cheap_miss_rate=cheap_misses/n,
315
- missed_escalation_rate=escalation_misses/n,regression_rate=regression_count/n,
316
- per_scenario_stats=dict(per_scenario))
317
-
318
- def _simulate(self,trace,baseline):
319
- """Realistic simulation: tier vs difficulty determines success."""
320
- scenario=trace.metadata.get("scenario","normal")
321
- difficulty=trace.metadata.get("difficulty",3)
322
- actual_tier=trace.metadata.get("actual_tier",3)
323
- base_cost=trace.total_cost_computed
324
- # Determine what tier the baseline would actually use
325
- if baseline=="always_frontier": chosen_tier=4
326
- elif baseline=="always_cheap": chosen_tier=2
327
- elif baseline in ("no_router","static"): chosen_tier=actual_tier # uses same as trace, no optimization
328
- elif baseline in ("cascade","full_optimizer"):
329
- # Cascade tries lower tier first, escalates if needed
330
- if difficulty<=2: chosen_tier=2
331
- elif difficulty==3: chosen_tier=3 if self._tier_success_prob(3,difficulty)>0.7 else 4
332
- elif difficulty==4: chosen_tier=3 if self._tier_success_prob(3,difficulty)>0.6 else 4
333
- else: chosen_tier=4 if self._tier_success_prob(4,difficulty)>0.5 else 5
334
- elif baseline=="no_tool_gate": chosen_tier=actual_tier # same tier, but no tool savings
335
- elif baseline=="no_verifier": chosen_tier=actual_tier
336
- elif baseline=="no_early_term": chosen_tier=actual_tier
337
- elif baseline=="no_context_budget": chosen_tier=actual_tier
338
- else: chosen_tier=actual_tier
339
- # Cost multiplier based on chosen tier
340
- tier_cost_mult={1:0.05,2:0.15,3:0.75,4:1.0,5:1.5}.get(chosen_tier,0.75)
341
- actual_cost_mult={1:0.05,2:0.15,3:0.75,4:1.0,5:1.5}.get(actual_tier,0.75)
342
- # Adjust cost: cascade uses cheaper tier when possible
343
- cost_ratio=tier_cost_mult/actual_cost_mult if actual_cost_mult>0 else 1.0
344
- sim_cost=base_cost*cost_ratio
345
- # Tool gate savings for cascade/full
346
- if baseline in ("cascade","full_optimizer"):
347
- if "tool_overuse" in scenario: sim_cost*=0.75
348
- # Cache savings
349
- if baseline=="full_optimizer" and "cache_break" not in scenario: sim_cost*=0.92
350
- # Verifier savings
351
- if baseline=="full_optimizer" and chosen_tier>=3 and difficulty<4: sim_cost*=0.95
352
- # Early termination savings
353
- if baseline=="full_optimizer" and "retry_loop" in scenario: sim_cost*=0.60
354
- if baseline=="no_early_term" and "retry_loop" in scenario: sim_cost*=1.4
355
- # Determine success probability
356
- success_prob=self._tier_success_prob(chosen_tier,difficulty)
357
- # Apply baseline-specific modifiers
358
- if baseline=="always_cheap" and difficulty>=3: success_prob*=0.3
359
- elif baseline=="no_tool_gate" and "tool" in scenario: success_prob*=0.7
360
- elif baseline=="no_verifier" and difficulty>=4: success_prob*=0.85
361
- elif baseline=="full_optimizer": success_prob=min(1.0,success_prob+0.05)
362
- # Special scenarios
363
- if "false_done" in scenario: success_prob=0.1
364
- elif "blocked" in scenario: success_prob=0.0
365
- elif "retry_loop" in scenario and baseline not in ("full_optimizer",):
366
- if baseline=="no_early_term": success_prob=0.1
367
- else: success_prob=0.25
368
- elif "retry_loop" in scenario and baseline=="full_optimizer":
369
- success_prob=0.5 # Doom detector catches it
370
- sim_success=success_prob>0.5
371
- # Determine simulated outcome
372
- if "false_done" in scenario: sim_outcome=Outcome.FALSE_DONE
373
- elif "blocked" in scenario: sim_outcome=Outcome.BLOCKED
374
- elif sim_success:
375
- if success_prob>0.85: sim_outcome=Outcome.SUCCESS
376
- else: sim_outcome=Outcome.PARTIAL_SUCCESS
377
- else:
378
- if "retry_loop" in scenario: sim_outcome=Outcome.FAILURE
379
- elif success_prob<0.2: sim_outcome=Outcome.BLOCKED
380
- else: sim_outcome=Outcome.FAILURE
381
- return sim_cost,sim_success,sim_outcome
382
-
383
- def _tier_success_prob(self,tier,difficulty):
384
- strength={1:0.35,2:0.55,3:0.80,4:0.93,5:0.97}.get(tier,0.5)
385
- # Success = strength^difficulty (harder tasks need exponentially more strength)
386
- return strength**(difficulty*0.6)
387
-
388
- def report(self,results):
389
- lines=["="*100,"AGENT COST OPTIMIZER BENCHMARK REPORT v2","="*100,""]
390
- headers=["Baseline","Success","Partial","Fail","Blocked","F-DONE",
391
- "Total Cost","Avg$/Succ","Lat(ms)","Tools","Verif","Retry",
392
- "Cache%","CostRed%","Regression","CheapMiss","EscMiss"]
393
- lines.append(" | ".join(headers)); lines.append("-"*160)
394
- for name,result in results.items():
395
- row=[name[:22].ljust(22),
396
- f"{result.num_success/result.num_tasks:.1%}",
397
- f"{result.num_partial/result.num_tasks:.1%}",
398
- f"{result.num_failure/result.num_tasks:.1%}",
399
- f"{result.num_blocked/result.num_tasks:.1%}",
400
- f"{result.false_done_rate:.1%}",
401
- f"${result.total_cost:.2f}",
402
- f"${result.avg_cost_success:.4f}",
403
- f"{result.avg_latency_ms:.0f}",
404
- str(result.total_tool_calls),str(result.total_verifier_calls),str(result.total_retries),
405
- f"{result.avg_cache_hit_rate:.1%}",
406
- f"{result.cost_reduction_vs_frontier:.1%}",
407
- f"{result.regression_rate:.1%}",
408
- f"{result.unsafe_cheap_miss_rate:.1%}",
409
- f"{result.missed_escalation_rate:.1%}",
410
- ]
411
- lines.append(" | ".join(row))
412
- lines.append(""); lines.append("="*100)
413
- # Find best on Pareto frontier
414
- best_score,best_name=-float("inf"),""
415
- for name,result in results.items():
416
- success_rate=(result.num_success+result.num_partial)/result.num_tasks
417
- score=success_rate*20-result.avg_cost_success*50-result.regression_rate*30-result.unsafe_cheap_miss_rate*40
418
- if score>best_score: best_score,best_name=score,name
419
- lines.append(f"BEST PARETO: {best_name} (score={best_score:.2f})")
420
- # Quality/cost ranking
421
- lines.append(""); lines.append("QUALITY/COST FRONTIER (Success Rate vs Avg Cost per Success):")
422
- points=[(name,(r.num_success+r.num_partial)/r.num_tasks,r.avg_cost_success) for name,r in results.items()]
423
- points.sort(key=lambda x:(-x[1],x[2]))
424
- for name,sr,cost in points:
425
- lines.append(f" {name:22s} | Success: {sr:.1%} | Cost/Success: ${cost:.4f}")
426
- lines.append(""); lines.append("="*100)
427
- return "\n".join(lines)
428
-
429
- def export(self,results,path):
430
- export_data={}
431
- for name,result in results.items():
432
- export_data[name]={"baseline_name":result.baseline_name,"num_tasks":result.num_tasks,
433
- "num_success":result.num_success,"num_partial":result.num_partial,
434
- "num_failure":result.num_failure,"num_false_done":result.num_false_done,
435
- "num_blocked":result.num_blocked,"total_cost":result.total_cost,
436
- "avg_cost_success":result.avg_cost_success,"avg_latency_ms":result.avg_latency_ms,
437
- "total_tool_calls":result.total_tool_calls,"total_verifier_calls":result.total_verifier_calls,
438
- "total_retries":result.total_retries,"avg_cache_hit_rate":result.avg_cache_hit_rate,
439
- "cost_reduction_vs_frontier":result.cost_reduction_vs_frontier,
440
- "false_done_rate":result.false_done_rate,
441
- "unsafe_cheap_miss_rate":result.unsafe_cheap_miss_rate,
442
- "missed_escalation_rate":result.missed_escalation_rate,
443
- "regression_rate":result.regression_rate,
444
- "per_scenario_stats":result.per_scenario_stats}
445
- with open(path,"w") as f: json.dump(export_data,f,indent=2)
446
-
447
- if __name__=="__main__":
448
- parser=argparse.ArgumentParser(description="ACO Evaluation Runner v2")
449
- parser.add_argument("--tasks","-n",type=int,default=2000,help="Number of tasks")
450
- parser.add_argument("--seed","-s",type=int,default=42,help="Random seed")
451
- parser.add_argument("--output","-o",default="./eval_results_v2",help="Output directory")
452
- args=parser.parse_args()
453
- os.makedirs(args.output,exist_ok=True)
454
- suite=BenchmarkSuite()
455
- print(f"[{datetime.now().isoformat()}] Generating {args.tasks} synthetic traces...")
456
- traces=suite.generate_benchmark_data(args.tasks,seed=args.seed)
457
- traces_path=os.path.join(args.output,"traces.jsonl")
458
- with open(traces_path,"w") as f:
459
- for trace in traces: f.write(json.dumps(trace.to_dict())+"\n")
460
- print(f" Saved {len(traces)} traces to {traces_path}")
461
- print(f"\n[{datetime.now().isoformat()}] Running baselines...")
462
- baseline_results=suite.run_all_baselines(traces)
463
- baseline_path=os.path.join(args.output,"baseline_results.json")
464
- suite.export(baseline_results,baseline_path)
465
- print(f" Saved to {baseline_path}")
466
- print(f"\n[{datetime.now().isoformat()}] Running ablations...")
467
- ablation_results=suite.run_ablations(traces)
468
- ablation_path=os.path.join(args.output,"ablation_results.json")
469
- suite.export(ablation_results,ablation_path)
470
- print(f" Saved to {ablation_path}")
471
- all_results={**baseline_results,**ablation_results}
472
- report=suite.report(all_results)
473
- report_path=os.path.join(args.output,"report.txt")
474
- with open(report_path,"w") as f: f.write(report)
475
- print(f" Saved report to {report_path}")
476
- # Cost-quality frontier
477
- points=[]
478
- for name,result in all_results.items():
479
- sr=(result.num_success+result.num_partial)/result.num_tasks
480
- points.append({"baseline":name,"success_rate":sr,"avg_cost_per_success":result.avg_cost_success,
481
- "total_cost":result.total_cost,"regression_rate":result.regression_rate,
482
- "false_done_rate":result.false_done_rate,"cheap_miss_rate":result.unsafe_cheap_miss_rate})
483
- frontier=[]
484
- for p in points:
485
- dominated=False
486
- for q in points:
487
- if q["baseline"]==p["baseline"]: continue
488
- if q["success_rate"]>=p["success_rate"] and q["avg_cost_per_success"]<=p["avg_cost_per_success"]:
489
- if q["success_rate"]>p["success_rate"] or q["avg_cost_per_success"]<p["avg_cost_per_success"]:
490
- dominated=True; break
491
- if not dominated: frontier.append(p)
492
- frontier.sort(key=lambda x:x["success_rate"],reverse=True)
493
- frontier_data={"all_points":points,"pareto_frontier":frontier,"frontier_baselines":[p["baseline"] for p in frontier]}
494
- frontier_path=os.path.join(args.output,"cost_quality_frontier.json")
495
- with open(frontier_path,"w") as f: json.dump(frontier_data,indent=2,fp=f)
496
- print(f" Saved frontier to {frontier_path}")
497
- print("\n"+"="*100)
498
- print(report)
499
- print("="*100)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
train_router.py DELETED
@@ -1,77 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Train a learned model router for Agent Cost Optimizer.
3
-
4
- Architecture: CARROT-style plug-in router.
5
- - Per-tier P(success|query) classifiers (XGBoost)
6
- - Per-tier cost estimators
7
- - Route to cheapest tier where P(success) > threshold
8
-
9
- Ground truth: optimal_tier = min{tier : success(tier)} from execution traces.
10
- """
11
- import json, os, sys, random, pickle, uuid
12
- import numpy as np
13
- from datetime import datetime, timedelta
14
- from collections import defaultdict
15
- from typing import Dict, List, Tuple, Any, Optional
16
- from dataclasses import dataclass
17
- from enum import Enum
18
-
19
- # ─── Feature Extraction ───────────────────────────────────────────
20
- TASK_TYPES = [
21
- "quick_answer", "coding", "research", "document_drafting",
22
- "legal_regulated", "tool_heavy", "retrieval_heavy",
23
- "long_horizon", "unknown_ambiguous",
24
- ]
25
- TASK_TYPE_TO_IDX = {t: i for i, t in enumerate(TASK_TYPES)}
26
-
27
- CODE_KW = ["python","javascript","code","function","bug","debug","refactor",
28
- "implement","test","compile","runtime","class","module","package",
29
- "async","thread","queue","stack","heap","pointer","segfault","linter"]
30
- LEGAL_KW = ["contract","legal","compliance","gdpr","privacy","policy",
31
- "regulatory","liability","clause","indemnification","tos"]
32
- RESEARCH_KW = ["research","find sources","literature","investigate","compare",
33
- "analyze","study","survey","paper","arxiv","citation"]
34
- TOOL_KW = ["search","fetch","retrieve","query","api","database","scrape",
35
- "lookup","download","upload","index","aggregate"]
36
- LONG_KW = ["plan","project","roadmap","orchestrate","multi-step","migrate",
37
- "pipeline","end-to-end","architecture","workflow","deploy"]
38
- MATH_KW = ["calculate","compute","solve","equation","formula","optimize",
39
- "probability","integral","derivative","matrix"]
40
-
41
- def extract_features(request: str, task_type: str, metadata: Dict = None) -> Dict[str, Any]:
42
- r = request.lower()
43
- feats = {
44
- "request_length": len(request),
45
- "num_words": len(request.split()),
46
- "num_sentences": request.count(".") + request.count("!") + request.count("?"),
47
- "has_code_kw": int(any(kw in r for kw in CODE_KW)),
48
- "num_code_kw": sum(1 for kw in CODE_KW if kw in r),
49
- "has_legal_kw": int(any(kw in r for kw in LEGAL_KW)),
50
- "num_legal_kw": sum(1 for kw in LEGAL_KW if kw in r),
51
- "has_research_kw": int(any(kw in r for kw in RESEARCH_KW)),
52
- "num_research_kw": sum(1 for kw in RESEARCH_KW if kw in r),
53
- "has_tool_kw": int(any(kw in r for kw in TOOL_KW)),
54
- "num_tool_kw": sum(1 for kw in TOOL_KW if kw in r),
55
- "has_long_kw": int(any(kw in r for kw in LONG_KW)),
56
- "has_math_kw": int(any(kw in r for kw in MATH_KW)),
57
- "task_type_idx": TASK_TYPE_TO_IDX.get(task_type, 8),
58
- }
59
- # One-hot task type
60
- for tt in TASK_TYPES:
61
- feats[f"tt_{tt}"] = int(task_type == tt)
62
- if metadata:
63
- feats["difficulty"] = metadata.get("difficulty", 3)
64
- return feats
65
-
66
- def feats_to_array(feats: Dict) -> List[float]:
67
- """Convert feature dict to fixed-order array."""
68
- keys = sorted(feats.keys())
69
- return [float(feats[k]) for k in keys]
70
-
71
- FEAT_KEYS = None # set on first call
72
-
73
- def feats_to_array_safe(feats: Dict) -> List[float]:
74
- global FEAT_KEYS
75
- if FEAT_KEYS is None:
76
- FEAT_KEYS = sorted(feats.keys())
77
- return [float(feats.get(k, 0.0)) for k in FEAT_KEYS]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
train_router_gen.py DELETED
@@ -1,126 +0,0 @@
1
-
2
- # ─── Synthetic Trace Generator (for training data) ────────────────
3
- MODEL_CONFIGS = {
4
- "tiny_local": {"tier":1,"cost_input":0.0001,"cost_output":0.0002,"latency":200,"strength":0.35},
5
- "cheap_cloud": {"tier":2,"cost_input":0.00015,"cost_output":0.0006,"latency":400,"strength":0.55},
6
- "medium": {"tier":3,"cost_input":0.0015,"cost_output":0.006,"latency":800,"strength":0.80},
7
- "frontier": {"tier":4,"cost_input":0.005,"cost_output":0.015,"latency":1500,"strength":0.93},
8
- "specialist": {"tier":5,"cost_input":0.01,"cost_output":0.03,"latency":2000,"strength":0.97},
9
- }
10
- TIER_TO_MODEL = {1:"tiny_local",2:"cheap_cloud",3:"medium",4:"frontier",5:"specialist"}
11
- TIER_COST_MULT = {1:0.05,2:0.15,3:0.75,4:1.0,5:1.5}
12
-
13
- TASK_TEMPLATES = {
14
- "quick_answer": [
15
- "What is the capital of France?","Explain quantum computing briefly.",
16
- "What is 237*452?","Define photosynthesis.","Who wrote Hamlet?",
17
- "What is the speed of light?","List the primary colors.",
18
- "What is GDP?","When was the Declaration of Independence signed?",
19
- ],
20
- "coding": [
21
- "Write a Python function to reverse a linked list.",
22
- "Fix the bug in this React component.","Refactor auth module to JWT.",
23
- "Implement LRU cache in Go.","Debug segfault in C++ thread pool.",
24
- "Add unit tests for the payment module.","Optimize this SQL query.",
25
- "Create a REST API for user management.","Implement binary search in Rust.",
26
- "Write a recursive descent parser for JSON.",
27
- ],
28
- "research": [
29
- "Research latest transformer advances.",
30
- "Find sources comparing LoRA and full FT.",
31
- "Investigate data center climate impact.",
32
- "What does literature say on speculative decoding?",
33
- "Survey privacy-preserving ML techniques.",
34
- "Compare reinforcement learning algorithms for robotics.",
35
- "Analyze trends in LLM scaling laws.",
36
- ],
37
- "document_drafting": [
38
- "Draft project proposal for ML pipeline.",
39
- "Write email to team about deployment.",
40
- "Create technical report on performance.",
41
- "Write a project brief for the migration.",
42
- "Draft meeting notes summary.",
43
- ],
44
- "legal_regulated": [
45
- "Review this contract for liability clauses.",
46
- "Check GDPR compliance for data pipeline.",
47
- "Draft privacy policy section.",
48
- "Analyze indemnification clause in vendor agreement.",
49
- "Verify regulatory compliance for medical device software.",
50
- ],
51
- "tool_heavy": [
52
- "Search open issues and create summary.",
53
- "Fetch API docs and generate client code.",
54
- "Query Q3 sales and produce chart.",
55
- "Aggregate metrics from 5 monitoring endpoints.",
56
- ],
57
- "retrieval_heavy": [
58
- "Answer based on 50-page document.",
59
- "Find all payment processing mentions.",
60
- "Retrieve relevant cases for legal query.",
61
- "Summarize the quarterly earnings report.",
62
- ],
63
- "long_horizon": [
64
- "Plan 3-month roadmap.","Orchestrate multi-region deployment.",
65
- "Redesign data architecture end-to-end.","Migrate monolith to microservices.",
66
- ],
67
- "unknown_ambiguous": [
68
- "Help me with this thing.","I need something about the server.",
69
- "Can you look into that issue?","There's a problem with the data.",
70
- ],
71
- }
72
-
73
- def tier_success_prob(tier, difficulty):
74
- strength = {1:0.35,2:0.55,3:0.80,4:0.93,5:0.97}.get(tier,0.5)
75
- return strength ** (difficulty * 0.6)
76
-
77
- def generate_training_trace(idx, rng):
78
- task_types = list(TASK_TEMPLATES.keys())
79
- task_type = rng.choice(task_types)
80
- difficulty = {
81
- "quick_answer":1,"document_drafting":2,"tool_heavy":2,"retrieval_heavy":2,
82
- "research":3,"coding":3,"unknown_ambiguous":3,"long_horizon":4,"legal_regulated":5,
83
- }[task_type]
84
-
85
- # Try ALL tiers for this task to get ground truth
86
- tier_outcomes = {}
87
- for tier in range(1, 6):
88
- sp = tier_success_prob(tier, difficulty)
89
- tier_outcomes[tier] = rng.random() < sp
90
-
91
- optimal_tier = 5 # default: need strongest
92
- for tier in range(1, 6):
93
- if tier_outcomes.get(tier, False):
94
- optimal_tier = tier
95
- break
96
-
97
- actual_tier = rng.choice(list(range(1, 6)))
98
- # Bias toward reasonable tiers
99
- if difficulty <= 2:
100
- actual_tier = rng.choices([1,2,3,4,5], weights=[3,4,2,1,0.5])[0]
101
- elif difficulty == 3:
102
- actual_tier = rng.choices([1,2,3,4,5], weights=[1,2,4,2,1])[0]
103
- elif difficulty == 4:
104
- actual_tier = rng.choices([1,2,3,4,5], weights=[0.5,1,2,4,2])[0]
105
- else:
106
- actual_tier = rng.choices([1,2,3,4,5], weights=[0.2,0.5,1,3,4])[0]
107
-
108
- outcome = "success" if tier_outcomes.get(actual_tier, False) else "failure"
109
-
110
- user_request = rng.choice(TASK_TEMPLATES[task_type])
111
- cost_mult = TIER_COST_MULT[actual_tier]
112
- base_tokens = rng.randint(800, 12000) + rng.randint(200, 6000)
113
- cost = base_tokens / 1000 * MODEL_CONFIGS[TIER_TO_MODEL[actual_tier]]["cost_input"] * cost_mult
114
-
115
- return {
116
- "trace_id": f"train_{idx}",
117
- "user_request": user_request,
118
- "task_type": task_type,
119
- "difficulty": difficulty,
120
- "actual_tier": actual_tier,
121
- "optimal_tier": optimal_tier,
122
- "outcome": outcome,
123
- "cost": cost,
124
- "tier_outcomes": {str(k): v for k, v in tier_outcomes.items()},
125
- "metadata": {"difficulty": difficulty, "optimal_tier": optimal_tier, "actual_tier": actual_tier},
126
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
training/check_bundle_keys.py DELETED
@@ -1,38 +0,0 @@
1
- """Check what feature keys the v10 router bundle actually expects."""
2
- import pickle
3
- from huggingface_hub import hf_hub_download
4
-
5
- bundle_path = hf_hub_download(
6
- repo_id="narcolepticchicken/agent-cost-optimizer",
7
- filename="router_models/router_bundle_v10_fixed.pkl")
8
-
9
- bundle = pickle.load(open(bundle_path, 'rb'))
10
-
11
- print("Bundle keys:", list(bundle.keys()))
12
- print("Feature keys:", bundle.get('feat_keys', bundle.get('feature_keys', 'NOT FOUND')))
13
- print("Number of features per classifier:")
14
-
15
- for k, clf in bundle.get('tier_clfs', {}).items():
16
- if hasattr(clf, 'n_features_in_'):
17
- print(f" Tier {k}: {clf.n_features_in_} features")
18
- elif hasattr(clf, 'get_booster'):
19
- try:
20
- print(f" Tier {k}: XGBoost, feature_names={clf.get_booster().feature_names[:5]}...")
21
- except:
22
- print(f" Tier {k}: XGBoost, can't read features")
23
- else:
24
- print(f" Tier {k}: unknown type {type(clf).__name__}")
25
-
26
- # Also check the available feature key files
27
- print("\nRouter models directory:")
28
- import os
29
- for f in os.listdir(os.path.dirname(bundle_path)):
30
- if 'feat' in f.lower() or 'key' in f.lower():
31
- print(f" {f}")
32
- # Also check the bundle files for feat_keys
33
- if f.endswith('.pkl'):
34
- b2 = pickle.load(open(os.path.join(os.path.dirname(bundle_path), f), 'rb'))
35
- if isinstance(b2, dict):
36
- for bk in b2:
37
- if 'feat' in str(bk).lower() or 'key' in str(bk).lower():
38
- print(f" {f} → {bk}: {b2[bk]}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
training/debug_sprout_parsing.py DELETED
@@ -1,83 +0,0 @@
1
- #!/usr/bin/env python3
2
- """DEBUG: Inspect SPROUT parsing failures to understand the 11,610 parse errors."""
3
- import json
4
- from datasets import load_dataset
5
-
6
- print("DEBUG: SPROUT parsing inspection")
7
- ds = load_dataset("CARROT-LLM-Routing/SPROUT", split="train")
8
- print(f"Total rows: {len(ds)}")
9
-
10
- MODEL_COLS = [
11
- "aws-claude-3-5-sonnet-v1", "openai-gpt-4o", "openai-gpt-4o-mini",
12
- "aws-titan-text-premier-v1", "wxai-granite-3-2b-instruct-8k-max-tokens",
13
- "wxai-granite-3-8b-instruct-8k-max-tokens", "wxai-llama-3-1-70b-instruct",
14
- "wxai-llama-3-1-8b-instruct", "wxai-llama-3-2-1b-instruct",
15
- "wxai-llama-3-2-3b-instruct", "wxai-llama-3-3-70b-instruct",
16
- "wxai-llama-3-405b-instruct", "wxai-mixtral-8x7b-instruct-v01",
17
- ]
18
-
19
- # Inspect first 50 rows and categorize failures
20
- error_types = {}
21
- success_count = 0
22
- for col in MODEL_COLS:
23
- for idx in range(min(100, len(ds))):
24
- val = ds[idx][col]
25
- if isinstance(val, dict):
26
- jr = val.get("judge_response", "")
27
- if isinstance(jr, str) and jr.strip():
28
- try:
29
- parsed = json.loads(jr)
30
- score = parsed.get("correctness_score")
31
- if score is not None:
32
- success_count += 1
33
- else:
34
- error_types.setdefault("no_correctness_score", []).append(col)
35
- except json.JSONDecodeError as e:
36
- error_types.setdefault(f"json_error", []).append((col, str(e)[:80], repr(jr)[:100]))
37
- elif isinstance(jr, dict):
38
- score = jr.get("correctness_score")
39
- if score is not None:
40
- success_count += 1
41
- else:
42
- error_types.setdefault("dict_no_score", []).append(col)
43
- elif jr == "" or jr is None:
44
- error_types.setdefault("empty_judge_response", []).append(col)
45
- else:
46
- error_types.setdefault(f"unexpected_jr_type:{type(jr).__name__}", []).append((col, repr(jr)[:100]))
47
- elif isinstance(val, str):
48
- try:
49
- parsed = json.loads(val)
50
- jr = parsed.get("judge_response", "")
51
- if isinstance(jr, str):
52
- jr_parsed = json.loads(jr)
53
- score = jr_parsed.get("correctness_score")
54
- if score is not None:
55
- success_count += 1
56
- else:
57
- error_types.setdefault("str_no_score", []).append(col)
58
- elif isinstance(jr, dict):
59
- score = jr.get("correctness_score")
60
- if score is not None:
61
- success_count += 1
62
- except json.JSONDecodeError as e:
63
- error_types.setdefault("str_json_error", []).append((col, str(e)[:80]))
64
- elif val is None:
65
- error_types.setdefault("null_value", []).append(col)
66
- else:
67
- error_types.setdefault(f"unexpected_type:{type(val).__name__}", []).append(col)
68
-
69
- print(f"\nSuccesses (first 100 rows): {success_count}")
70
- print(f"\nError categories:")
71
- for cat, examples in error_types.items():
72
- print(f" {cat}: {len(examples)}")
73
- if examples:
74
- print(f" Examples: {examples[:3]}")
75
-
76
- # Also dump the raw value for a specific failing case
77
- print("\n\n--- Raw value types (first 5 rows, first 3 models) ---")
78
- for idx in range(5):
79
- row = ds[idx]
80
- print(f"\nRow {idx} (key={row['key'][:20]}...):")
81
- for col in MODEL_COLS[:3]:
82
- val = row[col]
83
- print(f" {col}: type={type(val).__name__}, {str(val)[:120]}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
training/execution_feedback_loop.py DELETED
@@ -1,340 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Execution Feedback Loop: Route → Execute → Learn.
3
-
4
- Combines the BERT 5-class router with real SWE-bench execution outcomes.
5
- This is the "real execution feedback" the final report says is missing.
6
-
7
- Flow:
8
- 1. BERT router selects optimal tier for each SWE-bench task
9
- 2. Map tier → actual SWE-Router model, look up execution outcome
10
- 3. If cheap model fails: escalate to next tier, record feedback
11
- 4. Update routing thresholds using collected feedback
12
- 5. Compare: BERT, BERT+feedback, XGBoost v10, frontier, oracle
13
-
14
- Key insight from BAAR (2026): profile with small model, then decide.
15
- We extend: route, execute, observe outcome, feed back to routing policy.
16
- """
17
- import json, sys, numpy as np
18
- from collections import defaultdict
19
- from datasets import load_dataset
20
- from transformers import AutoTokenizer, AutoModelForSequenceClassification
21
- import torch
22
- import pickle
23
-
24
- MODELS = [
25
- "claude-opus-4.7", "gpt-5-mini", "gpt-5-nano", "gpt-5.2",
26
- "gemini-2.5-pro", "gemini-3-pro", "deepseek-v3.2", "deepseek-v4-flash",
27
- ]
28
- MODEL_TIER = {
29
- "deepseek-v4-flash": 1, "gpt-5-nano": 1,
30
- "gpt-5-mini": 2, "deepseek-v3.2": 2,
31
- "gemini-2.5-pro": 3,
32
- "claude-opus-4.7": 4, "gpt-5.2": 4,
33
- "gemini-3-pro": 5,
34
- }
35
- TIER_COST = {1: 0.01, 2: 0.05, 3: 0.15, 4: 0.30, 5: 0.50}
36
- TIER_TO_SWE = {
37
- 1: "deepseek-v4-flash", 2: "gpt-5-mini",
38
- 3: "gemini-2.5-pro", 4: "claude-opus-4.7", 5: "gemini-3-pro",
39
- }
40
-
41
- print("=" * 70)
42
- print("EXECUTION FEEDBACK LOOP: BERT Route → Execute → Learn")
43
- print("=" * 70)
44
-
45
- # ── 1. Load BERT 5-class router ─────────────────────────────────
46
- print("\n[1] Loading BERT 5-class router...")
47
- try:
48
- tokenizer = AutoTokenizer.from_pretrained(
49
- "narcolepticchicken/agent-cost-optimizer",
50
- subfolder="router_models/bert_5class",
51
- )
52
- model = AutoModelForSequenceClassification.from_pretrained(
53
- "narcolepticchicken/agent-cost-optimizer",
54
- subfolder="router_models/bert_5class",
55
- )
56
- print(" ✓ Loaded BERT 5-class from Hub")
57
- except Exception as e:
58
- print(f" ✗ Hub load failed: {e}")
59
- print(" Using distilbert-base-uncased (untrained, random weights)...")
60
- tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")
61
- model = AutoModelForSequenceClassification.from_pretrained(
62
- "distilbert-base-uncased", num_labels=5
63
- )
64
-
65
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
66
- model = model.to(device)
67
- model.eval()
68
-
69
- def bert_predict(problem_text):
70
- """BERT 5-class prediction: returns (predicted_tier, tier_probs_dict)."""
71
- enc = tokenizer(
72
- problem_text[:2000], truncation=True, max_length=512, return_tensors="pt"
73
- )
74
- enc = {k: v.to(device) for k, v in enc.items()}
75
- with torch.no_grad():
76
- logits = model(**enc).logits
77
- probs = torch.softmax(logits, dim=-1)[0].cpu().numpy()
78
- predicted_class = int(np.argmax(probs))
79
- predicted_tier = predicted_class + 1
80
- tier_probs = {t: float(probs[t - 1]) for t in range(1, 6)}
81
- return predicted_tier, tier_probs
82
-
83
- # ── 2. Load SWE-Router execution data ────────────────────────────
84
- print("\n[2] Loading SWE-Router execution data...")
85
- traces = defaultdict(dict)
86
- for model_name in MODELS:
87
- ds = load_dataset(f"SWE-Router/swebench-verified-{model_name}", split="test")
88
- for row in ds:
89
- iid = row["instance_id"]
90
- traces[iid][model_name] = {
91
- "resolved": row["resolved"],
92
- "cost": float(row["instance_cost"]),
93
- "api_calls": int(row["api_calls"]),
94
- "problem": row["problem_statement"],
95
- }
96
- print(f" {model_name}: loaded")
97
-
98
- n_tasks = len(traces)
99
- print(f" Total tasks: {n_tasks}")
100
-
101
- # ── 3. Load XGBoost v10 router for comparison ────────────────────
102
- print("\n[3] Loading XGBoost v10 router...")
103
- try:
104
- with open("/app/router_models/router_bundle_v10.pkl", "rb") as f:
105
- v10_bundle = pickle.load(f)
106
- print(f" ✓ Loaded v10 XGBoost ({v10_bundle['version']})")
107
- HAS_V10 = True
108
- except Exception as e:
109
- print(f" ✗ v10 bundle failed: {e} — trying v10_fixed")
110
- try:
111
- with open("/app/router_models/router_bundle_v10_fixed.pkl", "rb") as f:
112
- v10_bundle = pickle.load(f)
113
- print(f" ✓ Loaded v10_fixed")
114
- HAS_V10 = True
115
- except Exception as e2:
116
- print(f" ✗ Also failed: {e2} — skipping XGBoost comparison")
117
- HAS_V10 = False
118
-
119
- # ── 4. Run routing policies ──────────────────────────────────────
120
- print("\n[4] Running routing policies on all SWE-bench tasks...")
121
-
122
- policies = defaultdict(lambda: {"success": 0, "cost": 0.0, "escalations": 0, "n": 0})
123
-
124
- for iid, model_results in traces.items():
125
- problem = next(iter(model_results.values()))["problem"]
126
-
127
- # ──�� BERT direct ───
128
- bt, bp = bert_predict(problem)
129
- bm = TIER_TO_SWE.get(bt, "claude-opus-4.7")
130
- if bm in model_results:
131
- policies["bert_direct"]["success"] += int(model_results[bm]["resolved"])
132
- policies["bert_direct"]["cost"] += model_results[bm]["cost"]
133
- policies["bert_direct"]["n"] += 1
134
-
135
- # ─── BERT + feedback cascade ───
136
- try_tier = bt
137
- total_cost = 0.0
138
- success = False
139
- esc = 0
140
- while try_tier <= 5:
141
- tm = TIER_TO_SWE.get(try_tier, "claude-opus-4.7")
142
- if tm in model_results:
143
- total_cost += model_results[tm]["cost"]
144
- if model_results[tm]["resolved"]:
145
- success = True
146
- break
147
- try_tier += 1
148
- esc += 1
149
- policies["bert_feedback"]["success"] += int(success)
150
- policies["bert_feedback"]["cost"] += total_cost
151
- policies["bert_feedback"]["escalations"] += esc
152
- policies["bert_feedback"]["n"] += 1
153
-
154
- # ─── BERT cascade threshold ───
155
- cascade_tier = 5
156
- for t in range(1, 6):
157
- if bp[t] >= 0.3:
158
- cascade_tier = t
159
- break
160
- cm = TIER_TO_SWE.get(cascade_tier, "claude-opus-4.7")
161
- if cm in model_results:
162
- policies["bert_cascade"]["success"] += int(model_results[cm]["resolved"])
163
- policies["bert_cascade"]["cost"] += model_results[cm]["cost"]
164
- policies["bert_cascade"]["n"] += 1
165
-
166
- # ─── XGBoost v10 direct (if available) ───
167
- if HAS_V10:
168
- feat_keys = v10_bundle["feat_keys"]
169
- from collections import Counter
170
-
171
- # Extract features
172
- r = problem.lower()
173
- CODE_KW = ["python","javascript","code","function","bug","debug","refactor","implement","test",
174
- "compile","runtime","segfault","thread","async","class","module","import","error","traceback"]
175
- LEGAL_KW = ["contract","legal","compliance","gdpr","privacy","policy","regulatory","liability"]
176
- RESEARCH_KW = ["research","investigate","compare","analyze","survey","paper"]
177
- TOOL_KW = ["search","fetch","retrieve","query","api","database","scrape","aggregate"]
178
- CRITICAL_KW = ["critical","production","urgent","emergency","live","deployed","safety","security"]
179
- SIMPLE_KW = ["typo","simple","quick","brief","minor","small","easy","trivial","just"]
180
- LONG_KW = ["plan","project","roadmap","orchestrate","migrate","pipeline","deploy","architecture"]
181
- MATH_KW = ["calculate","compute","solve","equation","formula","optimize","probability"]
182
-
183
- feats = {
184
- 'req_len': len(problem), 'num_words': len(problem.split()),
185
- 'has_code': int(any(k in r for k in CODE_KW)),
186
- 'n_code': sum(1 for k in CODE_KW if k in r),
187
- 'has_legal': int(any(k in r for k in LEGAL_KW)),
188
- 'has_research': int(any(k in r for k in RESEARCH_KW)),
189
- 'has_tool': int(any(k in r for k in TOOL_KW)),
190
- 'has_critical': int(any(k in r for k in CRITICAL_KW)),
191
- 'has_simple': int(any(k in r for k in SIMPLE_KW)),
192
- 'has_long': int(any(k in r for k in LONG_KW)),
193
- 'has_math': int(any(k in r for k in MATH_KW)),
194
- 'has_error_msg': int('error' in r or 'traceback' in r),
195
- 'has_file_path': int('/' in r and ('.' in r.split('/')[0] if '/' in r else False)),
196
- 'n_lines': problem.count('\n') + 1,
197
- 'has_version': int('version' in r or 'update' in r),
198
- 'has_add': int('add' in r or 'new' in r or 'create' in r),
199
- 'has_fix': int('fix' in r or 'bug' in r or 'issue' in r),
200
- 'has_change': int('change' in r or 'modify' in r),
201
- 'has_remove': int('remove' in r or 'delete' in r),
202
- 'has_test': int('test' in r or 'spec' in r or 'assert' in r),
203
- 'has_doc': int('doc' in r or 'readme' in r),
204
- 'has_see_also': int('see also' in r or 'related' in r),
205
- 'has_steps_to_reproduce': int('steps to reproduce' in r or 'reproduce' in r),
206
- }
207
- feat_vec = np.array([feats.get(k, 0.0) for k in feat_keys], dtype=np.float32).reshape(1, -1)
208
-
209
- opt_clf = v10_bundle["opt_clf"]
210
- xgb_tier = int(opt_clf.predict(feat_vec)[0]) + 1
211
- xgb_model = TIER_TO_SWE.get(xgb_tier, "claude-opus-4.7")
212
- if xgb_model in model_results:
213
- policies["xgb_direct"]["success"] += int(model_results[xgb_model]["resolved"])
214
- policies["xgb_direct"]["cost"] += model_results[xgb_model]["cost"]
215
- policies["xgb_direct"]["n"] += 1
216
-
217
- # ─── Always frontier ───
218
- fm = "claude-opus-4.7"
219
- policies["frontier"]["success"] += int(model_results[fm]["resolved"])
220
- policies["frontier"]["cost"] += model_results[fm]["cost"]
221
- policies["frontier"]["n"] += 1
222
-
223
- # ─── Always cheap ───
224
- cm_cheap = "deepseek-v4-flash"
225
- if cm_cheap in model_results:
226
- policies["always_cheap"]["success"] += int(model_results[cm_cheap]["resolved"])
227
- policies["always_cheap"]["cost"] += model_results[cm_cheap]["cost"]
228
- policies["always_cheap"]["n"] += 1
229
-
230
- # ─── Oracle ───
231
- resolved = [(m, r) for m, r in model_results.items() if r["resolved"]]
232
- if resolved:
233
- cheapest = min(resolved, key=lambda x: TIER_COST.get(MODEL_TIER[x[0]], 1.0))
234
- policies["oracle"]["success"] += 1
235
- policies["oracle"]["cost"] += cheapest[1]["cost"]
236
- else:
237
- policies["oracle"]["cost"] += min(r["cost"] for r in model_results.values())
238
- policies["oracle"]["n"] += 1
239
-
240
- # ── 5. Results ───────────────────────────────────────────────────
241
- fr_cost = policies["frontier"]["cost"] / max(policies["frontier"]["n"], 1)
242
-
243
- print(f"\n{'='*70}")
244
- print("FINAL RESULTS: BERT 5-CLASS + EXECUTION FEEDBACK ON SWE-BENCH")
245
- print(f"{'='*70}")
246
- print(f"\n{'Policy':<25} {'Success':>10} {'AvgCost':>10} {'CostRed':>10} {'Esc':>8}")
247
- print("-" * 65)
248
-
249
- names = ["oracle"]
250
- if HAS_V10: names.append("xgb_direct")
251
- names += ["bert_feedback", "bert_cascade", "bert_direct", "frontier", "always_cheap"]
252
-
253
- for name in names:
254
- r = policies[name]
255
- sr = r["success"] / max(r["n"], 1)
256
- ac = r["cost"] / max(r["n"], 1)
257
- cr = (1 - ac / max(fr_cost, 0.0001)) * 100
258
- esc = r.get("escalations", 0) / max(r["n"], 1)
259
- print(f" {name:<23} {sr:>10.3f} {ac:>10.4f} {cr:>9.1f}% {esc:>8.2f}")
260
-
261
- # ── 6. Detailed feedback analysis ────────────────────────────────
262
- print(f"\n{'='*70}")
263
- print("FEEDBACK ANALYSIS")
264
- print(f"{'='*70}")
265
-
266
- correct_cheapest = 0
267
- over_estimated = 0
268
- under_estimated = 0
269
-
270
- for iid, model_results in traces.items():
271
- problem = next(iter(model_results.values()))["problem"]
272
- bt, bp = bert_predict(problem)
273
-
274
- actual_cheapest = 5
275
- for tier in range(1, 6):
276
- tm = TIER_TO_SWE.get(tier)
277
- if tm and tm in model_results and model_results[tm]["resolved"]:
278
- actual_cheapest = tier
279
- break
280
-
281
- if bt == actual_cheapest:
282
- correct_cheapest += 1
283
- elif bt > actual_cheapest:
284
- over_estimated += 1
285
- else:
286
- under_estimated += 1
287
-
288
- print(f" BERT == actual cheapest tier: {correct_cheapest}/{n_tasks} = {correct_cheapest/n_tasks*100:.1f}%")
289
- print(f" BERT over-estimated (conservative): {over_estimated}/{n_tasks} = {over_estimated/n_tasks*100:.1f}%")
290
- print(f" BERT under-estimated (risky): {under_estimated}/{n_tasks} = {under_estimated/n_tasks*100:.1f}%")
291
-
292
- # Per-tier prediction distribution
293
- print(f"\n BERT tier prediction distribution:")
294
- tier_pred_counts = defaultdict(int)
295
- for iid, model_results in traces.items():
296
- problem = next(iter(model_results.values()))["problem"]
297
- bt, _ = bert_predict(problem)
298
- tier_pred_counts[bt] += 1
299
- for t in range(1, 6):
300
- print(f" Tier {t}: {tier_pred_counts[t]} ({tier_pred_counts[t]/n_tasks*100:.1f}%)")
301
-
302
- # ── 7. Save results ──────────────────────────────────────────────
303
- results = {
304
- "router": "BERT 5-class v3 + execution feedback",
305
- "n_tasks": n_tasks,
306
- "policies": {
307
- name: {
308
- "success": r["success"],
309
- "avg_cost": round(r["cost"] / max(r["n"], 1), 4),
310
- "success_rate": round(r["success"] / max(r["n"], 1), 4),
311
- "cost_reduction_pct": round(
312
- (1 - (r["cost"] / max(r["n"], 1)) / max(fr_cost, 0.0001)) * 100, 1
313
- ),
314
- }
315
- for name, r in policies.items()
316
- },
317
- "feedback_analysis": {
318
- "correct_cheapest_pct": round(correct_cheapest / n_tasks * 100, 1),
319
- "over_estimated_pct": round(over_estimated / n_tasks * 100, 1),
320
- "under_estimated_pct": round(under_estimated / n_tasks * 100, 1),
321
- "tier_prediction_distribution": dict(tier_pred_counts),
322
- },
323
- }
324
- with open("/app/bert_feedback_results.json", "w") as f:
325
- json.dump(results, f, indent=2)
326
- print(f"\n ✓ Saved to /app/bert_feedback_results.json")
327
-
328
- from huggingface_hub import HfApi
329
- api = HfApi()
330
- api.upload_file(
331
- path_or_fileobj="/app/bert_feedback_results.json",
332
- path_in_repo="eval/bert_feedback_results.json",
333
- repo_id="narcolepticchicken/agent-cost-optimizer",
334
- repo_type="model",
335
- )
336
- print(f" ✓ Uploaded to Hub")
337
-
338
- print(f"\n{'='*70}")
339
- print("DONE! Execution feedback loop complete.")
340
- print("=" * 70)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
training/integration_real.py DELETED
@@ -1,362 +0,0 @@
1
- """Real ACO Integration Test with smolagents CodeAgent.
2
-
3
- Connects the ACO optimizer to a real smolagents CodeAgent running on
4
- SWE-bench tasks. The optimizer intercepts:
5
- - Model selection (route to tier)
6
- - Tool calls (gate unnecessary tools)
7
- - Retries (optimize recovery)
8
- - Verification (selective verifier)
9
- - Doom detection (early termination)
10
-
11
- This is a real integration test — it runs actual LLM calls and tool
12
- executions, not simulated ones.
13
-
14
- Usage:
15
- uv run --with smolagents --with transformers --with torch --with datasets
16
- --with scikit-learn --with numpy --with xgboost --with huggingface_hub
17
- integration_real.py
18
-
19
- Requirements: HF_TOKEN set, openai or hf-inference access.
20
- """
21
- import json, os, sys, time, traceback
22
- from typing import Dict, List, Optional, Any
23
- from dataclasses import dataclass
24
-
25
- # ── ACO Import ──────────────────────────────────────────────────
26
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
27
- from aco.optimizer import ACOOptimizer
28
- from aco.config import ACOConfig, RoutingPolicy
29
-
30
- # ── smolagents Import ───────────────────────────────────────────
31
- from smolagents import CodeAgent, LiteLLMModel, tool, ToolCallingAgent
32
- from datasets import load_dataset
33
-
34
- print("=" * 70)
35
- print("ACO + SMOLAGENTS REAL INTEGRATION TEST")
36
- print("=" * 70)
37
-
38
- # ── 1. Set up models ────────────────────────────────────────────
39
- # We configure models as LiteLLM targets that the ACO can route between
40
-
41
- TIER_MODELS = {
42
- 1: "openai/gpt-4o-mini", # cheap, fast
43
- 2: "openai/gpt-4o", # medium
44
- 3: "anthropic/claude-3-5-sonnet", # strong
45
- 4: "anthropic/claude-3-opus", # frontier
46
- }
47
-
48
- TIER_COST_PER_1K = {
49
- 1: 0.00015, # gpt-4o-mini input
50
- 2: 0.0025, # gpt-4o input
51
- 3: 0.003, # claude sonnet
52
- 4: 0.015, # claude opus
53
- }
54
-
55
- # ── 2. Set up ACO ───────────────────────────────────────────────
56
- config = ACOConfig()
57
- config.routing_policy = RoutingPolicy(
58
- routing_mode="cascade",
59
- feedback_escalation=True,
60
- max_retries=2,
61
- max_cost_per_task=2.0,
62
- safety_threshold=0.5,
63
- )
64
- config.enable_doom_detector = True
65
- config.enable_meta_tools = False
66
-
67
- # Download router bundle
68
- from huggingface_hub import hf_hub_download
69
- try:
70
- bundle_path = hf_hub_download(
71
- repo_id="narcolepticchicken/agent-cost-optimizer",
72
- filename="router_models/router_bundle_v10_fixed.pkl",
73
- )
74
- config.router_model_path = bundle_path
75
- print(f"✓ Router model loaded from Hub")
76
- except Exception as e:
77
- print(f"⚠ Router bundle not available: {e}")
78
- config.router_model_path = None
79
-
80
- optimizer = ACOOptimizer(config)
81
-
82
- # ── 3. ACO-Aware Model Provider ────────────────────────────────
83
- # This is the key integration: instead of the agent having one model,
84
- # ACO decides which model to use at each agent step.
85
-
86
- class ACOModelProvider:
87
- """Wraps LiteLLM with ACO routing decisions."""
88
-
89
- def __init__(self, optimizer: ACOOptimizer):
90
- self.optimizer = optimizer
91
- self.current_model = None
92
- self.current_tier = None
93
- self.step_count = 0
94
- self.total_cost = 0.0
95
- self.total_tokens = 0
96
- self.cached_models = {}
97
-
98
- def get_model(self, task_description: str, is_first_call: bool = False) -> LiteLLMModel:
99
- """Get the optimal model for the current context."""
100
- # ACO decides which model
101
- if is_first_call or self.current_model is None:
102
- start = self.optimizer.start_run(task_description)
103
- tier = start["routing"]["tier"]
104
- model_id = TIER_MODELS.get(tier, TIER_MODELS[4])
105
- self.current_tier = tier
106
- self.current_model = model_id
107
- print(f" ACO routed to: {model_id} (tier {tier}, "
108
- f"confidence={start['routing']['confidence']:.2f})")
109
-
110
- # Create/reuse LiteLLM model
111
- if self.current_model not in self.cached_models:
112
- self.cached_models[self.current_model] = LiteLLMModel(
113
- model_id=self.current_model,
114
- api_key=os.environ.get("OPENAI_API_KEY"),
115
- )
116
-
117
- return self.cached_models[self.current_model]
118
-
119
- def escalate(self, reason: str):
120
- """Escalate to a stronger model."""
121
- if self.current_tier < 4:
122
- self.current_tier += 1
123
- self.current_model = TIER_MODELS[self.current_tier]
124
- # Clear cache to force new model
125
- if self.current_model in self.cached_models:
126
- del self.cached_models[self.current_model]
127
- print(f" ⬆ Escalated to tier {self.current_tier}: {self.current_model}")
128
- print(f" Reason: {reason}")
129
-
130
- def record_step(self, input_tokens: int, output_tokens: int, success: bool,
131
- tool_calls: List = None):
132
- """Record telemetry for a completed step."""
133
- self.step_count += 1
134
- cost = input_tokens * TIER_COST_PER_1K.get(self.current_tier, 0.003) / 1000
135
- cost += output_tokens * TIER_COST_PER_1K.get(self.current_tier, 0.015) / 1000
136
- self.total_cost += cost
137
- self.total_tokens += input_tokens + output_tokens
138
-
139
- self.optimizer.record_step(
140
- model_call={
141
- "model_id": self.current_model,
142
- "input_tokens": input_tokens,
143
- "output_tokens": output_tokens,
144
- "cost": cost,
145
- "latency_ms": 2000,
146
- },
147
- tool_calls=tool_calls or [],
148
- context_size=input_tokens + output_tokens,
149
- retry_num=0,
150
- )
151
-
152
- # Check for doom
153
- doom = self.optimizer.check_doom(current_cost=self.total_cost)
154
- if doom.doomed:
155
- print(f" ⚠ DOOM DETECTED: {doom.reasoning}")
156
- return doom
157
-
158
- return None
159
-
160
- # ── 4. Define Agent with ACO integration ─────────────────────────
161
- # We use smolagents CodeAgent which can write and execute Python code
162
-
163
- # ACO-gated tools with cost tracking
164
- class ACOGatedTools:
165
- def __init__(self, optimizer, provider):
166
- self.optimizer = optimizer
167
- self.provider = provider
168
- self.call_count = {}
169
- self.skips = 0
170
-
171
- def should_call(self, tool_name: str) -> bool:
172
- """Gate a tool call through ACO."""
173
- decision = self.optimizer.gate_tool(tool_name, {})
174
- if decision.action == "skip":
175
- self.skips += 1
176
- return False
177
- self.call_count[tool_name] = self.call_count.get(tool_name, 0) + 1
178
- return True
179
-
180
- # Define tools
181
- class AgentTools:
182
- """Real SWE-bench tools for the agent."""
183
-
184
- def __init__(self, repo_path: str = "/tmp/repo"):
185
- self.repo_path = repo_path
186
- self.file_contents = {}
187
- self.exec_results = []
188
-
189
- def read_file(self, path: str) -> str:
190
- """Read a file from the repository."""
191
- full_path = os.path.join(self.repo_path, path)
192
- if os.path.exists(full_path):
193
- with open(full_path, 'r') as f:
194
- return f.read()
195
- return f"ERROR: File not found: {path}"
196
-
197
- def write_file(self, path: str, content: str) -> str:
198
- """Write content to a file."""
199
- full_path = os.path.join(self.repo_path, path)
200
- os.makedirs(os.path.dirname(full_path), exist_ok=True)
201
- with open(full_path, 'w') as f:
202
- f.write(content)
203
- return f"Successfully wrote to {path}"
204
-
205
- def execute_command(self, command: str) -> str:
206
- """Execute a shell command."""
207
- import subprocess
208
- try:
209
- result = subprocess.run(
210
- command, shell=True, capture_output=True, text=True,
211
- cwd=self.repo_path, timeout=60
212
- )
213
- return result.stdout + result.stderr
214
- except subprocess.TimeoutExpired:
215
- return "ERROR: Command timed out"
216
- except Exception as e:
217
- return f"ERROR: {e}"
218
-
219
- # ── 5. Run on SWE-bench tasks ───────────────────────────────────
220
- print("\n[5] Running on SWE-bench Verified tasks...")
221
-
222
- # Load SWE-bench tasks
223
- try:
224
- swe_dataset = load_dataset("princeton-nlp/SWE-bench_Verified", split="test")
225
- tasks = list(swe_dataset.select(range(3))) # Start with 3 tasks
226
- print(f" Loaded {len(tasks)} SWE-bench tasks")
227
- except Exception as e:
228
- print(f" ⚠ SWE-bench not accessible: {e}")
229
- # Fallback: mock task
230
- tasks = [{"instance_id": "mock-task", "problem_statement": "Fix the bug in the calculator: 2+2 should return 4, but returns 5."}]
231
-
232
- results = []
233
- for i, task in enumerate(tasks):
234
- print(f"\n{'─'*60}")
235
- print(f"TASK {i+1}/{len(tasks)}: {task.get('instance_id', 'unknown')}")
236
- print(f" Problem: {task.get('problem_statement', task.get('problem', 'N/A'))[:150]}...")
237
-
238
- # Initialize ACO
239
- provider = ACOModelProvider(optimizer)
240
- tools = AgentTools()
241
-
242
- start_time = time.time()
243
-
244
- try:
245
- # Create agent with ACO-routed model
246
- model = provider.get_model(task["problem_statement"], is_first_call=True)
247
-
248
- agent = CodeAgent(
249
- tools=[tools.read_file, tools.write_file, tools.execute_command],
250
- model=model,
251
- max_steps=5, # Limit steps for demo
252
- )
253
-
254
- # Run the agent
255
- prompt = f"""You are a software engineer fixing a bug.
256
-
257
- Problem: {task['problem_statement']}
258
-
259
- Please analyze the problem, find the bug, and fix it.
260
- Use the available tools to read files, write fixes, and test them.
261
- """
262
-
263
- result = agent.run(prompt)
264
-
265
- elapsed = time.time() - start_time
266
-
267
- print(f" Result: {str(result)[:200]}...")
268
- print(f" Time: {elapsed:.1f}s")
269
- print(f" Cost: ${provider.total_cost:.4f}")
270
- print(f" Tokens: {provider.total_tokens}")
271
- print(f" Steps: {provider.step_count}")
272
-
273
- results.append({
274
- "task_id": task.get("instance_id", f"task-{i}"),
275
- "success": "error" not in str(result).lower(),
276
- "time_s": round(elapsed, 1),
277
- "cost": round(provider.total_cost, 4),
278
- "tokens": provider.total_tokens,
279
- "steps": provider.step_count,
280
- "tier_used": provider.current_tier,
281
- "model_used": provider.current_model,
282
- "output": str(result)[:500],
283
- })
284
-
285
- # End ACO run
286
- optimizer.end_run(
287
- success="error" not in str(result).lower(),
288
- outcome="completed",
289
- artifacts=[task.get("instance_id", "unknown")],
290
- )
291
-
292
- except Exception as e:
293
- elapsed = time.time() - start_time
294
- print(f" ⚠ ERROR: {e}")
295
- traceback.print_exc()
296
-
297
- results.append({
298
- "task_id": task.get("instance_id", f"task-{i}"),
299
- "success": False,
300
- "time_s": round(elapsed, 1),
301
- "error": str(e),
302
- "cost": provider.total_cost if provider else 0,
303
- "tokens": provider.total_tokens if provider else 0,
304
- })
305
-
306
- optimizer.end_run(
307
- success=False,
308
- outcome="failed",
309
- failure_tags=["agent_error"],
310
- )
311
-
312
- # ── 6. Summary ──────────────────────────────────────────────────
313
- print(f"\n{'='*70}")
314
- print("INTEGRATION TEST RESULTS")
315
- print(f"{'='*70}")
316
-
317
- total_cost = sum(r.get("cost", 0) for r in results)
318
- total_tokens = sum(r.get("tokens", 0) for r in results)
319
- successes = sum(1 for r in results if r.get("success"))
320
- avg_time = sum(r.get("time_s", 0) for r in results) / max(len(results), 1)
321
-
322
- print(f"""
323
- Tasks attempted: {len(results)}
324
- Successes: {successes} ({successes/len(results)*100:.0f}%)
325
- Total cost: ${total_cost:.4f}
326
- Total tokens: {total_tokens:,}
327
- Avg time/task: {avg_time:.1f}s
328
-
329
- ACO Stats:
330
- {json.dumps(optimizer.get_stats(), indent=2)}
331
- """)
332
-
333
- # Save results
334
- with open("/tmp/aco_integration_results.json", "w") as f:
335
- json.dump({
336
- "results": results,
337
- "summary": {
338
- "n": len(results),
339
- "successes": successes,
340
- "total_cost": round(total_cost, 4),
341
- "avg_time": round(avg_time, 1),
342
- "total_tokens": total_tokens,
343
- },
344
- "aco_stats": optimizer.get_stats(),
345
- }, f, indent=2)
346
-
347
- from huggingface_hub import HfApi
348
- api = HfApi()
349
- try:
350
- api.upload_file(
351
- path_or_fileobj="/tmp/aco_integration_results.json",
352
- path_in_repo="eval/integration_test_results.json",
353
- repo_id="narcolepticchicken/agent-cost-optimizer",
354
- repo_type="model",
355
- )
356
- print(" ✓ Results uploaded to Hub")
357
- except Exception as e:
358
- print(f" ⚠ Upload failed: {e}")
359
-
360
- print(f"\n{'='*70}")
361
- print("DONE! Real integration test complete.")
362
- print("=" * 70)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
training/repro_test.py DELETED
@@ -1,190 +0,0 @@
1
- """Reproducibility test: v10+feedback router × 5 runs.
2
-
3
- Tests whether the 84.8% success / 36.4% cost reduction is stable or a fluke.
4
- Uses the v10 XGBoost bundle trained on SWE-Router data with different
5
- random seeds for the calibration/perturbation.
6
-
7
- Also tests sensitivity to the success threshold parameter (0.4, 0.5, 0.6).
8
- """
9
- import json, pickle, random, numpy as np
10
- from collections import defaultdict
11
- from datasets import load_dataset
12
- from sklearn.isotonic import IsotonicRegression
13
-
14
- # ── Router v10 features ─────────────────────────────────────────
15
- CODE_KW = ["python","javascript","code","function","bug","debug","refactor","implement",
16
- "test","compile","runtime","segfault","thread","async","class","module","import","error","traceback"]
17
- CRITICAL_KW = ["critical","production","urgent","emergency","live","deployed","safety","security"]
18
- SIMPLE_KW = ["typo","simple","quick","brief","minor","small","easy","trivial","just"]
19
- RESEARCH_KW = ["research","investigate","compare","analyze","survey","paper"]
20
- TOOL_KW = ["search","fetch","retrieve","query","api","database","scrape","aggregate"]
21
- LONG_KW = ["plan","project","roadmap","orchestrate","migrate","pipeline","deploy","architecture"]
22
-
23
- FEAT_KEYS = sorted(['req_len','num_words','has_code','n_code','has_legal','has_research',
24
- 'has_tool','has_critical','has_simple','has_long','has_math','has_error_msg',
25
- 'has_file_path','n_lines','has_version','has_add','has_fix','has_change',
26
- 'has_remove','has_test','has_doc','has_see_also','has_steps_to_reproduce'])
27
-
28
- TIER_TO_MODEL = {1:'deepseek-v4-flash',2:'gpt-5-mini',3:'gemini-2.5-pro',
29
- 4:'claude-opus-4.7',5:'gemini-3-pro'}
30
- MODELS = ["deepseek-v4-flash","gpt-5-nano","gpt-5-mini","deepseek-v3.2",
31
- "gemini-2.5-pro","claude-opus-4.7","gpt-5.2","gemini-3-pro"]
32
-
33
- def extract(text):
34
- r = text.lower()
35
- return np.array([float({
36
- 'req_len':len(text),'num_words':len(text.split()),
37
- 'has_code':int(any(k in r for k in CODE_KW)),
38
- 'n_code':sum(1 for k in CODE_KW if k in r),
39
- 'has_legal':int(any(k in r for k in["contract","legal","compliance"])),
40
- 'has_research':int(any(k in r for k in RESEARCH_KW)),
41
- 'has_tool':int(any(k in r for k in TOOL_KW)),
42
- 'has_critical':int(any(k in r for k in CRITICAL_KW)),
43
- 'has_simple':int(any(k in r for k in SIMPLE_KW)),
44
- 'has_long':int(any(k in r for k in LONG_KW)),
45
- 'has_math':int(any(k in r for k in["calculate","compute","solve","equation"])),
46
- 'has_error_msg':int('error'in r or'traceback'in r or'exception'in r),
47
- 'has_file_path':int('/'in r),
48
- 'n_lines':text.count('\n')+1,
49
- 'has_version':int('version'in r or'update'in r),
50
- 'has_add':int('add'in r or'new'in r or'create'in r),
51
- 'has_fix':int('fix'in r or'bug'in r or'issue'in r),
52
- 'has_change':int('change'in r or'modify'in r),
53
- 'has_remove':int('remove'in r or'delete'in r),
54
- 'has_test':int('test'in r or'spec'in r),
55
- 'has_doc':int('doc'in r or'readme'in r),
56
- 'has_see_also':int('see also'in r or'related'in r),
57
- 'has_steps_to_reproduce':int('reproduce'in r or'steps'in r),
58
- }.get(k,0)) for k in FEAT_KEYS], dtype=np.float32)
59
-
60
- # ── Load data ────────────────────────────────────────────────────
61
- print("Loading data...")
62
- traces = defaultdict(dict)
63
- for m in MODELS:
64
- ds = load_dataset(f"SWE-Router/swebench-verified-{m}", split="test")
65
- for row in ds:
66
- traces[row["instance_id"]][m] = {"resolved":row["resolved"], "cost":float(row["instance_cost"])}
67
- N = len(traces)
68
- print(f" {N} tasks")
69
-
70
- # ── Load router ──────────────────────────────────────────────────
71
- from huggingface_hub import hf_hub_download
72
- bundle = pickle.load(open(hf_hub_download(
73
- repo_id="narcolepticchicken/agent-cost-optimizer",
74
- filename="router_models/router_bundle_v10_fixed.pkl"), 'rb'))
75
- tier_clfs_raw = {int(k):v for k,v in bundle['tier_clfs'].items()}
76
- print(f" Router: {len(tier_clfs_raw)} tier classifiers")
77
-
78
- # ── Run 5 reproducibility trials ─────────────────────────────────
79
- SEEDS = [42, 123, 456, 789, 1024]
80
- THRESHOLDS = [0.4, 0.5, 0.6]
81
-
82
- print(f"\n{'='*75}")
83
- print(f"REPRODUCIBILITY TEST: v10+feedback × {len(SEEDS)} seeds × {len(THRESHOLDS)} thresholds")
84
- print(f"{'='*75}")
85
-
86
- all_results = []
87
-
88
- for seed in SEEDS:
89
- random.seed(seed)
90
- np.random.seed(seed)
91
-
92
- for thresh in THRESHOLDS:
93
- total_cost = 0.0
94
- resolved = 0
95
- escalated = 0
96
- tier1_only = 0
97
-
98
- for tid, tt in traces.items():
99
- problem = next(iter(tt.values())).get("problem","")
100
- x = extract(problem).reshape(1,-1)
101
-
102
- # Route
103
- route_tier = 5
104
- for t in range(1,6):
105
- if t in tier_clfs_raw:
106
- p = tier_clfs_raw[t].predict_proba(x)[0,1]
107
- if p >= thresh:
108
- route_tier = t
109
- break
110
-
111
- # Execute + escalate
112
- task_cost = 0.0
113
- task_resolved = False
114
- task_escalated = False
115
-
116
- for tier in range(route_tier, 6):
117
- model = TIER_TO_MODEL[tier]
118
- mt = tt.get(model, {})
119
- task_cost += mt.get("cost", 0.30)
120
-
121
- if mt.get("resolved", False):
122
- task_resolved = True
123
- break
124
-
125
- if tier > route_tier:
126
- task_escalated = True
127
-
128
- total_cost += task_cost
129
- resolved += int(task_resolved)
130
- escalated += int(task_escalated)
131
- if route_tier == 1 and not task_escalated:
132
- tier1_only += 1
133
-
134
- avg_cost = total_cost / N
135
- success_rate = resolved / N
136
- frontier_cost = sum(tt.get("claude-opus-4.7",{}).get("cost",0.317) for tt in traces.values()) / N
137
- cost_reduction = (1 - avg_cost / frontier_cost) * 100
138
-
139
- all_results.append({
140
- "seed": seed, "threshold": thresh,
141
- "resolved": resolved, "rate": round(success_rate, 4),
142
- "avg_cost": round(avg_cost, 4), "cost_reduction": round(cost_reduction, 1),
143
- "escalated": escalated, "tier1_only": tier1_only,
144
- })
145
-
146
- # ── Print ────────────────────────────────────────────────────────
147
- print(f"\n{'Seed':<8} {'Thr':>5} {'Resolved':>10} {'Rate':>8} {'AvgCost':>10} {'CostRed':>10} {'Escal.':>8} {'T1Only':>8}")
148
- print("-" * 75)
149
-
150
- for r in all_results:
151
- print(f" {r['seed']:<8} {r['threshold']:>5.1f} {r['resolved']:>10} {r['rate']*100:>7.1f}% ${r['avg_cost']:>9.4f} {r['cost_reduction']:>9.1f}% {r['escalated']:>8} {r['tier1_only']:>8}")
152
-
153
- # Summary stats
154
- rates = [r["rate"] for r in all_results]
155
- costs = [r["avg_cost"] for r in all_results]
156
- crs = [r["cost_reduction"] for r in all_results]
157
-
158
- print(f"\n{'─'*75}")
159
- print(f"SUMMARY:")
160
- print(f" Success rate: {np.mean(rates)*100:.2f}% ± {np.std(rates)*100:.2f}% (range: {np.min(rates)*100:.1f}-{np.max(rates)*100:.1f}%)")
161
- print(f" Avg cost: ${np.mean(costs):.4f} ± ${np.std(costs):.4f}")
162
- print(f" Cost reduction: {np.mean(crs):.1f}% ± {np.std(crs):.1f}%")
163
- print(f" Frontier: 78.2% @ $0.3167")
164
-
165
- # Best per threshold
166
- print(f"\n{'─'*75}")
167
- print(f"BEST PER THRESHOLD:")
168
- for thresh in THRESHOLDS:
169
- subset = [r for r in all_results if r["threshold"] == thresh]
170
- best = max(subset, key=lambda r: r["rate"])
171
- print(f" Threshold {thresh}: {best['rate']*100:.1f}% @ ${best['avg_cost']:.4f} ({best['cost_reduction']:.1f}% reduction) seed={best['seed']}")
172
-
173
- # Best overall
174
- best = max(all_results, key=lambda r: r["cost_reduction"] + r["rate"]*100 - 78.2)
175
- print(f"\n 🏆 BEST OVERALL: seed={best['seed']}, thresh={best['threshold']}")
176
- print(f" {best['rate']*100:.1f}% success @ ${best['avg_cost']:.4f} = {best['cost_reduction']:.1f}% cost reduction")
177
-
178
- # Save
179
- with open("/tmp/repro_results.json","w") as f:
180
- json.dump({"results": all_results, "summary": {
181
- "mean_rate": round(np.mean(rates),4), "std_rate": round(np.std(rates),4),
182
- "mean_cost": round(np.mean(costs),4), "std_cost": round(np.std(costs),4),
183
- "mean_cost_reduction": round(np.mean(crs),1), "std_cost_reduction": round(np.std(crs),1),
184
- }}, f, indent=2)
185
-
186
- from huggingface_hub import HfApi
187
- HfApi().upload_file(path_or_fileobj="/tmp/repro_results.json",
188
- path_in_repo="eval/reproducibility_results.json",
189
- repo_id="narcolepticchicken/agent-cost-optimizer", repo_type="model")
190
- print("\n✓ eval/reproducibility_results.json")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
training/repro_v2.py DELETED
@@ -1,119 +0,0 @@
1
- """Reproducibility test v2: v10+feedback router × 5 seeds.
2
-
3
- Uses the CORRECT 14 features from the v10 fixed bundle.
4
- """
5
- import json, pickle, random, numpy as np
6
- from collections import defaultdict
7
- from datasets import load_dataset
8
-
9
- # ── CORRECT v10 features (14, not 23) ────────────────────────────
10
- FEAT_KEYS = ['has_add', 'has_change', 'has_code', 'has_critical', 'has_doc',
11
- 'has_error_msg', 'has_file_path', 'has_fix', 'has_simple', 'has_test',
12
- 'n_code', 'n_lines', 'num_words', 'req_len']
13
-
14
- CODE_KW = ["python","javascript","code","function","bug","debug","refactor","implement",
15
- "test","compile","runtime","segfault","thread","async","class","module","import","error","traceback"]
16
- CRITICAL_KW = ["critical","production","urgent","emergency","live","deployed","safety","security"]
17
- SIMPLE_KW = ["typo","simple","quick","brief","minor","small","easy","trivial","just"]
18
-
19
- def extract(text):
20
- r = text.lower()
21
- return np.array([float({
22
- 'req_len':len(text),'num_words':len(text.split()),
23
- 'has_code':int(any(k in r for k in CODE_KW)),
24
- 'n_code':sum(1 for k in CODE_KW if k in r),
25
- 'has_critical':int(any(k in r for k in CRITICAL_KW)),
26
- 'has_simple':int(any(k in r for k in SIMPLE_KW)),
27
- 'has_error_msg':int('error'in r or'traceback'in r or'exception'in r),
28
- 'has_file_path':int('/'in r),
29
- 'n_lines':text.count('\n')+1,
30
- 'has_add':int('add'in r or'new'in r or'create'in r),
31
- 'has_fix':int('fix'in r or'bug'in r or'issue'in r),
32
- 'has_change':int('change'in r or'modify'in r),
33
- 'has_test':int('test'in r or'spec'in r),
34
- 'has_doc':int('doc'in r or'readme'in r),
35
- }.get(k,0)) for k in FEAT_KEYS], dtype=np.float32)
36
-
37
- TIER_TO_MODEL = {1:'deepseek-v4-flash',2:'gpt-5-mini',3:'gemini-2.5-pro',
38
- 4:'claude-opus-4.7',5:'gemini-3-pro'}
39
- MODELS = ["deepseek-v4-flash","gpt-5-nano","gpt-5-mini","deepseek-v3.2",
40
- "gemini-2.5-pro","claude-opus-4.7","gpt-5.2","gemini-3-pro"]
41
-
42
- from huggingface_hub import hf_hub_download
43
-
44
- print("Loading data + router...")
45
- traces = defaultdict(dict)
46
- for m in MODELS:
47
- ds = load_dataset(f"SWE-Router/swebench-verified-{m}", split="test")
48
- for row in ds:
49
- traces[row["instance_id"]][m] = {"resolved":row["resolved"], "cost":float(row["instance_cost"])}
50
- N = len(traces)
51
-
52
- bundle = pickle.load(open(hf_hub_download(
53
- repo_id="narcolepticchicken/agent-cost-optimizer",
54
- filename="router_models/router_bundle_v10_fixed.pkl"), 'rb'))
55
- tier_clfs_raw = {int(k):v for k,v in bundle['tier_clfs'].items()}
56
-
57
- print(f" {N} tasks, {len(tier_clfs_raw)} classifiers, {len(FEAT_KEYS)} features")
58
-
59
- # ── Run trials ───────────────────────────────────────────────────
60
- SEEDS = [42, 123, 456, 789, 1024]
61
- THRESHOLDS = [0.4, 0.5, 0.6]
62
-
63
- results = []
64
- for seed in SEEDS:
65
- random.seed(seed); np.random.seed(seed)
66
- for thresh in THRESHOLDS:
67
- tc, res, esc, t1only = 0.0, 0, 0, 0
68
- for tid, tt in traces.items():
69
- problem = next(iter(tt.values())).get("problem","")
70
- x = extract(problem).reshape(1,-1)
71
-
72
- route_tier = 5
73
- for t in range(1,6):
74
- if t in tier_clfs_raw:
75
- p = tier_clfs_raw[t].predict_proba(x)[0,1]
76
- if p >= thresh: route_tier = t; break
77
-
78
- task_cost, task_resolved, task_esc = 0.0, False, False
79
- for tier in range(route_tier, 6):
80
- mt = tt.get(TIER_TO_MODEL[tier], {})
81
- task_cost += mt.get("cost",0.30)
82
- if mt.get("resolved",False): task_resolved = True; break
83
- if tier > route_tier: task_esc = True
84
-
85
- tc += task_cost; res += task_resolved; esc += task_esc
86
- if route_tier == 1 and not task_esc: t1only += 1
87
-
88
- fc = sum(tt.get("claude-opus-4.7",{}).get("cost",0.317) for tt in traces.values())/N
89
- cr = (1-(tc/N)/fc)*100
90
- results.append({"seed":seed,"thresh":thresh,"resolved":res,"rate":res/N,
91
- "avg_cost":tc/N,"cost_red":cr,"escalated":esc,"t1only":t1only})
92
-
93
- # Print
94
- print(f"\n{'Seed':<8} {'Thr':>4} {'Resolved':>10} {'Rate':>8} {'AvgCost':>10} {'CostRed':>9} {'Esc':>6} {'T1':>6}")
95
- print("-"*68)
96
- for r in results:
97
- print(f" {r['seed']:<8} {r['thresh']:.1f} {r['resolved']:>10} {r['rate']*100:>7.1f}% ${r['avg_cost']:>9.4f} {r['cost_red']:>8.1f}% {r['escalated']:>6} {r['t1only']:>6}")
98
-
99
- rates = [r["rate"] for r in results]
100
- costs = [r["avg_cost"] for r in results]
101
- crs = [r["cost_red"] for r in results]
102
- print(f"\n Mean: {np.mean(rates)*100:.1f}% ±{np.std(rates)*100:.1f}% ${np.mean(costs):.4f}±{np.std(costs):.4f} {np.mean(crs):.1f}%±{np.std(crs):.1f}% reduction")
103
-
104
- # Best per threshold
105
- for thresh in THRESHOLDS:
106
- subset = [r for r in results if r["thresh"]==thresh]
107
- best = max(subset, key=lambda r: r["rate"])
108
- print(f" Best@thresh={thresh}: {best['rate']*100:.1f}% @ ${best['avg_cost']:.4f} ({best['cost_red']:.1f}%) seed={best['seed']}")
109
-
110
- best = max(results, key=lambda r: r["cost_red"]+r["rate"]*100-78.2)
111
- print(f"\n BEST: {best['rate']*100:.1f}% @ ${best['avg_cost']:.4f} ({best['cost_red']:.1f}% reduction)")
112
-
113
- with open("/tmp/repro_v2.json","w") as f:
114
- json.dump({"results":results,"means":{"rate":np.mean(rates),"cost":np.mean(costs),"cost_red":np.mean(crs)}},f,indent=2)
115
- from huggingface_hub import HfApi
116
- HfApi().upload_file(path_or_fileobj="/tmp/repro_v2.json",
117
- path_in_repo="eval/repro_v2.json",
118
- repo_id="narcolepticchicken/agent-cost-optimizer", repo_type="model")
119
- print("\n✓ eval/repro_v2.json")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
training/router_integration_benchmark.py DELETED
@@ -1,237 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Standalone integration: Replace heuristic router with trained XGBoost router.
4
-
5
- This script:
6
- 1. Generates 2K eval traces (same as standalone_eval_v2.py)
7
- 2. Runs the full benchmark with the TRAINED router replacing _route_learned()
8
- 3. Compares: heuristic, trained, and oracle routers
9
- """
10
-
11
- import json, os, sys, random, uuid, pickle
12
- import numpy as np
13
- from datetime import datetime, timedelta
14
- from dataclasses import dataclass, field
15
- from enum import Enum
16
- from collections import defaultdict
17
- from typing import Dict, List, Optional, Any
18
-
19
- # ─── Load trained models ─────────────────────────────────────────────
20
- print("="*80)
21
- print("ACO TRAINED ROUTER - INTEGRATION BENCHMARK")
22
- print("="*80)
23
-
24
- import xgboost as xgb
25
-
26
- MODEL_DIR = "/app/router_models"
27
- feat_keys = json.load(open(f"{MODEL_DIR}/feat_keys.json"))
28
- tier_config = json.load(open(f"{MODEL_DIR}/tier_config.json"))
29
- TIER_COST = {int(k):v for k,v in tier_config["tier_cost"].items()}
30
- TIER_STR = {int(k):v for k,v in tier_config["tier_str"].items()}
31
- TASK_FLOOR = tier_config["task_floor"]
32
-
33
- print(f"\n[1] Loading trained router models...")
34
- tier_clfs = {}
35
- for tier in range(1, 6):
36
- clf = xgb.XGBClassifier()
37
- clf.load_model(f"{MODEL_DIR}/tier_{tier}_success.json")
38
- tier_clfs[tier] = clf
39
- print(f" Loaded tier_{tier}_success.json")
40
-
41
- # ─── Feature extraction (must match training) ────────────────────────
42
- TASK_TYPES = ["quick_answer","coding","research","document_drafting",
43
- "legal_regulated","tool_heavy","retrieval_heavy",
44
- "long_horizon","unknown_ambiguous"]
45
- TT2IDX = {t:i for i,t in enumerate(TASK_TYPES)}
46
-
47
- CODE_KW = ["python","javascript","code","function","bug","debug","refactor",
48
- "implement","test","compile","runtime","class","module","async","thread"]
49
- LEGAL_KW = ["contract","legal","compliance","gdpr","privacy","policy","regulatory","liability"]
50
- RESEARCH_KW = ["research","find sources","literature","investigate","compare","analyze","survey"]
51
- TOOL_KW = ["search","fetch","retrieve","query","api","database","scrape","aggregate"]
52
- LONG_KW = ["plan","project","roadmap","orchestrate","multi-step","migrate","pipeline","deploy"]
53
- MATH_KW = ["calculate","compute","solve","equation","formula","optimize","probability"]
54
-
55
- def extract_features(request, task_type, difficulty=3):
56
- r = request.lower()
57
- f = {"req_len": len(request), "num_words": len(request.split()),
58
- "has_code": int(any(k in r for k in CODE_KW)),
59
- "n_code": sum(1 for k in CODE_KW if k in r),
60
- "has_legal": int(any(k in r for k in LEGAL_KW)),
61
- "n_legal": sum(1 for k in LEGAL_KW if k in r),
62
- "has_research": int(any(k in r for k in RESEARCH_KW)),
63
- "n_research": sum(1 for k in RESEARCH_KW if k in r),
64
- "has_tool": int(any(k in r for k in TOOL_KW)),
65
- "n_tool": sum(1 for k in TOOL_KW if k in r),
66
- "has_long": int(any(k in r for k in LONG_KW)),
67
- "has_math": int(any(k in r for k in MATH_KW)),
68
- "tt_idx": TT2IDX.get(task_type, 8), "difficulty": difficulty}
69
- for tt in TASK_TYPES:
70
- f[f"tt_{tt}"] = int(task_type == tt)
71
- return f
72
-
73
- def f2v(feats):
74
- return np.array([float(feats.get(k, 0.0)) for k in feat_keys], dtype=np.float32)
75
-
76
- # ─── Routing Functions ────────────────────────────────────────────────
77
- def route_trained(request, task_type, difficulty):
78
- """Trained router: per-tier P(success) + safety floor + asymmetric cost."""
79
- feats = extract_features(request, task_type, difficulty)
80
- x = f2v(feats).reshape(1, -1)
81
- floor = TASK_FLOOR.get(task_type, 2)
82
-
83
- best_tier = floor; best_score = float("inf")
84
- for tier in range(floor, 6):
85
- p_success = tier_clfs[tier].predict_proba(x)[0, 1]
86
- p_fail = 1.0 - p_success
87
- cost_norm = TIER_COST[tier] / TIER_COST[5]
88
- score = p_fail * 5.0 + cost_norm * 1.0 # asymmetric: 5x underkill penalty
89
- if score < best_score:
90
- best_score = score; best_tier = tier
91
- return best_tier
92
-
93
- def route_heuristic(task_type, difficulty):
94
- """Original heuristic router: difficulty + 1."""
95
- return min(difficulty + 1, 5)
96
-
97
- def route_frontier():
98
- return 4
99
-
100
- def route_cascade_trained(request, task_type, difficulty):
101
- """Cascade: start at floor, escalate if P(success) < threshold."""
102
- feats = extract_features(request, task_type, difficulty)
103
- x = f2v(feats).reshape(1, -1)
104
- floor = TASK_FLOOR.get(task_type, 2)
105
-
106
- for tier in range(floor, 6):
107
- p_success = tier_clfs[tier].predict_proba(x)[0, 1]
108
- if p_success >= 0.65:
109
- return tier
110
- return 4 # fallback to frontier
111
-
112
- # ─── Generate Evaluation Traces ────────────────────────────────────────
113
- print("\n[2] Generating 2K evaluation traces (different seed from training)...")
114
-
115
- class TaskType(Enum):
116
- QUICK_ANSWER="quick_answer"; CODING="coding"; RESEARCH="research"
117
- DOCUMENT_DRAFTING="document_drafting"; LEGAL_REGULATED="legal_regulated"
118
- TOOL_HEAVY="tool_heavy"; RETRIEVAL_HEAVY="retrieval_heavy"
119
- LONG_HORIZON="long_horizon"; UNKNOWN_AMBIGUOUS="unknown_ambiguous"
120
-
121
- TASK_TEMPLATES_EVAL = {
122
- "quick_answer":["What is the capital of France?","Explain quantum computing briefly.","What is 237*452?"],
123
- "coding":["Write a Python function to reverse a linked list.","Fix the bug in this React component.",
124
- "Implement LRU cache in Go.","Debug segfault in C++ thread pool."],
125
- "research":["Research latest transformer advances.","Find sources comparing LoRA and full FT.",
126
- "Investigate data center climate impact."],
127
- "document_drafting":["Draft project proposal for ML pipeline.","Write email to team about deployment."],
128
- "legal_regulated":["Review this contract for liability clauses.","Check GDPR compliance for data pipeline.",
129
- "Draft privacy policy section."],
130
- "tool_heavy":["Search open issues and create summary.","Fetch API docs and generate client code."],
131
- "retrieval_heavy":["Answer based on 50-page document.","Find all payment processing mentions."],
132
- "long_horizon":["Plan 3-month roadmap.","Orchestrate multi-region deployment."],
133
- "unknown_ambiguous":["Help me with this thing.","I need something about the server."],
134
- }
135
-
136
- def tsp(tier, diff):
137
- s = {1:0.35,2:0.55,3:0.80,4:0.93,5:0.97}[tier]
138
- return s ** (diff * 0.6)
139
-
140
- eval_rng = random.Random(999) # DIFFERENT seed for eval
141
- eval_traces = []
142
- for i in range(2000):
143
- tt = eval_rng.choice(list(TASK_TEMPLATES_EVAL.keys()))
144
- diff = {"quick_answer":1,"document_drafting":2,"tool_heavy":2,"retrieval_heavy":2,
145
- "research":3,"coding":3,"unknown_ambiguous":3,"long_horizon":4,"legal_regulated":5}[tt]
146
- tier_out = {t: eval_rng.random() < tsp(t, diff) for t in range(1,6)}
147
- opt = 5
148
- for t in range(1,6):
149
- if tier_out[t]: opt = t; break
150
- req = eval_rng.choice(TASK_TEMPLATES_EVAL[tt])
151
- eval_traces.append({"tt":tt,"diff":diff,"opt":opt,"tier_out":tier_out,"req":req})
152
-
153
- print(f" Generated {len(eval_traces)} eval traces")
154
-
155
- # ─── Evaluate All Routers ─────────────────────────────────────────────
156
- print("\n[3] Evaluating all routers on 2K traces...")
157
-
158
- def eval_router(name, route_fn):
159
- succ = 0; cost = 0.0; unsafe = 0; fd = 0
160
- td = defaultdict(int)
161
- for t in eval_traces:
162
- pred = route_fn(t)
163
- td[pred] += 1
164
- if t["tier_out"].get(pred, False):
165
- succ += 1
166
- elif pred < t["opt"]:
167
- unsafe += 1
168
- else:
169
- fd += 1
170
- cost += TIER_COST[pred]
171
- n = len(eval_traces)
172
- return {"success":succ/n, "avg_cost":cost/n, "unsafe_rate":unsafe/n,
173
- "false_done":fd/n, "tier_dist":dict(td)}
174
-
175
- routers = {
176
- "always_frontier": lambda t: 4,
177
- "always_cheap": lambda t: 1,
178
- "heuristic_diff+1": lambda t: min(t["diff"]+1, 5),
179
- "heuristic_floor": lambda t: TASK_FLOOR.get(t["tt"], 2),
180
- "trained_asymmetric": lambda t: route_trained(t["req"], t["tt"], t["diff"]),
181
- "trained_cascade_t0.65": lambda t: route_cascade_trained(t["req"], t["tt"], t["diff"]),
182
- "oracle": lambda t: t["opt"],
183
- }
184
-
185
- results = {}
186
- for name, fn in routers.items():
187
- results[name] = eval_router(name, fn)
188
- r = results[name]
189
- fc = results["always_frontier"]["avg_cost"]
190
- cr = (1 - r["avg_cost"]/fc)*100
191
- print(f" {name:<25} success={r['success']:.3f} cost={r['avg_cost']:.4f} costRed={cr:.1f}% unsafe={r['unsafe_rate']:.3f}")
192
-
193
- # ─── Per-Task-Type Breakdown ──────────────────────────────────────────
194
- print("\n\n[4] Per-task-type breakdown (trained vs heuristic vs frontier)...")
195
- for tt in sorted(set(t["tt"] for t in eval_traces)):
196
- tt_traces = [t for t in eval_traces if t["tt"] == tt]
197
- n_tt = len(tt_traces)
198
- if n_tt == 0: continue
199
-
200
- for rname, rfn in [("frontier", lambda t:4),
201
- ("heuristic", lambda t:min(t["diff"]+1,5)),
202
- ("trained", lambda t:route_trained(t["req"],t["tt"],t["diff"]))]:
203
- succ = sum(1 for t in tt_traces if t["tier_out"].get(rfn(t), False))
204
- cost = sum(TIER_COST[rfn(t)] for t in tt_traces)
205
- sr = succ/n_tt; ac = cost/n_tt
206
- if rname == "frontier":
207
- print(f"\n {tt} (n={n_tt}):")
208
- print(f" {rname:<12} success={sr:.3f} cost={ac:.4f}")
209
-
210
- # ─── Final Comparison ─────────────────────────────────────────────────
211
- print(f"\n\n{'='*80}")
212
- print("FINAL INTEGRATION BENCHMARK")
213
- print(f"{'='*80}")
214
- print(f"\n{'Router':<25} {'Success':>10} {'AvgCost':>10} {'CostRed':>10} {'Unsafe':>10} {'F-DONE':>10}")
215
- print("-"*75)
216
- fc = results["always_frontier"]["avg_cost"]
217
- for name, r in sorted(results.items(), key=lambda x: (-x[1]["success"], x[1]["avg_cost"])):
218
- cr = (1 - r["avg_cost"]/fc)*100
219
- print(f"{name:<25} {r['success']:>10.3f} {r['avg_cost']:>10.4f} {cr:>9.1f}% {r['unsafe_rate']:>10.3f} {r['false_done']:>10.3f}")
220
-
221
- # Quality/cost frontier
222
- print("\nPARETO FRONTIER:")
223
- pareto = []
224
- for name, r in results.items():
225
- if name == "always_cheap": continue
226
- dominated = False
227
- for name2, r2 in results.items():
228
- if name == name2: continue
229
- if r2["success"] >= r["success"] and r2["avg_cost"] <= r["avg_cost"]:
230
- if r2["success"] > r["success"] or r2["avg_cost"] < r["avg_cost"]:
231
- dominated = True; break
232
- if not dominated:
233
- pareto.append((name, r))
234
- cr = (1 - r["avg_cost"]/fc)*100
235
- print(f" {name:<25} success={r['success']:.3f} cost={r['avg_cost']:.4f} costRed={cr:.1f}%")
236
-
237
- print(f"\n\nDONE!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
training/router_v5_calibrated.py DELETED
@@ -1,393 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Trained Router v5: Calibrated + per-task thresholds + oversampled training.
3
-
4
- Key improvements over v4:
5
- 1. Platt scaling calibration on held-out data
6
- 2. Per-task-type escalation thresholds
7
- 3. Oversampled easy-task successes in training data
8
- """
9
- import json, os, sys, random, uuid
10
- import numpy as np
11
- from datetime import datetime
12
- from collections import defaultdict
13
- from typing import Dict, List, Optional, Tuple
14
-
15
- TASK_TYPES = ["quick_answer","coding","research","document_drafting",
16
- "legal_regulated","tool_heavy","retrieval_heavy",
17
- "long_horizon","unknown_ambiguous"]
18
- TT2IDX = {t:i for i,t in enumerate(TASK_TYPES)}
19
-
20
- CODE_KW = ["python","javascript","code","function","bug","debug","refactor",
21
- "implement","test","compile","runtime","class","module","async","thread"]
22
- LEGAL_KW = ["contract","legal","compliance","gdpr","privacy","policy","regulatory","liability"]
23
- RESEARCH_KW = ["research","find sources","literature","investigate","compare","analyze","survey"]
24
- TOOL_KW = ["search","fetch","retrieve","query","api","database","scrape","aggregate"]
25
- LONG_KW = ["plan","project","roadmap","orchestrate","multi-step","migrate","pipeline","deploy"]
26
- MATH_KW = ["calculate","compute","solve","equation","formula","optimize","probability"]
27
-
28
- TIER_STR = {1:0.35,2:0.55,3:0.80,4:0.93,5:0.97}
29
- TIER_COST = {1:0.05,2:0.15,3:0.75,4:1.0,5:1.5}
30
- TASK_FLOOR = {"legal_regulated":4,"long_horizon":3,"research":3,"coding":3,
31
- "unknown_ambiguous":3,"quick_answer":1,"document_drafting":2,
32
- "tool_heavy":2,"retrieval_heavy":2}
33
-
34
- # Per-task-type escalation thresholds (lower = more aggressive cost savings)
35
- TASK_THRESHOLDS = {
36
- "quick_answer": 0.35, # Easy tasks: low threshold, use cheap models
37
- "document_drafting": 0.45, # Medium-easy tasks
38
- "tool_heavy": 0.45, # Tool orchestration, not deep reasoning
39
- "retrieval_heavy": 0.45, # Retrieval-heavy, moderate reasoning
40
- "coding": 0.55, # Coding needs decent models
41
- "research": 0.55, # Research needs good models
42
- "unknown_ambiguous": 0.60, # Unknown = be careful
43
- "long_horizon": 0.60, # Long horizon = be careful
44
- "legal_regulated": 0.75, # Legal = always verify, escalate aggressively
45
- }
46
-
47
- TASK_TEMPLATES = {
48
- "quick_answer":["What is the capital of France?","Explain quantum computing briefly.",
49
- "What is 237*452?","Define photosynthesis.","Who wrote Hamlet?",
50
- "What is the speed of light?","List the primary colors.","What is GDP?",
51
- "What is 2+2?","Name the planets in the solar system."],
52
- "coding":["Write a Python function to reverse a linked list.",
53
- "Fix the bug in this React component.","Refactor auth module to JWT.",
54
- "Implement LRU cache in Go.","Debug segfault in C++ thread pool.",
55
- "Add unit tests for the payment module.","Optimize this SQL query.",
56
- "Create a REST API for user management.","Implement binary search in Rust.",
57
- "Write a fibonacci function with memoization."],
58
- "research":["Research latest transformer advances.",
59
- "Find sources comparing LoRA and full FT.",
60
- "Investigate data center climate impact.",
61
- "Survey privacy-preserving ML techniques.",
62
- "Compare reinforcement learning algorithms for robotics.",
63
- "Analyze recent papers on mixture of experts."],
64
- "document_drafting":["Draft project proposal for ML pipeline.",
65
- "Write email to team about deployment.","Create technical report on performance.",
66
- "Write a project brief for the migration.","Draft meeting agenda."],
67
- "legal_regulated":["Review this contract for liability clauses.",
68
- "Check GDPR compliance for data pipeline.","Draft privacy policy section.",
69
- "Verify regulatory compliance for medical device software.",
70
- "Analyze indemnification clause in vendor agreement."],
71
- "tool_heavy":["Search open issues and create summary.",
72
- "Fetch API docs and generate client code.","Query Q3 sales and produce chart.",
73
- "Aggregate logs from 3 services."],
74
- "retrieval_heavy":["Answer based on 50-page document.",
75
- "Find all payment processing mentions.","Retrieve relevant cases for legal query.",
76
- "Summarize findings from quarterly report."],
77
- "long_horizon":["Plan 3-month roadmap.","Orchestrate multi-region deployment.",
78
- "Redesign data architecture end-to-end.","Migrate monolith to microservices."],
79
- "unknown_ambiguous":["Help me with this thing.",
80
- "I need something about the server.","Can you look into that issue?",
81
- "There's a problem with the data."],
82
- }
83
-
84
- def tsp(tier, diff):
85
- return TIER_STR[tier] ** (diff * 0.6)
86
-
87
- def extract_features(request, task_type, difficulty=3):
88
- r = request.lower()
89
- f = {"req_len":len(request),"num_words":len(request.split()),
90
- "has_code":int(any(k in r for k in CODE_KW)),
91
- "n_code":sum(1 for k in CODE_KW if k in r),
92
- "has_legal":int(any(k in r for k in LEGAL_KW)),
93
- "n_legal":sum(1 for k in LEGAL_KW if k in r),
94
- "has_research":int(any(k in r for k in RESEARCH_KW)),
95
- "n_research":sum(1 for k in RESEARCH_KW if k in r),
96
- "has_tool":int(any(k in r for k in TOOL_KW)),
97
- "n_tool":sum(1 for k in TOOL_KW if k in r),
98
- "has_long":int(any(k in r for k in LONG_KW)),
99
- "has_math":int(any(k in r for k in MATH_KW)),
100
- "tt_idx":TT2IDX.get(task_type,8),"difficulty":difficulty}
101
- for tt in TASK_TYPES:
102
- f[f"tt_{tt}"] = int(task_type == tt)
103
- return f
104
-
105
- def gen_trace(idx, rng, oversample_easy=False):
106
- tt = rng.choice(list(TASK_TEMPLATES.keys()))
107
- diff = {"quick_answer":1,"document_drafting":2,"tool_heavy":2,"retrieval_heavy":2,
108
- "research":3,"coding":3,"unknown_ambiguous":3,"long_horizon":4,"legal_regulated":5}[tt]
109
- tier_out = {t: rng.random() < tsp(t, diff) for t in range(1,6)}
110
- opt = 5
111
- for t in range(1,6):
112
- if tier_out[t]: opt = t; break
113
-
114
- # When oversampling: bias actual_tier toward optimal to create more success examples
115
- if oversample_easy and opt <= 2:
116
- actual = rng.choices([1,2,3,4,5], weights=[2,5,2,1,0.5])[0]
117
- elif oversample_easy and opt <= 3:
118
- actual = rng.choices([1,2,3,4,5], weights=[0.5,1,5,2,0.5])[0]
119
- else:
120
- if diff <= 2:
121
- actual = rng.choices([1,2,3,4,5],weights=[3,4,2,1,0.5])[0]
122
- elif diff == 3:
123
- actual = rng.choices([1,2,3,4,5],weights=[1,2,4,2,1])[0]
124
- elif diff == 4:
125
- actual = rng.choices([1,2,3,4,5],weights=[0.5,1,2,4,2])[0]
126
- else:
127
- actual = rng.choices([1,2,3,4,5],weights=[0.2,0.5,1,3,4])[0]
128
-
129
- outcome = "success" if tier_out[actual] else "failure"
130
- req = rng.choice(TASK_TEMPLATES[tt])
131
- feats = extract_features(req, tt, diff)
132
- return {"feats":feats,"opt":opt,"actual":actual,"outcome":outcome,
133
- "tier_out":tier_out,"tt":tt,"diff":diff,"req":req}
134
-
135
- print("="*80)
136
- print("ACO TRAINED ROUTER v5: CALIBRATED + PER-TASK THRESHOLDS")
137
- print("="*80)
138
-
139
- # ─── Generate Training Data with Oversampling ────────────────────────
140
- print("\n[1] Generating 60K training traces (with easy-task oversampling)...")
141
- rng = random.Random(42)
142
-
143
- # Base 50K traces
144
- traces = [gen_trace(i, rng, oversample_easy=False) for i in range(40000)]
145
- # Add 20K oversampled easy-task traces
146
- traces += [gen_trace(i+40000, rng, oversample_easy=True) for i in range(20000)]
147
-
148
- print(f" Total: {len(traces)} traces")
149
-
150
- # Check success rate per tier
151
- for tier in range(1, 6):
152
- succ = sum(1 for t in traces if t["tier_out"].get(tier, False))
153
- print(f" Tier {tier}: success rate = {succ/len(traces):.3f}")
154
-
155
- # ─── Build Feature Matrix ────────────────────────────────────────────
156
- FEAT_KEYS = sorted(traces[0]["feats"].keys())
157
- def f2v(feats):
158
- return np.array([float(feats.get(k, 0.0)) for k in FEAT_KEYS], dtype=np.float32)
159
-
160
- X_all = np.array([f2v(t["feats"]) for t in traces])
161
- y_opt = np.array([t["opt"] for t in traces])
162
-
163
- per_tier_labels = {}
164
- for tier in range(1, 6):
165
- per_tier_labels[tier] = np.array([1 if t["tier_out"].get(tier, False) else 0 for t in traces])
166
-
167
- from sklearn.model_selection import train_test_split
168
- from sklearn.metrics import accuracy_score, f1_score, brier_score_loss
169
- import xgboost as xgb
170
- from sklearn.calibration import CalibratedClassifierCV
171
-
172
- X_train, X_test, idx_train, idx_test = train_test_split(
173
- X_all, range(len(traces)), test_size=0.2, random_state=42, stratify=y_opt
174
- )
175
- print(f"\n Train: {len(X_train)}, Test: {len(X_test)}")
176
-
177
- # ─── Train + Calibrate Per-Tier Classifiers ──────────────────────────
178
- print("\n[2] Training + calibrating per-tier P(success) classifiers...")
179
- tier_clfs = {}
180
- tier_calibrators = {}
181
-
182
- for tier in range(1, 6):
183
- y_tr = per_tier_labels[tier][idx_train]
184
- y_te = per_tier_labels[tier][idx_test]
185
-
186
- neg = (y_tr == 0).sum()
187
- pos = (y_tr == 1).sum()
188
- spw = neg / max(pos, 1)
189
-
190
- # Train XGBoost
191
- clf = xgb.XGBClassifier(
192
- n_estimators=200, max_depth=5, learning_rate=0.1,
193
- subsample=0.8, colsample_bytree=0.8,
194
- scale_pos_weight=min(spw, 5.0),
195
- objective="binary:logistic", eval_metric="logloss",
196
- random_state=42, verbosity=0,
197
- )
198
- clf.fit(X_train, y_tr)
199
-
200
- # Platt scaling calibration
201
- from sklearn.linear_model import LogisticRegression
202
- from sklearn.isotonic import IsotonicRegression
203
-
204
- # Get raw probabilities on test set for calibration
205
- y_prob_raw = clf.predict_proba(X_test)[:, 1]
206
-
207
- # Use isotonic regression for calibration (works better than Platt for small datasets)
208
- iso_reg = IsotonicRegression(out_of_bounds="clip")
209
- iso_reg.fit(y_prob_raw, y_te)
210
-
211
- # Evaluate calibration
212
- y_prob_cal = iso_reg.transform(y_prob_raw)
213
- brier_raw = brier_score_loss(y_te, y_prob_raw)
214
- brier_cal = brier_score_loss(y_te, y_prob_cal)
215
-
216
- acc = accuracy_score(y_te, clf.predict(X_test))
217
- f1 = f1_score(y_te, clf.predict(X_test), zero_division=0)
218
-
219
- tier_clfs[tier] = clf
220
- tier_calibrators[tier] = iso_reg
221
- print(f" Tier {tier}: acc={acc:.3f}, f1={f1:.3f}, brier_raw={brier_raw:.3f}, brier_cal={brier_cal:.3f}")
222
-
223
- # ─── Calibrated Router ────────────────────────────────────────────────
224
- print("\n[3] Building calibrated per-task-threshold router...")
225
-
226
- def route_calibrated(request, task_type, difficulty):
227
- """Calibrated router with per-task thresholds."""
228
- base_tier = min(difficulty + 1, 5)
229
- floor = TASK_FLOOR.get(task_type, 2)
230
- base_tier = max(base_tier, floor)
231
-
232
- feats = extract_features(request, task_type, difficulty)
233
- x = f2v(feats).reshape(1, -1)
234
-
235
- # Get CALIBRATED P(success) at base_tier
236
- p_raw = tier_clfs[base_tier].predict_proba(x)[0, 1]
237
- p_success = float(tier_calibrators[base_tier].transform([p_raw])[0])
238
-
239
- # Per-task threshold
240
- threshold = TASK_THRESHOLDS.get(task_type, 0.55)
241
-
242
- # Escalate if calibrated probability too low
243
- while p_success < threshold and base_tier < 5:
244
- base_tier += 1
245
- p_raw = tier_clfs[base_tier].predict_proba(x)[0, 1]
246
- p_success = float(tier_calibrators[base_tier].transform([p_raw])[0])
247
-
248
- return base_tier
249
-
250
- # ─── Evaluate ─────────────────────────────────────────────────────────
251
- print("\n[4] Evaluating all routers on 2K eval traces (seed=999)...")
252
-
253
- eval_rng = random.Random(999)
254
- eval_traces = []
255
- for i in range(2000):
256
- tt = eval_rng.choice(list(TASK_TEMPLATES.keys()))
257
- diff = {"quick_answer":1,"document_drafting":2,"tool_heavy":2,"retrieval_heavy":2,
258
- "research":3,"coding":3,"unknown_ambiguous":3,"long_horizon":4,"legal_regulated":5}[tt]
259
- tier_out = {t: eval_rng.random() < tsp(t, diff) for t in range(1,6)}
260
- opt = 5
261
- for t in range(1,6):
262
- if tier_out[t]: opt = t; break
263
- req = eval_rng.choice(TASK_TEMPLATES[tt])
264
- eval_traces.append({"tt":tt,"diff":diff,"opt":opt,"tier_out":tier_out,"req":req})
265
-
266
- print(f" Generated {len(eval_traces)} eval traces")
267
-
268
- def eval_router(name, route_fn):
269
- succ=0; cost=0.0; unsafe=0; fd=0; td=defaultdict(int)
270
- for t in eval_traces:
271
- pred = route_fn(t)
272
- td[pred] += 1
273
- if t["tier_out"].get(pred, False): succ += 1
274
- elif pred < t["opt"]: unsafe += 1
275
- else: fd += 1
276
- cost += TIER_COST[pred]
277
- n = len(eval_traces)
278
- return {"success":succ/n, "avg_cost":cost/n, "unsafe_rate":unsafe/n,
279
- "false_done":fd/n, "tier_dist":dict(td)}
280
-
281
- results = {}
282
- results["always_frontier"] = eval_router("always_frontier", lambda t: 4)
283
- results["always_cheap"] = eval_router("always_cheap", lambda t: 1)
284
- results["heuristic_diff+1"] = eval_router("heuristic_diff+1", lambda t: min(t["diff"]+1, 5))
285
- results["heuristic_floor"] = eval_router("heuristic_floor", lambda t: TASK_FLOOR.get(t["tt"], 2))
286
- results["oracle"] = eval_router("oracle", lambda t: t["opt"])
287
- # results["v4_prod_t0.55"] = eval_router("v4_prod_t0.55",
288
- # lambda t: route_v4(t, 0.55))
289
- results["v5_calibrated"] = eval_router("v5_calibrated",
290
- lambda t: route_calibrated(t["req"], t["tt"], t["diff"]))
291
-
292
- # v4 router for comparison
293
- def route_v4(t, threshold):
294
- base = min(t["diff"]+1, 5)
295
- floor = TASK_FLOOR.get(t["tt"], 2)
296
- base = max(base, floor)
297
- feats = extract_features(t["req"], t["tt"], t["diff"])
298
- x = f2v(feats).reshape(1, -1)
299
- ps = tier_clfs[base].predict_proba(x)[0, 1]
300
- while ps < threshold and base < 5:
301
- base += 1
302
- ps = tier_clfs[base].predict_proba(x)[0, 1]
303
- return base
304
-
305
- # Print
306
- print(f"\n{'Router':<25} {'Success':>10} {'AvgCost':>10} {'CostRed':>10} {'Unsafe':>10} {'F-DONE':>10}")
307
- print("-"*75)
308
- fc = results["always_frontier"]["avg_cost"]
309
- for name, r in sorted(results.items(), key=lambda x: (-x[1]["success"], x[1]["avg_cost"])):
310
- cr = (1 - r["avg_cost"]/fc)*100
311
- print(f"{name:<25} {r['success']:>10.3f} {r['avg_cost']:>10.4f} {cr:>9.1f}% {r['unsafe_rate']:>10.3f} {r['false_done']:>10.3f}")
312
-
313
- # Per-task breakdown
314
- print(f"\n\n[5] Per-task-type breakdown (calibrated v5 vs frontier vs heuristic)...")
315
- for tt in sorted(set(t["tt"] for t in eval_traces)):
316
- tt_traces = [t for t in eval_traces if t["tt"] == tt]
317
- n_tt = len(tt_traces)
318
- if n_tt == 0: continue
319
- print(f"\n {tt} (n={n_tt}):")
320
- for rname, rfn in [("frontier", lambda t:4),
321
- ("heuristic", lambda t:min(t["diff"]+1,5)),
322
- ("calibrated", lambda t:route_calibrated(t["req"],t["tt"],t["diff"])),
323
- ("oracle", lambda t:t["opt"])]:
324
- succ = sum(1 for t in tt_traces if t["tier_out"].get(rfn(t), False))
325
- cost = sum(TIER_COST[rfn(t)] for t in tt_traces)
326
- sr = succ/n_tt; ac = cost/n_tt
327
- cr = (1 - ac/fc)*100
328
- print(f" {rname:<12} success={sr:.3f} cost={ac:.4f} costRed={cr:.1f}%")
329
-
330
- # ─── Pareto Frontier ──────────────────────────────────────────────────
331
- print(f"\n\n[6] Pareto frontier analysis...")
332
- pareto = []
333
- for name, r in results.items():
334
- if name == "always_cheap": continue
335
- dominated = False
336
- for name2, r2 in results.items():
337
- if name == name2: continue
338
- if r2["success"] >= r["success"] and r2["avg_cost"] <= r["avg_cost"]:
339
- if r2["success"] > r["success"] or r2["avg_cost"] < r["avg_cost"]:
340
- dominated = True; break
341
- if not dominated:
342
- pareto.append((name, r))
343
- cr = (1 - r["avg_cost"]/fc)*100
344
- print(f" {name:<25} success={r['success']:.3f} cost={r['avg_cost']:.4f} costRed={cr:.1f}% unsafe={r['unsafe_rate']:.3f}")
345
-
346
- # ─── Save Final Production Model ──────────────────────────────────────
347
- print("\n\n[7] Saving final production model bundle...")
348
- os.makedirs("/app/router_models", exist_ok=True)
349
-
350
- import pickle
351
-
352
- bundle = {
353
- "tier_clfs": {str(k): v for k, v in tier_clfs.items()},
354
- "tier_calibrators": {str(k): v for k, v in tier_calibrators.items()},
355
- "feat_keys": FEAT_KEYS,
356
- "tier_config": {
357
- "tier_cost": TIER_COST,
358
- "tier_str": TIER_STR,
359
- "task_floor": TASK_FLOOR,
360
- "task_thresholds": TASK_THRESHOLDS,
361
- },
362
- "version": "5.0",
363
- "description": "ACO Production Router v5: calibrated + per-task thresholds + oversampled",
364
- }
365
-
366
- with open("/app/router_models/router_bundle_v5.pkl", "wb") as f:
367
- pickle.dump(bundle, f)
368
- print(f" Saved router_bundle_v5.pkl ({os.path.getsize('/app/router_models/router_bundle_v5.pkl')/1024:.0f} KB)")
369
-
370
- # Also save individual files
371
- for tier in range(1, 6):
372
- tier_clfs[tier].save_model(f"/app/router_models/v5_tier_{tier}_success.json")
373
- with open("/app/router_models/v5_feat_keys.json","w") as f:
374
- json.dump(FEAT_KEYS, f)
375
- with open("/app/router_models/v5_tier_config.json","w") as f:
376
- json.dump(bundle["tier_config"], f, indent=2)
377
- with open("/app/router_models/v5_calibrators.pkl","wb") as f:
378
- pickle.dump(tier_calibrators, f)
379
-
380
- # Save eval results
381
- with open("/app/router_models/v5_eval_results.json","w") as f:
382
- json.dump(results, f, indent=2, default=str)
383
-
384
- print(f"\n\n{'='*80}")
385
- print("FINAL v5 COMPARISON")
386
- print(f"{'='*80}")
387
- print(f"\n{'Router':<25} {'Success':>10} {'AvgCost':>10} {'CostRed':>10} {'Unsafe':>10} {'F-DONE':>10}")
388
- print("-"*75)
389
- for name, r in sorted(results.items(), key=lambda x: (-x[1]["success"], x[1]["avg_cost"])):
390
- cr = (1 - r["avg_cost"]/fc)*100
391
- print(f"{name:<25} {r['success']:>10.3f} {r['avg_cost']:>10.4f} {cr:>9.1f}% {r['unsafe_rate']:>10.3f} {r['false_done']:>10.3f}")
392
-
393
- print(f"\nDONE!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
training/router_v6_hybrid.py DELETED
@@ -1,338 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Trained Router v6: Hybrid heuristic + ML safety net.
3
-
4
- Key insight from v5: The ML classifiers alone can't beat the heuristic
5
- because difficulty is the dominant feature. But they CAN detect when
6
- the heuristic is wrong.
7
-
8
- Architecture:
9
- 1. Heuristic: difficulty+1 with safety floor (this is the base)
10
- 2. ML SAFETY NET: Check if P(success@heuristic_tier) < LOW_THRESHOLD
11
- If so, escalate to next tier (the ML caught a case the heuristic missed)
12
- 3. ML COST SAVER: Check if P(success@tier-1) >= HIGH_THRESHOLD
13
- If so, DOWNGRADE one tier (the ML says a cheaper tier would work)
14
- """
15
- import json, os, sys, random, uuid, pickle
16
- import numpy as np
17
- from collections import defaultdict
18
- from typing import Dict, List, Optional, Tuple
19
-
20
- TASK_TYPES = ["quick_answer","coding","research","document_drafting",
21
- "legal_regulated","tool_heavy","retrieval_heavy",
22
- "long_horizon","unknown_ambiguous"]
23
- TT2IDX = {t:i for i,t in enumerate(TASK_TYPES)}
24
-
25
- CODE_KW = ["python","javascript","code","function","bug","debug","refactor",
26
- "implement","test","compile","runtime","class","module","async","thread"]
27
- LEGAL_KW = ["contract","legal","compliance","gdpr","privacy","policy","regulatory","liability"]
28
- RESEARCH_KW = ["research","find sources","literature","investigate","compare","analyze","survey"]
29
- TOOL_KW = ["search","fetch","retrieve","query","api","database","scrape","aggregate"]
30
- LONG_KW = ["plan","project","roadmap","orchestrate","multi-step","migrate","pipeline","deploy"]
31
- MATH_KW = ["calculate","compute","solve","equation","formula","optimize","probability"]
32
-
33
- TIER_STR = {1:0.35,2:0.55,3:0.80,4:0.93,5:0.97}
34
- TIER_COST = {1:0.05,2:0.15,3:0.75,4:1.0,5:1.5}
35
- TASK_FLOOR = {"legal_regulated":4,"long_horizon":3,"research":3,"coding":3,
36
- "unknown_ambiguous":3,"quick_answer":1,"document_drafting":2,
37
- "tool_heavy":2,"retrieval_heavy":2}
38
-
39
- TASK_TEMPLATES = {
40
- "quick_answer":["What is the capital of France?","Explain quantum computing briefly.",
41
- "What is 237*452?","Define photosynthesis.","Who wrote Hamlet?",
42
- "What is the speed of light?","List the primary colors.","What is GDP?"],
43
- "coding":["Write a Python function to reverse a linked list.",
44
- "Fix the bug in this React component.","Refactor auth module to JWT.",
45
- "Implement LRU cache in Go.","Debug segfault in C++ thread pool.",
46
- "Add unit tests for the payment module.","Optimize this SQL query.",
47
- "Create a REST API for user management.","Implement binary search in Rust."],
48
- "research":["Research latest transformer advances.",
49
- "Find sources comparing LoRA and full FT.",
50
- "Investigate data center climate impact.",
51
- "Survey privacy-preserving ML techniques."],
52
- "document_drafting":["Draft project proposal for ML pipeline.",
53
- "Write email to team about deployment.","Create technical report on performance."],
54
- "legal_regulated":["Review this contract for liability clauses.",
55
- "Check GDPR compliance for data pipeline.","Draft privacy policy section."],
56
- "tool_heavy":["Search open issues and create summary.",
57
- "Fetch API docs and generate client code.","Query Q3 sales and produce chart."],
58
- "retrieval_heavy":["Answer based on 50-page document.",
59
- "Find all payment processing mentions.","Retrieve relevant cases for legal query."],
60
- "long_horizon":["Plan 3-month roadmap.","Orchestrate multi-region deployment.",
61
- "Redesign data architecture end-to-end."],
62
- "unknown_ambiguous":["Help me with this thing.",
63
- "I need something about the server.","Can you look into that issue?"],
64
- }
65
-
66
- def tsp(tier, diff):
67
- return TIER_STR[tier] ** (diff * 0.6)
68
-
69
- def extract_features(request, task_type, difficulty=3):
70
- r = request.lower()
71
- f = {"req_len":len(request),"num_words":len(request.split()),
72
- "has_code":int(any(k in r for k in CODE_KW)),
73
- "n_code":sum(1 for k in CODE_KW if k in r),
74
- "has_legal":int(any(k in r for k in LEGAL_KW)),
75
- "n_legal":sum(1 for k in LEGAL_KW if k in r),
76
- "has_research":int(any(k in r for k in RESEARCH_KW)),
77
- "n_research":sum(1 for k in RESEARCH_KW if k in r),
78
- "has_tool":int(any(k in r for k in TOOL_KW)),
79
- "n_tool":sum(1 for k in TOOL_KW if k in r),
80
- "has_long":int(any(k in r for k in LONG_KW)),
81
- "has_math":int(any(k in r for k in MATH_KW)),
82
- "tt_idx":TT2IDX.get(task_type,8),"difficulty":difficulty}
83
- for tt in TASK_TYPES:
84
- f[f"tt_{tt}"] = int(task_type == tt)
85
- return f
86
-
87
- def gen_trace(idx, rng):
88
- tt = rng.choice(list(TASK_TEMPLATES.keys()))
89
- diff = {"quick_answer":1,"document_drafting":2,"tool_heavy":2,"retrieval_heavy":2,
90
- "research":3,"coding":3,"unknown_ambiguous":3,"long_horizon":4,"legal_regulated":5}[tt]
91
- tier_out = {t: rng.random() < tsp(t, diff) for t in range(1,6)}
92
- opt = 5
93
- for t in range(1,6):
94
- if tier_out[t]: opt = t; break
95
- if diff <= 2: actual = rng.choices([1,2,3,4,5],weights=[3,4,2,1,0.5])[0]
96
- elif diff == 3: actual = rng.choices([1,2,3,4,5],weights=[1,2,4,2,1])[0]
97
- elif diff == 4: actual = rng.choices([1,2,3,4,5],weights=[0.5,1,2,4,2])[0]
98
- else: actual = rng.choices([1,2,3,4,5],weights=[0.2,0.5,1,3,4])[0]
99
- outcome = "success" if tier_out[actual] else "failure"
100
- req = rng.choice(TASK_TEMPLATES[tt])
101
- feats = extract_features(req, tt, diff)
102
- return {"feats":feats,"opt":opt,"actual":actual,"outcome":outcome,
103
- "tier_out":tier_out,"tt":tt,"diff":diff,"req":req}
104
-
105
- print("="*80)
106
- print("ACO TRAINED ROUTER v6: HYBRID HEURISTIC + ML SAFETY NET")
107
- print("="*80)
108
-
109
- # ─── Train Models ────────────────────────────────────────────────────
110
- print("\n[1] Generating 50K training traces...")
111
- rng = random.Random(42)
112
- traces = [gen_trace(i, rng) for i in range(50000)]
113
- FEAT_KEYS = sorted(traces[0]["feats"].keys())
114
- def f2v(feats):
115
- return np.array([float(feats.get(k, 0.0)) for k in FEAT_KEYS], dtype=np.float32)
116
-
117
- X_all = np.array([f2v(t["feats"]) for t in traces])
118
- y_opt = np.array([t["opt"] for t in traces])
119
- per_tier_labels = {}
120
- for tier in range(1,6):
121
- per_tier_labels[tier] = np.array([1 if t["tier_out"].get(tier,False) else 0 for t in traces])
122
-
123
- from sklearn.model_selection import train_test_split
124
- from sklearn.metrics import accuracy_score, f1_score
125
- from sklearn.calibration import IsotonicRegression
126
- import xgboost as xgb
127
-
128
- X_train, X_test, idx_train, idx_test = train_test_split(X_all, range(len(traces)), test_size=0.2, random_state=42, stratify=y_opt)
129
- print(f" Train: {len(X_train)}, Test: {len(X_test)}")
130
-
131
- print("\n[2] Training per-tier classifiers...")
132
- tier_clfs = {}
133
- tier_calibs = {}
134
- for tier in range(1,6):
135
- y_tr = per_tier_labels[tier][idx_train]
136
- y_te = per_tier_labels[tier][idx_test]
137
- neg = (y_tr==0).sum(); pos = (y_tr==1).sum()
138
- spw = neg/max(pos,1)
139
- clf = xgb.XGBClassifier(n_estimators=200,max_depth=5,learning_rate=0.1,
140
- subsample=0.8,colsample_bytree=0.8,scale_pos_weight=min(spw,5.0),
141
- objective="binary:logistic",eval_metric="logloss",random_state=42,verbosity=0)
142
- clf.fit(X_train, y_tr)
143
- y_prob = clf.predict_proba(X_test)[:,1]
144
- iso = IsotonicRegression(out_of_bounds="clip")
145
- iso.fit(y_prob, y_te)
146
- tier_clfs[tier] = clf
147
- tier_calibs[tier] = iso
148
- acc = accuracy_score(y_te, clf.predict(X_test))
149
- f1 = f1_score(y_te, clf.predict(X_test), zero_division=0)
150
- print(f" Tier {tier}: acc={acc:.3f}, f1={f1:.3f}")
151
-
152
- def get_calibrated_psuccess(x, tier):
153
- """Get calibrated P(success@tier) for a feature vector."""
154
- p_raw = tier_clfs[tier].predict_proba(x)[0, 1]
155
- return float(tier_calibs[tier].transform([p_raw])[0])
156
-
157
- # ─── Hybrid Router ────────────────────────────────────────────────────
158
- print("\n[3] Building hybrid heuristic + ML safety net router...")
159
-
160
- def route_hybrid(request, task_type, difficulty,
161
- safety_threshold=0.35, downgrade_threshold=0.80):
162
- """Hybrid: heuristic base + ML safety net + ML cost saver.
163
-
164
- 1. Start with heuristic tier (difficulty+1, safety floor)
165
- 2. SAFETY NET: If P(success@heuristic_tier) < safety_threshold, ESCALATE
166
- 3. COST SAVER: If P(success@tier-1) >= downgrade_threshold AND
167
- tier-1 >= safety_floor, DOWNGRADE one tier
168
- 4. Never go below safety floor or above 5
169
- """
170
- heuristic_tier = min(difficulty + 1, 5)
171
- floor = TASK_FLOOR.get(task_type, 2)
172
- heuristic_tier = max(heuristic_tier, floor)
173
-
174
- feats = extract_features(request, task_type, difficulty)
175
- x = f2v(feats).reshape(1, -1)
176
-
177
- tier = heuristic_tier
178
-
179
- # SAFETY NET: Check if heuristic tier is likely to fail
180
- p_success = get_calibrated_psuccess(x, tier)
181
- if p_success < safety_threshold and tier < 5:
182
- tier += 1
183
- p_success = get_calibrated_psuccess(x, tier)
184
-
185
- # COST SAVER: Check if a cheaper tier would also work
186
- # Only downgrade if: cheaper tier >= floor, P(success) high, and we're not already escalated
187
- if tier > floor and tier == heuristic_tier: # only if we didn't escalate
188
- cheaper_tier = tier - 1
189
- p_cheaper = get_calibrated_psuccess(x, cheaper_tier)
190
- if p_cheaper >= downgrade_threshold and cheaper_tier >= floor:
191
- tier = cheaper_tier
192
-
193
- return tier
194
-
195
- # ─── Generate Eval ─────────────────────────────────────────────────────
196
- print("\n[4] Generating 2K eval traces (seed=999)...")
197
- eval_rng = random.Random(999)
198
- eval_traces = []
199
- for i in range(2000):
200
- tt = eval_rng.choice(list(TASK_TEMPLATES.keys()))
201
- diff = {"quick_answer":1,"document_drafting":2,"tool_heavy":2,"retrieval_heavy":2,
202
- "research":3,"coding":3,"unknown_ambiguous":3,"long_horizon":4,"legal_regulated":5}[tt]
203
- tier_out = {t: eval_rng.random() < tsp(t, diff) for t in range(1,6)}
204
- opt = 5
205
- for t in range(1,6):
206
- if tier_out[t]: opt = t; break
207
- req = eval_rng.choice(TASK_TEMPLATES[tt])
208
- eval_traces.append({"tt":tt,"diff":diff,"opt":opt,"tier_out":tier_out,"req":req})
209
- print(f" Generated {len(eval_traces)} traces")
210
-
211
- # ─── Evaluate ──────────────────────────────────────────────────────────
212
- print("\n[5] Evaluating all routers...")
213
- n_test = len(eval_traces)
214
- results = {}
215
-
216
- def eval_router(name, route_fn):
217
- succ=0; cost=0.0; unsafe=0; fd=0; td=defaultdict(int)
218
- escalations=0; downgrades=0; heuristic_only=0
219
- for t in eval_traces:
220
- pred = route_fn(t)
221
- td[pred] += 1
222
- h_tier = min(t["diff"]+1, 5)
223
- h_tier = max(h_tier, TASK_FLOOR.get(t["tt"], 2))
224
- if pred > h_tier: escalations += 1
225
- elif pred < h_tier: downgrades += 1
226
- else: heuristic_only += 1
227
- if t["tier_out"].get(pred, False): succ += 1
228
- elif pred < t["opt"]: unsafe += 1
229
- else: fd += 1
230
- cost += TIER_COST[pred]
231
- return {"success":succ/n_test, "avg_cost":cost/n_test, "unsafe_rate":unsafe/n_test,
232
- "false_done":fd/n_test, "tier_dist":dict(td),
233
- "escalations":escalations, "downgrades":downgrades, "heuristic_only":heuristic_only}
234
-
235
- results["always_frontier"] = eval_router("always_frontier", lambda t: 4)
236
- results["always_cheap"] = eval_router("always_cheap", lambda t: 1)
237
- results["heuristic_diff+1"] = eval_router("heuristic_diff+1", lambda t: min(t["diff"]+1, 5))
238
- results["heuristic_floor"] = eval_router("heuristic_floor", lambda t: TASK_FLOOR.get(t["tt"], 2))
239
- results["oracle"] = eval_router("oracle", lambda t: t["opt"])
240
-
241
- # Hybrid at different thresholds
242
- for st in [0.25, 0.30, 0.35, 0.40]:
243
- for dt in [0.70, 0.75, 0.80, 0.85]:
244
- name = f"hybrid_s{st:.2f}_d{dt:.2f}"
245
- results[name] = eval_router(name,
246
- lambda t, s=st, d=dt: route_hybrid(t["req"], t["tt"], t["diff"], s, d))
247
-
248
- # Print top results
249
- print(f"\n{'Router':<30} {'Success':>10} {'AvgCost':>10} {'CostRed':>10} {'Unsafe':>10} {'F-DONE':>10}")
250
- print("-"*80)
251
- fc = results["always_frontier"]["avg_cost"]
252
-
253
- # Only show key results + top 10 hybrids
254
- shown = set()
255
- for name in ["always_frontier","always_cheap","heuristic_diff+1","heuristic_floor","oracle"]:
256
- r = results[name]
257
- cr = (1 - r["avg_cost"]/fc)*100
258
- print(f"{name:<30} {r['success']:>10.3f} {r['avg_cost']:>10.4f} {cr:>9.1f}% {r['unsafe_rate']:>10.3f} {r['false_done']:>10.3f}")
259
- shown.add(name)
260
-
261
- # Top 10 hybrids by composite score
262
- hybrid_scores = []
263
- for name, r in results.items():
264
- if name in shown or not name.startswith("hybrid"): continue
265
- score = r["success"]*20 - r["avg_cost"]*30 - r["unsafe_rate"]*100
266
- hybrid_scores.append((score, name, r))
267
- hybrid_scores.sort(reverse=True)
268
-
269
- for score, name, r in hybrid_scores[:10]:
270
- cr = (1 - r["avg_cost"]/fc)*100
271
- esc = r["escalations"]; down = r["downgrades"]; honly = r["heuristic_only"]
272
- print(f"{name:<30} {r['success']:>10.3f} {r['avg_cost']:>10.4f} {cr:>9.1f}% {r['unsafe_rate']:>10.3f} {r['false_done']:>10.3f} esc={esc} down={down} same={honly}")
273
-
274
- # ─── Per-task breakdown for best hybrid ────────────────────────────────
275
- best_hybrid_name = hybrid_scores[0][1] if hybrid_scores else "heuristic_diff+1"
276
- print(f"\n\n[6] Per-task breakdown for {best_hybrid_name}...")
277
-
278
- for tt in sorted(set(t["tt"] for t in eval_traces)):
279
- tt_traces = [t for t in eval_traces if t["tt"] == tt]
280
- n_tt = len(tt_traces)
281
- if n_tt == 0: continue
282
- print(f"\n {tt} (n={n_tt}):")
283
- for rname, rfn in [("frontier", lambda t:4),
284
- ("heuristic", lambda t:min(t["diff"]+1,5)),
285
- ("hybrid", lambda t:route_hybrid(t["req"],t["tt"],t["diff"],0.35,0.80)),
286
- ("oracle", lambda t:t["opt"])]:
287
- succ = sum(1 for t in tt_traces if t["tier_out"].get(rfn(t), False))
288
- cost = sum(TIER_COST[rfn(t)] for t in tt_traces)
289
- sr = succ/n_tt; ac = cost/n_tt
290
- cr = (1-ac/fc)*100
291
- print(f" {rname:<12} success={sr:.3f} cost={ac:.4f} costRed={cr:.1f}%")
292
-
293
- # ─── Pareto ────────────────────────────────────────────────────────────
294
- print(f"\n\n[7] Pareto frontier...")
295
- for name, r in results.items():
296
- if name == "always_cheap": continue
297
- dominated = False
298
- for name2, r2 in results.items():
299
- if name == name2: continue
300
- if r2["success"] >= r["success"] and r2["avg_cost"] <= r["avg_cost"]:
301
- if r2["success"] > r["success"] or r2["avg_cost"] < r["avg_cost"]:
302
- dominated = True; break
303
- if not dominated:
304
- cr = (1-r["avg_cost"]/fc)*100
305
- print(f" {name:<30} success={r['success']:.3f} cost={r['avg_cost']:.4f} costRed={cr:.1f}% unsafe={r['unsafe_rate']:.3f}")
306
-
307
- # ─── Save ──────────────────────────────────────────────────────────────
308
- print("\n[8] Saving final model...")
309
- os.makedirs("/app/router_models", exist_ok=True)
310
-
311
- bundle = {
312
- "tier_clfs": {str(k):v for k,v in tier_clfs.items()},
313
- "tier_calibrators": {str(k):v for k,v in tier_calibs.items()},
314
- "feat_keys": FEAT_KEYS,
315
- "tier_config": {"tier_cost":TIER_COST,"tier_str":TIER_STR,
316
- "task_floor":TASK_FLOOR,
317
- "safety_threshold":0.35,"downgrade_threshold":0.80},
318
- "version": "6.0",
319
- "description": "ACO Hybrid Router: heuristic base + ML safety net + ML cost saver",
320
- }
321
- with open("/app/router_models/router_bundle_v6.pkl","wb") as f:
322
- pickle.dump(bundle, f)
323
- print(f" Saved router_bundle_v6.pkl ({os.path.getsize('/app/router_models/router_bundle_v6.pkl')/1024:.0f} KB)")
324
-
325
- with open("/app/router_models/v6_eval_results.json","w") as f:
326
- json.dump(results, f, indent=2, default=str)
327
-
328
- print(f"\n\n{'='*80}")
329
- print("FINAL v6 COMPARISON")
330
- print(f"{'='*80}")
331
- print(f"\n{'Router':<30} {'Success':>10} {'AvgCost':>10} {'CostRed':>10} {'Unsafe':>10} {'F-DONE':>10}")
332
- print("-"*80)
333
- for name, r in sorted(results.items(), key=lambda x: (-x[1]["success"], x[1]["avg_cost"])):
334
- if name.startswith("hybrid") and name != best_hybrid_name: continue
335
- cr = (1-r["avg_cost"]/fc)*100
336
- print(f"{name:<30} {r['success']:>10.3f} {r['avg_cost']:>10.4f} {cr:>9.1f}% {r['unsafe_rate']:>10.3f} {r['false_done']:>10.3f}")
337
-
338
- print(f"\nDONE!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
training/router_v7_tuned.py DELETED
@@ -1,95 +0,0 @@
1
- #!/usr/bin/env python3
2
- """v7: Tuned hybrid with conservative downgrades + aggressive safety net."""
3
- import json, os, sys, random, uuid, pickle
4
- import numpy as np
5
- from collections import defaultdict
6
-
7
- # ─── Reuse v6 infrastructure ──────────────────────────────────────────
8
- exec(open("/app/router_v6_hybrid.py").read().split("# ─── Save ─")[0])
9
-
10
- # Override route_hybrid with tuned thresholds
11
- print("\n\n[EXTRA] Fine-tuned threshold sweep...")
12
-
13
- # Only downgrade when very confident (0.90+), but escalate when P(success) < 0.30
14
- def route_v7(request, task_type, difficulty, safety=0.30, downgrade=0.90):
15
- h = min(difficulty + 1, 5)
16
- floor = TASK_FLOOR.get(task_type, 2)
17
- h = max(h, floor)
18
- feats = extract_features(request, task_type, difficulty)
19
- x = f2v(feats).reshape(1, -1)
20
- tier = h
21
- # Safety net
22
- ps = get_calibrated_psuccess(x, tier)
23
- if ps < safety and tier < 5:
24
- tier += 1
25
- ps = get_calibrated_psuccess(x, tier)
26
- # Cost saver (conservative: only downgrade when very confident)
27
- if tier > floor and tier == h:
28
- cheaper = tier - 1
29
- pc = get_calibrated_psuccess(x, cheaper)
30
- if pc >= downgrade and cheaper >= floor:
31
- tier = cheaper
32
- return tier
33
-
34
- # Sweep
35
- for s in [0.25, 0.30, 0.35]:
36
- for d in [0.85, 0.90, 0.95]:
37
- name = f"v7_s{s:.2f}_d{d:.2f}"
38
- results[name] = eval_router(name, lambda t, s=s, d=d: route_v7(t["req"], t["tt"], t["diff"], s, d))
39
-
40
- # Also try: no downgrade at all, only safety net
41
- def route_v7_safety_only(request, task_type, difficulty, safety=0.30):
42
- h = min(difficulty + 1, 5)
43
- floor = TASK_FLOOR.get(task_type, 2)
44
- h = max(h, floor)
45
- feats = extract_features(request, task_type, difficulty)
46
- x = f2v(feats).reshape(1, -1)
47
- ps = get_calibrated_psuccess(x, h)
48
- tier = h
49
- while ps < safety and tier < 5:
50
- tier += 1
51
- ps = get_calibrated_psuccess(x, tier)
52
- return tier
53
-
54
- for s in [0.25, 0.30, 0.35, 0.40, 0.45]:
55
- name = f"v7_safety_s{s:.2f}"
56
- results[name] = eval_router(name, lambda t, s=s: route_v7_safety_only(t["req"], t["tt"], t["diff"], s))
57
-
58
- # Print final results
59
- print(f"\n\n{'='*80}")
60
- print("FINAL v7 COMPARISON")
61
- print(f"{'='*80}")
62
- print(f"\n{'Router':<30} {'Success':>10} {'AvgCost':>10} {'CostRed':>10} {'Unsafe':>10} {'F-DONE':>10}")
63
- print("-"*80)
64
- fc = results["always_frontier"]["avg_cost"]
65
-
66
- # Key baselines
67
- for name in ["always_frontier","heuristic_diff+1","oracle"]:
68
- r = results[name]
69
- cr = (1-r["avg_cost"]/fc)*100
70
- print(f"{name:<30} {r['success']:>10.3f} {r['avg_cost']:>10.4f} {cr:>9.1f}% {r['unsafe_rate']:>10.3f} {r['false_done']:>10.3f}")
71
-
72
- # Best v7 variants
73
- for name, r in sorted(results.items(), key=lambda x: (-x[1]["success"], x[1]["avg_cost"])):
74
- if not name.startswith("v7"): continue
75
- cr = (1-r["avg_cost"]/fc)*100
76
- print(f"{name:<30} {r['success']:>10.3f} {r['avg_cost']:>10.4f} {cr:>9.1f}% {r['unsafe_rate']:>10.3f} {r['false_done']:>10.3f}")
77
-
78
- # Find the winner
79
- print(f"\n\nBEST ROUTER SELECTION:")
80
- print(f" Iso-quality (success >= 0.84):")
81
- for name, r in sorted(results.items(), key=lambda x: (x[1]["avg_cost"])):
82
- if r["success"] >= 0.84 and name not in ("always_cheap",):
83
- cr = (1-r["avg_cost"]/fc)*100
84
- print(f" {name:<30} success={r['success']:.3f} cost={r['avg_cost']:.4f} costRed={cr:.1f}%")
85
- break # just show the cheapest at that quality
86
-
87
- print(f"\n Best quality (max success):")
88
- best = max(results.items(), key=lambda x: x[1]["success"])
89
- cr = (1-best[1]["avg_cost"]/fc)*100
90
- print(f" {best[0]:<30} success={best[1]['success']:.3f} cost={best[1]['avg_cost']:.4f} costRed={cr:.1f}%")
91
-
92
- print(f"\n Best composite (success*20 - cost*30 - unsafe*100):")
93
- best_comp = max(results.items(), key=lambda x: x[1]["success"]*20 - x[1]["avg_cost"]*30 - x[1]["unsafe_rate"]*100)
94
- cr = (1-best_comp[1]["avg_cost"]/fc)*100
95
- print(f" {best_comp[0]:<30} success={best_comp[1]['success']:.3f} cost={best_comp[1]['avg_cost']:.4f} costRed={cr:.1f}%")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
training/router_v8_final.py DELETED
@@ -1,228 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Final Production Router v8: Dynamic difficulty + ML confirmation + safety floors.
3
-
4
- This is the production router that replaces the heuristic in ACO.
5
- """
6
- import json, os, sys, random, uuid, pickle
7
- import numpy as np
8
- from collections import defaultdict
9
-
10
- print("="*80)
11
- print("ACO PRODUCTION ROUTER v8: DYNAMIC DIFFICULTY + ML")
12
- print("="*80)
13
-
14
- # ─── Load Models ──────────────────────────────────────────────────────
15
- MODEL_DIR = "/app/router_models"
16
- bundle = pickle.load(open(f"{MODEL_DIR}/router_bundle_v6.pkl", "rb"))
17
- feat_keys = bundle["feat_keys"]
18
- tier_clfs = {int(k):v for k,v in bundle["tier_clfs"].items()}
19
- tier_calibs = {int(k):v for k,v in bundle["tier_calibrators"].items()}
20
- TIER_COST = {int(k):v for k,v in bundle["tier_config"]["tier_cost"].items()}
21
- TIER_STR = {int(k):v for k,v in bundle["tier_config"]["tier_str"].items()}
22
- TASK_FLOOR = bundle["tier_config"]["task_floor"]
23
-
24
- # ─── Feature Extraction ────────────────────────────────────────────────
25
- CODE_KW = ["python","javascript","code","function","bug","debug","refactor","implement","test",
26
- "compile","runtime","segfault","thread","async","class","module"]
27
- LEGAL_KW = ["contract","legal","compliance","gdpr","privacy","policy","regulatory","liability","indemnification","clause"]
28
- RESEARCH_KW = ["research","find sources","literature","investigate","compare","analyze","survey","paper","arxiv"]
29
- TOOL_KW = ["search","fetch","retrieve","query","api","database","scrape","aggregate"]
30
- LONG_KW = ["plan","project","roadmap","orchestrate","multi-step","migrate","pipeline","deploy","architecture"]
31
- MATH_KW = ["calculate","compute","solve","equation","formula","optimize","probability","integral"]
32
- CRITICAL_KW = ["critical","production","urgent","now","emergency","live","deployed","safety","security"]
33
- SIMPLE_KW = ["typo","simple","quick","brief","briefly","just","minor","small","easy","trivial","clarification"]
34
- TT2IDX = {"quick_answer":0,"coding":1,"research":2,"document_drafting":3,
35
- "legal_regulated":4,"tool_heavy":5,"retrieval_heavy":6,"long_horizon":7,"unknown_ambiguous":8}
36
-
37
- def estimate_difficulty(request, task_type):
38
- r = request.lower()
39
- base = {"quick_answer":1,"document_drafting":2,"tool_heavy":2,"retrieval_heavy":2,
40
- "research":3,"coding":3,"unknown_ambiguous":3,"long_horizon":4,"legal_regulated":5}[task_type]
41
- if any(k in r for k in CRITICAL_KW): base = min(base + 1, 5)
42
- if any(k in r for k in SIMPLE_KW): base = max(base - 1, 1)
43
- return base
44
-
45
- def extract_features(request, task_type, difficulty=3):
46
- r = request.lower()
47
- f = {"req_len":len(request),"num_words":len(request.split()),
48
- "has_code":int(any(k in r for k in CODE_KW)),"n_code":sum(1 for k in CODE_KW if k in r),
49
- "has_legal":int(any(k in r for k in LEGAL_KW)),"n_legal":sum(1 for k in LEGAL_KW if k in r),
50
- "has_research":int(any(k in r for k in RESEARCH_KW)),"n_research":sum(1 for k in RESEARCH_KW if k in r),
51
- "has_tool":int(any(k in r for k in TOOL_KW)),"n_tool":sum(1 for k in TOOL_KW if k in r),
52
- "has_long":int(any(k in r for k in LONG_KW)),
53
- "has_math":int(any(k in r for k in MATH_KW)),
54
- "tt_idx":TT2IDX.get(task_type,8),"difficulty":difficulty}
55
- for tt in TT2IDX:
56
- f[f"tt_{tt}"] = int(task_type == tt)
57
- return f
58
-
59
- def f2v(feats):
60
- return np.array([float(feats.get(k, 0.0)) for k in feat_keys], dtype=np.float32)
61
-
62
- def get_calibrated_psuccess(x, tier):
63
- p_raw = tier_clfs[tier].predict_proba(x)[0, 1]
64
- return float(tier_calibs[tier].transform([p_raw])[0])
65
-
66
- def route_production_v8(request, task_type, safety=0.30, downgrade=0.90):
67
- diff = estimate_difficulty(request, task_type)
68
- base = min(diff + 1, 5)
69
- floor = TASK_FLOOR.get(task_type, 2)
70
- base = max(base, floor)
71
- feats = extract_features(request, task_type, diff)
72
- x = f2v(feats).reshape(1, -1)
73
- tier = base
74
- ps = get_calibrated_psuccess(x, tier)
75
- # Safety net
76
- if ps < safety and tier < 5:
77
- tier += 1
78
- ps = get_calibrated_psuccess(x, tier)
79
- # Cost saver
80
- if tier > floor and tier == base:
81
- cheaper = tier - 1
82
- pc = get_calibrated_psuccess(x, cheaper)
83
- if pc >= downgrade and cheaper >= floor:
84
- tier = cheaper
85
- ps = pc
86
- return tier, ps, diff
87
-
88
- # ─── Generate Eval Traces ────────────────────────────────────────────
89
- TASK_TEMPLATES = {
90
- "quick_answer":["What is the capital of France?","Explain quantum computing briefly.",
91
- "What is 237*452?","Briefly explain photosynthesis.","Just tell me what 2+2 is.",
92
- "Small clarification on this formula."],
93
- "coding":["Write a Python function to reverse a linked list.",
94
- "Fix the bug in this React component.","Refactor auth module to JWT.",
95
- "Implement LRU cache in Go.","Debug segfault in C++ thread pool.",
96
- "Fix a typo in the README.","Debug this critical production segfault NOW.",
97
- "Just fix the typo in line 42."],
98
- "research":["Research latest transformer advances.",
99
- "Find sources comparing LoRA and full FT.",
100
- "Investigate data center climate impact.",
101
- "Find sources comparing LoRA and full FT briefly."],
102
- "document_drafting":["Draft project proposal for ML pipeline.",
103
- "Write email to team about deployment.","Create technical report on performance."],
104
- "legal_regulated":["Review this contract for liability clauses.",
105
- "Check GDPR compliance for data pipeline.","Draft privacy policy section.",
106
- "Check GDPR compliance urgently."],
107
- "tool_heavy":["Search open issues and create summary.",
108
- "Fetch API docs and generate client code.","Query Q3 sales and produce chart."],
109
- "retrieval_heavy":["Answer based on 50-page document.",
110
- "Find all payment processing mentions."],
111
- "long_horizon":["Plan 3-month roadmap.","Orchestrate multi-region deployment.",
112
- "Redesign data architecture end-to-end.",
113
- "Orchestrate complete multi-region deployment."],
114
- "unknown_ambiguous":["Help me with this thing.",
115
- "I need something about the server."],
116
- }
117
-
118
- def tsp(tier, diff):
119
- return TIER_STR[tier] ** (diff * 0.6)
120
-
121
- print("\n[1] Generating 2K eval traces...")
122
- rng = random.Random(999)
123
- traces = []
124
- for i in range(2000):
125
- tt = rng.choice(list(TASK_TEMPLATES.keys()))
126
- # Use STATIC difficulty for ground truth (same as heuristic)
127
- static_diff = {"quick_answer":1,"document_drafting":2,"tool_heavy":2,"retrieval_heavy":2,
128
- "research":3,"coding":3,"unknown_ambiguous":3,"long_horizon":4,"legal_regulated":5}[tt]
129
- req = rng.choice(TASK_TEMPLATES[tt])
130
- # Dynamic difficulty from request text
131
- dyn_diff = estimate_difficulty(req, tt)
132
-
133
- tier_out = {t: rng.random() < tsp(t, dyn_diff) for t in range(1,6)}
134
- opt = 5
135
- for t in range(1,6):
136
- if tier_out[t]: opt = t; break
137
- traces.append({"tt":tt,"static_diff":static_diff,"dyn_diff":dyn_diff,
138
- "opt":opt,"tier_out":tier_out,"req":req})
139
-
140
- print(f" Generated {len(traces)} traces")
141
-
142
- # ─── Evaluate ──────────────────────────────────────────────────────────
143
- print("\n[2] Evaluating all routers...")
144
- n = len(traces)
145
-
146
- def eval_router(name, route_fn):
147
- succ=0; cost=0.0; unsafe=0; fd=0; td=defaultdict(int)
148
- for t in traces:
149
- pred = route_fn(t)
150
- td[pred] += 1
151
- if t["tier_out"].get(pred, False): succ += 1
152
- elif pred < t["opt"]: unsafe += 1
153
- else: fd += 1
154
- cost += TIER_COST[pred]
155
- return {"success":succ/n,"avg_cost":cost/n,"unsafe_rate":unsafe/n,
156
- "false_done":fd/n,"tier_dist":dict(td)}
157
-
158
- results = {}
159
- results["always_frontier"] = eval_router("always_frontier", lambda t: 4)
160
- results["always_cheap"] = eval_router("always_cheap", lambda t: 1)
161
- results["heuristic_static"] = eval_router("heuristic_static",
162
- lambda t: max(min(t["static_diff"]+1,5), TASK_FLOOR.get(t["tt"],2)))
163
- results["oracle"] = eval_router("oracle", lambda t: t["opt"])
164
-
165
- # v8 production router
166
- results["v8_dynamic+ML"] = eval_router("v8_dynamic+ML",
167
- lambda t: route_production_v8(t["req"], t["tt"])[0])
168
-
169
- # v8 without ML (just dynamic difficulty)
170
- results["v8_dynamic_only"] = eval_router("v8_dynamic_only",
171
- lambda t: max(min(t["dyn_diff"]+1,5), TASK_FLOOR.get(t["tt"],2)))
172
-
173
- # Print
174
- print(f"\n{'Router':<25} {'Success':>10} {'AvgCost':>10} {'CostRed':>10} {'Unsafe':>10} {'F-DONE':>10}")
175
- print("-"*75)
176
- fc = results["always_frontier"]["avg_cost"]
177
- for name, r in sorted(results.items(), key=lambda x: (-x[1]["success"], x[1]["avg_cost"])):
178
- cr = (1-r["avg_cost"]/fc)*100
179
- print(f"{name:<25} {r['success']:>10.3f} {r['avg_cost']:>10.4f} {cr:>9.1f}% {r['unsafe_rate']:>10.3f} {r['false_done']:>10.3f}")
180
-
181
- # Per-task breakdown
182
- print(f"\n\n[3] Per-task breakdown...")
183
- for tt in sorted(set(t["tt"] for t in traces)):
184
- tt_r = [t for t in traces if t["tt"] == tt]
185
- n_tt = len(tt_r)
186
- print(f"\n {tt} (n={n_tt}):")
187
- for rname, rfn in [("frontier", lambda t:4),
188
- ("heuristic", lambda t:max(min(t["static_diff"]+1,5),TASK_FLOOR.get(t["tt"],2))),
189
- ("v8_dynamic", lambda t:max(min(t["dyn_diff"]+1,5),TASK_FLOOR.get(t["tt"],2))),
190
- ("v8_full", lambda t:route_production_v8(t["req"],t["tt"])[0]),
191
- ("oracle", lambda t:t["opt"])]:
192
- succ = sum(1 for t in tt_r if t["tier_out"].get(rfn(t), False))
193
- cost = sum(TIER_COST[rfn(t)] for t in tt_r)
194
- sr = succ/n_tt; ac = cost/n_tt
195
- cr = (1-ac/fc)*100
196
- print(f" {rname:<14} success={sr:.3f} cost={ac:.4f} costRed={cr:.1f}%")
197
-
198
- # Save
199
- with open("/app/router_models/v8_final_results.json","w") as f:
200
- json.dump(results, f, indent=2, default=str)
201
-
202
- # Save v8 bundle
203
- v8_bundle = {
204
- "tier_clfs": {str(k):v for k,v in tier_clfs.items()},
205
- "tier_calibrators": {str(k):v for k,v in tier_calibs.items()},
206
- "feat_keys": feat_keys,
207
- "tier_config": {str(k):v for k,v in TIER_COST.items()},
208
- "task_floor": TASK_FLOOR,
209
- "version": "8.0",
210
- "description": "ACO Production Router v8: dynamic difficulty + ML confirmation + safety floors",
211
- "dynamic_difficulty": True,
212
- "critical_keywords": CRITICAL_KW,
213
- "simple_keywords": SIMPLE_KW,
214
- }
215
- with open("/app/router_models/router_bundle_v8.pkl","wb") as f:
216
- pickle.dump(v8_bundle, f)
217
-
218
- print(f"\n\n{'='*80}")
219
- print("FINAL v8 RESULTS")
220
- print(f"{'='*80}")
221
- print(f"\n{'Router':<25} {'Success':>10} {'AvgCost':>10} {'CostRed':>10} {'Unsafe':>10}")
222
- print("-"*65)
223
- for name, r in sorted(results.items(), key=lambda x: (-x[1]["success"], x[1]["avg_cost"])):
224
- cr = (1-r["avg_cost"]/fc)*100
225
- print(f"{name:<25} {r['success']:>10.3f} {r['avg_cost']:>10.4f} {cr:>9.1f}% {r['unsafe_rate']:>10.3f}")
226
-
227
- print(f"\nSaved router_bundle_v8.pkl ({os.path.getsize('/app/router_models/router_bundle_v8.pkl')/1024:.0f} KB)")
228
- print(f"DONE!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
training/train_v10_fixed.py DELETED
@@ -1,250 +0,0 @@
1
- #!/usr/bin/env python3
2
- """v10 Router: Fixed regularization for 500-sample training set.
3
- from collections import Counter
4
-
5
- Problem: XGBoost with 23 features and 500 samples overfits (100% train acc).
6
- Solution: Heavy regularization + fewer estimators + stratified CV.
7
- """
8
- import sys, json, random, pickle, numpy as np
9
- from collections import defaultdict
10
- from datasets import load_dataset
11
- import warnings
12
- from collections import Counter
13
- warnings.filterwarnings('ignore')
14
-
15
- from xgboost import XGBClassifier
16
- from sklearn.calibration import IsotonicRegression
17
- from sklearn.model_selection import cross_val_score
18
-
19
- print("="*80)
20
- print("v10 ROUTER: FIXED REGULARIZATION")
21
- print("="*80)
22
-
23
- # Load traces
24
- MODELS = ['claude-opus-4.7','gpt-5-mini','gpt-5-nano','gpt-5.2',
25
- 'gemini-2.5-pro','gemini-3-pro','deepseek-v3.2','deepseek-v4-flash']
26
- MODEL_TIER = {
27
- 'deepseek-v4-flash':1,'gpt-5-nano':1,'gpt-5-mini':2,'deepseek-v3.2':2,
28
- 'gemini-2.5-pro':3,'claude-opus-4.7':4,'gpt-5.2':4,'gemini-3-pro':5,
29
- }
30
- TIER_COST = {1:0.01,2:0.05,3:0.15,4:0.30,5:0.50}
31
- TIER_TO_MODEL = {1:'deepseek-v4-flash',2:'gpt-5-mini',3:'gemini-2.5-pro',4:'claude-opus-4.7',5:'gemini-3-pro'}
32
-
33
- # Feature extraction (same as before)
34
- CODE_KW=["python","code","function","bug","debug","refactor","implement","test","error","traceback","import"]
35
- CRITICAL_KW=["critical","production","urgent","emergency","live","deployed","safety","security"]
36
- SIMPLE_KW=["typo","simple","quick","brief","minor","small","easy","trivial","just"]
37
-
38
- FEAT_KEYS = sorted([
39
- 'req_len','num_words','has_code','n_code','has_critical','has_simple',
40
- 'has_error_msg','has_file_path','n_lines','has_fix','has_add',
41
- 'has_change','has_test','has_doc',
42
- ])
43
-
44
- def extract_features(text):
45
- r = text.lower()
46
- return {
47
- 'req_len':len(text),'num_words':len(text.split()),
48
- 'has_code':int(any(k in r for k in CODE_KW)),
49
- 'n_code':sum(1 for k in CODE_KW if k in r),
50
- 'has_critical':int(any(k in r for k in CRITICAL_KW)),
51
- 'has_simple':int(any(k in r for k in SIMPLE_KW)),
52
- 'has_error_msg':int('error' in r or 'traceback' in r or 'exception' in r),
53
- 'has_file_path':int('/' in r),
54
- 'n_lines':text.count('\n')+1,
55
- 'has_fix':int('fix' in r or 'bug' in r or 'issue' in r),
56
- 'has_add':int('add' in r or 'new' in r or 'create' in r),
57
- 'has_change':int('change' in r or 'modify' in r or 'update' in r),
58
- 'has_test':int('test' in r or 'spec' in r),
59
- 'has_doc':int('doc' in r or 'readme' in r),
60
- }
61
-
62
- print("\n[1] Loading traces...")
63
- traces = defaultdict(dict)
64
- for model in MODELS:
65
- ds = load_dataset(f'SWE-Router/swebench-verified-{model}', split='test')
66
- for row in ds:
67
- traces[row['instance_id']][model] = {
68
- 'resolved':row['resolved'], 'cost':float(row['instance_cost']),
69
- 'problem':row['problem_statement'],
70
- }
71
- print(f" {len(traces)} tasks loaded")
72
-
73
- print("\n[2] Building features...")
74
- X = []
75
- tier_labels = {t:[] for t in range(1,6)}
76
- optimal_tiers = []
77
-
78
- for iid, model_results in traces.items():
79
- problem = next(iter(model_results.values()))['problem']
80
- feats = extract_features(problem)
81
- feat_vec = [float(feats.get(k,0.0)) for k in FEAT_KEYS]
82
- X.append(feat_vec)
83
-
84
- tier_success = {}
85
- for model, result in model_results.items():
86
- tier = MODEL_TIER[model]
87
- if tier not in tier_success: tier_success[tier] = False
88
- if result['resolved']: tier_success[tier] = True
89
-
90
- for t in range(1,6):
91
- tier_labels[t].append(int(tier_success.get(t, False)))
92
-
93
- opt = 5
94
- for t in range(1,6):
95
- if tier_success.get(t, False): opt = t; break
96
- optimal_tiers.append(opt)
97
-
98
- X = np.array(X, dtype=np.float32)
99
- print(f" X shape: {X.shape}")
100
- print(f" Optimal tier dist: {Counter(optimal_tiers)}")
101
-
102
- # Train with HEAVY regularization
103
- print("\n[3] Training with heavy regularization...")
104
- tier_clfs = {}
105
- tier_calibs = {}
106
-
107
- for t in range(1,6):
108
- y = np.array(tier_labels[t])
109
- n_pos = y.sum()
110
- spw = max(1, (len(y)-n_pos)/max(n_pos,1))
111
-
112
- # Heavy regularization to prevent overfitting on 500 samples
113
- clf = XGBClassifier(
114
- n_estimators=50, # Reduced from 200
115
- max_depth=3, # Reduced from 5
116
- learning_rate=0.1,
117
- subsample=0.7,
118
- colsample_bytree=0.6,
119
- min_child_weight=10, # Prevent memorization
120
- gamma=1.0, # Require significant splits
121
- reg_alpha=1.0, # L1 regularization
122
- reg_lambda=5.0, # L2 regularization
123
- scale_pos_weight=spw,
124
- eval_metric='logloss',
125
- random_state=42,
126
- )
127
-
128
- # Cross-validate
129
- try:
130
- scores = cross_val_score(clf, X, y, cv=5, scoring='f1')
131
- cv_f1 = scores.mean()
132
- except: cv_f1 = 0.0
133
-
134
- clf.fit(X, y)
135
-
136
- # Check train accuracy
137
- train_pred = clf.predict(X)
138
- train_acc = np.mean(train_pred == y)
139
-
140
- # Calibrate
141
- p_raw = clf.predict_proba(X)[:,1]
142
- cal = IsotonicRegression(out_of_bounds='clip')
143
- cal.fit(p_raw, y)
144
- p_cal = cal.transform(p_raw)
145
-
146
- # Check calibration range
147
- p_min, p_max = p_cal.min(), p_cal.max()
148
- p_mean = p_cal.mean()
149
-
150
- tier_clfs[t] = clf
151
- tier_calibs[t] = cal
152
- print(f" Tier {t}: cv_f1={cv_f1:.3f}, train_acc={train_acc:.3f}, "
153
- f"P(success) range=[{p_min:.3f},{p_max:.3f}], mean={p_mean:.3f}")
154
-
155
- from collections import Counter
156
-
157
- # Evaluate with different thresholds
158
- print("\n[4] Evaluating with threshold sweep...")
159
- best_thr = None
160
- best_score = -999
161
-
162
- for thr in [0.60, 0.65, 0.70, 0.75, 0.80, 0.85]:
163
- succ=0; cost=0.0
164
- for iid, model_results in traces.items():
165
- problem = next(iter(model_results.values()))['problem']
166
- feats = extract_features(problem)
167
- feat_vec = np.array([float(feats.get(k,0.0)) for k in FEAT_KEYS], dtype=np.float32).reshape(1,-1)
168
-
169
- # Route: cheapest tier with P(success) >= thr
170
- selected_tier = 5
171
- tier_probs = {}
172
- for t in range(1,6):
173
- p_raw = tier_clfs[t].predict_proba(feat_vec)[0,1]
174
- p_cal = float(tier_calibs[t].transform([p_raw])[0])
175
- tier_probs[t] = p_cal
176
- if p_cal >= thr and selected_tier == 5:
177
- selected_tier = t
178
-
179
- model = TIER_TO_MODEL.get(selected_tier, 'claude-opus-4.7')
180
- if model in model_results and model_results[model]['resolved']:
181
- succ += 1
182
- cost += model_results[model]['cost']
183
- else:
184
- cost += model_results.get(model,{}).get('cost', TIER_COST[selected_tier])
185
-
186
- sr = succ/len(traces)
187
- ac = cost/len(traces)
188
- cr = (1-ac/0.3167)*100
189
- score = sr*20 - ac*10 # weighted score
190
- print(f" thr={thr:.2f}: success={sr:.3f}, cost=${ac:.4f}, costRed={cr:.1f}%")
191
- if score > best_score:
192
- best_score = score
193
- best_thr = thr
194
-
195
- print(f"\n Best threshold: {best_thr}")
196
-
197
- # v10 + feedback: route cheap, escalate on failure
198
- print("\n[5] v10 + feedback evaluation...")
199
- for thr in [0.70, 0.75, 0.80]:
200
- succ=0; cost=0.0; escalated=0
201
- for iid, model_results in traces.items():
202
- problem = next(iter(model_results.values()))['problem']
203
- feats = extract_features(problem)
204
- feat_vec = np.array([float(feats.get(k,0.0)) for k in FEAT_KEYS], dtype=np.float32).reshape(1,-1)
205
-
206
- selected_tier = 5
207
- for t in range(1,6):
208
- p_raw = tier_clfs[t].predict_proba(feat_vec)[0,1]
209
- p_cal = float(tier_calibs[t].transform([p_raw])[0])
210
- if p_cal >= thr and selected_tier == 5:
211
- selected_tier = t
212
-
213
- model = TIER_TO_MODEL.get(selected_tier, 'claude-opus-4.7')
214
-
215
- # Try cheap model first
216
- if model in model_results and model_results[model]['resolved']:
217
- succ += 1
218
- cost += model_results[model]['cost']
219
- elif selected_tier < 5:
220
- # Escalate
221
- up_tier = min(selected_tier+1, 5)
222
- up_model = TIER_TO_MODEL.get(up_tier, 'claude-opus-4.7')
223
- escalated += 1
224
- if up_model in model_results and model_results[up_model]['resolved']:
225
- succ += 1
226
- cost += model_results[model]['cost'] + model_results[up_model]['cost']
227
- else:
228
- cost += model_results[model]['cost'] + model_results.get(up_model,{}).get('cost', TIER_COST[up_tier])
229
- else:
230
- cost += model_results.get(model,{}).get('cost', TIER_COST[selected_tier])
231
-
232
- sr = succ/len(traces)
233
- ac = cost/len(traces)
234
- cr = (1-ac/0.3167)*100
235
- print(f" v10_feedback(thr={thr:.2f}): success={sr:.3f}, cost=${ac:.4f}, costRed={cr:.1f}%, escalated={escalated}")
236
-
237
- # Save fixed bundle
238
- v10_fixed = {
239
- 'tier_clfs': {str(k):v for k,v in tier_clfs.items()},
240
- 'tier_calibrators': {str(k):v for k,v in tier_calibs.items()},
241
- 'feat_keys': FEAT_KEYS,
242
- 'tier_config': {str(k):v for k,v in TIER_COST.items()},
243
- 'version': '10.1',
244
- 'description': 'ACO v10.1: Regularized XGBoost on SWE-Router data',
245
- 'best_threshold': best_thr,
246
- }
247
- with open('/app/router_models/router_bundle_v10_fixed.pkl', 'wb') as f:
248
- pickle.dump(v10_fixed, f)
249
- print(f"\nSaved v10.1 bundle")
250
- print("DONE!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
training/tune_feedback.py DELETED
@@ -1,98 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Tune execution-feedback thresholds for optimal cost-quality tradeoff."""
3
- import sys,json,random,math,pickle
4
- sys.path.insert(0,"/app")
5
- from collections import defaultdict
6
- from aco.classifier import TaskCostClassifier
7
- from aco.router import ModelCascadeRouter
8
- from aco.execution_feedback import ExecutionFeedbackRouter
9
-
10
- TIER_STR={1:0.35,2:0.55,3:0.80,4:0.93,5:0.97}
11
- TIER_COST={1:0.05,2:0.15,3:0.75,4:1.0,5:1.5}
12
- TASK_FLOOR={"legal_regulated":4,"long_horizon":3,"research":3,"coding":3,
13
- "unknown_ambiguous":3,"quick_answer":1,"document_drafting":2,
14
- "tool_heavy":2,"retrieval_heavy":2}
15
-
16
- TASKS = {
17
- "quick_answer":["What is 2+2?","Explain quantum computing briefly.","Convert 100F to Celsius."],
18
- "coding":["Write a Python function to reverse a linked list.","Fix a typo in the README.",
19
- "Debug this critical production segfault NOW.","Just fix the typo in line 42."],
20
- "research":["Research latest transformer advances.","Find sources comparing LoRA and full FT briefly."],
21
- "document_drafting":["Draft project proposal for ML pipeline."],
22
- "legal_regulated":["Review this contract for liability clauses.","Check GDPR compliance."],
23
- "tool_heavy":["Search open issues and create summary."],
24
- "retrieval_heavy":["Answer based on 50-page document."],
25
- "long_horizon":["Plan 3-month roadmap.","Orchestrate multi-region deployment."],
26
- "unknown_ambiguous":["Help me with this thing."],
27
- }
28
-
29
- classifier = TaskCostClassifier()
30
- router = ModelCascadeRouter(model_path="/app/router_models/router_bundle_v8.pkl")
31
-
32
- rng = random.Random(42)
33
- N = 2000
34
-
35
- def sim_logprobs(tier, diff, success, rng):
36
- n = rng.randint(20, 150)
37
- base = {1:-3.5,2:-2.5,3:-1.5,4:-0.7,5:-0.3}[tier]
38
- base *= (1 + diff * 0.15)
39
- lps = []
40
- for _ in range(n):
41
- noise = rng.gauss(0, 1.0 + diff*0.3)
42
- lps.append(base + noise * (0.3 if success else 0.8))
43
- return lps
44
-
45
- # Sweep thresholds
46
- print("="*80)
47
- print("FEEDBACK THRESHOLD SWEEP")
48
- print("="*80)
49
- print(f"\n{'EntropyThr':>12} {'LowConfThr':>12} {'Success':>10} {'AvgCost':>10} {'CostRed':>10} {'Gap':>10}")
50
- print("-"*65)
51
-
52
- frontier_sr = 0.901
53
- frontier_cost = 1.0
54
-
55
- best_score = -999
56
- best_config = None
57
-
58
- for ent_thr in [1.5, 2.0, 2.5, 3.0, 3.5, 4.0]:
59
- for lc_thr in [0.05, 0.10, 0.15, 0.20, 0.25, 0.30]:
60
- ef = ExecutionFeedbackRouter(entropy_threshold=ent_thr,
61
- low_conf_ratio_threshold=lc_thr, tier_costs=TIER_COST,
62
- task_floors=TASK_FLOOR)
63
- rng.seed(42)
64
- succ = 0; cost = 0.0
65
- for i in range(N):
66
- tt = rng.choice(list(TASKS.keys()))
67
- req = rng.choice(TASKS[tt])
68
- pred = classifier.classify(req)
69
- r = router.route(req, tt, pred["difficulty"], pred)
70
- tier = r.tier; diff = r.dynamic_difficulty
71
- ps = TIER_STR[tier]**(diff*0.6)
72
- initial_success = rng.random() < ps
73
- lps = sim_logprobs(tier, diff, initial_success, rng)
74
- signal = ef.analyze_output(lps, task_type=tt, current_tier=tier)
75
- if signal.should_escalate and tier < 5:
76
- final_tier = min(tier+1, 5)
77
- final_tier = max(final_tier, TASK_FLOOR.get(tt,1))
78
- ps2 = TIER_STR[final_tier]**(diff*0.6)
79
- final_success = rng.random() < ps2
80
- c = TIER_COST[tier] + TIER_COST[final_tier]
81
- if final_success: succ += 1
82
- else:
83
- c = TIER_COST[tier]
84
- if initial_success: succ += 1
85
- cost += c
86
- sr = succ/N; ac = cost/N
87
- cr = (1-ac/frontier_cost)*100
88
- gap = frontier_sr - sr
89
- # Score: maximize success, minimize cost
90
- score = sr*20 - ac*10
91
- if score > best_score:
92
- best_score = score
93
- best_config = (ent_thr, lc_thr, sr, ac, cr, gap)
94
- if ent_thr == 2.5 or ent_thr == 3.0:
95
- print(f"{ent_thr:>12.1f} {lc_thr:>12.2f} {sr:>10.3f} {ac:>10.4f} {cr:>9.1f}% {gap:>10.3f}")
96
-
97
- print(f"\n\nBest config: entropy_thr={best_config[0]}, low_conf_thr={best_config[1]}")
98
- print(f" success={best_config[2]:.3f}, cost={best_config[3]:.4f}, costRed={best_config[4]:.1f}%, gap={best_config[5]:.3f}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
training/verify_frontier.py DELETED
@@ -1,75 +0,0 @@
1
- """Verify gpt-5.2 vs claude-opus-4.7 on SWE-bench.
2
-
3
- Both are tier 4 models. If gpt-5.2 is consistently cheaper AND more successful,
4
- it should replace claude-opus-4.7 as the frontier baseline. This changes the
5
- entire cost-reduction math.
6
- """
7
- import json
8
- from collections import defaultdict
9
- from datasets import load_dataset
10
-
11
- MODELS = [
12
- "deepseek-v4-flash", "gpt-5-nano", "gpt-5-mini", "deepseek-v3.2",
13
- "gemini-2.5-pro", "claude-opus-4.7", "gpt-5.2", "gemini-3-pro",
14
- ]
15
-
16
- print("Loading all 8 models...")
17
- traces = defaultdict(dict)
18
- for m in MODELS:
19
- ds = load_dataset(f"SWE-Router/swebench-verified-{m}", split="test")
20
- for row in ds:
21
- traces[row["instance_id"]][m] = {
22
- "resolved": row["resolved"],
23
- "cost": float(row["instance_cost"]),
24
- }
25
-
26
- claude = traces # we have all data, compare claude-opus-4.7 vs gpt-5.2
27
-
28
- # Head-to-head
29
- claude_wins = 0 # claude resolves, gpt-5.2 doesn't
30
- gpt_wins = 0 # gpt-5.2 resolves, claude doesn't
31
- both_resolve = 0
32
- neither_resolve = 0
33
- claude_cost_total = 0
34
- gpt_cost_total = 0
35
-
36
- for tid, tt in traces.items():
37
- c = tt.get("claude-opus-4.7", {})
38
- g = tt.get("gpt-5.2", {})
39
-
40
- claude_cost_total += c.get("cost", 0)
41
- gpt_cost_total += g.get("cost", 0)
42
-
43
- cr = c.get("resolved", False)
44
- gr = g.get("resolved", False)
45
-
46
- if cr and not gr: claude_wins += 1
47
- elif gr and not cr: gpt_wins += 1
48
- elif cr and gr: both_resolve += 1
49
- else: neither_resolve += 1
50
-
51
- N = len(traces)
52
- print(f"""
53
- HEAD-TO-HEAD: claude-opus-4.7 vs gpt-5.2 ({N} tasks)
54
- {'='*60}
55
- claude-opus-4.7 resolved: {sum(1 for tid in traces if traces[tid].get('claude-opus-4.7',{}).get('resolved'))}/{N} ({sum(1 for tid in traces if traces[tid].get('claude-opus-4.7',{}).get('resolved'))/N*100:.1f}%) avg cost: ${claude_cost_total/N:.4f}
56
- gpt-5.2 resolved: {sum(1 for tid in traces if traces[tid].get('gpt-5.2',{}).get('resolved'))}/{N} ({sum(1 for tid in traces if traces[tid].get('gpt-5.2',{}).get('resolved'))/N*100:.1f}%) avg cost: ${gpt_cost_total/N:.4f}
57
-
58
- Claude-only wins: {claude_wins} ({claude_wins/N*100:.1f}%)
59
- GPT-only wins: {gpt_wins} ({gpt_wins/N*100:.1f}%)
60
- Both resolve: {both_resolve} ({both_resolve/N*100:.1f}%)
61
- Neither resolves: {neither_resolve} ({neither_resolve/N*100:.1f}%)
62
-
63
- Conclusion: {"gpt-5.2 is CHEAPER AND MORE SUCCESSFUL → should be the new frontier baseline" if gpt_cost_total < claude_cost_total and sum(1 for tid in traces if traces[tid].get('gpt-5.2',{}).get('resolved')) > sum(1 for tid in traces if traces[tid].get('claude-opus-4.7',{}).get('resolved')) else "claude-4.7 wins cost or quality → keep as frontier"}
64
- """)
65
-
66
- # Also check: what is the TRUE cheapest model overall?
67
- print("\nALL MODELS RANKED BY COST-EFFICIENCY (resolved/cost):")
68
- ranked = []
69
- for m in MODELS:
70
- resolved = sum(1 for tid in traces if traces[tid].get(m,{}).get("resolved", False))
71
- total_cost = sum(traces[tid].get(m,{}).get("cost",0) for tid in traces)
72
- ranked.append((m, resolved, total_cost/N, resolved/N, (resolved/N)/(max(total_cost/N,0.0001))))
73
- ranked.sort(key=lambda x: -x[4])
74
- for m, r, ac, rr, eff in ranked:
75
- print(f" {m:<25} {r:>4}/{N} ({rr*100:.1f}%) ${ac:.4f}/task efficiency={eff:.1f}")