Skip to content

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 each TextDelta event costs about 0.25 ms. With text_chunks=10 it 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 text is 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 with asyncio.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/.