Upload aco/benchmark.py
Browse files- aco/benchmark.py +175 -2
aco/benchmark.py
CHANGED
|
@@ -1,2 +1,175 @@
|
|
| 1 |
-
|
| 2 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""ACO Benchmark Suite — compares cost optimization configs across 5 task types."""
|
| 2 |
+
|
| 3 |
+
import json, time, hashlib, random
|
| 4 |
+
from typing import Dict, List, Tuple, Callable
|
| 5 |
+
from dataclasses import dataclass, field
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
@dataclass
|
| 9 |
+
class StepMetrics:
|
| 10 |
+
step_num: int; model_used: str; input_tokens: int; output_tokens: int
|
| 11 |
+
context_size: int = 0; tool_calls: int = 0; verifier_calls: int = 0
|
| 12 |
+
retries: int = 0; latency_ms: float = 0.0; cost_usd: float = 0.0
|
| 13 |
+
|
| 14 |
+
@dataclass
|
| 15 |
+
class TaskResult:
|
| 16 |
+
task_id: str; task_type: str; success: bool
|
| 17 |
+
steps: list = field(default_factory=list); total_cost_usd: float = 0.0
|
| 18 |
+
total_llm_calls: int = 0; total_tool_calls: int = 0; total_retries: int = 0
|
| 19 |
+
false_done: bool = False
|
| 20 |
+
|
| 21 |
+
@dataclass
|
| 22 |
+
class RunReport:
|
| 23 |
+
config_name: str; config_description: str = ""
|
| 24 |
+
results: list = field(default_factory=list)
|
| 25 |
+
@property
|
| 26 |
+
def success_rate(self):
|
| 27 |
+
return sum(1 for r in self.results if r.success)/max(len(self.results),1)
|
| 28 |
+
@property
|
| 29 |
+
def avg_cost_per_success(self):
|
| 30 |
+
s = [r.total_cost_usd for r in self.results if r.success]
|
| 31 |
+
return sum(s)/len(s) if s else float('inf')
|
| 32 |
+
@property
|
| 33 |
+
def avg_llm_calls(self):
|
| 34 |
+
return sum(r.total_llm_calls for r in self.results)/max(len(self.results),1)
|
| 35 |
+
@property
|
| 36 |
+
def avg_tool_calls(self):
|
| 37 |
+
return sum(r.total_tool_calls for r in self.results)/max(len(self.results),1)
|
| 38 |
+
@property
|
| 39 |
+
def false_done_rate(self):
|
| 40 |
+
return sum(1 for r in self.results if r.false_done)/max(len(self.results),1)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
PRICING = {
|
| 44 |
+
"gpt-4.1-mini": (0.15, 0.60), "gpt-4o": (2.50, 10.0),
|
| 45 |
+
"claude-sonnet-4": (3.0, 15.0), "llama-3.1-8b": (0.06, 0.06),
|
| 46 |
+
"claude-haiku": (0.80, 4.0), "gemini-flash": (0.075, 0.30),
|
| 47 |
+
}
|
| 48 |
+
TIER_MODELS = {"cheap": "llama-3.1-8b", "medium": "gpt-4.1-mini", "frontier": "claude-sonnet-4"}
|
| 49 |
+
SUCCESS_RATES = {
|
| 50 |
+
"cheap": {"coding": 0.55, "research": 0.40, "tool_use": 0.50, "qa": 0.60, "long_horizon": 0.30},
|
| 51 |
+
"medium": {"coding": 0.78, "research": 0.65, "tool_use": 0.75, "qa": 0.85, "long_horizon": 0.55},
|
| 52 |
+
"frontier": {"coding": 0.92, "research": 0.85, "tool_use": 0.90, "qa": 0.95, "long_horizon": 0.80},
|
| 53 |
+
}
|
| 54 |
+
ALL_TASKS = {
|
| 55 |
+
"coding": [
|
| 56 |
+
("code_1", "Write a Python function to sort a list of dicts by key", "easy"),
|
| 57 |
+
("code_2", "Fix the race condition in this multithreaded queue", "hard"),
|
| 58 |
+
("code_3", "Add input validation to all API endpoints", "medium"),
|
| 59 |
+
("code_4", "Migrate from SQLAlchemy 1.4 to 2.0 async API", "hard"),
|
| 60 |
+
("code_5", "Write unit tests for the User model", "medium"),
|
| 61 |
+
],
|
| 62 |
+
"research": [
|
| 63 |
+
("research_1", "What's the SOTA for cost-aware LLM routing?", "medium"),
|
| 64 |
+
("research_2", "Find all papers about speculative decoding for agents", "hard"),
|
| 65 |
+
("research_3", "How does Anthropic's prompt caching work?", "easy"),
|
| 66 |
+
("research_4", "Survey test-time compute allocation 2023-2025", "hard"),
|
| 67 |
+
("research_5", "What datasets exist for training agent cost routers?", "medium"),
|
| 68 |
+
],
|
| 69 |
+
"tool_use": [
|
| 70 |
+
("tool_1", "What's the weather in Paris today?", "easy"),
|
| 71 |
+
("tool_2", "Find top 5 restaurants in Tokyo with ratings", "medium"),
|
| 72 |
+
("tool_3", "Compare GDP of France and Germany over last decade", "medium"),
|
| 73 |
+
("tool_4", "Translate 'hello' to Japanese", "easy"),
|
| 74 |
+
("tool_5", "Compound interest on $10k at 5% over 20 years", "easy"),
|
| 75 |
+
],
|
| 76 |
+
"qa": [
|
| 77 |
+
("qa_1", "What is the capital of France?", "easy"),
|
| 78 |
+
("qa_2", "Explain difference between TCP and UDP", "easy"),
|
| 79 |
+
("qa_3", "GDPR Article 22 implications for automated decisions?", "hard"),
|
| 80 |
+
("qa_4", "Review this contract for liability clauses", "hard"),
|
| 81 |
+
("qa_5", "Summarize key findings from IPCC AR6 report", "medium"),
|
| 82 |
+
],
|
| 83 |
+
"long_horizon": [
|
| 84 |
+
("long_1", "Set up complete CI/CD pipeline for Python project", "hard"),
|
| 85 |
+
("long_2", "Research, design, and implement a caching layer", "hard"),
|
| 86 |
+
("long_3", "Write README, add tests, set up pre-commit hooks", "medium"),
|
| 87 |
+
("long_4", "Migrate DB schema, update models, add rollback", "hard"),
|
| 88 |
+
("long_5", "Audit codebase for security vulnerabilities, fix top 3", "hard"),
|
| 89 |
+
],
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
class SimulatedAgent:
|
| 94 |
+
def __init__(self, seed=42):
|
| 95 |
+
self.rng = random.Random(seed)
|
| 96 |
+
def run_task(self, task_id, text, tier, task_type, difficulty, use_tools, use_verifier):
|
| 97 |
+
model = TIER_MODELS[tier]; ip, op = PRICING[model]
|
| 98 |
+
base_p = SUCCESS_RATES[tier].get(task_type, 0.7)
|
| 99 |
+
diff_m = {"easy": 1.15, "medium": 1.0, "hard": 0.80}
|
| 100 |
+
p = min(0.99, base_p * diff_m[difficulty])
|
| 101 |
+
nsteps = {"easy": self.rng.randint(1,3), "medium": self.rng.randint(2,5), "hard": self.rng.randint(3,8)}[difficulty]
|
| 102 |
+
result = TaskResult(task_id=task_id, task_type=task_type, success=False)
|
| 103 |
+
for i in range(nsteps):
|
| 104 |
+
it = self.rng.randint(200,3000); ot = self.rng.randint(50,1500)
|
| 105 |
+
tc = self.rng.randint(0,3) if use_tools else 0
|
| 106 |
+
tf = self.rng.randint(0,1) if tc>0 else 0
|
| 107 |
+
vc = 1 if use_verifier else 0
|
| 108 |
+
rt = self.rng.randint(0,2) if tf>0 else 0
|
| 109 |
+
cost = it/1e6*ip + ot/1e6*op
|
| 110 |
+
result.steps.append(StepMetrics(i+1, model, it, ot, it+ot, tc, vc, rt, it*2+ot*10, cost))
|
| 111 |
+
result.total_cost_usd += cost; result.total_llm_calls += 1
|
| 112 |
+
result.total_tool_calls += tc; result.total_retries += rt
|
| 113 |
+
result.success = self.rng.random() < (p * (1 - 0.03*result.total_retries))
|
| 114 |
+
return result
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
class BenchmarkRunner:
|
| 118 |
+
def __init__(self, agent=None):
|
| 119 |
+
self.agent = agent or SimulatedAgent()
|
| 120 |
+
self.reports = {}
|
| 121 |
+
def run_config(self, name, desc, router_fn):
|
| 122 |
+
report = RunReport(name, desc)
|
| 123 |
+
for task_type, tasks in ALL_TASKS.items():
|
| 124 |
+
for tid, text, diff in tasks:
|
| 125 |
+
tier, tools, verifier = router_fn(text, diff, task_type)
|
| 126 |
+
result = self.agent.run_task(tid, text, tier, task_type, diff, tools, verifier)
|
| 127 |
+
report.results.append(result)
|
| 128 |
+
self.reports[name] = report
|
| 129 |
+
return report
|
| 130 |
+
def run_all_baselines(self):
|
| 131 |
+
self.run_config("A_always_frontier", "Frontier + tools + verifier",
|
| 132 |
+
lambda t,d,tt: ("frontier", True, True))
|
| 133 |
+
self.run_config("B_always_cheap", "Cheap model, no tools",
|
| 134 |
+
lambda t,d,tt: ("cheap", False, False))
|
| 135 |
+
self.run_config("C_static_routing", "Static: easy→cheap, medium→medium, hard→frontier",
|
| 136 |
+
lambda t,d,tt: ({"easy":"cheap","medium":"medium","hard":"frontier"}[d],
|
| 137 |
+
d!="easy", d=="hard"))
|
| 138 |
+
self.run_config("D_prompt_router", "Keyword-based heuristic routing",
|
| 139 |
+
lambda t,d,tt: self._prompt_route(t, d, tt))
|
| 140 |
+
def _prompt_route(self, text, diff, tt):
|
| 141 |
+
t = text.lower()
|
| 142 |
+
if any(k in t for k in ["fix","debug","migrate","critical","vulnerability"]):
|
| 143 |
+
return ("frontier", True, True)
|
| 144 |
+
if any(k in t for k in ["test","add","write","survey","find","setup"]):
|
| 145 |
+
return ("medium", True, False)
|
| 146 |
+
return ("cheap", False, False)
|
| 147 |
+
def compare(self):
|
| 148 |
+
if "A_always_frontier" not in self.reports: return {}
|
| 149 |
+
bl = self.reports["A_always_frontier"]
|
| 150 |
+
bs = bl.success_rate; bc = bl.avg_cost_per_success
|
| 151 |
+
comp = {}
|
| 152 |
+
for name, rpt in self.reports.items():
|
| 153 |
+
sr = rpt.success_rate; cs = rpt.avg_cost_per_success
|
| 154 |
+
cr = ((bc-cs)/bc*100) if sr >= bs*0.95 else None
|
| 155 |
+
comp[name] = {"success": f"{sr:.1%}", "cost_per_success": f"${cs:.4f}",
|
| 156 |
+
"cost_reduction": f"{cr:.1f}%" if cr else "N/A",
|
| 157 |
+
"llm_calls": f"{rpt.avg_llm_calls:.1f}",
|
| 158 |
+
"tool_calls": f"{rpt.avg_tool_calls:.1f}",
|
| 159 |
+
"false_done": f"{rpt.false_done_rate:.1%}"}
|
| 160 |
+
return comp
|
| 161 |
+
def print_report(self):
|
| 162 |
+
comp = self.compare()
|
| 163 |
+
print(f"\n{'='*100}")
|
| 164 |
+
print("ACO BENCHMARK COMPARISON (25 tasks)")
|
| 165 |
+
print(f"{'='*100}")
|
| 166 |
+
print(f"{'Config':<25} {'Success':>8} {'Cost/Success':>13} {'Reduction':>12} {'LLM':>6} {'Tools':>7} {'FalseDONE':>10}")
|
| 167 |
+
print("-"*100)
|
| 168 |
+
for n,s in comp.items():
|
| 169 |
+
print(f"{n:<25} {s['success']:>8} {s['cost_per_success']:>13} {s['cost_reduction']:>12} {s['llm_calls']:>6} {s['tool_calls']:>7} {s['false_done']:>10}")
|
| 170 |
+
print("="*100)
|
| 171 |
+
|
| 172 |
+
if __name__ == "__main__":
|
| 173 |
+
runner = BenchmarkRunner()
|
| 174 |
+
runner.run_all_baselines()
|
| 175 |
+
runner.print_report()
|