Benchmarks¶
How much time and memory does the agent library itself cost? These compare pyox-agents with pydantic-ai, the OpenAI Agents SDK, Microsoft Agent Framework, LangGraph, LangChain, LlamaIndex and Agno on the same tasks, against the same fake OpenAI server, which answers instantly. So the numbers are the overhead of each library: called directly from Python, with each streaming mode, and behind a FastAPI streaming endpoint under load.
Called directly from Python¶
Python 3.13.17, Linux x86_64 (Intel(R) Xeon(R) Processor @ 2.80GHz). 500 runs per measurement, 100 at a time for throughput. Versions: pyox-agents 0.1.0, pydantic-ai 2.61.0, openai-agents 0.20.0, agent-framework 1.23.0, langgraph 1.2.18, langchain 1.4.7, llama-index 0.14.25, agno 3.1.12.
Latency p50 of one run, and in parentheses how many times the pyox-agents time it is. Fastest first.
| Library | Startup | text |
tool |
parallel_tools |
Throughput, tool |
Peak memory, tool |
|---|---|---|---|---|---|---|
| pyox-agents | 132 ms | 105.2 ms (1.0x) | 8.3 ms (1.0x) | 10.9 ms (1.0x) | 230 runs/s | 35 MB |
| agno | 1614 ms | 121.2 ms (1.2x) | 17.6 ms (2.1x) | 28.1 ms (2.6x) | 57 runs/s | 94 MB |
| agent-framework | 1462 ms | 147.7 ms (1.4x) | 21.7 ms (2.6x) | 32.6 ms (3.0x) | 40 runs/s | 129 MB |
| openai-agents | 3176 ms | 111.8 ms (1.1x) | 24.7 ms (3.0x) | 35.4 ms (3.3x) | 41 runs/s | 143 MB |
| langchain | 2760 ms | 151.1 ms (1.4x) | 27.2 ms (3.3x) | 44.1 ms (4.0x) | 40 runs/s | 125 MB |
| langgraph | 2752 ms | 158.6 ms (1.5x) | 27.6 ms (3.3x) | 46.2 ms (4.2x) | 37 runs/s | 123 MB |
| pydantic-ai | 2158 ms | 213.1 ms (2.0x) | 35.4 ms (4.2x) | 48.4 ms (4.4x) | 25 runs/s | 119 MB |
| llama-index | 3243 ms | 181.5 ms (1.7x) | 41.0 ms (4.9x) | 65.8 ms (6.0x) | 23 runs/s | 196 MB |
All the numbers
`text`: one long streamed answer | Library | Latency p50 | Latency p95 | First text | CPU per run | Throughput | Failed | Peak memory | |---|--:|--:|--:|--:|--:|--:|--:| | pyox-agents | 105.2 ms | 135.9 ms | 3.3 ms | 120.0 ms | 14 runs/s | 0 | 50 MB | | openai-agents | 111.8 ms | 157.3 ms | 74.4 ms | 113.6 ms | 8 runs/s | 0 | 184 MB | | agno | 121.2 ms | 197.2 ms | 8.2 ms | 133.3 ms | 9 runs/s | 0 | 100 MB | | agent-framework | 147.7 ms | 212.8 ms | 7.6 ms | 157.2 ms | 6 runs/s | 0 | 381 MB | | langchain | 151.1 ms | 230.3 ms | 148.9 ms | 160.0 ms | 6 runs/s | 0 | 234 MB | | langgraph | 158.6 ms | 240.4 ms | 155.5 ms | 166.6 ms | 6 runs/s | 0 | 223 MB | | llama-index | 181.5 ms | 264.8 ms | 170.3 ms | 191.4 ms | 4 runs/s | 0 | 282 MB | | pydantic-ai | 213.1 ms | 266.1 ms | 10.0 ms | 215.2 ms | 4 runs/s | 0 | 126 MB | `tool`: one tool call, then the answer | Library | Latency p50 | Latency p95 | First text | CPU per run | Throughput | Failed | Peak memory | |---|--:|--:|--:|--:|--:|--:|--:| | pyox-agents | 8.3 ms | 11.7 ms | 3.5 ms | 7.3 ms | 230 runs/s | 0 | 35 MB | | agno | 17.6 ms | 26.1 ms | 12.8 ms | 17.3 ms | 57 runs/s | 0 | 94 MB | | agent-framework | 21.7 ms | 31.4 ms | 15.1 ms | 21.6 ms | 40 runs/s | 0 | 129 MB | | openai-agents | 24.7 ms | 38.3 ms | 22.7 ms | 25.2 ms | 41 runs/s | 0 | 143 MB | | langchain | 27.2 ms | 39.8 ms | 26.0 ms | 26.9 ms | 40 runs/s | 0 | 125 MB | | langgraph | 27.6 ms | 40.0 ms | 26.0 ms | 27.1 ms | 37 runs/s | 0 | 123 MB | | pydantic-ai | 35.4 ms | 52.8 ms | 23.3 ms | 35.9 ms | 25 runs/s | 0 | 119 MB | | llama-index | 41.0 ms | 62.5 ms | 36.9 ms | 42.2 ms | 23 runs/s | 0 | 196 MB | `parallel_tools`: five tool calls at once, then the answer | Library | Latency p50 | Latency p95 | First text | CPU per run | Throughput | Failed | Peak memory | |---|--:|--:|--:|--:|--:|--:|--:| | pyox-agents | 10.9 ms | 15.3 ms | 6.1 ms | 11.5 ms | 147 runs/s | 0 | 36 MB | | agno | 28.1 ms | 40.2 ms | 22.8 ms | 28.2 ms | 37 runs/s | 0 | 99 MB | | agent-framework | 32.6 ms | 51.3 ms | 25.0 ms | 34.5 ms | 26 runs/s | 0 | 157 MB | | openai-agents | 35.4 ms | 53.9 ms | 33.0 ms | 36.0 ms | 29 runs/s | 0 | 147 MB | | langchain | 44.1 ms | 60.0 ms | 42.8 ms | 43.3 ms | 25 runs/s | 0 | 133 MB | | langgraph | 46.2 ms | 59.9 ms | 44.3 ms | 44.6 ms | 24 runs/s | 0 | 128 MB | | pydantic-ai | 48.4 ms | 68.2 ms | 37.0 ms | 48.9 ms | 18 runs/s | 0 | 122 MB | | llama-index | 65.8 ms | 91.2 ms | 61.6 ms | 66.4 ms | 14 runs/s | 0 | 204 MB | Startup | Library | Startup | Memory after startup | |---|--:|--:| | pyox-agents | 132 ms | 25 MB | | agent-framework | 1462 ms | 67 MB | | agno | 1614 ms | 74 MB | | pydantic-ai | 2158 ms | 92 MB | | langgraph | 2752 ms | 108 MB | | langchain | 2760 ms | 109 MB | | openai-agents | 3176 ms | 114 MB | | llama-index | 3243 ms | 132 MB |Each scenario is one streamed agent run: text streams a long answer (~450 chunks), tool makes one tool call
and parallel_tools five at once, then answer. Startup is importing the library and creating an agent.
Throughput runs 100 at once in one process.
- Tools: pyox-agents is 2 to 6 times faster than every other library, serves 4 to 10 times more runs per second and uses a third to a fifth of the memory.
- Long streamed text: only 1.1 to 2 times faster with the default
text_chunks="delta", because eachTextDeltaevent costs about 0.25 ms. Withtext_chunks=10it takes 15 ms, 7 to 14 times less than the others. - Startup: 0.13 s vs 1.5 to 3.2 s.
- "First text" for
textis close to the whole run for the OpenAI Agents SDK, LangChain, LangGraph and LlamaIndex. This is most likely an artifact of the instant fake model: they pass pieces through a queue between asyncio tasks.
Streaming modes (text_chunks)¶
pyox-agents can group streamed text into fewer TextDelta events. Cost of each mode on the text scenario:
| Mode | Latency p50 | Latency p95 | First text | CPU per run | Throughput | Failed | Peak memory |
|---|---|---|---|---|---|---|---|
| text_chunks='paragraph' | 6.7 ms | 8.6 ms | 4.0 ms | 6.4 ms | 432 runs/s | 0 | 48 MB |
| text_chunks='message' | 7.6 ms | 9.8 ms | 6.8 ms | 6.1 ms | 409 runs/s | 0 | 46 MB |
| text_chunks=10 | 14.7 ms | 21.3 ms | 3.2 ms | 15.5 ms | 112 runs/s | 0 | 57 MB |
| text_chunks='delta' | 111.1 ms | 142.1 ms | 3.3 ms | 125.0 ms | 12 runs/s | 0 | 49 MB |
The cost is per event, not per character. With a real model, the wait for the first TextDelta grows with the
group size (the whole answer with "message"), so 10 is the safe choice for live UIs.
Serving a stream: run.batches() and run.ai_sdk()¶
Three ways to turn a run into an AI SDK stream for a web response: one Python step per event, one per batch
of waiting events (run.batches()), or the whole stream made in Rust (run.ai_sdk()). Run it with
uv run stream_bench.py (stream_bench.py).
Intel(R) Xeon(R) Processor @ 2.80GHz.
Instant model (the cost of the Python side alone):
| Way | Runs at a time | Latency p50 | CPU per run | Throughput | Python steps per run |
|---|---|---|---|---|---|
async for event in run + Python |
1 | 112.4 ms | 123.6 ms | 9 runs/s | 449 |
run.batches() + Python |
1 | 9.1 ms | 10.3 ms | 106 runs/s | 10 |
run.ai_sdk() (Rust) |
1 | 7.6 ms | 8.9 ms | 129 runs/s | 12 |
async for event in run + Python |
50 | 3871.3 ms | 114.2 ms | 13 runs/s | 449 |
run.batches() + Python |
50 | 292.0 ms | 10.3 ms | 137 runs/s | 20 |
run.ai_sdk() (Rust) |
50 | 84.7 ms | 5.7 ms | 354 runs/s | 11 |
Paced model (100 pieces, 10 ms apart):
| Way | Runs at a time | Latency p50 | CPU per run | Throughput | Python steps per run |
|---|---|---|---|---|---|
async for event in run + Python |
1 | 1053.4 ms | 80.8 ms | 1 runs/s | 101 |
run.batches() + Python |
1 | 1054.0 ms | 81.7 ms | 1 runs/s | 101 |
run.ai_sdk() (Rust) |
1 | 1055.4 ms | 79.7 ms | 1 runs/s | 101 |
async for event in run + Python |
50 | 1186.0 ms | 29.8 ms | 42 runs/s | 101 |
run.batches() + Python |
50 | 1161.1 ms | 30.1 ms | 43 runs/s | 101 |
run.ai_sdk() (Rust) |
50 | 1123.6 ms | 26.3 ms | 44 runs/s | 101 |
Each Python step costs far more than the event it carries. A batch holds the events that were already waiting, so batching only pays off when events pile up: a model that answers faster than Python reads it (cached answers, a fast local model), or a busy server that falls behind. A real model streaming one piece every few milliseconds gives one event per step either way, and the three ways cost about the same.
Sync code: agent.run_sync()¶
The sync API runs the agent in Rust with no Python event loop. Here
against the async API and against asyncio.run around it, what sync code (a Django view, a Celery task) would do
without it. 8 at a time means 8 tasks on one event loop for the async API, and 8 threads, like a Gunicorn
gthread worker, for the other two
(sync_bench.py).
Intel(R) Xeon(R) Processor @ 2.80GHz. tool scenario, 1000 runs per row.
| Way | Runs at a time | Latency p50 | CPU per run | Throughput |
|---|---|---|---|---|
await agent.run(...) |
1 | 2.77 ms | 2.32 ms | 339 runs/s |
agent.run_sync(...) |
1 | 1.87 ms | 1.01 ms | 505 runs/s |
asyncio.run(agent.run(...)) |
1 | 2.80 ms | 2.55 ms | 340 runs/s |
await agent.run(...) |
8 | 9.27 ms | 2.07 ms | 726 runs/s |
agent.run_sync(...) |
8 | 6.50 ms | 0.95 ms | 1143 runs/s |
asyncio.run(agent.run(...)) |
8 | 18.10 ms | 3.67 ms | 429 runs/s |
A sync run takes half the CPU of an async one: each event is a plain hand-over to a waiting thread, where the async API goes through the event loop. A sync run still holds its thread until it ends, so for hundreds of runs at once an async server is the better fit.
Behind a FastAPI streaming endpoint¶
Each library serves POST /chat from one uvicorn process
(server.py), and k6
users each send requests in a loop, reading the whole streamed answer
(load.js). "First byte" is when the first
text arrives; "Failed" counts requests without the full answer.
Python 3.13.17, one uvicorn process, Intel(R) Xeon(R) Processor @ 2.80GHz. Each measurement: 20s of load.
text: one long streamed answer¶
| Library | Req/s, 1 user | Req/s, 10 users | Req/s, 50 users | Req/s, 100 users | First byte p50, 100 users | Latency avg / p50 / p90 / p99, 100 users | CPU per request | Peak memory | Failed |
|---|---|---|---|---|---|---|---|---|---|
| pyox-agents | 11 | 16 | 16 | 17 | 49 ms | 5.6 / 5.5 / 7.4 / 9.3 s | 90.2 ms | 83 MB | 0 |
| openai-agents | 8 | 8 | 8 | 8 | 6442 ms | 11.1 / 10.3 / 18.2 / 20.9 s | 119.2 ms | 153 MB | 0 |
| agno | 7 | 7 | 7 | 7 | 5318 ms | 12.1 / 5.9 / 31.9 / 34.9 s | 151.5 ms | 105 MB | 0 |
| langchain | 6 | 7 | 7 | 6 | 6021 ms | 14.8 / 12.2 / 28.5 / 28.5 s | 154.2 ms | 147 MB | 0 |
| langgraph | 6 | 7 | 6 | 6 | 10360 ms | 15.8 / 13.5 / 26.5 / 26.8 s | 158.8 ms | 191 MB | 0 |
| llama-index | 5 | 5 | 5 | 5 | 9192 ms | 17.0 / 15.6 / 30.0 / 30.0 s | 191.2 ms | 216 MB | 0 |
| agent-framework | 5 | 5 | 5 | 5 | 10114 ms | 15.5 / 12.4 / 32.5 / 37.0 s | 199.9 ms | 214 MB | 0 |
| pydantic-ai | 4 | 4 | 4 | 3 | 922 ms | 28.7 / 28.8 / 29.0 / 29.0 s | 287.3 ms | 134 MB | 0 |
tool: one tool call, then the answer¶
| Library | Req/s, 1 user | Req/s, 10 users | Req/s, 50 users | Req/s, 100 users | First byte p50, 100 users | Latency avg / p50 / p90 / p99, 100 users | CPU per request | Peak memory | Failed |
|---|---|---|---|---|---|---|---|---|---|
| pyox-agents | 145 | 234 | 254 | 247 | 118 ms | 0.4 / 0.4 / 0.6 / 0.9 s | 5.8 ms | 65 MB | 0 |
| agno | 49 | 56 | 54 | 56 | 1729 ms | 1.7 / 1.7 / 1.9 / 6.2 s | 17.7 ms | 107 MB | 0 |
| openai-agents | 44 | 50 | 48 | 47 | 1627 ms | 2.1 / 2.1 / 2.4 / 2.7 s | 21.3 ms | 159 MB | 0 |
| agent-framework | 46 | 46 | 42 | 42 | 2152 ms | 2.3 / 2.2 / 2.7 / 8.6 s | 23.5 ms | 148 MB | 0 |
| langchain | 40 | 46 | 41 | 41 | 1770 ms | 2.4 / 2.4 / 2.6 / 4.1 s | 24.1 ms | 133 MB | 0 |
| langgraph | 36 | 42 | 42 | 39 | 1804 ms | 2.5 / 2.5 / 2.7 / 4.0 s | 25.2 ms | 133 MB | 0 |
| pydantic-ai | 30 | 33 | 30 | 28 | 2042 ms | 3.5 / 3.6 / 3.7 / 3.9 s | 34.9 ms | 134 MB | 0 |
| llama-index | 27 | 24 | 25 | 24 | 3262 ms | 4.1 / 4.1 / 4.4 / 6.8 s | 42.2 ms | 200 MB | 0 |
All the numbers
| Scenario | Library | Users | Requests/s | First byte p50 | p99 | Latency avg | p50 | p90 | p99 | Failed | Server CPU per request | Server peak memory | |---|---|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:| | text | pyox-agents | 1 | 11 | 4.0 ms | 8.9 ms | 92.0 ms | 91.4 ms | 101.8 ms | 115.4 ms | 0 | 101.9 ms | 56 MB | | text | pydantic-ai | 1 | 4 | 10.9 ms | 17.7 ms | 248.7 ms | 243.5 ms | 279.4 ms | 316.8 ms | 0 | 242.9 ms | 106 MB | | text | openai-agents | 1 | 8 | 67.9 ms | 126.5 ms | 129.4 ms | 122.7 ms | 155.0 ms | 210.4 ms | 0 | 124.9 ms | 134 MB | | text | agent-framework | 1 | 5 | 8.5 ms | 21.8 ms | 201.3 ms | 187.8 ms | 236.1 ms | 390.7 ms | 0 | 195.9 ms | 120 MB | | text | langgraph | 1 | 6 | 137.4 ms | 287.3 ms | 157.9 ms | 149.1 ms | 185.2 ms | 300.1 ms | 0 | 152.8 ms | 122 MB | | text | langchain | 1 | 6 | 134.8 ms | 286.1 ms | 155.2 ms | 145.9 ms | 183.4 ms | 295.7 ms | 0 | 150.4 ms | 123 MB | | text | llama-index | 1 | 5 | 159.4 ms | 372.9 ms | 188.5 ms | 177.9 ms | 215.3 ms | 388.0 ms | 0 | 183.2 ms | 188 MB | | text | agno | 1 | 7 | 7.8 ms | 12.0 ms | 149.5 ms | 144.3 ms | 167.3 ms | 220.2 ms | 0 | 144.9 ms | 96 MB | | text | pyox-agents | 10 | 16 | 6.7 ms | 31.1 ms | 630.9 ms | 643.5 ms | 693.6 ms | 726.2 ms | 0 | 100.7 ms | 59 MB | | text | pydantic-ai | 10 | 4 | 67.9 ms | 97.1 ms | 2314.1 ms | 2324.0 ms | 2432.0 ms | 2441.3 ms | 0 | 230.0 ms | 110 MB | | text | openai-agents | 10 | 8 | 856.9 ms | 1257.5 ms | 1189.9 ms | 1144.5 ms | 1332.8 ms | 2121.0 ms | 0 | 117.7 ms | 141 MB | | text | agent-framework | 10 | 5 | 1087.9 ms | 2096.5 ms | 1932.2 ms | 1945.6 ms | 2355.7 ms | 2410.1 ms | 0 | 196.9 ms | 167 MB | | text | langgraph | 10 | 7 | 847.1 ms | 1554.1 ms | 1497.3 ms | 1486.6 ms | 1603.2 ms | 2553.4 ms | 0 | 148.0 ms | 129 MB | | text | langchain | 10 | 7 | 1136.8 ms | 1626.5 ms | 1487.3 ms | 1448.9 ms | 1573.6 ms | 1818.1 ms | 0 | 147.3 ms | 132 MB | | text | llama-index | 10 | 5 | 1192.2 ms | 2587.2 ms | 1925.1 ms | 1840.7 ms | 2349.4 ms | 2697.0 ms | 0 | 195.3 ms | 208 MB | | text | agno | 10 | 7 | 774.3 ms | 1386.7 ms | 1449.5 ms | 1437.6 ms | 1561.7 ms | 1576.9 ms | 0 | 145.0 ms | 98 MB | | text | pyox-agents | 50 | 16 | 27.4 ms | 133.2 ms | 2991.4 ms | 2970.7 ms | 3376.2 ms | 4048.2 ms | 0 | 94.1 ms | 71 MB | | text | pydantic-ai | 50 | 4 | 364.1 ms | 633.8 ms | 13763.6 ms | 13707.1 ms | 14060.0 ms | 14147.9 ms | 0 | 273.1 ms | 122 MB | | text | openai-agents | 50 | 8 | 3209.6 ms | 5702.4 ms | 5980.1 ms | 6015.0 ms | 6340.2 ms | 6466.9 ms | 0 | 118.8 ms | 146 MB | | text | agent-framework | 50 | 5 | 5451.6 ms | 29112.8 ms | 8244.7 ms | 5917.5 ms | 21448.1 ms | 29600.5 ms | 0 | 193.5 ms | 168 MB | | text | langgraph | 50 | 6 | 7150.2 ms | 8103.6 ms | 7756.4 ms | 7565.1 ms | 8215.8 ms | 12308.4 ms | 0 | 153.7 ms | 182 MB | | text | langchain | 50 | 7 | 3314.9 ms | 10508.5 ms | 7357.6 ms | 7518.7 ms | 11313.6 ms | 13906.4 ms | 0 | 149.9 ms | 137 MB | | text | llama-index | 50 | 5 | 5663.2 ms | 10096.0 ms | 9405.9 ms | 9435.8 ms | 11356.8 ms | 15557.6 ms | 0 | 197.4 ms | 208 MB | | text | agno | 50 | 7 | 4554.5 ms | 9771.1 ms | 6720.6 ms | 6995.7 ms | 7902.2 ms | 14473.5 ms | 0 | 148.2 ms | 105 MB | | text | pyox-agents | 100 | 17 | 49.0 ms | 308.8 ms | 5585.5 ms | 5495.0 ms | 7427.6 ms | 9287.9 ms | 0 | 90.2 ms | 83 MB | | text | pydantic-ai | 100 | 3 | 922.3 ms | 1755.2 ms | 28749.9 ms | 28810.1 ms | 28987.7 ms | 28992.4 ms | 0 | 287.3 ms | 134 MB | | text | openai-agents | 100 | 8 | 6442.5 ms | 17313.3 ms | 11065.4 ms | 10319.9 ms | 18228.4 ms | 20940.6 ms | 0 | 119.2 ms | 153 MB | | text | agent-framework | 100 | 5 | 10114.4 ms | 36805.1 ms | 15504.1 ms | 12423.7 ms | 32546.1 ms | 36973.6 ms | 0 | 199.9 ms | 214 MB | | text | langgraph | 100 | 6 | 10359.7 ms | 25366.8 ms | 15758.9 ms | 13529.0 ms | 26544.3 ms | 26754.9 ms | 0 | 158.8 ms | 191 MB | | text | langchain | 100 | 6 | 6020.7 ms | 28418.8 ms | 14817.7 ms | 12175.4 ms | 28463.9 ms | 28473.8 ms | 0 | 154.2 ms | 147 MB | | text | llama-index | 100 | 5 | 9192.4 ms | 29762.6 ms | 17032.4 ms | 15578.6 ms | 29953.6 ms | 29996.2 ms | 0 | 191.2 ms | 216 MB | | text | agno | 100 | 7 | 5317.7 ms | 34796.8 ms | 12129.1 ms | 5927.4 ms | 31883.1 ms | 34937.2 ms | 0 | 151.5 ms | 101 MB | | tool | pyox-agents | 1 | 145 | 3.2 ms | 6.0 ms | 6.7 ms | 6.4 ms | 8.1 ms | 11.2 ms | 0 | 6.2 ms | 55 MB | | tool | pydantic-ai | 1 | 30 | 19.9 ms | 29.9 ms | 33.1 ms | 31.8 ms | 38.2 ms | 47.1 ms | 0 | 31.1 ms | 107 MB | | tool | openai-agents | 1 | 44 | 19.7 ms | 31.3 ms | 22.7 ms | 21.6 ms | 28.9 ms | 34.0 ms | 0 | 20.8 ms | 135 MB | | tool | agent-framework | 1 | 46 | 12.3 ms | 19.5 ms | 21.6 ms | 20.4 ms | 26.1 ms | 33.4 ms | 0 | 20.1 ms | 93 MB | | tool | langgraph | 1 | 36 | 23.7 ms | 37.1 ms | 27.3 ms | 25.3 ms | 32.9 ms | 39.7 ms | 0 | 24.4 ms | 120 MB | | tool | langchain | 1 | 40 | 22.7 ms | 33.5 ms | 25.0 ms | 23.9 ms | 30.8 ms | 35.4 ms | 0 | 23.2 ms | 122 MB | | tool | llama-index | 1 | 27 | 32.0 ms | 49.3 ms | 37.4 ms | 35.4 ms | 44.8 ms | 54.0 ms | 0 | 35.5 ms | 186 MB | | tool | agno | 1 | 49 | 12.3 ms | 21.2 ms | 20.2 ms | 18.9 ms | 25.7 ms | 30.9 ms | 0 | 18.8 ms | 97 MB | | tool | pyox-agents | 10 | 234 | 9.5 ms | 16.9 ms | 42.6 ms | 42.0 ms | 51.0 ms | 59.5 ms | 0 | 6.2 ms | 56 MB | | tool | pydantic-ai | 10 | 33 | 171.8 ms | 256.3 ms | 304.1 ms | 296.1 ms | 354.6 ms | 473.7 ms | 0 | 30.3 ms | 111 MB | | tool | openai-agents | 10 | 50 | 160.5 ms | 264.7 ms | 199.2 ms | 191.7 ms | 231.4 ms | 297.8 ms | 0 | 19.9 ms | 140 MB | | tool | agent-framework | 10 | 46 | 197.6 ms | 422.6 ms | 215.5 ms | 206.3 ms | 248.8 ms | 441.9 ms | 0 | 21.5 ms | 110 MB | | tool | langgraph | 10 | 42 | 167.2 ms | 236.5 ms | 240.2 ms | 237.4 ms | 271.9 ms | 312.3 ms | 0 | 23.9 ms | 122 MB | | tool | langchain | 10 | 46 | 190.5 ms | 325.6 ms | 218.8 ms | 212.6 ms | 254.8 ms | 343.9 ms | 0 | 21.6 ms | 124 MB | | tool | llama-index | 10 | 24 | 333.6 ms | 499.1 ms | 418.1 ms | 414.1 ms | 473.6 ms | 584.4 ms | 0 | 42.2 ms | 189 MB | | tool | agno | 10 | 56 | 166.4 ms | 253.7 ms | 176.9 ms | 173.2 ms | 201.0 ms | 260.1 ms | 0 | 17.6 ms | 98 MB | | tool | pyox-agents | 50 | 254 | 67.5 ms | 140.2 ms | 196.2 ms | 192.9 ms | 242.6 ms | 306.0 ms | 0 | 5.7 ms | 61 MB | | tool | pydantic-ai | 50 | 30 | 929.2 ms | 1160.7 ms | 1673.7 ms | 1673.6 ms | 1783.1 ms | 1890.5 ms | 0 | 33.4 ms | 127 MB | | tool | openai-agents | 50 | 48 | 771.4 ms | 1013.5 ms | 1038.2 ms | 1024.8 ms | 1134.9 ms | 1260.5 ms | 0 | 20.9 ms | 148 MB | | tool | agent-framework | 50 | 42 | 1105.6 ms | 2208.7 ms | 1163.0 ms | 1115.0 ms | 1571.5 ms | 2216.4 ms | 0 | 23.5 ms | 148 MB | | tool | langgraph | 50 | 42 | 813.2 ms | 1216.7 ms | 1175.9 ms | 1170.2 ms | 1258.8 ms | 1525.1 ms | 0 | 23.6 ms | 126 MB | | tool | langchain | 50 | 41 | 901.1 ms | 1276.0 ms | 1218.3 ms | 1211.0 ms | 1313.5 ms | 1537.1 ms | 0 | 24.4 ms | 127 MB | | tool | llama-index | 50 | 25 | 1552.7 ms | 2251.9 ms | 1956.2 ms | 1990.4 ms | 2130.2 ms | 2684.0 ms | 0 | 40.0 ms | 194 MB | | tool | agno | 50 | 54 | 900.9 ms | 1826.4 ms | 908.7 ms | 908.1 ms | 966.4 ms | 1835.0 ms | 0 | 18.2 ms | 102 MB | | tool | pyox-agents | 100 | 247 | 118.0 ms | 339.5 ms | 403.2 ms | 384.9 ms | 603.2 ms | 898.1 ms | 0 | 5.8 ms | 65 MB | | tool | pydantic-ai | 100 | 28 | 2042.2 ms | 2371.4 ms | 3504.6 ms | 3601.8 ms | 3656.3 ms | 3914.9 ms | 0 | 34.9 ms | 134 MB | | tool | openai-agents | 100 | 47 | 1626.7 ms | 2305.2 ms | 2097.8 ms | 2108.6 ms | 2417.1 ms | 2696.6 ms | 0 | 21.3 ms | 159 MB | | tool | agent-framework | 100 | 42 | 2152.0 ms | 8563.1 ms | 2277.1 ms | 2160.5 ms | 2723.8 ms | 8580.1 ms | 0 | 23.5 ms | 148 MB | | tool | langgraph | 100 | 39 | 1804.2 ms | 3065.2 ms | 2496.9 ms | 2535.7 ms | 2671.4 ms | 3999.3 ms | 0 | 25.2 ms | 133 MB | | tool | langchain | 100 | 41 | 1769.5 ms | 3465.5 ms | 2409.8 ms | 2393.6 ms | 2583.5 ms | 4149.1 ms | 0 | 24.1 ms | 133 MB | | tool | llama-index | 100 | 24 | 3261.9 ms | 5886.3 ms | 4065.0 ms | 4121.8 ms | 4444.7 ms | 6754.5 ms | 0 | 42.2 ms | 200 MB | | tool | agno | 100 | 56 | 1729.1 ms | 6242.5 ms | 1736.7 ms | 1735.9 ms | 1889.1 ms | 6249.5 ms | 0 | 17.7 ms | 107 MB |- One process is CPU-bound with every library, so throughput stops growing at about 10 users. pyox-agents gets 2 to 6 times more requests per second on streamed text and 4 to 10 times more with a tool call.
- With 100 users, pyox-agents starts every answer within about 50 ms and streams them side by side. The others make most answers wait 1 to 10 seconds before they start. Agno's p50 is as good as pyox-agents' on streamed text, but it runs a started answer to the end while the others wait, so its p90 and p99 are about 4 times worse. The average is the fairest single number.
Behind a Django streaming endpoint¶
The same load test with Django instead of FastAPI: an async view returning a StreamingHttpResponse, served by one
uvicorn process (ASGI), with async tools
(django_server.py). Only
pyox-agents, Agno (the fastest other library above) and pydantic-ai. Run it with uv run load_test.py --server django.
Python 3.13.18, one uvicorn process, Intel(R) Xeon(R) Processor @ 2.10GHz. Each measurement: 20s of load.
text: one long streamed answer¶
| Library | Req/s, 1 user | Req/s, 10 users | Req/s, 50 users | Req/s, 100 users | First byte p50, 100 users | Latency avg / p50 / p90 / p99, 100 users | CPU per request | Peak memory | Failed |
|---|---|---|---|---|---|---|---|---|---|
| pyox-agents | 14 | 20 | 19 | 19 | 41 ms | 5.1 / 4.9 / 7.1 / 9.2 s | 79.4 ms | 88 MB | 0 |
| agno | 11 | 11 | 11 | 11 | 3676 ms | 7.6 / 4.3 / 25.6 / 28.0 s | 87.1 ms | 115 MB | 0 |
| pydantic-ai | 6 | 6 | 5 | 4 | 584 ms | 22.1 / 22.2 / 22.4 / 22.4 s | 221.6 ms | 142 MB | 0 |
tool: one tool call, then the answer¶
| Library | Req/s, 1 user | Req/s, 10 users | Req/s, 50 users | Req/s, 100 users | First byte p50, 100 users | Latency avg / p50 / p90 / p99, 100 users | CPU per request | Peak memory | Failed |
|---|---|---|---|---|---|---|---|---|---|
| pyox-agents | 163 | 220 | 265 | 232 | 89 ms | 0.4 / 0.4 / 0.5 / 0.6 s | 5.4 ms | 68 MB | 0 |
| agno | 67 | 69 | 70 | 67 | 1445 ms | 1.5 / 1.4 / 1.6 / 5.7 s | 14.8 ms | 123 MB | 0 |
| pydantic-ai | 34 | 41 | 32 | 30 | 1798 ms | 3.2 / 3.4 / 3.6 / 3.8 s | 33.4 ms | 145 MB | 0 |
All the numbers
| Scenario | Library | Users | Requests/s | First byte p50 | p99 | Latency avg | p50 | p90 | p99 | Failed | Server CPU per request | Server peak memory | |---|---|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:| | text | pyox-agents | 1 | 14 | 4.4 ms | 7.8 ms | 68.8 ms | 67.5 ms | 81.2 ms | 93.9 ms | 0 | 76.4 ms | 55 MB | | text | agno | 1 | 11 | 7.7 ms | 13.0 ms | 91.4 ms | 86.4 ms | 107.0 ms | 141.8 ms | 0 | 88.6 ms | 102 MB | | text | pydantic-ai | 1 | 6 | 10.8 ms | 17.9 ms | 162.0 ms | 157.6 ms | 185.5 ms | 233.3 ms | 0 | 157.2 ms | 112 MB | | text | pyox-agents | 10 | 20 | 6.0 ms | 21.1 ms | 508.9 ms | 503.7 ms | 557.8 ms | 594.3 ms | 0 | 79.0 ms | 59 MB | | text | agno | 10 | 11 | 737.0 ms | 986.5 ms | 878.6 ms | 871.4 ms | 966.4 ms | 1102.5 ms | 0 | 87.3 ms | 104 MB | | text | pydantic-ai | 10 | 6 | 68.9 ms | 96.6 ms | 1630.1 ms | 1595.7 ms | 1857.9 ms | 1969.4 ms | 0 | 161.5 ms | 116 MB | | text | pyox-agents | 50 | 19 | 22.4 ms | 123.6 ms | 2514.5 ms | 2547.3 ms | 2806.0 ms | 3144.5 ms | 0 | 76.3 ms | 73 MB | | text | agno | 50 | 11 | 3037.4 ms | 4470.3 ms | 4197.0 ms | 4258.1 ms | 4924.7 ms | 4960.0 ms | 0 | 87.2 ms | 115 MB | | text | pydantic-ai | 50 | 5 | 376.5 ms | 481.6 ms | 9541.2 ms | 9846.8 ms | 9968.0 ms | 10118.9 ms | 0 | 198.7 ms | 129 MB | | text | pyox-agents | 100 | 19 | 40.7 ms | 316.1 ms | 5098.0 ms | 4860.6 ms | 7080.8 ms | 9206.9 ms | 0 | 79.4 ms | 88 MB | | text | agno | 100 | 11 | 3675.8 ms | 27865.6 ms | 7577.6 ms | 4344.8 ms | 25603.7 ms | 27962.0 ms | 0 | 87.1 ms | 115 MB | | text | pydantic-ai | 100 | 4 | 584.1 ms | 1202.4 ms | 22126.2 ms | 22168.9 ms | 22376.9 ms | 22390.5 ms | 0 | 221.6 ms | 142 MB | | tool | pyox-agents | 1 | 163 | 3.0 ms | 6.2 ms | 5.9 ms | 5.6 ms | 7.1 ms | 10.9 ms | 0 | 5.9 ms | 55 MB | | tool | agno | 1 | 67 | 9.9 ms | 16.9 ms | 14.7 ms | 13.8 ms | 17.4 ms | 24.0 ms | 0 | 14.0 ms | 103 MB | | tool | pydantic-ai | 1 | 34 | 18.8 ms | 28.3 ms | 28.8 ms | 27.7 ms | 34.1 ms | 42.9 ms | 0 | 26.9 ms | 113 MB | | tool | pyox-agents | 10 | 220 | 9.7 ms | 20.8 ms | 45.2 ms | 44.1 ms | 54.1 ms | 74.5 ms | 0 | 5.8 ms | 57 MB | | tool | agno | 10 | 69 | 135.9 ms | 224.6 ms | 144.7 ms | 140.4 ms | 164.3 ms | 229.7 ms | 0 | 14.5 ms | 105 MB | | tool | pydantic-ai | 10 | 41 | 137.5 ms | 304.4 ms | 245.8 ms | 236.6 ms | 289.1 ms | 415.7 ms | 0 | 24.6 ms | 119 MB | | tool | pyox-agents | 50 | 265 | 35.5 ms | 73.6 ms | 188.2 ms | 185.7 ms | 213.3 ms | 252.7 ms | 0 | 4.8 ms | 62 MB | | tool | agno | 50 | 70 | 698.0 ms | 1459.3 ms | 706.6 ms | 702.3 ms | 776.6 ms | 1463.5 ms | 0 | 14.4 ms | 114 MB | | tool | pydantic-ai | 50 | 32 | 853.7 ms | 1163.9 ms | 1556.1 ms | 1553.3 ms | 1656.4 ms | 1889.9 ms | 0 | 31.1 ms | 134 MB | | tool | pyox-agents | 100 | 232 | 89.2 ms | 169.8 ms | 427.2 ms | 422.9 ms | 500.2 ms | 589.4 ms | 0 | 5.4 ms | 68 MB | | tool | agno | 100 | 67 | 1445.1 ms | 5647.2 ms | 1451.3 ms | 1449.1 ms | 1625.5 ms | 5650.4 ms | 0 | 14.8 ms | 123 MB | | tool | pydantic-ai | 100 | 30 | 1798.0 ms | 2136.1 ms | 3248.9 ms | 3383.5 ms | 3619.7 ms | 3751.3 ms | 0 | 33.4 ms | 145 MB |- Same picture as FastAPI: pyox-agents serves 3.5 times more requests per second than Agno with a tool call (232 vs 67 with 100 users) and 8 times more than pydantic-ai, and about 2 times more than Agno on streamed text.
- With 100 users it starts answers within 40 to 90 ms; Agno and pydantic-ai take 0.6 to 3.7 s.
- Async tools run on the event loop. A sync tool (
@tool def ...) also runs on the loop and blocks it while it runs; mark slow ones@tool(run_in_thread=True)to run them withasyncio.to_thread.
Responses API over WebSocket¶
pyox-agents (Client(transport="websocket")) and the OpenAI Agents SDK (use_responses_websocket=True), each also
over HTTP, on a tool loop of 1, 5 or 20 calls, one per turn, against a fake Responses API
(responses_server.py).
A local server cannot reproduce OpenAI's per-connection response cache, where most of WebSocket mode's speedup
comes from, so this is each library's own overhead and the upload it saves: over WebSocket, turns after the first
send only the new tool result. The Agents SDK chains turns with auto_previous_response_id=True and runs one
request at a time per connection, so its concurrent runs wait for each other.
Python 3.13.17, Intel(R) Xeon(R) Processor @ 2.10GHz, the fake server, model delay 0 ms per turn. 500 runs per measurement, 100 at a time for throughput.
Latency p50 of one run, by number of tool calls (one per turn). Fastest first.
| Agent | tool_loop_1 |
tool_loop_5 |
tool_loop_20 |
Throughput, tool_loop_20 |
Upload per run, tool_loop_20 |
Connections, tool_loop_20 |
|---|---|---|---|---|---|---|
| pyox-agents websocket | 4.3 ms | 7.6 ms | 16.9 ms | 92 runs/s | 10.0 KB | 200 |
| pyox-agents http | 4.8 ms | 9.1 ms | 25.8 ms | 91 runs/s | 47.6 KB | 0 |
| openai-agents websocket | 12.0 ms | 33.2 ms | 147.1 ms | 7 runs/s | 12.4 KB | 1 |
| openai-agents http | 18.6 ms | 68.9 ms | 493.1 ms | 2 runs/s | 57.5 KB | 0 |
All the numbers
`tool_loop_1` | Agent | Latency p50 | Latency p95 | First text | CPU per run | Throughput | Failed | Peak memory | |---|--:|--:|--:|--:|--:|--:|--:| | pyox-agents websocket | 4.3 ms | 5.7 ms | 1.4 ms | 4.6 ms | 338 runs/s | 0 | 44 MB | | pyox-agents http | 4.8 ms | 6.5 ms | 2.0 ms | 4.8 ms | 357 runs/s | 0 | 36 MB | | openai-agents websocket | 12.0 ms | 17.5 ms | 10.8 ms | 11.9 ms | 75 runs/s | 0 | 157 MB | | openai-agents http | 18.6 ms | 24.8 ms | 17.0 ms | 18.3 ms | 50 runs/s | 0 | 168 MB | `tool_loop_5` | Agent | Latency p50 | Latency p95 | First text | CPU per run | Throughput | Failed | Peak memory | |---|--:|--:|--:|--:|--:|--:|--:| | pyox-agents websocket | 7.6 ms | 9.9 ms | 4.6 ms | 8.3 ms | 237 runs/s | 0 | 47 MB | | pyox-agents http | 9.1 ms | 12.0 ms | 6.3 ms | 9.1 ms | 213 runs/s | 0 | 37 MB | | openai-agents websocket | 33.2 ms | 44.6 ms | 30.5 ms | 32.2 ms | 30 runs/s | 0 | 171 MB | | openai-agents http | 68.9 ms | 92.0 ms | 65.3 ms | 67.8 ms | 14 runs/s | 0 | 167 MB | `tool_loop_20` | Agent | Latency p50 | Latency p95 | First text | CPU per run | Throughput | Failed | Peak memory | |---|--:|--:|--:|--:|--:|--:|--:| | pyox-agents websocket | 16.9 ms | 21.3 ms | 14.1 ms | 19.1 ms | 92 runs/s | 0 | 53 MB | | pyox-agents http | 25.8 ms | 30.8 ms | 23.0 ms | 24.6 ms | 91 runs/s | 0 | 45 MB | | openai-agents websocket | 147.1 ms | 187.7 ms | 142.3 ms | 138.3 ms | 7 runs/s | 0 | 199 MB | | openai-agents http | 493.1 ms | 582.5 ms | 487.7 ms | 480.2 ms | 2 runs/s | 0 | 174 MB |With the fake model waiting 10 ms before each answer:
Python 3.13.17, Intel(R) Xeon(R) Processor @ 2.10GHz, the fake server, model delay 10 ms per turn. 500 runs per measurement, 100 at a time for throughput.
Latency p50 of one run, by number of tool calls (one per turn). Fastest first.
| Agent | tool_loop_1 |
tool_loop_5 |
tool_loop_20 |
Throughput, tool_loop_20 |
Upload per run, tool_loop_20 |
Connections, tool_loop_20 |
|---|---|---|---|---|---|---|
| pyox-agents websocket | 25.8 ms | 70.8 ms | 244.0 ms | 81 runs/s | 10.0 KB | 241 |
| pyox-agents http | 26.2 ms | 72.5 ms | 251.2 ms | 79 runs/s | 47.6 KB | 0 |
| openai-agents websocket | 34.9 ms | 110.3 ms | 386.5 ms | 3 runs/s | 12.4 KB | 1 |
| openai-agents http | 43.7 ms | 142.5 ms | 834.3 ms | 2 runs/s | 57.5 KB | 0 |
All the numbers
`tool_loop_1` | Agent | Latency p50 | Latency p95 | First text | CPU per run | Throughput | Failed | Peak memory | |---|--:|--:|--:|--:|--:|--:|--:| | pyox-agents websocket | 25.8 ms | 30.1 ms | 22.4 ms | 5.1 ms | 351 runs/s | 0 | 46 MB | | pyox-agents http | 26.2 ms | 28.9 ms | 22.9 ms | 5.3 ms | 347 runs/s | 0 | 36 MB | | openai-agents websocket | 34.9 ms | 40.0 ms | 31.9 ms | 13.6 ms | 32 runs/s | 0 | 157 MB | | openai-agents http | 43.7 ms | 49.9 ms | 38.6 ms | 22.2 ms | 47 runs/s | 0 | 166 MB | `tool_loop_5` | Agent | Latency p50 | Latency p95 | First text | CPU per run | Throughput | Failed | Peak memory | |---|--:|--:|--:|--:|--:|--:|--:| | pyox-agents websocket | 70.8 ms | 74.7 ms | 67.3 ms | 9.5 ms | 233 runs/s | 0 | 48 MB | | pyox-agents http | 72.5 ms | 76.4 ms | 69.1 ms | 10.3 ms | 221 runs/s | 0 | 38 MB | | openai-agents websocket | 110.3 ms | 123.1 ms | 105.5 ms | 44.1 ms | 11 runs/s | 0 | 171 MB | | openai-agents http | 142.5 ms | 166.2 ms | 137.3 ms | 79.9 ms | 12 runs/s | 0 | 166 MB | `tool_loop_20` | Agent | Latency p50 | Latency p95 | First text | CPU per run | Throughput | Failed | Peak memory | |---|--:|--:|--:|--:|--:|--:|--:| | pyox-agents websocket | 244.0 ms | 255.7 ms | 240.1 ms | 26.8 ms | 81 runs/s | 0 | 53 MB | | pyox-agents http | 251.2 ms | 265.1 ms | 247.6 ms | 31.1 ms | 79 runs/s | 0 | 45 MB | | openai-agents websocket | 386.5 ms | 415.9 ms | 380.1 ms | 153.6 ms | 3 runs/s | 0 | 199 MB | | openai-agents http | 834.3 ms | 931.7 ms | 825.4 ms | 598.0 ms | 2 runs/s | 0 | 174 MB |How it is measured, and how to run the benchmarks yourself, is in
benchmarks/.