agent-cost-optimizer / aco /benchmark.py
narcolepticchicken's picture
Upload aco/benchmark.py
0ec5e6d verified
Raw
History Blame Contribute Delete
8.65 kB
"""ACO Benchmark Suite — compares cost optimization configs across 5 task types."""
import json, time, hashlib, random
from typing import Dict, List, Tuple, Callable
from dataclasses import dataclass, field
@dataclass
class StepMetrics:
step_num: int; model_used: str; input_tokens: int; output_tokens: int
context_size: int = 0; tool_calls: int = 0; verifier_calls: int = 0
retries: int = 0; latency_ms: float = 0.0; cost_usd: float = 0.0
@dataclass
class TaskResult:
task_id: str; task_type: str; success: bool
steps: list = field(default_factory=list); total_cost_usd: float = 0.0
total_llm_calls: int = 0; total_tool_calls: int = 0; total_retries: int = 0
false_done: bool = False
@dataclass
class RunReport:
config_name: str; config_description: str = ""
results: list = field(default_factory=list)
@property
def success_rate(self):
return sum(1 for r in self.results if r.success)/max(len(self.results),1)
@property
def avg_cost_per_success(self):
s = [r.total_cost_usd for r in self.results if r.success]
return sum(s)/len(s) if s else float('inf')
@property
def avg_llm_calls(self):
return sum(r.total_llm_calls for r in self.results)/max(len(self.results),1)
@property
def avg_tool_calls(self):
return sum(r.total_tool_calls for r in self.results)/max(len(self.results),1)
@property
def false_done_rate(self):
return sum(1 for r in self.results if r.false_done)/max(len(self.results),1)
PRICING = {
"gpt-4.1-mini": (0.15, 0.60), "gpt-4o": (2.50, 10.0),
"claude-sonnet-4": (3.0, 15.0), "llama-3.1-8b": (0.06, 0.06),
"claude-haiku": (0.80, 4.0), "gemini-flash": (0.075, 0.30),
}
TIER_MODELS = {"cheap": "llama-3.1-8b", "medium": "gpt-4.1-mini", "frontier": "claude-sonnet-4"}
SUCCESS_RATES = {
"cheap": {"coding": 0.55, "research": 0.40, "tool_use": 0.50, "qa": 0.60, "long_horizon": 0.30},
"medium": {"coding": 0.78, "research": 0.65, "tool_use": 0.75, "qa": 0.85, "long_horizon": 0.55},
"frontier": {"coding": 0.92, "research": 0.85, "tool_use": 0.90, "qa": 0.95, "long_horizon": 0.80},
}
ALL_TASKS = {
"coding": [
("code_1", "Write a Python function to sort a list of dicts by key", "easy"),
("code_2", "Fix the race condition in this multithreaded queue", "hard"),
("code_3", "Add input validation to all API endpoints", "medium"),
("code_4", "Migrate from SQLAlchemy 1.4 to 2.0 async API", "hard"),
("code_5", "Write unit tests for the User model", "medium"),
],
"research": [
("research_1", "What's the SOTA for cost-aware LLM routing?", "medium"),
("research_2", "Find all papers about speculative decoding for agents", "hard"),
("research_3", "How does Anthropic's prompt caching work?", "easy"),
("research_4", "Survey test-time compute allocation 2023-2025", "hard"),
("research_5", "What datasets exist for training agent cost routers?", "medium"),
],
"tool_use": [
("tool_1", "What's the weather in Paris today?", "easy"),
("tool_2", "Find top 5 restaurants in Tokyo with ratings", "medium"),
("tool_3", "Compare GDP of France and Germany over last decade", "medium"),
("tool_4", "Translate 'hello' to Japanese", "easy"),
("tool_5", "Compound interest on $10k at 5% over 20 years", "easy"),
],
"qa": [
("qa_1", "What is the capital of France?", "easy"),
("qa_2", "Explain difference between TCP and UDP", "easy"),
("qa_3", "GDPR Article 22 implications for automated decisions?", "hard"),
("qa_4", "Review this contract for liability clauses", "hard"),
("qa_5", "Summarize key findings from IPCC AR6 report", "medium"),
],
"long_horizon": [
("long_1", "Set up complete CI/CD pipeline for Python project", "hard"),
("long_2", "Research, design, and implement a caching layer", "hard"),
("long_3", "Write README, add tests, set up pre-commit hooks", "medium"),
("long_4", "Migrate DB schema, update models, add rollback", "hard"),
("long_5", "Audit codebase for security vulnerabilities, fix top 3", "hard"),
],
}
class SimulatedAgent:
def __init__(self, seed=42):
self.rng = random.Random(seed)
def run_task(self, task_id, text, tier, task_type, difficulty, use_tools, use_verifier):
model = TIER_MODELS[tier]; ip, op = PRICING[model]
base_p = SUCCESS_RATES[tier].get(task_type, 0.7)
diff_m = {"easy": 1.15, "medium": 1.0, "hard": 0.80}
p = min(0.99, base_p * diff_m[difficulty])
nsteps = {"easy": self.rng.randint(1,3), "medium": self.rng.randint(2,5), "hard": self.rng.randint(3,8)}[difficulty]
result = TaskResult(task_id=task_id, task_type=task_type, success=False)
for i in range(nsteps):
it = self.rng.randint(200,3000); ot = self.rng.randint(50,1500)
tc = self.rng.randint(0,3) if use_tools else 0
tf = self.rng.randint(0,1) if tc>0 else 0
vc = 1 if use_verifier else 0
rt = self.rng.randint(0,2) if tf>0 else 0
cost = it/1e6*ip + ot/1e6*op
result.steps.append(StepMetrics(i+1, model, it, ot, it+ot, tc, vc, rt, it*2+ot*10, cost))
result.total_cost_usd += cost; result.total_llm_calls += 1
result.total_tool_calls += tc; result.total_retries += rt
result.success = self.rng.random() < (p * (1 - 0.03*result.total_retries))
return result
class BenchmarkRunner:
def __init__(self, agent=None):
self.agent = agent or SimulatedAgent()
self.reports = {}
def run_config(self, name, desc, router_fn):
report = RunReport(name, desc)
for task_type, tasks in ALL_TASKS.items():
for tid, text, diff in tasks:
tier, tools, verifier = router_fn(text, diff, task_type)
result = self.agent.run_task(tid, text, tier, task_type, diff, tools, verifier)
report.results.append(result)
self.reports[name] = report
return report
def run_all_baselines(self):
self.run_config("A_always_frontier", "Frontier + tools + verifier",
lambda t,d,tt: ("frontier", True, True))
self.run_config("B_always_cheap", "Cheap model, no tools",
lambda t,d,tt: ("cheap", False, False))
self.run_config("C_static_routing", "Static: easy→cheap, medium→medium, hard→frontier",
lambda t,d,tt: ({"easy":"cheap","medium":"medium","hard":"frontier"}[d],
d!="easy", d=="hard"))
self.run_config("D_prompt_router", "Keyword-based heuristic routing",
lambda t,d,tt: self._prompt_route(t, d, tt))
def _prompt_route(self, text, diff, tt):
t = text.lower()
if any(k in t for k in ["fix","debug","migrate","critical","vulnerability"]):
return ("frontier", True, True)
if any(k in t for k in ["test","add","write","survey","find","setup"]):
return ("medium", True, False)
return ("cheap", False, False)
def compare(self):
if "A_always_frontier" not in self.reports: return {}
bl = self.reports["A_always_frontier"]
bs = bl.success_rate; bc = bl.avg_cost_per_success
comp = {}
for name, rpt in self.reports.items():
sr = rpt.success_rate; cs = rpt.avg_cost_per_success
cr = ((bc-cs)/bc*100) if sr >= bs*0.95 else None
comp[name] = {"success": f"{sr:.1%}", "cost_per_success": f"${cs:.4f}",
"cost_reduction": f"{cr:.1f}%" if cr else "N/A",
"llm_calls": f"{rpt.avg_llm_calls:.1f}",
"tool_calls": f"{rpt.avg_tool_calls:.1f}",
"false_done": f"{rpt.false_done_rate:.1%}"}
return comp
def print_report(self):
comp = self.compare()
print(f"\n{'='*100}")
print("ACO BENCHMARK COMPARISON (25 tasks)")
print(f"{'='*100}")
print(f"{'Config':<25} {'Success':>8} {'Cost/Success':>13} {'Reduction':>12} {'LLM':>6} {'Tools':>7} {'FalseDONE':>10}")
print("-"*100)
for n,s in comp.items():
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}")
print("="*100)
if __name__ == "__main__":
runner = BenchmarkRunner()
runner.run_all_baselines()
runner.print_report()