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 +0 -60
- docs/bert_5class_final_report.md +0 -96
- docs/bert_eval_report.md +0 -60
- docs/combined_final_report.md +0 -121
- docs/final_report.md +0 -126
- docs/final_report_v2.md +0 -128
- run_benchmark.py +0 -124
- run_eval.py +0 -21
- standalone_eval.py +0 -402
- standalone_eval_v2.py +0 -499
- train_router.py +0 -77
- train_router_gen.py +0 -126
- training/check_bundle_keys.py +0 -38
- training/debug_sprout_parsing.py +0 -83
- training/execution_feedback_loop.py +0 -340
- training/integration_real.py +0 -362
- training/repro_test.py +0 -190
- training/repro_v2.py +0 -119
- training/router_integration_benchmark.py +0 -237
- training/router_v5_calibrated.py +0 -393
- training/router_v6_hybrid.py +0 -338
- training/router_v7_tuned.py +0 -95
- training/router_v8_final.py +0 -228
- training/train_v10_fixed.py +0 -250
- training/tune_feedback.py +0 -98
- training/verify_frontier.py +0 -75
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}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|