Skip to content

Performance

agentl ships a small, reproducible benchmark suite in benchmarks/. It uses only local resources: a temp directory, SQLite, an optional Postgres, a local HTTP server and a fake LLM provider. No real LLM or external network is called.

python -m benchmarks            # full run
python -m benchmarks --quick    # 10% of the data, for CI
python -m benchmarks --only csv.read transforms --json out.json
AGENTL_TEST_PG_DSN=postgresql://user@127.0.0.1:5432/db python -m benchmarks   # include Postgres

CI runs --quick on every tag and weekly, and uploads the JSON as an artifact.

Results

agentl 0.6.0, Python 3.13, x86_64, 4 CPUs (a shared cloud container). Your numbers will differ: compare runs on the same machine, not across machines.

Scenario Result What it measures
import 158 ms over bare Python import agentl, median of 5 fresh processes. No optional module (pandas, requests, boto3, ...) is loaded
csv.read 565k records/s Read 200k rows from CSV (6.6 MB), median of 3
transforms 812k records/s filter + dedupe + select on 200k records, median of 3
sqlite.write 721k records/s Write 100k records to SQLite, median of 3
postgres.upsert 75k/s insert, 67k/s update Upsert 100k records twice (first inserts, then updates)
http.get fan-out 461 req/s at concurrency 16 (5.2x vs 1) 1000 requests to a local server with 10 ms latency
agent step 1,471 records/s at concurrency 32 500 records, fake provider with 20 ms latency. agentl overhead is about 0.3 ms per record
agent cache re-run 31x faster, 0 tokens Same 500 records again: every call is a cache hit
chunked +790 MB all at once, +61 MB with chunk_size: 50000 (and 2x faster) 1M rows (70 MB CSV) through csv.read → select → sqlite.write; peak RSS growth of a fresh process
arrow csv-pandas-parquet 6.47 s with dicts, 0.63 s with Arrow (10.3x), identical Parquet 500k-row CSV through csv.read → @dataframe → parquet.write
arrow parquet-postgres 4.69 s INSERT, 0.21 s COPY from Arrow (22x), identical rows 200k rows, parquet.read → postgres.write; needs AGENTL_TEST_PG_DSN
parallel steps 1.08 s one at a time, 0.51 s in parallel Two independent 0.5 s sources and a step on one of them, max_parallel_steps 1 vs 4
agent cascade 0.69 USD with one strong model, 0.13 with llm: [cheap, strong] (5.2x less) 500 records, 350 distinct (both deduplicated to 350); the cheap model fails one record in ten, which goes to the strong one
agent call shapes 0.99 USD default, 0.45 with prompt caching, 0.42 with records_per_call: 20, 0.50 in batch mode 500 records, fake provider with a 400-token fixed prefix (see below)

With agentl and without it

python -m benchmarks --compare runs the same jobs written three ways: with agentl, in plain Python as a careful developer would write it, and with pandas where it fits. Each variant runs in a fresh process and the run fails unless every variant wrote the same data. The plain variants do not import agentl. Imports and setup happen before the measurement starts.

python -m benchmarks --compare            # full run, median of 3
python -m benchmarks --compare --quick    # 10% of the data, one run

agentl 0.6.1, Python 3.13, x86_64, 4 CPUs (a shared cloud container). Peak MB is the growth of the process peak RSS during the job; read and written MB are bytes through system calls (/proc/self/io, Linux only), so they include the run state agentl keeps in SQLite.

CSV → drop empty notes → keep 3 columns → SQLite, 1M rows (70 MB CSV)

Variant Seconds CPU s Peak MB Read MB Written MB
agentl, chunk_size: 50000 5.33 5.13 60 67.2 33.3
plain Python, csv + sqlite3 in batches 3.01 2.90 0 65.7 32.9
pandas, read_csv + to_sql 4.00 3.88 156 65.8 32.9

One LLM call per record, 500 records, fake provider with 20 ms latency

Variant Seconds CPU s Peak MB Read MB Written MB
agentl, concurrency: 8 1.37 0.14 0 0.9 0.4
agentl, second run (answers from the cache) 0.03 0.02 0 0.1 0.3
plain Python, a loop 10.10 0.03 0 0.0 0.0
plain Python, 8 threads 1.28 0.05 0 0.0 0.0

CSV → pandas function → Parquet, 500k rows

Variant Seconds CPU s Peak MB Read MB Written MB
agentl, @dataframe with the Arrow path 0.63 0.97 0 32.0 2.9
pandas only 0.97 0.96 0 20.1 2.5

What this says:

  • On plain record work agentl costs time: about 1.8x the hand-written loop and 1.3x pandas on the CSV job, for streaming in bounded memory (60 MB against 156 MB for pandas), run state, retries and the event log. A tight script that streams by hand is faster and uses less memory.
  • On LLM work the time is the provider's. agentl matches 8 hand-written threads and is 7x faster than a loop; the gain over hand-written code is the cache (a re-run of unchanged records makes no calls), budgets and spend tracking, not raw speed.
  • With pandas, the Arrow path reads CSV with pyarrow and was faster than pd.read_csv here; it reads more bytes because of the run state.
  • Peak MB of 0 means the job did not grow the process past what imports and setup had used.

Call shapes

agent call shapes runs the same 500 records four ways (concurrency 8, price 3 / 15 / 0.30 per million input / output / cached input):

Calls Tokens Cached Cost (USD) Seconds
default 500 250,000 0 0.99 1.3
prompt caching 500 250,000 199,600 0.45 1.3
records_per_call: 20 25 60,000 0 0.42 0.1
mode: batch (price.batch: 0.5) 500 250,000 0 0.50 n/a

The fake provider charges a 400-token prefix per call plus 60 input and 40 output tokens per record, and answers every call in 20 ms. That makes the token and cost columns a model of the arithmetic, not a measurement of a real API. In particular the seconds for records_per_call are optimistic: a real model takes longer to write 20 answers than one. Batch mode has no meaningful time here: the fake answers at once, a real batch takes minutes to hours. A run against a real API has not been done yet.

Caveats

  • The HTTP fan-out is likely limited by the test server (Python's http.server), not by agentl. A real API with more capacity may scale further; this was not measured.
  • The agent numbers isolate agentl's overhead. With a real provider, throughput is set by the provider's latency and rate limits, so use concurrency to the level your plan allows.
  • Each scenario runs once per invocation (micro scenarios take the median of 3). Expect noise of around 10% between runs on shared machines.

Footprint

The core install depends only on pyyaml and pydantic. Everything else is an extra, imported only when a step uses it. tests/test_footprint.py fails if a new core dependency appears or if import agentl starts loading a heavy module.