Delete router_models/baarbundle.pkl, router_models/productionbundle.pkl, router_models/routerbundle.pkl, router_models/router_bundle_v6.pkl, router_models/router_bundle_v8.pkl, router_models/router_bundle_v10_fixed.pkl, router_models/feat_keys.json, router_models/tier_config.json, eval/baar_results.json, eval/bert_vs_xgboost_results.json/*, docs/trained_router_report.md, aco/router_v10.py, aco/conformal.py, aco/learned_router.py, training/train_router_real.py, training/benchmark_final.py, training/benchmark_with_feedback.py, training/bfcl_eval.py, training/aco_eval.py, FINAL_RESULTS.md
Browse files- FINAL_RESULTS.md +0 -137
- aco/conformal.py +0 -112
- aco/learned_router.py +0 -151
- aco/router_v10.py +0 -151
- docs/trained_router_report.md +0 -69
- eval/baar_results.json +0 -62
- router_models/feat_keys.json +0 -1
- router_models/router_bundle_v10_fixed.pkl +0 -3
- router_models/router_bundle_v6.pkl +0 -3
- router_models/router_bundle_v8.pkl +0 -3
- router_models/tier_config.json +0 -27
- training/aco_eval.py +0 -181
- training/benchmark_final.py +0 -143
- training/benchmark_with_feedback.py +0 -199
- training/bfcl_eval.py +0 -102
- training/train_router_real.py +0 -435
FINAL_RESULTS.md
DELETED
|
@@ -1,137 +0,0 @@
|
|
| 1 |
-
# ACO (Agent Cost Optimizer) — Final Results
|
| 2 |
-
|
| 3 |
-
## The Cascade Wins. Everything Else Is Seasoning.
|
| 4 |
-
|
| 5 |
-
After trace analysis, anti-oracle testing, causal simulation, and multi-module benchmarking
|
| 6 |
-
across 500 SWE-bench instances, here is the definitive ranking:
|
| 7 |
-
|
| 8 |
-
```
|
| 9 |
-
┌─────────────────────────────────────────┬────────┬──────────┬──────────┬──────────────┐
|
| 10 |
-
│ Strategy │ Solved │ Cost │ $/Solved │ Ship? │
|
| 11 |
-
├─────────────────────────────────────────┼────────┼──────────┼──────────┼──────────────┤
|
| 12 |
-
│ Always Frontier │ 391 │ $158.34 │ $0.405 │ baseline │
|
| 13 |
-
│ Cascade T1→T2→T4 │ 416 │ $76.48 │ $0.184 │ ✅ PROD │
|
| 14 |
-
│ Safe Proposal T1→T4 │ 411 │ $82.10 │ $0.200 │ 🧪 Experimental│
|
| 15 |
-
│ Safe Proposal + T2 │ 411 │ $71.50 │ $0.174 │ 🧪 Best cost │
|
| 16 |
-
│ Per-Step Oracle (cheating) │ 411 │ $81.78 │ $0.199 │ ❌ Lookahead │
|
| 17 |
-
└─────────────────────────────────────────┴────────┴──────────┴──────────┴──────────────┘
|
| 18 |
-
```
|
| 19 |
-
|
| 20 |
-
**The cascade beats frontier on BOTH cost AND quality** — 416 solved vs 391, at 51.7% less cost.
|
| 21 |
-
This is the core result. Safe proposal variants save more money but lose 5 instances
|
| 22 |
-
that T2 would have caught.
|
| 23 |
-
|
| 24 |
-
## 5 Things We Built and Tested
|
| 25 |
-
|
| 26 |
-
### 1. Live Cascade Causal Simulation
|
| 27 |
-
- **Job 6a0131f2**: Causal simulation where ACOLiveAgent makes routing decisions at
|
| 28 |
-
pre_turn(), outcomes looked up from SWE-Router oracle datasets
|
| 29 |
-
- **Result**: 416/500 solved, $76.48, $0.184/solved, 51.7% savings
|
| 30 |
-
- **Key insight**: Cascade solves 25 MORE instances than frontier because T1/T2
|
| 31 |
-
catch cases Claude misses. Different models have different failure modes.
|
| 32 |
-
- **Tier distribution**: T1=316 (63.2%), T2=43 (8.6%), T4=141 (28.2%)
|
| 33 |
-
- **Cost per API call**: T1=$0.000277, T2=$0.002163, T4=$0.017307 (62.4x ratio)
|
| 34 |
-
|
| 35 |
-
### 2. Cache-Aware Prompt Layout
|
| 36 |
-
- **Job 6a01332b**: Analyzed 500 traces for cacheable vs dynamic content
|
| 37 |
-
- **Result**: Only 1.5% of tokens are static (135 of 9,066). The system prompt
|
| 38 |
-
in SWE-bench agents is tiny. Almost everything is in the conversation transcript.
|
| 39 |
-
- **Cache hit rate**: Already 97.6% across turns — the prior conversation IS the cache.
|
| 40 |
-
- **Conclusion**: Cache-aware layout adds negligible value for SWE-bench agents.
|
| 41 |
-
The prefix caching optimization matters for different workloads (RAG, long system prompts).
|
| 42 |
-
|
| 43 |
-
### 3. Macro Tool Mining
|
| 44 |
-
- **Job 6a013424**: Extracted 109K 2-command and 147K 3-command sequences
|
| 45 |
-
- **Result**: 6 macro tools identified, 13,578 turns saved, $44.43 saved
|
| 46 |
-
- **Top macros**:
|
| 47 |
-
- `repo_search`: find + cat (179 occurrences)
|
| 48 |
-
- `locate_symbol`: grep + cat (565 occurrences)
|
| 49 |
-
- `run_test_and_summarize`: pytest + grep failures (1,187 occurrences — most valuable)
|
| 50 |
-
- `read_and_patch`: cat + sed (334 occurrences)
|
| 51 |
-
- `submit_patch`: git diff + submit (123 occurrences)
|
| 52 |
-
- **220,189 sequences are 100% exploration** — can stay entirely on T1
|
| 53 |
-
- **Implementation**: Each macro replaces 2-3 LLM turns with one deterministic subprocess call
|
| 54 |
-
|
| 55 |
-
### 4. Doom Rescue Policy
|
| 56 |
-
- **Job 6a0134d4**: Analyzed 1,942 error streaks across 1,000 traces
|
| 57 |
-
- **Result**: **58-72% of "doomed" runs eventually recover and resolve the instance**
|
| 58 |
-
- **Never terminate at 3 errors**: At streak≥3, 72% (T4) and 58% (T1) eventually succeed.
|
| 59 |
-
Terminating is destroying value, not saving it.
|
| 60 |
-
- **Rescue policy**: One T4 "reset + diagnose" call ($0.025) → expected value over $0.20
|
| 61 |
-
- Summarize state into clean scratchpad
|
| 62 |
-
- Classify error: dependency, wrong_file, bad_patch, test_env, repeating
|
| 63 |
-
- Apply targeted recovery strategy
|
| 64 |
-
- Terminate only if rescue produces no new plan
|
| 65 |
-
- **Net savings**: $124.88 across 500 instances (recovers 452 solves, costs $18.40 in rescues)
|
| 66 |
-
- **Most fatal errors**: `not_found` (55% fatality), `timeout` (51%), `syntax` (45%)
|
| 67 |
-
vs least fatal: `permission` (11%), `other` (29%)
|
| 68 |
-
|
| 69 |
-
### 5. Provider Routing
|
| 70 |
-
- **Job 6a0135e8**: Compared 7 providers across 3 tiers
|
| 71 |
-
- **Per-call costs** (15K input / 2K output):
|
| 72 |
-
- T1 DeepSeek Direct: $0.00266 (cheapest, 800ms)
|
| 73 |
-
- T2 OpenAI Direct: $0.00345 live, $0.00255 with cache (600ms)
|
| 74 |
-
- T4 AWS Bedrock: $0.30 live, $0.17 with cache hit (2,200ms, 20% cheaper than Anthropic)
|
| 75 |
-
- **Batch API**: 50% off on OpenAI/Anthropic for offline workloads
|
| 76 |
-
- Verifier 411 calls: $64.73 live → $32.37 batch (saves $32.36)
|
| 77 |
-
- **Best provider stack**: DeepSeek (T1) + OpenAI (T2) + AWS Bedrock (T4)
|
| 78 |
-
- **Agent loops**: Live calls only. Latency matters. Use Anthropic Direct if 2.2s is too slow.
|
| 79 |
-
- **Eval/verifier/training**: Use Batch APIs. 50% discount.
|
| 80 |
-
|
| 81 |
-
## What We Learned (The Hard Way)
|
| 82 |
-
|
| 83 |
-
1. **ML routing is unnecessary**: The gap between cascade (83.2%) and oracle (86.8%) is 19 instances.
|
| 84 |
-
Too narrow for ML to add value. XGBoost and BERT both failed to beat static cascade.
|
| 85 |
-
|
| 86 |
-
2. **Per-step prediction routing doesn't work**: 98.3% false positive rate on edit detection.
|
| 87 |
-
Can't predict command type from conversation state. Safe proposal model is the fix.
|
| 88 |
-
|
| 89 |
-
3. **Cache-aware layout adds nothing for SWE-bench**: Only 1.5% static content. Different story
|
| 90 |
-
for RAG agents with large system prompts.
|
| 91 |
-
|
| 92 |
-
4. **Never kill a run at 3 errors**: 58-72% recover. Rescue, don't terminate.
|
| 93 |
-
|
| 94 |
-
5. **Command-type prediction is a red herring**: Don't predict what the model will do.
|
| 95 |
-
Let it propose, then gate the dangerous proposals.
|
| 96 |
-
|
| 97 |
-
## What's Production-Ready
|
| 98 |
-
|
| 99 |
-
```python
|
| 100 |
-
from aco_live import ACOLiveAgent
|
| 101 |
-
|
| 102 |
-
# Best all-around: Cascade T1→T2→T4
|
| 103 |
-
aco = ACOLiveAgent(strategy='cascade', max_cost=2.0)
|
| 104 |
-
|
| 105 |
-
# Before each turn:
|
| 106 |
-
decision = aco.pre_turn(messages)
|
| 107 |
-
# → {'model': 'deepseek-v4-flash', 'tier': 1}
|
| 108 |
-
|
| 109 |
-
response = call_llm(model=decision['model'], messages=...)
|
| 110 |
-
|
| 111 |
-
# After each turn:
|
| 112 |
-
result = aco.post_turn(response, cost=cost, success=success)
|
| 113 |
-
# → {'action': 'continue'|'escalate'|'review_needed'|'done'}
|
| 114 |
-
```
|
| 115 |
-
|
| 116 |
-
## Remaining Unknowns
|
| 117 |
-
|
| 118 |
-
1. **Causal divergence in live agents**: Would T1's exploration discover different bugs than T4's?
|
| 119 |
-
Can only be tested in actual Docker-execution agent runs.
|
| 120 |
-
2. **Multi-harness validation**: Only tested on SWE-bench coding tasks. Research agents, RAG agents,
|
| 121 |
-
personal assistants may have different optimal strategies.
|
| 122 |
-
3. **Model version drift**: Models improve. Cascade order needs periodic revalidation.
|
| 123 |
-
4. **Live safe proposal**: T1 proposing + T4 reviewing edits in a real agent loop (not simulation).
|
| 124 |
-
|
| 125 |
-
## Code Location
|
| 126 |
-
|
| 127 |
-
- `aco/aco_live.py` — Drop-in ACOLiveAgent wrapper (cascade, safe_proposal, safe_proposal_t2)
|
| 128 |
-
- `aco/per_step_router.py` — Command classifier + per-step router
|
| 129 |
-
- `aco/classifier.py` — Task cost classifier
|
| 130 |
-
- `aco/telemetry.py` — Cost telemetry collector
|
| 131 |
-
- `aco/tool_gate.py` — Tool duplicate detection
|
| 132 |
-
- `aco/doom_detector.py` — Early termination (use rescue policy, not termination!)
|
| 133 |
-
- `aco/verifier_budgeter.py` — Selective verifier calls
|
| 134 |
-
- `aco/retry_optimizer.py` — Error-type-specific recovery
|
| 135 |
-
- `aco/context_budgeter.py` — Context compression
|
| 136 |
-
- `aco/meta_tool_miner.py` — Macro tool extraction
|
| 137 |
-
- `aco/cache_layout.py` — Cache-aware prompt layout
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
aco/conformal.py
DELETED
|
@@ -1,112 +0,0 @@
|
|
| 1 |
-
"""Conformal calibration for escalation thresholds.
|
| 2 |
-
|
| 3 |
-
Implements RouteNLP-style conformal risk control:
|
| 4 |
-
P(failure AND no escalation) <= alpha
|
| 5 |
-
|
| 6 |
-
Method:
|
| 7 |
-
1. Compute nonconformity scores from calibrated P(success)
|
| 8 |
-
2. Find conformal quantile threshold
|
| 9 |
-
3. Guarantee coverage under exchangeability
|
| 10 |
-
"""
|
| 11 |
-
import numpy as np
|
| 12 |
-
from typing import Dict, List, Optional, Tuple
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
class ConformalEscalationCalibrator:
|
| 16 |
-
"""Calibrate escalation thresholds with distribution-free coverage guarantees.
|
| 17 |
-
|
| 18 |
-
Based on RouteNLP (arxiv 2604.23577) and Conformal Risk Control
|
| 19 |
-
(Angelopoulos et al., arxiv 2208.02814).
|
| 20 |
-
|
| 21 |
-
Guarantee: P(y=fail AND no_escalation) <= alpha
|
| 22 |
-
"""
|
| 23 |
-
|
| 24 |
-
def __init__(self, alpha: float = 0.05):
|
| 25 |
-
self.alpha = alpha
|
| 26 |
-
self.thresholds: Dict[int, float] = {}
|
| 27 |
-
self.calibrated = False
|
| 28 |
-
|
| 29 |
-
def calibrate(
|
| 30 |
-
self,
|
| 31 |
-
psuccess: Dict[int, np.ndarray],
|
| 32 |
-
outcomes: Dict[int, np.ndarray],
|
| 33 |
-
) -> Dict[int, float]:
|
| 34 |
-
"""Calibrate per-tier escalation thresholds.
|
| 35 |
-
|
| 36 |
-
Args:
|
| 37 |
-
psuccess: {tier: array of calibrated P(success)}
|
| 38 |
-
outcomes: {tier: array of binary outcomes (1=success, 0=fail)}
|
| 39 |
-
|
| 40 |
-
Returns:
|
| 41 |
-
{tier: conformal_threshold}
|
| 42 |
-
"""
|
| 43 |
-
for tier in sorted(psuccess.keys()):
|
| 44 |
-
p = psuccess[tier]
|
| 45 |
-
y = outcomes[tier]
|
| 46 |
-
n = len(y)
|
| 47 |
-
|
| 48 |
-
# Nonconformity: 1 - P(success) for failed examples
|
| 49 |
-
# These are the scores we want to bound
|
| 50 |
-
failed_mask = y == 0
|
| 51 |
-
if failed_mask.sum() == 0:
|
| 52 |
-
self.thresholds[tier] = 1.0
|
| 53 |
-
continue
|
| 54 |
-
|
| 55 |
-
# Conformal risk control: find threshold lam such that
|
| 56 |
-
# R_hat(lam) <= alpha, where R_hat = (1/n) * sum 1[p >= lam AND y=0]
|
| 57 |
-
# This means: fraction of examples with P(success) >= lam that actually failed <= alpha
|
| 58 |
-
|
| 59 |
-
# Sort P(success) values
|
| 60 |
-
sorted_p = np.sort(p[failed_mask])
|
| 61 |
-
|
| 62 |
-
# Conformal quantile: ceiling of (1-alpha)*(n+1)/n
|
| 63 |
-
q = int(np.ceil((1 - self.alpha) * (n + 1) / n))
|
| 64 |
-
q = min(q, len(sorted_p))
|
| 65 |
-
|
| 66 |
-
# Threshold: if P(success) < this, escalate
|
| 67 |
-
# We want the (1-alpha) quantile of failure nonconformity scores
|
| 68 |
-
threshold = sorted_p[q - 1] if q > 0 else 0.0
|
| 69 |
-
self.thresholds[tier] = float(threshold)
|
| 70 |
-
|
| 71 |
-
self.calibrated = True
|
| 72 |
-
return self.thresholds
|
| 73 |
-
|
| 74 |
-
def should_escalate(self, tier: int, psuccess: float) -> bool:
|
| 75 |
-
"""Decide whether to escalate from this tier.
|
| 76 |
-
|
| 77 |
-
Returns True if P(success) is below conformal threshold,
|
| 78 |
-
meaning we can't guarantee success at this tier with 1-alpha coverage.
|
| 79 |
-
"""
|
| 80 |
-
if not self.calibrated:
|
| 81 |
-
return psuccess < 0.65 # fallback to heuristic
|
| 82 |
-
threshold = self.thresholds.get(tier, 0.65)
|
| 83 |
-
return psuccess < threshold
|
| 84 |
-
|
| 85 |
-
def coverage_check(
|
| 86 |
-
self,
|
| 87 |
-
psuccess: Dict[int, np.ndarray],
|
| 88 |
-
outcomes: Dict[int, np.ndarray],
|
| 89 |
-
) -> Dict[int, Dict[str, float]]:
|
| 90 |
-
"""Verify conformal coverage on test data."""
|
| 91 |
-
results = {}
|
| 92 |
-
for tier in sorted(psuccess.keys()):
|
| 93 |
-
p = psuccess[tier]
|
| 94 |
-
y = outcomes[tier]
|
| 95 |
-
threshold = self.thresholds.get(tier, 0.65)
|
| 96 |
-
|
| 97 |
-
no_escalate = p >= threshold
|
| 98 |
-
failed_no_escalate = (y == 0) & no_escalate
|
| 99 |
-
n_no_escalate = no_escalate.sum()
|
| 100 |
-
|
| 101 |
-
violation_rate = failed_no_escalate.sum() / max(n_no_escalate, 1)
|
| 102 |
-
escalation_rate = 1 - no_escalate.mean()
|
| 103 |
-
|
| 104 |
-
results[tier] = {
|
| 105 |
-
"violation_rate": float(violation_rate),
|
| 106 |
-
"escalation_rate": float(escalation_rate),
|
| 107 |
-
"threshold": float(threshold),
|
| 108 |
-
"n_no_escalate": int(n_no_escalate),
|
| 109 |
-
"n_violations": int(failed_no_escalate.sum()),
|
| 110 |
-
"covered": violation_rate <= self.alpha,
|
| 111 |
-
}
|
| 112 |
-
return results
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
aco/learned_router.py
DELETED
|
@@ -1,151 +0,0 @@
|
|
| 1 |
-
"""Trained Production Router - Replaces heuristic routing.
|
| 2 |
-
|
| 3 |
-
Architecture: difficulty-first + ML confirmation + safety floors.
|
| 4 |
-
|
| 5 |
-
Usage:
|
| 6 |
-
from aco.learned_router import TrainedRouter
|
| 7 |
-
|
| 8 |
-
router = TrainedRouter.from_pretrained("narcolepticchicken/agent-cost-optimizer")
|
| 9 |
-
tier, confidence = router.predict("Write a Python function", "coding", difficulty=3)
|
| 10 |
-
"""
|
| 11 |
-
|
| 12 |
-
import json
|
| 13 |
-
import os
|
| 14 |
-
import pickle
|
| 15 |
-
from typing import Dict, List, Optional, Tuple
|
| 16 |
-
from dataclasses import dataclass
|
| 17 |
-
from collections import defaultdict
|
| 18 |
-
|
| 19 |
-
try:
|
| 20 |
-
import numpy as np
|
| 21 |
-
import xgboost as xgb
|
| 22 |
-
HAS_ML = True
|
| 23 |
-
except ImportError:
|
| 24 |
-
HAS_ML = False
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
TASK_TYPES = ["quick_answer","coding","research","document_drafting",
|
| 28 |
-
"legal_regulated","tool_heavy","retrieval_heavy",
|
| 29 |
-
"long_horizon","unknown_ambiguous"]
|
| 30 |
-
TT2IDX = {t:i for i,t in enumerate(TASK_TYPES)}
|
| 31 |
-
|
| 32 |
-
CODE_KW = ["python","javascript","code","function","bug","debug","refactor",
|
| 33 |
-
"implement","test","compile","runtime","class","module","async","thread"]
|
| 34 |
-
LEGAL_KW = ["contract","legal","compliance","gdpr","privacy","policy","regulatory","liability"]
|
| 35 |
-
RESEARCH_KW = ["research","find sources","literature","investigate","compare","analyze","survey"]
|
| 36 |
-
TOOL_KW = ["search","fetch","retrieve","query","api","database","scrape","aggregate"]
|
| 37 |
-
LONG_KW = ["plan","project","roadmap","orchestrate","multi-step","migrate","pipeline","deploy"]
|
| 38 |
-
MATH_KW = ["calculate","compute","solve","equation","formula","optimize","probability"]
|
| 39 |
-
|
| 40 |
-
# Default safety floors per task type
|
| 41 |
-
TASK_FLOOR = {
|
| 42 |
-
"legal_regulated":4,"long_horizon":3,"research":3,"coding":3,
|
| 43 |
-
"unknown_ambiguous":3,"quick_answer":1,"document_drafting":2,
|
| 44 |
-
"tool_heavy":2,"retrieval_heavy":2,
|
| 45 |
-
}
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
class TrainedRouter:
|
| 49 |
-
"""Production trained router: difficulty-first + ML confirmation + safety floors."""
|
| 50 |
-
|
| 51 |
-
def __init__(self, tier_clfs: Dict, feat_keys: List[str],
|
| 52 |
-
tier_config: Dict, escalation_threshold: float = 0.55):
|
| 53 |
-
self.tier_clfs = tier_clfs
|
| 54 |
-
self.feat_keys = feat_keys
|
| 55 |
-
self.tier_config = tier_config
|
| 56 |
-
self.tier_cost = {int(k):v for k,v in tier_config["tier_cost"].items()}
|
| 57 |
-
self.task_floor = tier_config.get("task_floor", TASK_FLOOR)
|
| 58 |
-
self.escalation_threshold = escalation_threshold
|
| 59 |
-
self._trained = True
|
| 60 |
-
|
| 61 |
-
def extract_features(self, request: str, task_type: str, difficulty: int = 3) -> Dict:
|
| 62 |
-
r = request.lower()
|
| 63 |
-
f = {"req_len":len(request),"num_words":len(request.split()),
|
| 64 |
-
"has_code":int(any(k in r for k in CODE_KW)),
|
| 65 |
-
"n_code":sum(1 for k in CODE_KW if k in r),
|
| 66 |
-
"has_legal":int(any(k in r for k in LEGAL_KW)),
|
| 67 |
-
"n_legal":sum(1 for k in LEGAL_KW if k in r),
|
| 68 |
-
"has_research":int(any(k in r for k in RESEARCH_KW)),
|
| 69 |
-
"n_research":sum(1 for k in RESEARCH_KW if k in r),
|
| 70 |
-
"has_tool":int(any(k in r for k in TOOL_KW)),
|
| 71 |
-
"n_tool":sum(1 for k in TOOL_KW if k in r),
|
| 72 |
-
"has_long":int(any(k in r for k in LONG_KW)),
|
| 73 |
-
"has_math":int(any(k in r for k in MATH_KW)),
|
| 74 |
-
"tt_idx":TT2IDX.get(task_type,8),"difficulty":difficulty}
|
| 75 |
-
for tt in TASK_TYPES:
|
| 76 |
-
f[f"tt_{tt}"] = int(task_type == tt)
|
| 77 |
-
return f
|
| 78 |
-
|
| 79 |
-
def _feats_to_vec(self, feats: Dict):
|
| 80 |
-
import numpy as np
|
| 81 |
-
return np.array([float(feats.get(k, 0.0)) for k in self.feat_keys], dtype=np.float32)
|
| 82 |
-
|
| 83 |
-
def predict(self, request: str, task_type: str, difficulty: int = 3,
|
| 84 |
-
escalation_threshold: Optional[float] = None) -> Tuple[int, float]:
|
| 85 |
-
"""Predict optimal tier using difficulty-first + ML confirmation.
|
| 86 |
-
|
| 87 |
-
Returns: (tier, confidence)
|
| 88 |
-
"""
|
| 89 |
-
threshold = escalation_threshold or self.escalation_threshold
|
| 90 |
-
|
| 91 |
-
# Step 1: difficulty -> base_tier
|
| 92 |
-
base_tier = min(difficulty + 1, 5)
|
| 93 |
-
|
| 94 |
-
# Step 2: apply safety floor
|
| 95 |
-
floor = self.task_floor.get(task_type, 2)
|
| 96 |
-
base_tier = max(base_tier, floor)
|
| 97 |
-
|
| 98 |
-
if not HAS_ML or not self._trained:
|
| 99 |
-
return base_tier, 0.6
|
| 100 |
-
|
| 101 |
-
# Step 3: ML confirmation
|
| 102 |
-
feats = self.extract_features(request, task_type, difficulty)
|
| 103 |
-
x = self._feats_to_vec(feats).reshape(1, -1)
|
| 104 |
-
|
| 105 |
-
p_success = self.tier_clfs[base_tier].predict_proba(x)[0, 1]
|
| 106 |
-
confidence = p_success
|
| 107 |
-
|
| 108 |
-
# Step 4: escalate if P(success) too low
|
| 109 |
-
while p_success < threshold and base_tier < 5:
|
| 110 |
-
base_tier += 1
|
| 111 |
-
p_success = self.tier_clfs[base_tier].predict_proba(x)[0, 1]
|
| 112 |
-
confidence = p_success
|
| 113 |
-
|
| 114 |
-
return base_tier, float(confidence)
|
| 115 |
-
|
| 116 |
-
@classmethod
|
| 117 |
-
def from_pretrained(cls, repo_id: str, escalation_threshold: float = 0.55,
|
| 118 |
-
cache_dir: Optional[str] = None):
|
| 119 |
-
"""Load trained router from HuggingFace Hub."""
|
| 120 |
-
from huggingface_hub import hf_hub_download
|
| 121 |
-
|
| 122 |
-
bundle_path = hf_hub_download(
|
| 123 |
-
repo_id=repo_id, filename="router_models/router_bundle.pkl",
|
| 124 |
-
cache_dir=cache_dir,
|
| 125 |
-
)
|
| 126 |
-
|
| 127 |
-
with open(bundle_path, "rb") as f:
|
| 128 |
-
import pickle
|
| 129 |
-
bundle = pickle.load(f)
|
| 130 |
-
|
| 131 |
-
return cls(
|
| 132 |
-
tier_clfs={int(k): v for k, v in bundle["tier_clfs"].items()},
|
| 133 |
-
feat_keys=bundle["feat_keys"],
|
| 134 |
-
tier_config=bundle["tier_config"],
|
| 135 |
-
escalation_threshold=escalation_threshold,
|
| 136 |
-
)
|
| 137 |
-
|
| 138 |
-
@classmethod
|
| 139 |
-
def from_local(cls, model_dir: str, escalation_threshold: float = 0.55):
|
| 140 |
-
"""Load from local directory."""
|
| 141 |
-
bundle_path = os.path.join(model_dir, "router_bundle.pkl")
|
| 142 |
-
with open(bundle_path, "rb") as f:
|
| 143 |
-
import pickle
|
| 144 |
-
bundle = pickle.load(f)
|
| 145 |
-
|
| 146 |
-
return cls(
|
| 147 |
-
tier_clfs={int(k): v for k, v in bundle["tier_clfs"].items()},
|
| 148 |
-
feat_keys=bundle["feat_keys"],
|
| 149 |
-
tier_config=bundle["tier_config"],
|
| 150 |
-
escalation_threshold=escalation_threshold,
|
| 151 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
aco/router_v10.py
DELETED
|
@@ -1,151 +0,0 @@
|
|
| 1 |
-
"""ACO v10 Router: Trained on REAL SWE-Router execution data.
|
| 2 |
-
|
| 3 |
-
Key difference from v8: Uses XGBoost models trained on 500 real
|
| 4 |
-
SWE-bench tasks across 8 models, not synthetic data.
|
| 5 |
-
|
| 6 |
-
Routes based on problem-statement features → per-tier P(success) →
|
| 7 |
-
optimal tier selection. Supports cascade + feedback escalation.
|
| 8 |
-
"""
|
| 9 |
-
import numpy as np
|
| 10 |
-
import pickle, os, json
|
| 11 |
-
from typing import Dict, Optional, Tuple
|
| 12 |
-
from dataclasses import dataclass
|
| 13 |
-
|
| 14 |
-
CODE_KW = ["python","javascript","code","function","bug","debug","refactor",
|
| 15 |
-
"implement","test","compile","runtime","segfault","thread","async","class",
|
| 16 |
-
"module","import","error","traceback"]
|
| 17 |
-
CRITICAL_KW = ["critical","production","urgent","emergency","live","deployed",
|
| 18 |
-
"safety","security"]
|
| 19 |
-
SIMPLE_KW = ["typo","simple","quick","brief","minor","small","easy","trivial","just"]
|
| 20 |
-
RESEARCH_KW = ["research","investigate","compare","analyze","survey","paper"]
|
| 21 |
-
TOOL_KW = ["search","fetch","retrieve","query","api","database","scrape","aggregate"]
|
| 22 |
-
LONG_KW = ["plan","project","roadmap","orchestrate","migrate","pipeline","deploy","architecture"]
|
| 23 |
-
|
| 24 |
-
FEAT_KEYS = sorted([
|
| 25 |
-
'req_len','num_words','has_code','n_code','has_legal','has_research',
|
| 26 |
-
'has_tool','has_critical','has_simple','has_long','has_math',
|
| 27 |
-
'has_error_msg','has_file_path','n_lines','has_version','has_add',
|
| 28 |
-
'has_fix','has_change','has_remove','has_test','has_doc',
|
| 29 |
-
'has_see_also','has_steps_to_reproduce',
|
| 30 |
-
])
|
| 31 |
-
|
| 32 |
-
TIER_TO_MODEL = {
|
| 33 |
-
1: 'deepseek-v4-flash', 2: 'gpt-5-mini',
|
| 34 |
-
3: 'gemini-2.5-pro', 4: 'claude-opus-4.7', 5: 'gemini-3-pro',
|
| 35 |
-
}
|
| 36 |
-
|
| 37 |
-
TIER_COST = {1:0.01, 2:0.05, 3:0.15, 4:0.30, 5:0.50}
|
| 38 |
-
|
| 39 |
-
@dataclass
|
| 40 |
-
class V10RoutingDecision:
|
| 41 |
-
tier: int
|
| 42 |
-
model: str
|
| 43 |
-
confidence: float
|
| 44 |
-
cost_estimate: float
|
| 45 |
-
tier_probs: Dict[int, float]
|
| 46 |
-
escalated: bool = False
|
| 47 |
-
|
| 48 |
-
class V10Router:
|
| 49 |
-
def __init__(self, model_path: str = None, success_threshold: float = 0.5):
|
| 50 |
-
self.success_threshold = success_threshold
|
| 51 |
-
self.tier_clfs = None
|
| 52 |
-
self.tier_calibs = None
|
| 53 |
-
self.opt_clf = None
|
| 54 |
-
self.feat_keys = FEAT_KEYS
|
| 55 |
-
if model_path and os.path.exists(model_path):
|
| 56 |
-
self._load(model_path)
|
| 57 |
-
|
| 58 |
-
def _load(self, path):
|
| 59 |
-
bundle = pickle.load(open(path, 'rb'))
|
| 60 |
-
self.tier_clfs = {int(k):v for k,v in bundle.get('tier_clfs',{}).items()}
|
| 61 |
-
self.tier_calibs = {int(k):v for k,v in bundle.get('tier_calibrators',{}).items()}
|
| 62 |
-
self.opt_clf = bundle.get('opt_clf', None)
|
| 63 |
-
self.feat_keys = bundle.get('feat_keys', FEAT_KEYS)
|
| 64 |
-
|
| 65 |
-
def _extract(self, text: str) -> np.ndarray:
|
| 66 |
-
r = text.lower()
|
| 67 |
-
feats = {
|
| 68 |
-
'req_len': len(text), 'num_words': len(text.split()),
|
| 69 |
-
'has_code': int(any(k in r for k in CODE_KW)),
|
| 70 |
-
'n_code': sum(1 for k in CODE_KW if k in r),
|
| 71 |
-
'has_legal': int(any(k in r for k in ["contract","legal","compliance"])),
|
| 72 |
-
'has_research': int(any(k in r for k in RESEARCH_KW)),
|
| 73 |
-
'has_tool': int(any(k in r for k in TOOL_KW)),
|
| 74 |
-
'has_critical': int(any(k in r for k in CRITICAL_KW)),
|
| 75 |
-
'has_simple': int(any(k in r for k in SIMPLE_KW)),
|
| 76 |
-
'has_long': int(any(k in r for k in LONG_KW)),
|
| 77 |
-
'has_math': int(any(k in r for k in ["calculate","compute","solve","equation"])),
|
| 78 |
-
'has_error_msg': int('error' in r or 'traceback' in r or 'exception' in r),
|
| 79 |
-
'has_file_path': int('/' in r),
|
| 80 |
-
'n_lines': text.count('\n') + 1,
|
| 81 |
-
'has_version': int('version' in r or 'update' in r),
|
| 82 |
-
'has_add': int('add' in r or 'new' in r or 'create' in r),
|
| 83 |
-
'has_fix': int('fix' in r or 'bug' in r or 'issue' in r),
|
| 84 |
-
'has_change': int('change' in r or 'modify' in r),
|
| 85 |
-
'has_remove': int('remove' in r or 'delete' in r),
|
| 86 |
-
'has_test': int('test' in r or 'spec' in r),
|
| 87 |
-
'has_doc': int('doc' in r or 'readme' in r),
|
| 88 |
-
'has_see_also': int('see also' in r or 'related' in r),
|
| 89 |
-
'has_steps_to_reproduce': int('reproduce' in r or 'steps' in r),
|
| 90 |
-
}
|
| 91 |
-
return np.array([float(feats.get(k,0.0)) for k in self.feat_keys], dtype=np.float32).reshape(1,-1)
|
| 92 |
-
|
| 93 |
-
def route_cascade(self, text: str) -> V10RoutingDecision:
|
| 94 |
-
"""Route to cheapest tier with P(success) >= threshold."""
|
| 95 |
-
x = self._extract(text)
|
| 96 |
-
tier_probs = {}
|
| 97 |
-
if self.tier_clfs:
|
| 98 |
-
for t in range(1, 6):
|
| 99 |
-
if t in self.tier_clfs:
|
| 100 |
-
p_raw = self.tier_clfs[t].predict_proba(x)[0,1]
|
| 101 |
-
p_cal = float(self.tier_calibs[t].transform([p_raw])[0])
|
| 102 |
-
tier_probs[t] = p_cal
|
| 103 |
-
else:
|
| 104 |
-
tier_probs[t] = 0.5
|
| 105 |
-
else:
|
| 106 |
-
tier_probs = {1:0.67,2:0.72,3:0.50,4:0.84,5:0.70}
|
| 107 |
-
|
| 108 |
-
# Find cheapest tier above threshold
|
| 109 |
-
selected_tier = 5
|
| 110 |
-
for t in range(1, 6):
|
| 111 |
-
if tier_probs.get(t, 0) >= self.success_threshold:
|
| 112 |
-
selected_tier = t
|
| 113 |
-
break
|
| 114 |
-
|
| 115 |
-
model = TIER_TO_MODEL.get(selected_tier, 'claude-opus-4.7')
|
| 116 |
-
return V10RoutingDecision(
|
| 117 |
-
tier=selected_tier, model=model,
|
| 118 |
-
confidence=tier_probs.get(selected_tier, 0.5),
|
| 119 |
-
cost_estimate=TIER_COST.get(selected_tier, 0.30),
|
| 120 |
-
tier_probs=tier_probs,
|
| 121 |
-
)
|
| 122 |
-
|
| 123 |
-
def route_direct(self, text: str) -> V10RoutingDecision:
|
| 124 |
-
"""Predict optimal tier directly."""
|
| 125 |
-
x = self._extract(text)
|
| 126 |
-
if self.opt_clf:
|
| 127 |
-
tier = int(self.opt_clf.predict(x)[0]) + 1
|
| 128 |
-
else:
|
| 129 |
-
tier = 4 # fallback
|
| 130 |
-
model = TIER_TO_MODEL.get(tier, 'claude-opus-4.7')
|
| 131 |
-
return V10RoutingDecision(
|
| 132 |
-
tier=tier, model=model,
|
| 133 |
-
confidence=0.8, cost_estimate=TIER_COST.get(tier, 0.30),
|
| 134 |
-
tier_probs={},
|
| 135 |
-
)
|
| 136 |
-
|
| 137 |
-
def route_with_feedback(self, text: str, initial_success: bool = True) -> V10RoutingDecision:
|
| 138 |
-
"""Route with feedback: start cheap, escalate on failure."""
|
| 139 |
-
initial = self.route_cascade(text)
|
| 140 |
-
if initial_success:
|
| 141 |
-
return initial
|
| 142 |
-
# Escalate
|
| 143 |
-
escalated_tier = min(initial.tier + 1, 5)
|
| 144 |
-
model = TIER_TO_MODEL.get(escalated_tier, 'claude-opus-4.7')
|
| 145 |
-
return V10RoutingDecision(
|
| 146 |
-
tier=escalated_tier, model=model,
|
| 147 |
-
confidence=initial.tier_probs.get(escalated_tier, 0.8),
|
| 148 |
-
cost_estimate=TIER_COST.get(initial.tier, 0.01) + TIER_COST.get(escalated_tier, 0.30),
|
| 149 |
-
tier_probs=initial.tier_probs,
|
| 150 |
-
escalated=True,
|
| 151 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
docs/trained_router_report.md
DELETED
|
@@ -1,69 +0,0 @@
|
|
| 1 |
-
# Trained Router Report
|
| 2 |
-
|
| 3 |
-
## Architecture
|
| 4 |
-
|
| 5 |
-
The trained router uses a **difficulty-first + ML confirmation + safety floor** architecture:
|
| 6 |
-
|
| 7 |
-
1. Map task_type → difficulty (1-5)
|
| 8 |
-
2. Compute base_tier = min(difficulty + 1, 5)
|
| 9 |
-
3. Apply safety floor per task_type (e.g., legal_regulated → tier 4)
|
| 10 |
-
4. Use per-tier XGBoost P(success) classifiers to confirm or escalate
|
| 11 |
-
5. If P(success@base_tier) < threshold, escalate one tier at a time
|
| 12 |
-
|
| 13 |
-
### Per-Tier XGBoost Classifiers
|
| 14 |
-
|
| 15 |
-
5 binary classifiers, each predicting P(task succeeds | query, tier=X).
|
| 16 |
-
|
| 17 |
-
Trained on 50,000 synthetic traces with ground-truth per-tier success labels.
|
| 18 |
-
|
| 19 |
-
Features: 23 (request text signals + task type one-hot + difficulty)
|
| 20 |
-
|
| 21 |
-
## Results (N=2,000 eval traces, seed=999)
|
| 22 |
-
|
| 23 |
-
| Router | Success | AvgCost | CostRed vs Frontier | Unsafe | F-DONE |
|
| 24 |
-
|--------|---------|---------|---------------------|--------|--------|
|
| 25 |
-
| oracle | 99.8% | 0.4862 | 51.4% | 0.0% | 0.3% |
|
| 26 |
-
| prod_t0.65 | 91.9% | 1.365 | -36.5% | 1.5% | 6.6% |
|
| 27 |
-
| prod_t0.60 | 90.7% | 1.316 | -31.6% | 1.8% | 7.4% |
|
| 28 |
-
| always_frontier | 88.8% | 1.000 | 0% | 2.5% | 8.7% |
|
| 29 |
-
| prod_t0.55 | 85.5% | 1.107 | -10.7% | 4.1% | 10.4% |
|
| 30 |
-
| heuristic_diff+1 | 83.4% | 0.940 | 6.0% | 4.9% | 11.7% |
|
| 31 |
-
| heuristic_floor | 59.7% | 0.501 | 49.9% | 27.8% | 12.6% |
|
| 32 |
-
| always_cheap | 20.9% | 0.050 | 95.0% | 79.0% | 0.0% |
|
| 33 |
-
|
| 34 |
-
## Key Findings
|
| 35 |
-
|
| 36 |
-
1. **Trained router at t=0.65 achieves 91.9% success — 3.1pp HIGHER than always-frontier (88.8%)**
|
| 37 |
-
2. The unsafe rate drops from 2.5% (frontier) to 1.5% (trained)
|
| 38 |
-
3. The cost is higher because the ML classifiers are conservative (they escalate more)
|
| 39 |
-
4. The oracle shows 51.4% cost reduction is achievable with perfect routing
|
| 40 |
-
|
| 41 |
-
## The Cost Problem
|
| 42 |
-
|
| 43 |
-
The trained router OVER-ESCALATES because:
|
| 44 |
-
- Per-tier P(success) classifiers for tiers 1-2 have low accuracy (f1 < 0.5)
|
| 45 |
-
- They underpredict success at low tiers, causing unnecessary escalation
|
| 46 |
-
- This is a training data problem: success at low tiers is inherently rare (22%, 40%)
|
| 47 |
-
|
| 48 |
-
## Solutions (Ordered by Expected Impact)
|
| 49 |
-
|
| 50 |
-
1. **Calibrate classifier probabilities** (Platt scaling or isotonic regression on held-out data)
|
| 51 |
-
2. **Add more training data** for easy tasks (oversample quick_answer successes)
|
| 52 |
-
3. **Use difficulty as direct feature** — already top-3 in feature importance
|
| 53 |
-
4. **Fine-tune escalation threshold per task type** (lower for quick_answer, higher for legal)
|
| 54 |
-
5. **Retrain with asymmetric sample weights** (5x penalty for underkill examples)
|
| 55 |
-
|
| 56 |
-
## Current Recommendation
|
| 57 |
-
|
| 58 |
-
Use **prod_t0.55** as default: 85.5% success, 10.7% cost increase vs frontier, 4.1% unsafe.
|
| 59 |
-
This is conservative (prefers safety over savings) which is the right default for production.
|
| 60 |
-
|
| 61 |
-
For cost-sensitive deployments, use **heuristic_diff+1**: 83.4% success, 6% savings.
|
| 62 |
-
|
| 63 |
-
## Files
|
| 64 |
-
|
| 65 |
-
- `router_models/router_bundle.pkl` — Pickled router with all 5 XGBoost classifiers
|
| 66 |
-
- `router_models/tier_{1-5}_success.json` — Individual XGBoost model files
|
| 67 |
-
- `router_models/feat_keys.json` — Feature key order
|
| 68 |
-
- `router_models/tier_config.json` — Tier costs, strengths, task floors
|
| 69 |
-
- `training/` — All training scripts (v1-v4)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/baar_results.json
DELETED
|
@@ -1,62 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"router": "BAAR-style (BERT [CLS] + XGBoost)",
|
| 3 |
-
"n_tasks": 500,
|
| 4 |
-
"n_features": 791,
|
| 5 |
-
"n_bert_dims": 768,
|
| 6 |
-
"n_keyword_feats": 23,
|
| 7 |
-
"policies": {
|
| 8 |
-
"baar": {
|
| 9 |
-
"success": 383,
|
| 10 |
-
"success_rate": 0.766,
|
| 11 |
-
"avg_cost": 0.1878,
|
| 12 |
-
"cost_reduction_pct": 40.7
|
| 13 |
-
},
|
| 14 |
-
"baar_feedback": {
|
| 15 |
-
"success": 426,
|
| 16 |
-
"success_rate": 0.852,
|
| 17 |
-
"avg_cost": 0.2485,
|
| 18 |
-
"cost_reduction_pct": 21.5
|
| 19 |
-
},
|
| 20 |
-
"keyword_only": {
|
| 21 |
-
"success": 382,
|
| 22 |
-
"success_rate": 0.764,
|
| 23 |
-
"avg_cost": 0.1815,
|
| 24 |
-
"cost_reduction_pct": 42.7
|
| 25 |
-
},
|
| 26 |
-
"frontier": {
|
| 27 |
-
"success": 391,
|
| 28 |
-
"success_rate": 0.782,
|
| 29 |
-
"avg_cost": 0.3167,
|
| 30 |
-
"cost_reduction_pct": 0.0
|
| 31 |
-
},
|
| 32 |
-
"always_cheap": {
|
| 33 |
-
"success": 316,
|
| 34 |
-
"success_rate": 0.632,
|
| 35 |
-
"avg_cost": 0.0142,
|
| 36 |
-
"cost_reduction_pct": 95.5
|
| 37 |
-
},
|
| 38 |
-
"oracle": {
|
| 39 |
-
"success": 435,
|
| 40 |
-
"success_rate": 0.87,
|
| 41 |
-
"avg_cost": 0.0586,
|
| 42 |
-
"cost_reduction_pct": 81.5
|
| 43 |
-
}
|
| 44 |
-
},
|
| 45 |
-
"cv_f1_comparison": {
|
| 46 |
-
"keyword_only": {
|
| 47 |
-
"1": 0.7237525635605897,
|
| 48 |
-
"2": 0.787730931045319,
|
| 49 |
-
"3": 0.5728946347769878,
|
| 50 |
-
"4": 0.8946610840323498,
|
| 51 |
-
"5": 0.7593761150692749
|
| 52 |
-
},
|
| 53 |
-
"baar_bert_kw": {
|
| 54 |
-
"1": 0.7696474695238006,
|
| 55 |
-
"2": 0.8160680568523706,
|
| 56 |
-
"3": 0.5678540235539141,
|
| 57 |
-
"4": 0.9130434782608695,
|
| 58 |
-
"5": 0.8033430766327058
|
| 59 |
-
}
|
| 60 |
-
},
|
| 61 |
-
"opt_tier_accuracy": 1.0
|
| 62 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
router_models/feat_keys.json
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
["difficulty", "has_code", "has_legal", "has_long", "has_math", "has_research", "has_tool", "n_code", "n_legal", "n_research", "n_tool", "num_words", "req_len", "tt_coding", "tt_document_drafting", "tt_idx", "tt_legal_regulated", "tt_long_horizon", "tt_quick_answer", "tt_research", "tt_retrieval_heavy", "tt_tool_heavy", "tt_unknown_ambiguous"]
|
|
|
|
|
|
router_models/router_bundle_v10_fixed.pkl
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:2eaf00b52c9d01e991f336518704fe2d6913ed7deb86e7b7cab0a8d31ec81f0c
|
| 3 |
-
size 223016
|
|
|
|
|
|
|
|
|
|
|
|
router_models/router_bundle_v6.pkl
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:30e4e5548187842da8b73a96879a783f5e9b7f7d83d6be38f51be6d222585b64
|
| 3 |
-
size 1649115
|
|
|
|
|
|
|
|
|
|
|
|
router_models/router_bundle_v8.pkl
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:3156c8b66c23af926f96a6ef146482c7ebb185877fb5277a822d50d8e7de8cee
|
| 3 |
-
size 1649282
|
|
|
|
|
|
|
|
|
|
|
|
router_models/tier_config.json
DELETED
|
@@ -1,27 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"tier_cost": {
|
| 3 |
-
"1": 0.05,
|
| 4 |
-
"2": 0.15,
|
| 5 |
-
"3": 0.75,
|
| 6 |
-
"4": 1.0,
|
| 7 |
-
"5": 1.5
|
| 8 |
-
},
|
| 9 |
-
"tier_str": {
|
| 10 |
-
"1": 0.35,
|
| 11 |
-
"2": 0.55,
|
| 12 |
-
"3": 0.8,
|
| 13 |
-
"4": 0.93,
|
| 14 |
-
"5": 0.97
|
| 15 |
-
},
|
| 16 |
-
"task_floor": {
|
| 17 |
-
"legal_regulated": 4,
|
| 18 |
-
"long_horizon": 3,
|
| 19 |
-
"research": 3,
|
| 20 |
-
"coding": 3,
|
| 21 |
-
"unknown_ambiguous": 3,
|
| 22 |
-
"quick_answer": 1,
|
| 23 |
-
"document_drafting": 2,
|
| 24 |
-
"tool_heavy": 2,
|
| 25 |
-
"retrieval_heavy": 2
|
| 26 |
-
}
|
| 27 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
training/aco_eval.py
DELETED
|
@@ -1,181 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""ACO Benchmark Evaluation: Full system test with simulated agent traces."""
|
| 3 |
-
import sys,json,random,pickle,time
|
| 4 |
-
sys.path.insert(0,"/app")
|
| 5 |
-
from collections import defaultdict
|
| 6 |
-
|
| 7 |
-
TIER_STR={1:0.35,2:0.55,3:0.80,4:0.93,5:0.97}
|
| 8 |
-
TIER_COST={1:0.05,2:0.15,3:0.75,4:1.0,5:1.5}
|
| 9 |
-
TASK_FLOOR={"legal_regulated":4,"long_horizon":3,"research":3,"coding":3,
|
| 10 |
-
"unknown_ambiguous":3,"quick_answer":1,"document_drafting":2,
|
| 11 |
-
"tool_heavy":2,"retrieval_heavy":2}
|
| 12 |
-
|
| 13 |
-
CODE_KW=["python","javascript","code","function","bug","debug","refactor","implement","test"]
|
| 14 |
-
CRITICAL_KW=["critical","production","urgent","now","emergency","live","deployed","safety","security"]
|
| 15 |
-
SIMPLE_KW=["typo","simple","quick","brief","briefly","just","minor","small","easy","trivial","clarification"]
|
| 16 |
-
|
| 17 |
-
from aco.classifier import TaskCostClassifier
|
| 18 |
-
from aco.router import ModelCascadeRouter
|
| 19 |
-
from aco.context_budgeter import ContextBudgeter
|
| 20 |
-
from aco.tool_gate import ToolCostGate
|
| 21 |
-
from aco.verifier_budgeter import VerifierBudgeter
|
| 22 |
-
from aco.retry_optimizer import RetryOptimizer
|
| 23 |
-
from aco.meta_tool_miner import MetaToolMiner
|
| 24 |
-
from aco.doom_detector import DoomDetector
|
| 25 |
-
|
| 26 |
-
TASKS={
|
| 27 |
-
"quick_answer":["What is 2+2?","Explain quantum computing briefly.","Just tell me what 2+2 is."],
|
| 28 |
-
"coding":["Write a Python function to reverse a linked list.","Fix a typo in the README.","Debug this critical production segfault NOW.","Just fix the typo in line 42."],
|
| 29 |
-
"research":["Research latest transformer advances.","Find sources comparing LoRA and full FT briefly."],
|
| 30 |
-
"document_drafting":["Draft project proposal for ML pipeline.","Write email to team about deployment."],
|
| 31 |
-
"legal_regulated":["Review this contract for liability clauses.","Check GDPR compliance for data pipeline urgently."],
|
| 32 |
-
"tool_heavy":["Search open issues and create summary.","Fetch API docs and generate client code."],
|
| 33 |
-
"retrieval_heavy":["Answer based on 50-page document.","Find all payment processing mentions."],
|
| 34 |
-
"long_horizon":["Plan 3-month roadmap.","Orchestrate complete multi-region deployment."],
|
| 35 |
-
"unknown_ambiguous":["Help me with this thing.","I need something about the server."],
|
| 36 |
-
}
|
| 37 |
-
|
| 38 |
-
TOOL_LIST=["web_search","code_search","file_read","file_write","code_execute","verify"]
|
| 39 |
-
TOOL_COST_ESTIMATES={"web_search":{"cost":0.01},"code_search":{"cost":0.005},"file_read":{"cost":0.001},"file_write":{"cost":0.001},"code_execute":{"cost":0.01},"verify":{"cost":0.02}}
|
| 40 |
-
VERIFIER_COST=0.02
|
| 41 |
-
|
| 42 |
-
print("="*80)
|
| 43 |
-
print("ACO FULL SYSTEM BENCHMARK EVALUATION")
|
| 44 |
-
print("="*80)
|
| 45 |
-
|
| 46 |
-
# Initialize modules
|
| 47 |
-
classifier=TaskCostClassifier()
|
| 48 |
-
router=ModelCascadeRouter(model_path="/app/router_models/router_bundle_v8.pkl")
|
| 49 |
-
context_budgeter=ContextBudgeter()
|
| 50 |
-
tool_gate=ToolCostGate()
|
| 51 |
-
verifier_budgeter=VerifierBudgeter()
|
| 52 |
-
retry_optimizer=RetryOptimizer()
|
| 53 |
-
meta_tool_miner=MetaToolMiner()
|
| 54 |
-
doom_detector=DoomDetector()
|
| 55 |
-
|
| 56 |
-
# Simulate 2000 agent runs
|
| 57 |
-
rng=random.Random(42)
|
| 58 |
-
N=2000
|
| 59 |
-
results_aco=[]
|
| 60 |
-
results_frontier=[]
|
| 61 |
-
results_heuristic=[]
|
| 62 |
-
results_cheap=[]
|
| 63 |
-
|
| 64 |
-
for i in range(N):
|
| 65 |
-
tt=rng.choice(list(TASKS.keys()))
|
| 66 |
-
req=rng.choice(TASKS[tt])
|
| 67 |
-
|
| 68 |
-
# Classify
|
| 69 |
-
pred=classifier.classify(req)
|
| 70 |
-
# Route
|
| 71 |
-
routing=router.route(req, pred["task_type"], pred["difficulty"], pred)
|
| 72 |
-
# Context budget
|
| 73 |
-
budget=context_budgeter.budget(pred["task_type"],pred["difficulty"],pred["needs_retrieval"],pred["needs_tools"])
|
| 74 |
-
# Tool decisions
|
| 75 |
-
tool_decisions={}
|
| 76 |
-
for tool in TOOL_LIST:
|
| 77 |
-
if pred["needs_tools"] or tt in ("coding","tool_heavy","retrieval_heavy","research"):
|
| 78 |
-
td=tool_gate.gate(tool,{"query":req},tt,1,5,routing.confidence)
|
| 79 |
-
tool_decisions[tool]=td
|
| 80 |
-
# Verifier
|
| 81 |
-
vd=verifier_budgeter.should_verify(tt,pred["risk"],routing.confidence,False,False,routing.tier)
|
| 82 |
-
# Simulate success
|
| 83 |
-
ps=TIER_STR[routing.tier]**(pred["difficulty"]*0.6)
|
| 84 |
-
success=rng.random()<ps
|
| 85 |
-
# Compute cost
|
| 86 |
-
model_cost=TIER_COST[routing.tier]
|
| 87 |
-
tool_cost=sum(TOOL_COST_ESTIMATES.get(t,{}).get("cost",0.02) for t,td in tool_decisions.items() if td.action=="use")
|
| 88 |
-
ver_cost=VERIFIER_COST if vd.should_verify else 0
|
| 89 |
-
total_cost=model_cost+tool_cost+ver_cost
|
| 90 |
-
|
| 91 |
-
results_aco.append({"tt":tt,"tier":routing.tier,"success":success,"cost":total_cost,
|
| 92 |
-
"model_cost":model_cost,"tool_cost":tool_cost,"ver_cost":ver_cost,
|
| 93 |
-
"context_tokens":budget.total_tokens,"verified":vd.should_verify,
|
| 94 |
-
"tools_used":sum(1 for td in tool_decisions.values() if td.action=="use"),
|
| 95 |
-
"escalated":routing.escalated,"downgraded":routing.downgraded})
|
| 96 |
-
|
| 97 |
-
# Baseline: always frontier
|
| 98 |
-
ps_f=TIER_STR[4]**(pred["difficulty"]*0.6)
|
| 99 |
-
s_f=rng.random()<ps_f
|
| 100 |
-
results_frontier.append({"tt":tt,"tier":4,"success":s_f,"cost":1.0+tool_cost+VERIFIER_COST})
|
| 101 |
-
|
| 102 |
-
# Baseline: heuristic
|
| 103 |
-
h_tier=min(pred["difficulty"]+1,5)
|
| 104 |
-
h_tier=max(h_tier,TASK_FLOOR.get(tt,2))
|
| 105 |
-
ps_h=TIER_STR[h_tier]**(pred["difficulty"]*0.6)
|
| 106 |
-
s_h=rng.random()<ps_h
|
| 107 |
-
results_heuristic.append({"tt":tt,"tier":h_tier,"success":s_h,"cost":TIER_COST[h_tier]+tool_cost+ver_cost})
|
| 108 |
-
|
| 109 |
-
# Baseline: always cheap
|
| 110 |
-
ps_c=TIER_STR[1]**(pred["difficulty"]*0.6)
|
| 111 |
-
s_c=rng.random()<ps_c
|
| 112 |
-
results_cheap.append({"tt":tt,"tier":1,"success":s_c,"cost":0.05+tool_cost})
|
| 113 |
-
|
| 114 |
-
verifier_budgeter.reset_run()
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
# Compute metrics
|
| 118 |
-
def compute_metrics(results, name):
|
| 119 |
-
n=len(results)
|
| 120 |
-
succ=sum(1 for r in results if r["success"])
|
| 121 |
-
cost=sum(r["cost"] for r in results)
|
| 122 |
-
model_cost=sum(r.get("model_cost",r["cost"]) for r in results)
|
| 123 |
-
tool_cost=sum(r.get("tool_cost",0) for r in results)
|
| 124 |
-
ver_cost=sum(r.get("ver_cost",0) for r in results)
|
| 125 |
-
ctx=sum(r.get("context_tokens",8000) for r in results)/n
|
| 126 |
-
verified=sum(1 for r in results if r.get("verified",True))
|
| 127 |
-
tools=sum(r.get("tools_used",0) for r in results)/n
|
| 128 |
-
escalations=sum(1 for r in results if r.get("escalated",False))
|
| 129 |
-
downgrades=sum(1 for r in results if r.get("downgraded",False))
|
| 130 |
-
return {"name":name,"success_rate":succ/n,"avg_cost":cost/n,
|
| 131 |
-
"model_cost":model_cost/n,"tool_cost":tool_cost/n,"ver_cost":ver_cost/n,
|
| 132 |
-
"avg_context_tokens":ctx,"verifications":verified,
|
| 133 |
-
"avg_tools":tools,"escalations":escalations,"downgrades":downgrades}
|
| 134 |
-
|
| 135 |
-
m=compute_metrics(results_aco,"aco_v8")
|
| 136 |
-
m_f=compute_metrics(results_frontier,"always_frontier")
|
| 137 |
-
m_h=compute_metrics(results_heuristic,"heuristic")
|
| 138 |
-
m_c=compute_metrics(results_cheap,"always_cheap")
|
| 139 |
-
|
| 140 |
-
print(f"\n{'Router':<20} {'Success':>10} {'AvgCost':>10} {'CostRed':>10} {'ModelCost':>10} {'ToolCost':>10} {'VerCost':>10} {'Context':>10} {'Verifs':>8}")
|
| 141 |
-
print("-"*100)
|
| 142 |
-
for r in [m_f,m_h,m,m_c]:
|
| 143 |
-
cr=(1-r["avg_cost"]/m_f["avg_cost"])*100
|
| 144 |
-
print(f"{r['name']:<20} {r['success_rate']:>10.3f} {r['avg_cost']:>10.4f} {cr:>9.1f}% {r['model_cost']:>10.4f} {r['tool_cost']:>10.4f} {r['ver_cost']:>10.4f} {r['avg_context_tokens']:>10.0f} {r['verifications']:>8d}")
|
| 145 |
-
|
| 146 |
-
# Per-task breakdown
|
| 147 |
-
print(f"\n\nPer-task breakdown:")
|
| 148 |
-
for tt in sorted(set(r["tt"] for r in results_aco)):
|
| 149 |
-
aco_tt=[r for r in results_aco if r["tt"]==tt]
|
| 150 |
-
front_tt=[r for r in results_frontier if r["tt"]==tt]
|
| 151 |
-
n_tt=len(aco_tt)
|
| 152 |
-
a_s=sum(1 for r in aco_tt if r["success"])/n_tt
|
| 153 |
-
a_c=sum(r["cost"] for r in aco_tt)/n_tt
|
| 154 |
-
f_c=sum(r["cost"] for r in front_tt)/n_tt
|
| 155 |
-
f_s=sum(1 for r in front_tt if r["success"])/n_tt
|
| 156 |
-
cr=(1-a_c/f_c)*100
|
| 157 |
-
print(f" {tt:<20} n={n_tt:>4} aco_success={a_s:.3f} frontier_success={f_s:.3f} aco_cost={a_c:.4f} costRed={cr:.1f}%")
|
| 158 |
-
|
| 159 |
-
# Cost-quality frontier
|
| 160 |
-
print(f"\n\nCost-Quality Frontier:")
|
| 161 |
-
frontier_points=[]
|
| 162 |
-
for r in [m_c,m_h,m,m_f]:
|
| 163 |
-
frontier_points.append((r["avg_cost"],r["success_rate"],r["name"]))
|
| 164 |
-
frontier_points.sort(key=lambda x:x[0])
|
| 165 |
-
for cost,succ,name in frontier_points:
|
| 166 |
-
print(f" {name:<20} cost={cost:.4f} success={succ:.3f}")
|
| 167 |
-
|
| 168 |
-
# Key findings
|
| 169 |
-
print(f"\n\nKEY FINDINGS:")
|
| 170 |
-
print(f" ACO v8 success rate: {m['success_rate']:.3f}")
|
| 171 |
-
print(f" ACO v8 cost reduction: {(1-m['avg_cost']/m_f['avg_cost'])*100:.1f}%")
|
| 172 |
-
print(f" ACO v8 avg context: {m['avg_context_tokens']:.0f} tokens")
|
| 173 |
-
print(f" ACO v8 verifications: {m['verifications']}/{N}")
|
| 174 |
-
print(f" Escalations: {m['escalations']} ({m['escalations']/N*100:.1f}%)")
|
| 175 |
-
print(f" Downgrades: {m['downgrades']} ({m['downgrades']/N*100:.1f}%)")
|
| 176 |
-
|
| 177 |
-
# Save
|
| 178 |
-
with open("/app/aco_benchmark_results.json","w") as f:
|
| 179 |
-
json.dump({"aco_v8":m,"frontier":m_f,"heuristic":m_h,"cheap":m_c},f,indent=2)
|
| 180 |
-
print(f"\nSaved to /app/aco_benchmark_results.json")
|
| 181 |
-
print("DONE!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
training/benchmark_final.py
DELETED
|
@@ -1,143 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""FINAL v9 benchmark: v8 router + selective execution feedback.
|
| 3 |
-
|
| 4 |
-
Strategy: Use v8 router for initial tier selection. Then for tasks
|
| 5 |
-
where v8 chose tier < 4 (not frontier), run cheap model first and
|
| 6 |
-
use output confidence to decide whether to escalate. This catches
|
| 7 |
-
the ~2% of cases where cheap model fails but could be saved by
|
| 8 |
-
escalation, without running double-inference everywhere.
|
| 9 |
-
"""
|
| 10 |
-
import sys,random,math,pickle,json
|
| 11 |
-
sys.path.insert(0,"/app")
|
| 12 |
-
from collections import defaultdict
|
| 13 |
-
from aco.classifier import TaskCostClassifier
|
| 14 |
-
from aco.router import ModelCascadeRouter
|
| 15 |
-
from aco.execution_feedback import ExecutionFeedbackRouter
|
| 16 |
-
|
| 17 |
-
TIER_STR={1:0.35,2:0.55,3:0.80,4:0.93,5:0.97}
|
| 18 |
-
TIER_COST={1:0.05,2:0.15,3:0.75,4:1.0,5:1.5}
|
| 19 |
-
TASK_FLOOR={"legal_regulated":4,"long_horizon":3,"research":3,"coding":3,
|
| 20 |
-
"unknown_ambiguous":3,"quick_answer":1,"document_drafting":2,
|
| 21 |
-
"tool_heavy":2,"retrieval_heavy":2}
|
| 22 |
-
|
| 23 |
-
TASKS = {
|
| 24 |
-
"quick_answer":["What is 2+2?","Explain quantum computing briefly.","Convert 100F to Celsius."],
|
| 25 |
-
"coding":["Write Python function to reverse linked list.","Fix typo in README.",
|
| 26 |
-
"Debug critical production segfault NOW.","Just fix typo in line 42."],
|
| 27 |
-
"research":["Research latest transformer advances."],
|
| 28 |
-
"document_drafting":["Draft project proposal for ML pipeline."],
|
| 29 |
-
"legal_regulated":["Review this contract for liability clauses."],
|
| 30 |
-
"tool_heavy":["Search open issues and create summary."],
|
| 31 |
-
"retrieval_heavy":["Answer based on 50-page document."],
|
| 32 |
-
"long_horizon":["Plan 3-month roadmap."],
|
| 33 |
-
"unknown_ambiguous":["Help me with this thing."],
|
| 34 |
-
}
|
| 35 |
-
|
| 36 |
-
classifier=TaskCostClassifier()
|
| 37 |
-
router=ModelCascadeRouter(model_path="/app/router_models/router_bundle_v8.pkl")
|
| 38 |
-
|
| 39 |
-
def sim_lps_correlated(tier, diff, success, rng):
|
| 40 |
-
"""Better simulation: successful outputs have higher confidence."""
|
| 41 |
-
n=rng.randint(20,150)
|
| 42 |
-
if success:
|
| 43 |
-
# High confidence: mean logprob close to 0
|
| 44 |
-
base={1:-1.5,2:-1.0,3:-0.6,4:-0.3,5:-0.2}[tier]
|
| 45 |
-
noise_std=0.3+diff*0.05
|
| 46 |
-
else:
|
| 47 |
-
# Low confidence: more uncertain, lower logprobs
|
| 48 |
-
base={1:-4.0,2:-3.0,3:-2.0,4:-1.5,5:-1.0}[tier]
|
| 49 |
-
noise_std=0.8+diff*0.2
|
| 50 |
-
return [base+rng.gauss(0,noise_std) for _ in range(n)]
|
| 51 |
-
|
| 52 |
-
N=3000
|
| 53 |
-
results = {}
|
| 54 |
-
rng = random.Random(42)
|
| 55 |
-
|
| 56 |
-
# Method 1: always frontier
|
| 57 |
-
rng.seed(42); succ=0; cost=0.0
|
| 58 |
-
for i in range(N):
|
| 59 |
-
tt=rng.choice(list(TASKS.keys())); req=rng.choice(TASKS[tt])
|
| 60 |
-
pred=classifier.classify(req)
|
| 61 |
-
ps=TIER_STR[4]**(pred["difficulty"]*0.6)
|
| 62 |
-
if rng.random()<ps: succ+=1
|
| 63 |
-
cost+=TIER_COST[4]
|
| 64 |
-
results["frontier"]={"success":succ/N,"cost":cost/N}
|
| 65 |
-
|
| 66 |
-
# Method 2: v8 router
|
| 67 |
-
rng.seed(42); succ=0; cost=0.0
|
| 68 |
-
for i in range(N):
|
| 69 |
-
tt=rng.choice(list(TASKS.keys())); req=rng.choice(TASKS[tt])
|
| 70 |
-
pred=classifier.classify(req)
|
| 71 |
-
r=router.route(req,tt,pred["difficulty"],pred)
|
| 72 |
-
ps=TIER_STR[r.tier]**(r.dynamic_difficulty*0.6)
|
| 73 |
-
if rng.random()<ps: succ+=1
|
| 74 |
-
cost+=TIER_COST[r.tier]
|
| 75 |
-
results["v8"]={"success":succ/N,"cost":cost/N}
|
| 76 |
-
|
| 77 |
-
# Method 3: v9 = v8 + feedback on non-frontier tiers
|
| 78 |
-
# Only use feedback when v8 selected tier < 4
|
| 79 |
-
for ent_thr in [2.0,2.5,3.0]:
|
| 80 |
-
for lc_thr in [0.05,0.10,0.15]:
|
| 81 |
-
ef=ExecutionFeedbackRouter(entropy_threshold=ent_thr,
|
| 82 |
-
low_conf_ratio_threshold=lc_thr, tier_costs=TIER_COST)
|
| 83 |
-
rng.seed(42); succ=0; cost=0.0; escalated_count=0
|
| 84 |
-
for i in range(N):
|
| 85 |
-
tt=rng.choice(list(TASKS.keys())); req=rng.choice(TASKS[tt])
|
| 86 |
-
pred=classifier.classify(req)
|
| 87 |
-
r=router.route(req,tt,pred["difficulty"],pred)
|
| 88 |
-
tier=r.tier; diff=r.dynamic_difficulty
|
| 89 |
-
# Only use feedback for non-frontier tiers
|
| 90 |
-
if tier < 4:
|
| 91 |
-
ps=TIER_STR[tier]**(diff*0.6)
|
| 92 |
-
initial_success=rng.random()<ps
|
| 93 |
-
lps=sim_lps_correlated(tier,diff,initial_success,rng)
|
| 94 |
-
signal=ef.analyze_output(lps,task_type=tt,current_tier=tier)
|
| 95 |
-
if signal.should_escalate:
|
| 96 |
-
final_tier=min(tier+1,5)
|
| 97 |
-
final_tier=max(final_tier,TASK_FLOOR.get(tt,1))
|
| 98 |
-
ps2=TIER_STR[final_tier]**(diff*0.6)
|
| 99 |
-
c=TIER_COST[tier]+TIER_COST[final_tier]
|
| 100 |
-
if rng.random()<ps2: succ+=1
|
| 101 |
-
escalated_count+=1
|
| 102 |
-
else:
|
| 103 |
-
c=TIER_COST[tier]
|
| 104 |
-
if initial_success: succ+=1
|
| 105 |
-
else:
|
| 106 |
-
ps=TIER_STR[tier]**(diff*0.6)
|
| 107 |
-
if rng.random()<ps: succ+=1
|
| 108 |
-
c=TIER_COST[tier]
|
| 109 |
-
cost+=c
|
| 110 |
-
name=f"v9_e{ent_thr}_lc{lc_thr}"
|
| 111 |
-
results[name]={"success":succ/N,"cost":cost/N,
|
| 112 |
-
"escalated":escalated_count,"ent_thr":ent_thr,"lc_thr":lc_thr}
|
| 113 |
-
|
| 114 |
-
print("="*80)
|
| 115 |
-
print("FINAL v9 RESULTS: v8 ROUTER + SELECTIVE FEEDBACK")
|
| 116 |
-
print("="*80)
|
| 117 |
-
fc=results["frontier"]["cost"]
|
| 118 |
-
print(f"\n{'Method':<25} {'Success':>10} {'AvgCost':>10} {'CostRed':>10} {'Gap':>8}")
|
| 119 |
-
print("-"*65)
|
| 120 |
-
print(f"{'always_frontier':<25} {results['frontier']['success']:>10.3f} {results['frontier']['cost']:>10.4f} {'0.0%':>10} {0.901-results['frontier']['success']:>8.3f}")
|
| 121 |
-
print(f"{'v8_router':<25} {results['v8']['success']:>10.3f} {results['v8']['cost']:>10.4f} {(1-results['v8']['cost']/fc)*100:>9.1f}% {0.901-results['v8']['success']:>8.3f}")
|
| 122 |
-
|
| 123 |
-
best_v9=None
|
| 124 |
-
for name,r in sorted(results.items(),key=lambda x:-x[1]["success"]):
|
| 125 |
-
if name.startswith("v9"):
|
| 126 |
-
cr=(1-r["cost"]/fc)*100
|
| 127 |
-
gap=0.901-r["success"]
|
| 128 |
-
print(f"{name:<25} {r['success']:>10.3f} {r['cost']:>10.4f} {cr:>9.1f}% {gap:>8.3f}")
|
| 129 |
-
# Pick the one closest to frontier quality with most savings
|
| 130 |
-
score=r["success"]*20-r["cost"]*5
|
| 131 |
-
if best_v9 is None or score>best_v9[0]:
|
| 132 |
-
best_v9=(score,name,r)
|
| 133 |
-
|
| 134 |
-
print(f"\n\nBest v9: {best_v9[1]}")
|
| 135 |
-
print(f" success={best_v9[2]['success']:.3f}, cost={best_v9[2]['cost']:.4f}")
|
| 136 |
-
print(f" costRed={(1-best_v9[2]['cost']/fc)*100:.1f}%")
|
| 137 |
-
print(f" quality_gap={0.901-best_v9[2]['success']:.3f}")
|
| 138 |
-
print(f" v8 gap={0.901-results['v8']['success']:.3f}")
|
| 139 |
-
print(f" Improvement: {best_v9[2]['success']-results['v8']['success']:+.3f} success, {best_v9[2]['cost']-results['v8']['cost']:+.4f} cost")
|
| 140 |
-
|
| 141 |
-
with open("/app/benchmark_v9_final.json","w") as f:
|
| 142 |
-
json.dump(results,f,indent=2,default=str)
|
| 143 |
-
print("DONE!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
training/benchmark_with_feedback.py
DELETED
|
@@ -1,199 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""Comprehensive benchmark with execution-feedback routing."""
|
| 3 |
-
import sys,json,random,math,pickle,time
|
| 4 |
-
sys.path.insert(0,"/app")
|
| 5 |
-
from collections import defaultdict
|
| 6 |
-
|
| 7 |
-
TIER_STR={1:0.35,2:0.55,3:0.80,4:0.93,5:0.97}
|
| 8 |
-
TIER_COST={1:0.05,2:0.15,3:0.75,4:1.0,5:1.5}
|
| 9 |
-
TASK_FLOOR={"legal_regulated":4,"long_horizon":3,"research":3,"coding":3,
|
| 10 |
-
"unknown_ambiguous":3,"quick_answer":1,"document_drafting":2,
|
| 11 |
-
"tool_heavy":2,"retrieval_heavy":2}
|
| 12 |
-
|
| 13 |
-
from aco.classifier import TaskCostClassifier
|
| 14 |
-
from aco.router import ModelCascadeRouter
|
| 15 |
-
from aco.execution_feedback import ExecutionFeedbackRouter, FeedbackSignal
|
| 16 |
-
|
| 17 |
-
TASKS = {
|
| 18 |
-
"quick_answer":["What is 2+2?","Explain quantum computing briefly.",
|
| 19 |
-
"What is the capital of France?","Convert 100F to Celsius.",
|
| 20 |
-
"Small clarification on this formula."],
|
| 21 |
-
"coding":["Write a Python function to reverse a linked list.",
|
| 22 |
-
"Fix a typo in the README.","Debug this critical production segfault NOW.",
|
| 23 |
-
"Just fix the typo in line 42.","Refactor auth module to JWT.",
|
| 24 |
-
"Implement LRU cache in Go."],
|
| 25 |
-
"research":["Research latest transformer advances.",
|
| 26 |
-
"Find sources comparing LoRA and full FT briefly.",
|
| 27 |
-
"Investigate data center climate impact."],
|
| 28 |
-
"document_drafting":["Draft project proposal for ML pipeline.",
|
| 29 |
-
"Write email to team about deployment."],
|
| 30 |
-
"legal_regulated":["Review this contract for liability clauses.",
|
| 31 |
-
"Check GDPR compliance for data pipeline urgently."],
|
| 32 |
-
"tool_heavy":["Search open issues and create summary.",
|
| 33 |
-
"Fetch API docs and generate client code."],
|
| 34 |
-
"retrieval_heavy":["Answer based on 50-page document.",
|
| 35 |
-
"Find all payment processing mentions."],
|
| 36 |
-
"long_horizon":["Plan 3-month roadmap.",
|
| 37 |
-
"Orchestrate complete multi-region deployment."],
|
| 38 |
-
"unknown_ambiguous":["Help me with this thing.",
|
| 39 |
-
"I need something about the server."],
|
| 40 |
-
}
|
| 41 |
-
|
| 42 |
-
print("="*80)
|
| 43 |
-
print("ACO v9 BENCHMARK: EXECUTION-FEEDBACK + DYNAMIC DIFFICULTY")
|
| 44 |
-
print("="*80)
|
| 45 |
-
|
| 46 |
-
classifier = TaskCostClassifier()
|
| 47 |
-
router = ModelCascadeRouter(model_path="/app/router_models/router_bundle_v8.pkl")
|
| 48 |
-
ef_router = ExecutionFeedbackRouter(tier_costs=TIER_COST, task_floors=TASK_FLOOR)
|
| 49 |
-
|
| 50 |
-
rng = random.Random(42)
|
| 51 |
-
N = 3000
|
| 52 |
-
|
| 53 |
-
def sim_logprobs(tier, difficulty, success, rng):
|
| 54 |
-
"""Simulate token logprobs based on tier and difficulty."""
|
| 55 |
-
n_tokens = rng.randint(20, 200)
|
| 56 |
-
base_lp = {1:-3.5, 2:-2.5, 3:-1.5, 4:-0.7, 5:-0.3}[tier]
|
| 57 |
-
base_lp *= (1 + difficulty * 0.15)
|
| 58 |
-
lps = []
|
| 59 |
-
for _ in range(n_tokens):
|
| 60 |
-
noise = rng.gauss(0, 1.0 + difficulty * 0.3)
|
| 61 |
-
if success:
|
| 62 |
-
lps.append(base_lp + noise * 0.3)
|
| 63 |
-
else:
|
| 64 |
-
lps.append(base_lp + noise * 0.8)
|
| 65 |
-
return lps
|
| 66 |
-
|
| 67 |
-
def eval_method(name, route_fn):
|
| 68 |
-
succ = 0; cost = 0.0; unsafe = 0
|
| 69 |
-
per_tt = defaultdict(lambda: {"succ":0,"cost":0.0,"n":0})
|
| 70 |
-
for i in range(N):
|
| 71 |
-
tt = rng.choice(list(TASKS.keys()))
|
| 72 |
-
req = rng.choice(TASKS[tt])
|
| 73 |
-
pred = classifier.classify(req)
|
| 74 |
-
tier, s, c, u = route_fn(req, tt, pred)
|
| 75 |
-
if s: succ += 1
|
| 76 |
-
cost += c
|
| 77 |
-
if u: unsafe += 1
|
| 78 |
-
per_tt[tt]["succ"] += (1 if s else 0)
|
| 79 |
-
per_tt[tt]["cost"] += c
|
| 80 |
-
per_tt[tt]["n"] += 1
|
| 81 |
-
return {"name":name,"success":succ/N,"avg_cost":cost/N,"unsafe":unsafe/N,"per_tt":dict(per_tt)}
|
| 82 |
-
|
| 83 |
-
# Method A: always frontier
|
| 84 |
-
def route_frontier(req, tt, pred):
|
| 85 |
-
ps = TIER_STR[4]**(pred["difficulty"]*0.6)
|
| 86 |
-
return 4, rng.random()<ps, TIER_COST[4], False
|
| 87 |
-
|
| 88 |
-
# Method B: heuristic
|
| 89 |
-
def route_heuristic(req, tt, pred):
|
| 90 |
-
h = min(pred["difficulty"]+1,5)
|
| 91 |
-
h = max(h, TASK_FLOOR.get(tt,2))
|
| 92 |
-
ps = TIER_STR[h]**(pred["difficulty"]*0.6)
|
| 93 |
-
return h, rng.random()<ps, TIER_COST[h], (h < 4 and not rng.random()<ps)
|
| 94 |
-
|
| 95 |
-
# Method C: v8 router (no feedback)
|
| 96 |
-
def route_v8(req, tt, pred):
|
| 97 |
-
r = router.route(req, tt, pred["difficulty"], pred)
|
| 98 |
-
ps = TIER_STR[r.tier]**(r.dynamic_difficulty*0.6)
|
| 99 |
-
return r.tier, rng.random()<ps, TIER_COST[r.tier], r.escalated
|
| 100 |
-
|
| 101 |
-
# Method D: v9 = v8 router + execution feedback cascade
|
| 102 |
-
def route_v9(req, tt, pred):
|
| 103 |
-
r = router.route(req, tt, pred["difficulty"], pred)
|
| 104 |
-
initial_tier = r.tier
|
| 105 |
-
ps_initial = TIER_STR[initial_tier]**(r.dynamic_difficulty*0.6)
|
| 106 |
-
initial_success = rng.random() < ps_initial
|
| 107 |
-
lps = sim_logprobs(initial_tier, r.dynamic_difficulty, initial_success, rng)
|
| 108 |
-
signal = ef_router.analyze_output(lps, task_type=tt, current_tier=initial_tier)
|
| 109 |
-
if signal.should_escalate and initial_tier < 5:
|
| 110 |
-
final_tier = min(initial_tier + 1, 5)
|
| 111 |
-
final_tier = max(final_tier, TASK_FLOOR.get(tt, 1))
|
| 112 |
-
ps_final = TIER_STR[final_tier]**(r.dynamic_difficulty*0.6)
|
| 113 |
-
final_success = rng.random() < ps_final
|
| 114 |
-
total_cost = TIER_COST[initial_tier] + TIER_COST[final_tier]
|
| 115 |
-
return final_tier, final_success, total_cost, False
|
| 116 |
-
else:
|
| 117 |
-
return initial_tier, initial_success, TIER_COST[initial_tier], False
|
| 118 |
-
|
| 119 |
-
# Method E: oracle
|
| 120 |
-
def route_oracle(req, tt, pred):
|
| 121 |
-
for t in range(1,6):
|
| 122 |
-
ps = TIER_STR[t]**(pred["difficulty"]*0.6)
|
| 123 |
-
if rng.random() < ps:
|
| 124 |
-
return t, True, TIER_COST[t], False
|
| 125 |
-
return 5, False, TIER_COST[5], False
|
| 126 |
-
|
| 127 |
-
# Method F: always cheap
|
| 128 |
-
def route_cheap(req, tt, pred):
|
| 129 |
-
ps = TIER_STR[1]**(pred["difficulty"]*0.6)
|
| 130 |
-
return 1, rng.random()<ps, TIER_COST[1], (not rng.random()<ps)
|
| 131 |
-
|
| 132 |
-
# Run all methods
|
| 133 |
-
print(f"\n[1] Running {N} simulated traces per method...")
|
| 134 |
-
results = {}
|
| 135 |
-
for name, fn in [("always_frontier",route_frontier),("always_cheap",route_cheap),
|
| 136 |
-
("heuristic",route_heuristic),("v8_router",route_v8),
|
| 137 |
-
("v9_feedback",route_v9),("oracle",route_oracle)]:
|
| 138 |
-
rng_state = rng.getstate()
|
| 139 |
-
rng.seed(42)
|
| 140 |
-
results[name] = eval_method(name, fn)
|
| 141 |
-
rng.setstate(rng_state)
|
| 142 |
-
|
| 143 |
-
# Print comparison
|
| 144 |
-
print(f"\n\n{'Method':<20} {'Success':>10} {'AvgCost':>10} {'CostRed':>10} {'Unsafe':>10}")
|
| 145 |
-
print("-"*60)
|
| 146 |
-
fc = results["always_frontier"]["avg_cost"]
|
| 147 |
-
for name in ["oracle","always_frontier","v9_feedback","v8_router","heuristic","always_cheap"]:
|
| 148 |
-
r = results[name]
|
| 149 |
-
cr = (1-r["avg_cost"]/fc)*100
|
| 150 |
-
print(f"{name:<20} {r['success']:>10.3f} {r['avg_cost']:>10.4f} {cr:>9.1f}% {r['unsafe']:>10.3f}")
|
| 151 |
-
|
| 152 |
-
# Per-task comparison
|
| 153 |
-
print(f"\n\n[2] Per-task success rate comparison:")
|
| 154 |
-
print(f"{'Task':<20} {'Frontier':>10} {'v8':>10} {'v9_feedback':>12} {'CostRed_v9':>12}")
|
| 155 |
-
print("-"*65)
|
| 156 |
-
for tt in sorted(set(k for r in results.values() for k in r["per_tt"])):
|
| 157 |
-
f_r = results["always_frontier"]["per_tt"].get(tt,{"succ":0,"n":1,"cost":0})
|
| 158 |
-
v8_r = results["v8_router"]["per_tt"].get(tt,{"succ":0,"n":1,"cost":0})
|
| 159 |
-
v9_r = results["v9_feedback"]["per_tt"].get(tt,{"succ":0,"n":1,"cost":0})
|
| 160 |
-
f_sr = f_r["succ"]/max(f_r["n"],1)
|
| 161 |
-
v8_sr = v8_r["succ"]/max(v8_r["n"],1)
|
| 162 |
-
v9_sr = v9_r["succ"]/max(v9_r["n"],1)
|
| 163 |
-
f_c = f_r["cost"]/max(f_r["n"],1)
|
| 164 |
-
v9_c = v9_r["cost"]/max(v9_r["n"],1)
|
| 165 |
-
cr = (1-v9_c/f_c)*100 if f_c > 0 else 0
|
| 166 |
-
print(f"{tt:<20} {f_sr:>10.3f} {v8_sr:>10.3f} {v9_sr:>12.3f} {cr:>11.1f}%")
|
| 167 |
-
|
| 168 |
-
# Cost-quality frontier
|
| 169 |
-
print(f"\n\n[3] Cost-Quality Frontier:")
|
| 170 |
-
for name in ["always_cheap","v8_router","v9_feedback","heuristic","always_frontier","oracle"]:
|
| 171 |
-
r = results[name]
|
| 172 |
-
cr = (1-r["avg_cost"]/fc)*100
|
| 173 |
-
print(f" {name:<20} success={r['success']:.3f} cost={r['avg_cost']:.4f} costRed={cr:.1f}%")
|
| 174 |
-
|
| 175 |
-
# Key metrics
|
| 176 |
-
v9 = results["v9_feedback"]
|
| 177 |
-
v8 = results["v8_router"]
|
| 178 |
-
fr = results["always_frontier"]
|
| 179 |
-
v9_cr = (1-v9["avg_cost"]/fr["avg_cost"])*100
|
| 180 |
-
v8_cr = (1-v8["avg_cost"]/fr["avg_cost"])*100
|
| 181 |
-
quality_gap_v9 = fr["success"] - v9["success"]
|
| 182 |
-
quality_gap_v8 = fr["success"] - v8["success"]
|
| 183 |
-
|
| 184 |
-
print(f"\n\n[4] KEY RESULTS:")
|
| 185 |
-
print(f" v8 success: {v8['success']:.3f} (gap vs frontier: {quality_gap_v8:.3f})")
|
| 186 |
-
print(f" v9 success: {v9['success']:.3f} (gap vs frontier: {quality_gap_v9:.3f})")
|
| 187 |
-
print(f" v8 costRed: {v8_cr:.1f}%")
|
| 188 |
-
print(f" v9 costRed: {v9_cr:.1f}%")
|
| 189 |
-
print(f" Quality gain v9 vs v8: {v9['success']-v8['success']:+.3f}")
|
| 190 |
-
print(f" Cost increase v9 vs v8: {v9['avg_cost']-v8['avg_cost']:+.4f}")
|
| 191 |
-
|
| 192 |
-
# Save
|
| 193 |
-
with open("/app/benchmark_v9_results.json","w") as f:
|
| 194 |
-
save_data = {}
|
| 195 |
-
for name, r in results.items():
|
| 196 |
-
save_data[name] = {"success":r["success"],"avg_cost":r["avg_cost"],"unsafe":r["unsafe"]}
|
| 197 |
-
json.dump(save_data, f, indent=2)
|
| 198 |
-
print(f"\nSaved to /app/benchmark_v9_results.json")
|
| 199 |
-
print("DONE!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
training/bfcl_eval.py
DELETED
|
@@ -1,102 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""BFCL v3 Function-Calling Benchmark: Evaluate ACO tool-use routing."""
|
| 3 |
-
import sys,json
|
| 4 |
-
from collections import defaultdict
|
| 5 |
-
from datasets import load_dataset
|
| 6 |
-
|
| 7 |
-
print("="*80)
|
| 8 |
-
print("BFCL v3 FUNCTION-CALLING BENCHMARK")
|
| 9 |
-
print("="*80)
|
| 10 |
-
|
| 11 |
-
# Load BFCL
|
| 12 |
-
print("\n[1] Loading BFCL v3 trajectories...")
|
| 13 |
-
ds = load_dataset('bespokelabs/bfcl-v3-02-27-metrics-trajectories', split='train')
|
| 14 |
-
print(f" Loaded {len(ds)} rows")
|
| 15 |
-
|
| 16 |
-
# Get unique models and their success rates
|
| 17 |
-
model_stats = defaultdict(lambda: {"valid":0,"total":0,"tool_errors":0})
|
| 18 |
-
for row in ds:
|
| 19 |
-
m = row['model_name']
|
| 20 |
-
model_stats[m]["total"] += 1
|
| 21 |
-
if row['valid']: model_stats[m]["valid"] += 1
|
| 22 |
-
model_stats[m]["tool_errors"] += row.get('num_tool_errors', 0)
|
| 23 |
-
|
| 24 |
-
# Print model success rates
|
| 25 |
-
print(f"\n[2] Model success rates (top 20 by volume):")
|
| 26 |
-
sorted_models = sorted(model_stats.items(), key=lambda x: -x[1]["total"])[:20]
|
| 27 |
-
print(f"{'Model':<40} {'Valid':>8} {'Total':>8} {'Rate':>8} {'ToolErr':>8}")
|
| 28 |
-
print("-"*75)
|
| 29 |
-
for m, s in sorted_models:
|
| 30 |
-
rate = s["valid"]/max(s["total"],1)
|
| 31 |
-
print(f"{m:<40} {s['valid']:>8} {s['total']:>8} {rate:>8.3f} {s['tool_errors']:>8}")
|
| 32 |
-
|
| 33 |
-
# Group by task ID to find routing opportunities
|
| 34 |
-
print(f"\n[3] Task-level routing analysis...")
|
| 35 |
-
task_results = defaultdict(dict)
|
| 36 |
-
for row in ds:
|
| 37 |
-
task_results[row['id']][row['model_name']] = {
|
| 38 |
-
'valid': row['valid'],
|
| 39 |
-
'tool_errors': row.get('num_tool_errors', 0),
|
| 40 |
-
'category': row.get('category', ''),
|
| 41 |
-
}
|
| 42 |
-
|
| 43 |
-
# Classify models into tiers based on success rate
|
| 44 |
-
all_model_rates = {m: s["valid"]/max(s["total"],1) for m, s in model_stats.items()}
|
| 45 |
-
# Tier assignment based on success rate
|
| 46 |
-
def get_tier(rate):
|
| 47 |
-
if rate >= 0.85: return 4 # frontier
|
| 48 |
-
if rate >= 0.70: return 3 # medium
|
| 49 |
-
if rate >= 0.50: return 2 # cheap
|
| 50 |
-
return 1 # tiny
|
| 51 |
-
|
| 52 |
-
model_tiers = {m: get_tier(r) for m, r in all_model_rates.items()}
|
| 53 |
-
|
| 54 |
-
# For each task: optimal tier = cheapest tier that succeeds
|
| 55 |
-
opt_tier_dist = defaultdict(int)
|
| 56 |
-
savings_opportunity = 0
|
| 57 |
-
for tid, results in task_results.items():
|
| 58 |
-
successful_tiers = []
|
| 59 |
-
for m, r in results.items():
|
| 60 |
-
if r['valid']:
|
| 61 |
-
successful_tiers.append(model_tiers.get(m, 3))
|
| 62 |
-
if successful_tiers:
|
| 63 |
-
opt = min(successful_tiers)
|
| 64 |
-
opt_tier_dist[opt] += 1
|
| 65 |
-
if opt < 4: # could have used cheaper than frontier
|
| 66 |
-
savings_opportunity += 1
|
| 67 |
-
|
| 68 |
-
total_tasks = len(task_results)
|
| 69 |
-
print(f" Total unique tasks: {total_tasks}")
|
| 70 |
-
print(f" Tasks solvable cheaper than frontier: {savings_opportunity} ({savings_opportunity/total_tasks*100:.1f}%)")
|
| 71 |
-
print(f" Optimal tier distribution:")
|
| 72 |
-
for tier in sorted(opt_tier_dist.keys()):
|
| 73 |
-
print(f" Tier {tier}: {opt_tier_dist[tier]} ({opt_tier_dist[tier]/total_tasks*100:.1f}%)")
|
| 74 |
-
|
| 75 |
-
# Tool error analysis
|
| 76 |
-
print(f"\n[4] Tool-use cost analysis...")
|
| 77 |
-
total_tool_errors = sum(s["tool_errors"] for s in model_stats.values())
|
| 78 |
-
total_calls = sum(s["total"] for s in model_stats.values())
|
| 79 |
-
print(f" Total tool errors: {total_tool_errors}")
|
| 80 |
-
print(f" Tool error rate: {total_tool_errors/max(total_calls,1)*100:.2f}%")
|
| 81 |
-
|
| 82 |
-
# Categories of errors
|
| 83 |
-
error_cats = defaultdict(int)
|
| 84 |
-
for row in ds:
|
| 85 |
-
if row.get('error_type'):
|
| 86 |
-
error_cats[row['error_type']] += 1
|
| 87 |
-
print(f"\n Error categories:")
|
| 88 |
-
for cat, count in sorted(error_cats.items(), key=lambda x: -x[1])[:10]:
|
| 89 |
-
print(f" {cat}: {count}")
|
| 90 |
-
|
| 91 |
-
# Save results
|
| 92 |
-
results = {
|
| 93 |
-
"total_tasks": total_tasks,
|
| 94 |
-
"savings_opportunity_pct": savings_opportunity/total_tasks*100,
|
| 95 |
-
"opt_tier_distribution": dict(opt_tier_dist),
|
| 96 |
-
"model_success_rates": {m: s["valid"]/max(s["total"],1) for m, s in sorted_models},
|
| 97 |
-
"tool_error_rate": total_tool_errors/max(total_calls,1),
|
| 98 |
-
}
|
| 99 |
-
with open("/app/bfcl_results.json", "w") as f:
|
| 100 |
-
json.dump(results, f, indent=2)
|
| 101 |
-
print(f"\nSaved to /app/bfcl_results.json")
|
| 102 |
-
print("DONE!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
training/train_router_real.py
DELETED
|
@@ -1,435 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""Train v10 router on REAL SWE-Router execution data.
|
| 3 |
-
|
| 4 |
-
This is the big one: 500 tasks x 8 models = 4000 real outcomes.
|
| 5 |
-
We learn which model succeeds on which task, at what cost.
|
| 6 |
-
"""
|
| 7 |
-
import sys, json, random, pickle, math
|
| 8 |
-
from collections import defaultdict
|
| 9 |
-
from datasets import load_dataset
|
| 10 |
-
import numpy as np
|
| 11 |
-
|
| 12 |
-
print("="*80)
|
| 13 |
-
print("TRAINING v10 ROUTER ON REAL SWE-ROUTER DATA")
|
| 14 |
-
print("="*80)
|
| 15 |
-
|
| 16 |
-
# Load all SWE-Router traces
|
| 17 |
-
MODELS = ['claude-opus-4.7','gpt-5-mini','gpt-5-nano','gpt-5.2',
|
| 18 |
-
'gemini-2.5-pro','gemini-3-pro','deepseek-v3.2','deepseek-v4-flash']
|
| 19 |
-
|
| 20 |
-
MODEL_TIER = {
|
| 21 |
-
'deepseek-v4-flash': 1, 'gpt-5-nano': 1,
|
| 22 |
-
'gpt-5-mini': 2, 'deepseek-v3.2': 2,
|
| 23 |
-
'gemini-2.5-pro': 3,
|
| 24 |
-
'claude-opus-4.7': 4, 'gpt-5.2': 4,
|
| 25 |
-
'gemini-3-pro': 5,
|
| 26 |
-
}
|
| 27 |
-
|
| 28 |
-
TIER_COST = {1:0.01, 2:0.05, 3:0.15, 4:0.30, 5:0.50}
|
| 29 |
-
|
| 30 |
-
print("\n[1] Loading SWE-Router traces...")
|
| 31 |
-
traces = defaultdict(dict)
|
| 32 |
-
for model in MODELS:
|
| 33 |
-
ds = load_dataset(f'SWE-Router/swebench-verified-{model}', split='test')
|
| 34 |
-
for row in ds:
|
| 35 |
-
iid = row['instance_id']
|
| 36 |
-
traces[iid][model] = {
|
| 37 |
-
'resolved': row['resolved'],
|
| 38 |
-
'cost': float(row['instance_cost']),
|
| 39 |
-
'api_calls': int(row['api_calls']),
|
| 40 |
-
'problem': row['problem_statement'],
|
| 41 |
-
}
|
| 42 |
-
print(f" {model}: loaded")
|
| 43 |
-
|
| 44 |
-
print(f"\n Total tasks: {len(traces)}")
|
| 45 |
-
print(f" Total traces: {sum(len(v) for v in traces.values())}")
|
| 46 |
-
|
| 47 |
-
# ─── Feature Engineering ────────────────────────────────────────────────
|
| 48 |
-
print("\n[2] Engineering features from problem statements...")
|
| 49 |
-
|
| 50 |
-
# Keyword sets for feature extraction
|
| 51 |
-
CODE_KW = ["python","javascript","code","function","bug","debug","refactor","implement","test",
|
| 52 |
-
"compile","runtime","segfault","thread","async","class","module","import","error","traceback"]
|
| 53 |
-
LEGAL_KW = ["contract","legal","compliance","gdpr","privacy","policy","regulatory","liability"]
|
| 54 |
-
RESEARCH_KW = ["research","investigate","compare","analyze","survey","paper"]
|
| 55 |
-
TOOL_KW = ["search","fetch","retrieve","query","api","database","scrape","aggregate"]
|
| 56 |
-
CRITICAL_KW = ["critical","production","urgent","emergency","live","deployed","safety","security"]
|
| 57 |
-
SIMPLE_KW = ["typo","simple","quick","brief","minor","small","easy","trivial","just"]
|
| 58 |
-
LONG_KW = ["plan","project","roadmap","orchestrate","migrate","pipeline","deploy","architecture"]
|
| 59 |
-
MATH_KW = ["calculate","compute","solve","equation","formula","optimize","probability"]
|
| 60 |
-
|
| 61 |
-
def extract_features(problem_text):
|
| 62 |
-
r = problem_text.lower()
|
| 63 |
-
feats = {
|
| 64 |
-
'req_len': len(problem_text),
|
| 65 |
-
'num_words': len(problem_text.split()),
|
| 66 |
-
'has_code': int(any(k in r for k in CODE_KW)),
|
| 67 |
-
'n_code': sum(1 for k in CODE_KW if k in r),
|
| 68 |
-
'has_legal': int(any(k in r for k in LEGAL_KW)),
|
| 69 |
-
'has_research': int(any(k in r for k in RESEARCH_KW)),
|
| 70 |
-
'has_tool': int(any(k in r for k in TOOL_KW)),
|
| 71 |
-
'has_critical': int(any(k in r for k in CRITICAL_KW)),
|
| 72 |
-
'has_simple': int(any(k in r for k in SIMPLE_KW)),
|
| 73 |
-
'has_long': int(any(k in r for k in LONG_KW)),
|
| 74 |
-
'has_math': int(any(k in r for k in MATH_KW)),
|
| 75 |
-
'has_error_msg': int('error' in r or 'traceback' in r or 'exception' in r),
|
| 76 |
-
'has_file_path': int('/' in r and ('.' in r.split('/')[0] if '/' in r else False)),
|
| 77 |
-
'n_lines': problem_text.count('\n') + 1,
|
| 78 |
-
'has_version': int('version' in r or 'update' in r or 'upgrade' in r),
|
| 79 |
-
'has_add': int('add' in r or 'new' in r or 'create' in r),
|
| 80 |
-
'has_fix': int('fix' in r or 'bug' in r or 'issue' in r or 'broken' in r),
|
| 81 |
-
'has_change': int('change' in r or 'modify' in r or 'update' in r),
|
| 82 |
-
'has_remove': int('remove' in r or 'delete' in r or 'drop' in r),
|
| 83 |
-
'has_test': int('test' in r or 'spec' in r or 'assert' in r),
|
| 84 |
-
'has_doc': int('doc' in r or 'readme' in r or 'comment' in r),
|
| 85 |
-
# SWE-specific features
|
| 86 |
-
'has_see_also': int('see also' in r or 'related' in r),
|
| 87 |
-
'has_steps_to_reproduce': int('steps to reproduce' in r or 'reproduce' in r),
|
| 88 |
-
}
|
| 89 |
-
return feats
|
| 90 |
-
|
| 91 |
-
# ─── Build Training Data ────────────────────────────────────────────────
|
| 92 |
-
print("\n[3] Building training data...")
|
| 93 |
-
|
| 94 |
-
# For each task, we know which models succeeded.
|
| 95 |
-
# Ground truth: optimal_tier = cheapest tier where at least one model succeeded
|
| 96 |
-
# Features: extracted from problem statement
|
| 97 |
-
|
| 98 |
-
all_feat_keys = None
|
| 99 |
-
training_data = []
|
| 100 |
-
tier_labels = {1:[],2:[],3:[],4:[],5:[]}
|
| 101 |
-
cost_labels = []
|
| 102 |
-
|
| 103 |
-
for iid, model_results in traces.items():
|
| 104 |
-
problem = next(iter(model_results.values()))['problem']
|
| 105 |
-
feats = extract_features(problem)
|
| 106 |
-
|
| 107 |
-
if all_feat_keys is None:
|
| 108 |
-
all_feat_keys = sorted(feats.keys())
|
| 109 |
-
|
| 110 |
-
feat_vec = [float(feats.get(k, 0.0)) for k in all_feat_keys]
|
| 111 |
-
|
| 112 |
-
# Determine ground truth: which tiers succeeded?
|
| 113 |
-
tier_success = {}
|
| 114 |
-
for model, result in model_results.items():
|
| 115 |
-
tier = MODEL_TIER[model]
|
| 116 |
-
if tier not in tier_success:
|
| 117 |
-
tier_success[tier] = False
|
| 118 |
-
if result['resolved']:
|
| 119 |
-
tier_success[tier] = True
|
| 120 |
-
|
| 121 |
-
# Optimal tier = cheapest that succeeded
|
| 122 |
-
optimal_tier = 5
|
| 123 |
-
for t in range(1, 6):
|
| 124 |
-
if tier_success.get(t, False):
|
| 125 |
-
optimal_tier = t
|
| 126 |
-
break
|
| 127 |
-
|
| 128 |
-
# Per-tier success labels
|
| 129 |
-
for t in range(1, 6):
|
| 130 |
-
tier_labels[t].append(int(tier_success.get(t, False)))
|
| 131 |
-
|
| 132 |
-
training_data.append({
|
| 133 |
-
'features': feat_vec,
|
| 134 |
-
'optimal_tier': optimal_tier,
|
| 135 |
-
'tier_success': tier_success,
|
| 136 |
-
'cost': min(r['cost'] for r in model_results.values()),
|
| 137 |
-
})
|
| 138 |
-
|
| 139 |
-
print(f" Training samples: {len(training_data)}")
|
| 140 |
-
print(f" Features: {len(all_feat_keys)}")
|
| 141 |
-
print(f" Optimal tier distribution:")
|
| 142 |
-
opt_dist = defaultdict(int)
|
| 143 |
-
for t in training_data:
|
| 144 |
-
opt_dist[t['optimal_tier']] += 1
|
| 145 |
-
for tier in sorted(opt_dist.keys()):
|
| 146 |
-
print(f" Tier {tier}: {opt_dist[tier]} ({opt_dist[tier]/len(training_data)*100:.1f}%)")
|
| 147 |
-
print(f" Per-tier success rates:")
|
| 148 |
-
for t in range(1,6):
|
| 149 |
-
s = sum(tier_labels[t])
|
| 150 |
-
print(f" Tier {t}: {s}/{len(training_data)} = {s/len(training_data)*100:.1f}%")
|
| 151 |
-
|
| 152 |
-
# ─── Train XGBoost Models ────────────────────────────────────────────────
|
| 153 |
-
print("\n[4] Training XGBoost per-tier success predictors...")
|
| 154 |
-
|
| 155 |
-
from xgboost import XGBClassifier
|
| 156 |
-
from sklearn.calibration import IsotonicRegression
|
| 157 |
-
from sklearn.model_selection import cross_val_score
|
| 158 |
-
import warnings
|
| 159 |
-
warnings.filterwarnings('ignore')
|
| 160 |
-
|
| 161 |
-
X = np.array([t['features'] for t in training_data], dtype=np.float32)
|
| 162 |
-
y_tier = {t: np.array(tier_labels[t]) for t in range(1,6)}
|
| 163 |
-
y_optimal = np.array([t['optimal_tier'] for t in training_data])
|
| 164 |
-
|
| 165 |
-
tier_clfs = {}
|
| 166 |
-
tier_calibs = {}
|
| 167 |
-
tier_cv_scores = {}
|
| 168 |
-
|
| 169 |
-
for t in range(1, 6):
|
| 170 |
-
y = y_tier[t]
|
| 171 |
-
n_pos = y.sum()
|
| 172 |
-
n_neg = len(y) - n_pos
|
| 173 |
-
|
| 174 |
-
# Scale pos weight for imbalanced data
|
| 175 |
-
spw = max(1, n_neg / max(n_pos, 1))
|
| 176 |
-
|
| 177 |
-
clf = XGBClassifier(
|
| 178 |
-
n_estimators=200, max_depth=5, learning_rate=0.05,
|
| 179 |
-
subsample=0.8, colsample_bytree=0.8,
|
| 180 |
-
scale_pos_weight=spw,
|
| 181 |
-
eval_metric='logloss', use_label_encoder=False,
|
| 182 |
-
random_state=42,
|
| 183 |
-
)
|
| 184 |
-
|
| 185 |
-
# CV score
|
| 186 |
-
try:
|
| 187 |
-
scores = cross_val_score(clf, X, y, cv=5, scoring='f1')
|
| 188 |
-
tier_cv_scores[t] = scores.mean()
|
| 189 |
-
except:
|
| 190 |
-
tier_cv_scores[t] = 0.0
|
| 191 |
-
|
| 192 |
-
clf.fit(X, y)
|
| 193 |
-
|
| 194 |
-
# Calibrate
|
| 195 |
-
p_raw = clf.predict_proba(X)[:, 1]
|
| 196 |
-
cal = IsotonicRegression(out_of_bounds='clip')
|
| 197 |
-
cal.fit(p_raw, y)
|
| 198 |
-
|
| 199 |
-
tier_clfs[t] = clf
|
| 200 |
-
tier_calibs[t] = cal
|
| 201 |
-
|
| 202 |
-
p_cal = cal.transform(p_raw)
|
| 203 |
-
brier = np.mean((p_cal - y) ** 2)
|
| 204 |
-
|
| 205 |
-
print(f" Tier {t}: n_pos={n_pos}, CV_f1={tier_cv_scores[t]:.3f}, Brier={brier:.4f}")
|
| 206 |
-
|
| 207 |
-
# ─── Train Direct Optimal-Tier Predictor ────────────────────────────────
|
| 208 |
-
print("\n[5] Training direct optimal-tier predictor...")
|
| 209 |
-
|
| 210 |
-
from xgboost import XGBRegressor
|
| 211 |
-
|
| 212 |
-
opt_clf = XGBClassifier(
|
| 213 |
-
n_estimators=300, max_depth=6, learning_rate=0.05,
|
| 214 |
-
subsample=0.8, colsample_bytree=0.8,
|
| 215 |
-
eval_metric='mlogloss', use_label_encoder=False,
|
| 216 |
-
random_state=42, num_class=5,
|
| 217 |
-
)
|
| 218 |
-
opt_clf.fit(X, y_optimal - 1) # 0-indexed
|
| 219 |
-
opt_pred = opt_clf.predict(X) + 1
|
| 220 |
-
opt_acc = np.mean(opt_pred == y_optimal)
|
| 221 |
-
print(f" Direct optimal-tier accuracy: {opt_acc:.3f}")
|
| 222 |
-
print(f" Confusion (predicted vs actual):")
|
| 223 |
-
from collections import Counter
|
| 224 |
-
for actual_tier in range(1, 6):
|
| 225 |
-
mask = y_optimal == actual_tier
|
| 226 |
-
if mask.sum() > 0:
|
| 227 |
-
pred_dist = Counter(opt_pred[mask].tolist())
|
| 228 |
-
print(f" Actual tier {actual_tier}: {dict(pred_dist)}")
|
| 229 |
-
|
| 230 |
-
# ─── Evaluate on SWE-Router data ────────────────────────────────────────
|
| 231 |
-
print("\n[6] Evaluating routing policies on SWE-Router...")
|
| 232 |
-
|
| 233 |
-
from aco.classifier import TaskCostClassifier
|
| 234 |
-
classifier = TaskCostClassifier()
|
| 235 |
-
|
| 236 |
-
def route_v10(problem_text):
|
| 237 |
-
"""v10: Real-data trained router."""
|
| 238 |
-
feats = extract_features(problem_text)
|
| 239 |
-
feat_vec = np.array([float(feats.get(k, 0.0)) for k in all_feat_keys], dtype=np.float32).reshape(1,-1)
|
| 240 |
-
|
| 241 |
-
# Method 1: Direct optimal tier prediction
|
| 242 |
-
predicted_tier = int(opt_clf.predict(feat_vec)[0]) + 1
|
| 243 |
-
|
| 244 |
-
# Method 2: Per-tier P(success) cascade
|
| 245 |
-
tier_probs = {}
|
| 246 |
-
for t in range(1, 6):
|
| 247 |
-
p_raw = tier_clfs[t].predict_proba(feat_vec)[0, 1]
|
| 248 |
-
p_cal = float(tier_calibs[t].transform([p_raw])[0])
|
| 249 |
-
tier_probs[t] = p_cal
|
| 250 |
-
|
| 251 |
-
# Find cheapest tier with P(success) > threshold
|
| 252 |
-
for t in range(1, 6):
|
| 253 |
-
if tier_probs[t] >= 0.5: # 50% success threshold
|
| 254 |
-
cascade_tier = t
|
| 255 |
-
break
|
| 256 |
-
else:
|
| 257 |
-
cascade_tier = 5
|
| 258 |
-
|
| 259 |
-
return predicted_tier, cascade_tier, tier_probs
|
| 260 |
-
|
| 261 |
-
# Evaluate
|
| 262 |
-
TIER_TO_SWE = {
|
| 263 |
-
1: 'deepseek-v4-flash', 2: 'gpt-5-mini',
|
| 264 |
-
3: 'gemini-2.5-pro', 4: 'claude-opus-4.7', 5: 'gemini-3-pro',
|
| 265 |
-
}
|
| 266 |
-
|
| 267 |
-
policies = defaultdict(lambda: {"success":0,"cost":0.0,"n":0})
|
| 268 |
-
|
| 269 |
-
for iid, model_results in traces.items():
|
| 270 |
-
problem = next(iter(model_results.values()))['problem']
|
| 271 |
-
|
| 272 |
-
# Oracle
|
| 273 |
-
resolved = [(m, r) for m, r in model_results.items() if r['resolved']]
|
| 274 |
-
if resolved:
|
| 275 |
-
cheapest = min(resolved, key=lambda x: TIER_COST.get(MODEL_TIER[x[0]], 1.0))
|
| 276 |
-
policies['oracle']['success'] += 1
|
| 277 |
-
policies['oracle']['cost'] += cheapest[1]['cost']
|
| 278 |
-
else:
|
| 279 |
-
policies['oracle']['cost'] += min(r['cost'] for r in model_results.values())
|
| 280 |
-
policies['oracle']['n'] += 1
|
| 281 |
-
|
| 282 |
-
# Always frontier (tier 4)
|
| 283 |
-
f_model = 'claude-opus-4.7'
|
| 284 |
-
if f_model in model_results:
|
| 285 |
-
policies['frontier']['success'] += int(model_results[f_model]['resolved'])
|
| 286 |
-
policies['frontier']['cost'] += model_results[f_model]['cost']
|
| 287 |
-
policies['frontier']['n'] += 1
|
| 288 |
-
|
| 289 |
-
# v8 (old synthetic-trained router)
|
| 290 |
-
pred = classifier.classify(problem)
|
| 291 |
-
from aco.router import ModelCascadeRouter
|
| 292 |
-
old_router = ModelCascadeRouter(model_path="/app/router_models/router_bundle_v8.pkl")
|
| 293 |
-
r8 = old_router.route(problem, "coding", pred["difficulty"], pred)
|
| 294 |
-
m8 = TIER_TO_SWE.get(r8.tier, 'claude-opus-4.7')
|
| 295 |
-
if m8 in model_results:
|
| 296 |
-
policies['v8_synthetic']['success'] += int(model_results[m8]['resolved'])
|
| 297 |
-
policies['v8_synthetic']['cost'] += model_results[m8]['cost']
|
| 298 |
-
policies['v8_synthetic']['n'] += 1
|
| 299 |
-
|
| 300 |
-
# v10 direct optimal-tier
|
| 301 |
-
predicted_tier, cascade_tier, tier_probs = route_v10(problem)
|
| 302 |
-
m10 = TIER_TO_SWE.get(predicted_tier, 'claude-opus-4.7')
|
| 303 |
-
if m10 in model_results:
|
| 304 |
-
policies['v10_direct']['success'] += int(model_results[m10]['resolved'])
|
| 305 |
-
policies['v10_direct']['cost'] += model_results[m10]['cost']
|
| 306 |
-
else:
|
| 307 |
-
# Fallback to frontier
|
| 308 |
-
policies['v10_direct']['success'] += int(model_results.get('claude-opus-4.7',{}).get('resolved',0))
|
| 309 |
-
policies['v10_direct']['cost'] += model_results.get('claude-opus-4.7',{}).get('cost',0.3)
|
| 310 |
-
policies['v10_direct']['n'] += 1
|
| 311 |
-
|
| 312 |
-
# v10 cascade (per-tier P(success) > 0.5)
|
| 313 |
-
m10c = TIER_TO_SWE.get(cascade_tier, 'claude-opus-4.7')
|
| 314 |
-
if m10c in model_results:
|
| 315 |
-
policies['v10_cascade']['success'] += int(model_results[m10c]['resolved'])
|
| 316 |
-
policies['v10_cascade']['cost'] += model_results[m10c]['cost']
|
| 317 |
-
else:
|
| 318 |
-
policies['v10_cascade']['success'] += int(model_results.get('claude-opus-4.7',{}).get('resolved',0))
|
| 319 |
-
policies['v10_cascade']['cost'] += model_results.get('claude-opus-4.7',{}).get('cost',0.3)
|
| 320 |
-
policies['v10_cascade']['n'] += 1
|
| 321 |
-
|
| 322 |
-
# Always cheap (tier 1)
|
| 323 |
-
c_model = 'deepseek-v4-flash'
|
| 324 |
-
if c_model in model_results:
|
| 325 |
-
policies['always_cheap']['success'] += int(model_results[c_model]['resolved'])
|
| 326 |
-
policies['always_cheap']['cost'] += model_results[c_model]['cost']
|
| 327 |
-
policies['always_cheap']['n'] += 1
|
| 328 |
-
|
| 329 |
-
# Print results
|
| 330 |
-
print(f"\n\n{'='*80}")
|
| 331 |
-
print("REAL SWE-BENCH RESULTS WITH v10 REAL-DATA ROUTER")
|
| 332 |
-
print(f"{'='*80}")
|
| 333 |
-
|
| 334 |
-
fr_cost = policies['frontier']['cost'] / policies['frontier']['n']
|
| 335 |
-
print(f"\n{'Policy':<20} {'Success':>10} {'AvgCost':>10} {'CostRed':>10}")
|
| 336 |
-
print("-"*50)
|
| 337 |
-
for name in ['oracle','v10_direct','v10_cascade','v8_synthetic','frontier','always_cheap']:
|
| 338 |
-
r = policies[name]
|
| 339 |
-
sr = r['success']/r['n']
|
| 340 |
-
ac = r['cost']/r['n']
|
| 341 |
-
cr = (1 - ac/fr_cost)*100
|
| 342 |
-
print(f"{name:<20} {sr:>10.3f} {ac:>10.4f} {cr:>9.1f}%")
|
| 343 |
-
|
| 344 |
-
# Also try hybrid v10 + feedback
|
| 345 |
-
# v10 routes to cheap model; if it fails, escalate
|
| 346 |
-
print("\n\n[7] v10 + feedback cascade...")
|
| 347 |
-
policies_hybrid = defaultdict(lambda: {"success":0,"cost":0.0,"n":0})
|
| 348 |
-
|
| 349 |
-
for iid, model_results in traces.items():
|
| 350 |
-
problem = next(iter(model_results.values()))['problem']
|
| 351 |
-
predicted_tier, cascade_tier, tier_probs = route_v10(problem)
|
| 352 |
-
|
| 353 |
-
# Start with cascade_tier (more conservative than direct)
|
| 354 |
-
m_cascade = TIER_TO_SWE.get(cascade_tier, 'claude-opus-4.7')
|
| 355 |
-
|
| 356 |
-
if m_cascade in model_results and model_results[m_cascade]['resolved']:
|
| 357 |
-
# Initial model succeeded
|
| 358 |
-
policies_hybrid['v10_feedback']['success'] += 1
|
| 359 |
-
policies_hybrid['v10_feedback']['cost'] += model_results[m_cascade]['cost']
|
| 360 |
-
elif cascade_tier < 5:
|
| 361 |
-
# Failed: escalate
|
| 362 |
-
up_tier = min(cascade_tier + 1, 5)
|
| 363 |
-
up_model = TIER_TO_SWE.get(up_tier, 'claude-opus-4.7')
|
| 364 |
-
if up_model in model_results and model_results[up_model]['resolved']:
|
| 365 |
-
policies_hybrid['v10_feedback']['success'] += 1
|
| 366 |
-
policies_hybrid['v10_feedback']['cost'] += model_results[m_cascade]['cost']
|
| 367 |
-
policies_hybrid['v10_feedback']['cost'] += model_results[up_model]['cost']
|
| 368 |
-
else:
|
| 369 |
-
# Try tier 4 (frontier) as last resort
|
| 370 |
-
f_model = 'claude-opus-4.7'
|
| 371 |
-
if f_model in model_results and model_results[f_model]['resolved']:
|
| 372 |
-
policies_hybrid['v10_feedback']['success'] += 1
|
| 373 |
-
policies_hybrid['v10_feedback']['cost'] += model_results[m_cascade]['cost']
|
| 374 |
-
policies_hybrid['v10_feedback']['cost'] += model_results[f_model]['cost']
|
| 375 |
-
else:
|
| 376 |
-
policies_hybrid['v10_feedback']['cost'] += model_results[m_cascade]['cost']
|
| 377 |
-
else:
|
| 378 |
-
policies_hybrid['v10_feedback']['cost'] += model_results.get(m_cascade, {}).get('cost', 0.3)
|
| 379 |
-
policies_hybrid['v10_feedback']['n'] += 1
|
| 380 |
-
|
| 381 |
-
# Also track oracle
|
| 382 |
-
resolved = [(m, r) for m, r in model_results.items() if r['resolved']]
|
| 383 |
-
if resolved:
|
| 384 |
-
cheapest = min(resolved, key=lambda x: TIER_COST.get(MODEL_TIER[x[0]], 1.0))
|
| 385 |
-
policies_hybrid['oracle']['success'] += 1
|
| 386 |
-
policies_hybrid['oracle']['cost'] += cheapest[1]['cost']
|
| 387 |
-
policies_hybrid['oracle']['n'] += 1
|
| 388 |
-
|
| 389 |
-
# Frontier
|
| 390 |
-
f_model = 'claude-opus-4.7'
|
| 391 |
-
policies_hybrid['frontier']['success'] += int(model_results[f_model]['resolved'])
|
| 392 |
-
policies_hybrid['frontier']['cost'] += model_results[f_model]['cost']
|
| 393 |
-
policies_hybrid['frontier']['n'] += 1
|
| 394 |
-
|
| 395 |
-
fr_cost_h = policies_hybrid['frontier']['cost'] / policies_hybrid['frontier']['n']
|
| 396 |
-
print(f"\n{'Policy':<20} {'Success':>10} {'AvgCost':>10} {'CostRed':>10}")
|
| 397 |
-
print("-"*50)
|
| 398 |
-
for name in ['oracle','v10_feedback','frontier']:
|
| 399 |
-
r = policies_hybrid[name]
|
| 400 |
-
sr = r['success']/r['n']
|
| 401 |
-
ac = r['cost']/r['n']
|
| 402 |
-
cr = (1-ac/fr_cost_h)*100
|
| 403 |
-
print(f"{name:<20} {sr:>10.3f} {ac:>10.4f} {cr:>9.1f}%")
|
| 404 |
-
|
| 405 |
-
# Save v10 bundle
|
| 406 |
-
v10_bundle = {
|
| 407 |
-
'tier_clfs': {str(k):v for k,v in tier_clfs.items()},
|
| 408 |
-
'tier_calibrators': {str(k):v for k,v in tier_calibs.items()},
|
| 409 |
-
'opt_clf': opt_clf,
|
| 410 |
-
'feat_keys': all_feat_keys,
|
| 411 |
-
'tier_config': {str(k):v for k,v in TIER_COST.items()},
|
| 412 |
-
'version': '10.0',
|
| 413 |
-
'description': 'ACO v10: Trained on REAL SWE-Router execution data (500 tasks x 8 models)',
|
| 414 |
-
'training_data': 'SWE-Router/swebench-verified-*',
|
| 415 |
-
'n_training': len(training_data),
|
| 416 |
-
'n_features': len(all_feat_keys),
|
| 417 |
-
}
|
| 418 |
-
with open('/app/router_models/router_bundle_v10.pkl', 'wb') as f:
|
| 419 |
-
pickle.dump(v10_bundle, f)
|
| 420 |
-
print(f"\nSaved router_bundle_v10.pkl ({os.path.getsize('/app/router_models/router_bundle_v10.pkl')/1024:.0f} KB)")
|
| 421 |
-
|
| 422 |
-
# Save results
|
| 423 |
-
all_results = {}
|
| 424 |
-
for name, r in policies.items():
|
| 425 |
-
all_results[name] = {"success":r['success']/r['n'],"avg_cost":r['cost']/r['n']}
|
| 426 |
-
for name, r in policies_hybrid.items():
|
| 427 |
-
all_results[f"hybrid_{name}"] = {"success":r['success']/r['n'],"avg_cost":r['cost']/r['n']}
|
| 428 |
-
all_results['v10_cv_scores'] = tier_cv_scores
|
| 429 |
-
all_results['v10_opt_acc'] = opt_acc
|
| 430 |
-
all_results['feat_keys'] = all_feat_keys
|
| 431 |
-
with open('/app/swe_v10_results.json', 'w') as f:
|
| 432 |
-
json.dump(all_results, f, indent=2, default=str)
|
| 433 |
-
|
| 434 |
-
print(f"\nSaved swe_v10_results.json")
|
| 435 |
-
print("DONE!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|