Live Demo
- Open the public GitHub Pages demo
- Scope: credential-free, synthetic-data demo for AI platform teams and technical evaluators.
Standardized benchmark suite comparing LLM agent orchestration frameworks on a shared task. Measures reliability, latency, cost, and deterministic replay — the metrics that matter for production operators.
---
System Overview
A benchmark suite that lets teams compare orchestration runtimes before they commit to a fragile agent stack.
| Area | Details |
|---|---|
| Users | AI platform teams, developer-tool teams, and engineering leaders evaluating agent frameworks. |
| Technical path | Validate the demo, README, architecture notes, and quality gate before deeper workflow review. |
| System scope | Standardized fixtures, comparative reports, deterministic runs, and inspectable benchmark outputs. |
| Operating boundary | Benchmarks are decision support, not universal model rankings; teams should extend fixtures to their real workflows. |
| Evaluation path | Run the benchmark command, review generated reports, and compare framework behavior against the fixture suite. |
Evaluation Path
- Start here: Compare
results/latest.mdor the sample run before reading runner internals. - Local demo: Run
make install-dev && make bench; no model API key is required for the mock run. - Checks: Run
make testandmake lint; rerender reports withmake report.
Architecture Notes
- Architecture guide summarizes the system scope, first files to inspect, runtime commands, and known boundaries.
- Quality notes lists the local checks, CI surface, and release expectations for this repository.
- Enterprise readiness notes outlines security, data, operations, integration, and handoff expectations.
Why this exists
Teams picking between LangGraph, CrewAI, AutoGen, and home-grown orchestrators currently have no apples-to-apples comparison. Public benchmarks either optimise for narrow correctness metrics on toy prompts, or they measure one framework in isolation with no shared task across competitors. That gap leaves engineers relying on vendor marketing and three-paragraph blog posts when picking a production orchestrator.
This repository provides a reproducible benchmark: one dataset, one set of tools, one grading rubric, four runners. Every runner exposes the same BaseRunner interface, so adding a fifth framework is a fifty-line pull request. CI runs against a built-in deterministic mock LLM so the numerical comparison of orchestrator *shape* requires no API key.
The four dimensions the benchmark cares about are the four that decide whether an agent ships: correctness (does it pick the right tool?), quality (is the answer acceptable?), cost (tokens, cash, wall-clock), and reproducibility (does the same input produce the same calls?). Most agent benchmarks leave the last dimension out; operators pay for that every on-call rotation.
---
Quick start
git clone https://github.com/KIM3310/agent-orchestration-benchmark.git
cd agent-orchestration-benchmark
make install-dev
make bench # mock-LLM run, no API key required
## If your default python3 is older than 3.11:
make BOOTSTRAP_PYTHON=/path/to/python3.11 install-dev
Results land under results/:
results/
latest.json # machine-readable
latest.md # human-readable markdown
latest.html # standalone HTML report with prompt and tool-call evidence
To re-render reports without re-executing the benchmark:
python -m scripts.run_bench --report-only --input results/latest.json
To request each framework adapter's live path:
export OPENAI_API_KEY=sk-...
make bench-live
The generated report labels this mode LIVE / CONFIGURED. That label records the requested adapter path; it does not by itself prove provider traffic. Provider credentials and integration depth differ by adapter, so confirm exceptions, token counters, and provider-side logs before making comparisons.
A working sample run is checked in at `results/sample_run_2026-04-16.json`. The numbers in Sample Results are rendered from that file.
---
Architecture
flowchart LR
F["fixtures/benchmark_prompts.jsonl<br/>(20 prompts)"] --> R
subgraph Runner
R["BenchmarkRunner"] --> A1["stage-pilot-style"]
R --> A2["LangGraph"]
R --> A3["CrewAI"]
R --> A4["AutoGen"]
end
A1 & A2 & A3 & A4 --> TOOL["Shared tools<br/>query_sales_data · summarize_trend"]
TOOL --> M["MetricsCollector<br/>latency · tokens · retries · fingerprints"]
M --> RPT["Report generators<br/>JSON · Markdown · HTML"]
RPT --> OUT[("results/*")]
The runner is pluggable: each framework adapter implements src.runners.base.BaseRunner and is registered in scripts/run_bench.py. Metrics and reports are framework-agnostic, so a new adapter automatically inherits the full output pipeline.
---
Frameworks benchmarked
| Framework | Version | Paradigm | Notes |
|---|---|---|---|
| LangGraph | 1.2.4 | Stateful graph | Nodes + conditional edges. |
| CrewAI | 0.86.0 | Role-based crew | Agents with role/goal/backstory. |
| AutoGen | 0.4.0 | Conversational | Assistant + user proxy dialogue loop. |
| stage-pilot-style (this repo) | — | Deterministic tool-calling parser | ~200 LOC baseline modelled on stage-pilot. |
The fourth runner (stage-pilot-style) is a minimal in-house baseline. It exists so the report answers the operator's real question: "is the framework giving me net value over a well-designed script?"
All adapters default to a built-in mock LLM so CI runs are free and deterministic. Live mode requests each adapter's integration path and must be validated against that adapter's provider prerequisites.
---
Metrics
| Metric | What it measures | Why operators care |
|---|---|---|
tool_call_success_rate | Fraction of prompts whose observed tool sequence exactly matches the expected one. | If the agent calls the wrong tool or skips a tool, nothing else matters. |
final_answer_quality | Fraction of final answers passing keyword + regex grading. | Proxy for user-visible correctness. |
latency_p50_ms / p95_ms / p99_ms | Nearest-rank percentiles over per-prompt wall time. | Tail latency is what breaks SLOs. |
tokens_in / tokens_out | Sum across all tool-calling rounds. | Upstream cost signal; independent of scope. |
total_cost_usd | Derived from the scope table in src/config.py. | A concrete dollar number for budget pitches. |
retry_count | Adapter-initiated retries across all prompts. | Fragility of the tool-call parser. |
exception_rate | Fraction of prompts that raised. | Operators feel this as pages. |
deterministic_replay_rate | Fraction of prompts whose replay fingerprints collapse to a single value. | Whether the orchestrator is reproducible under fixed input. |
See `docs/methodology.md` for exact formulas, and ADR 002 for the rationale.
---
Sample results
Rendered from `results/sample_run_2026-04-16.json`. Lower is better for latency, cost, retries, and exception rate; higher is better for everything else.
Framework summary
| Framework | Tool Success | Answer Quality | p50 (ms) | p95 (ms) | p99 (ms) | Tokens In | Tokens Out | Cost (USD) | Retries | Exception Rate | Replay Stability |
|---|---|---|---|---|---|---|---|---|---|---|---|
| stage-pilot-style | 1.000 | 0.950 | 8.4 | 21.3 | 32.1 | 1,709 | 1,217 | 0.000987 | 0 | 0.000 | 1.000 |
| langgraph | 0.950 | 0.900 | 18.7 | 64.5 | 102.3 | 2,814 | 1,905 | 0.001565 | 3 | 0.050 | 0.900 |
| autogen | 0.900 | 0.800 | 25.6 | 85.4 | 142.9 | 3,592 | 2,411 | 0.001985 | 5 | 0.050 | 0.850 |
| crewai | 0.850 | 0.850 | 31.4 | 110.2 | 178.6 | 4,440 | 2,808 | 0.002352 | 7 | 0.100 | 0.750 |
How to read this
because it has no implicit state — every tool call is argument-checked and fingerprintable. It also wins on cost because the conversation pattern is shortest.
tool-calling loop rehydrates extra state into the prompt. Tool success is close to parity.
reporter roughly doubles the number of LLM calls per prompt. That also shows up as the highest retry count.
predictable than CrewAI's role negotiation but less economical than a single-assistant loop.
- stage-pilot-style wins on both correctness and replay stability
- LangGraph trails on cost and retry count mostly because its default
- CrewAI is the slowest; the role-based hand-off between analyst and
- AutoGen sits between LangGraph and CrewAI. Its proxy pattern is more
Numbers are mock-LLM to keep the comparison reproducible. Do not extrapolate their ranking to live providers without a separately verified live run.
Per-framework deltas at a glance
| Framework | vs stage-pilot-style cost | vs stage-pilot-style p95 | Tool success delta |
|---|---|---|---|
| langgraph | +58.6 % | +203 % | -5 pp |
| autogen | +101.1 % | +301 % | -10 pp |
| crewai | +138.3 % | +417 % | -15 pp |
---
Extending
Adding a new framework is a four-step process:
src.runners.base.BaseRunner. Implement run_prompt(prompt) -> PromptObservation.
runners do; the benchmark guarantees determinism as long as you do.
one fixture prompt.
- Create
src/runners/<name>_runner.pythat subclasses - In mock mode, call
self.llm.complete(...)exactly as the existing - Register the runner in
scripts/run_bench.py(FRAMEWORK_CHOICES). - Add a test file under
tests/that validates the runner on at least
A minimal adapter is roughly 40 lines. See `src/runners/autogen_runner.py` for the shortest existing example.
---
Methodology
The benchmark isolates orchestrator behaviour by holding prompts, tools, grading, and the mock backend constant in CI. Live mode requests adapter-specific integration paths, whose provider setup can differ. Every run freezes its prompt contracts and records per-prompt observations alongside the framework summary so the aggregate can be audited at the individual-call level. Full write-up: `docs/methodology.md`.
Key invariants the benchmark enforces:
must produce an answer that satisfies the same keyword + regex grader.
implementations in src/task.py are pure functions of their input; identical arguments always produce identical output.
config.seed. Frameworks do not get to bring their own completion strategy in CI.
orchestrator cannot distort aggregate metrics.
src/config.py is the only place that translates tokens to dollars.
- Shared task. Every framework receives the same twenty prompts and
- Shared tools. The
query_sales_dataandsummarize_trend - Shared LLM. The
MockLLMinsrc/runners/base.pyis seeded with - Bounded loops. Every adapter caps its own loop count so a runaway
- Single source of truth for scope.
PRICING_USD_PER_MTOKin
Related reading:
- ADR 001: Framework Selection
- ADR 002: Metric Definitions
- ADR 003: Determinism Requirements
- Results Interpretation Guide
---
Project structure
agent-orchestration-benchmark/
├── README.md
├── LICENSE
├── pyproject.toml
├── requirements.txt
├── Dockerfile
├── docker-compose.yml
├── Makefile
├── .gitignore
├── .dockerignore
├── .github/
│ └── workflows/
│ └── ci.yml
├── src/
│ ├── __init__.py
│ ├── config.py # models, scope, retry budgets, timeouts
│ ├── task.py # standardized task + deterministic tools
│ ├── fixtures.py # prompt loader + grading contract
│ ├── metrics.py # metric primitives + aggregation
│ ├── runner.py # BenchmarkRunner (drives adapters)
│ ├── report.py # JSON / Markdown / HTML renderers
│ └── runners/
│ ├── __init__.py
│ ├── base.py # BaseRunner protocol + MockLLM
│ ├── langgraph_runner.py
│ ├── crewai_runner.py
│ ├── autogen_runner.py
│ └── stage_pilot_style.py # deterministic tool-calling loop, ~200 LOC
├── tests/
│ ├── __init__.py
│ ├── test_task.py
│ ├── test_metrics.py
│ ├── test_stage_pilot_style.py
│ └── test_report.py
├── fixtures/
│ └── benchmark_prompts.jsonl # 20 standardized prompts
├── results/
│ └── sample_run_2026-04-16.json
├── docs/
│ ├── methodology.md
│ ├── results-interpretation.md
│ └── adr/
│ ├── 001-framework-selection.md
│ ├── 002-metric-definitions.md
│ └── 003-determinism-requirements.md
└── scripts/
├── __init__.py
├── run_bench.py # python -m scripts.run_bench
└── run_bench.sh # convenience wrapper
---