A small, self-contained, zero-cost benchmark that measures how much you can cut an LLM coding-agent's context cost by being smart about what you send.
It runs a 6-step coding task over a tiny fake repository twice:
| variant | strategy |
|---|---|
naive |
every step re-sends the FULL conversation history + FULL uncompressed repo, every time |
optimized |
context selection + history summarization + prompt-caching-friendly prompt structure |
It then reports real, measured numbers for both: input tokens (counted
locally with tiktoken), output tokens (reported by the free LLM), projected
USD cost at published paid rates, and task correctness - plus the % savings.
Real coding agents hold multi-step conversations. That means constantly re-feeding the context window with:
- the full repo or files,
- the full message history - which grows, and grows, and is re-sent every turn,
$ per tokeninput that is often the dominant cost line.
The naive agent ships everything every time: tokens grow ~quadratically with steps. The whole point of this repo is to quantify, honestly, how much an optimized context strategy saves - and to verify it does not sacrifice task correctness.
A fake repository with 3 small Python files (+1 reference doc):
fake_repo/
├── config.py # constants; TIMEOUT_SECONDS must be changed 30 -> 15
├── utils.py # contains a planted bug in uniq_preserving_order
├── processor.py # logic; summarize() semantics asked about
└── docs/STYLE.md # read-only project conventions (stable + cacheable)
The agent must complete 6 sequential steps:
| # | task | kind | graded how |
|---|---|---|---|
| 1 | Find & fix the bug in uniq_preserving_order |
edit | execute the returned function on 4 test cases |
| 2 | Add a chunked(items, n) generator to utils.py |
edit | execute it on chunk-size/edge cases |
| 3 | Change TIMEOUT_SECONDS 30 → 15 in config.py |
edit | parse the returned line == 15 |
| 4 | What does summarize([2, 4, 6]) return? |
question | parse dict == {"count":3, "total":12} |
| 5 | What is the current RETRY_DELAY_MS? |
question | parse int == 500 |
| 6 | What is len(list(chunked(range(10), 3)))? |
question | parse int == 4 (depends on step 2!) |
The model's code answers are written back to the variant's own copy of the repo, so later steps act on a genuinely evolved codebase - not a static dump. Correctness is graded per step from the model's actual output, plus an independent end-of-run smoke test that imports the edited files and checks the final repository state. Both variants must pass the same 6 steps; if the optimized one fails it is reported honestly as a failure.
Only the file(s) a step actually touches are included - and for two steps even
just the single function that answers them. The repo is small, but the same
ratio applies to real repos: an agent fixing a bug in cashier.py does not
need analytics.py, models.py and migrations/.
Why it saves tokens: token spend on input is proportional to what you send. Shipping 1 relevant file instead of 50 real files is a ~50× cut.
Raw history turns accumulate until they exceed the
SUMMARY_TOKEN_THRESHOLD (default 600 tokens); then the previous turns are
collapsed by the LLM itself into a 3-sentence summary ("fixed
uniq_preserving_order in utils.py; added chunked(...); TIMEOUT_SECONDS = 15").
Why it saves tokens: an O(n²) transcript becomes O(n) - a constant-sized block regardless of how many steps the agent has taken. The summary's own LLM call is counted transparently in the report (it is far cheaper than re-sending every raw turn on every remaining step).
The messages are built as a stable byte-identical prefix followed by a dynamic tail:
[system prompt] + [project conventions doc] + [ack]
^--------- constant every step -> cache-eligible -----------^
+ [summarized history] + [current task + relevant files]
The prefix reuses the exact same tokens on every request - precisely what
prompt caching matches on - so the provider can serve it at the discounted
cached-input rate (GPT-4o-mini: $0.075/M vs $0.15/M). The benchmark measures
the real cache-eligible prefix per request as the longest token-exact
common prefix between consecutive requests (see caching.py), using
tiktoken, and reports:
cache-eligible= raw repeated-prefix tokens,cache-qualified= the subset that would actually qualify, given OpenAI's documented minimum cacheable prefix length (default 1024 tokens,--cache-min-prefix).
Honest note: at this toy scale most prompts are below 1024 tokens, so
cache-qualifiedis often 0 - the code still shows the mechanics and the README explains how the same structure saves real money once the repeated context (docs, tool schemas, unchanged files) exceeds that threshold.cache-eligibleis reported separately so the structural gain is visible.
The naive build (NaiveContext) does [system] + [every previous turn verbatim, each already containing a full repo dump] + [current task + full repo again]. Input tokens grow journal-fashion every step. The optimized
build replaces whole-repo dumps with one relevant file and replaces raw
history with a tiny summary - while keeping a cacheable prefix that the naive
blob never isolates.
No paid API is used, ever. The model is resolved in this order:
- Groq free tier - if
GROQ_API_KEYis set (default modelllama-3.3-70b-versatile). - Google Gemini free tier - if
GEMINI_API_KEYis set (default modelgemini-2.0-flash). - Local Ollama - talks to
http://localhost:11434(default modelqwen2.5-coder:3b; falls back tollama3.1:8bstyle models if you prefer--model llama3.1:8b).
If a provider is unavailable at runtime the script prints a clear message and
tries the next free backend (then, if the current call fails mid-run, it
re-routes to another free backend). If all backends are unavailable it exits
with setup instructions: set GROQ_API_KEY / GEMINI_API_KEY, or run
ollama serve and ollama pull qwen2.5-coder:3b.
All input-token counts are computed locally with
tiktoken(o200k_base- the GPT-4o family tokenizer matching the priced model), so they are real and reproducible with zero spend. Output tokens come from the provider's usage report for the free completion.
The USD figures are projected costs: the real token counts from the run, multiplied by published paid pricing. The benchmark itself spends $0.00.
| model | input $/1M | cached input $/1M | output $/1M | source |
|---|---|---|---|---|
| gpt-4o-mini (primary) | 0.150 | 0.075 | 0.600 | https://developers.openai.com/api/docs/pricing (accessed 2026-08-30) |
| claude-haiku-4.5 (comparison) | 1.00 | 0.10 | 5.00 | https://platform.claude.com/docs/en/about-claude/pricing (accessed 2026-08-30) |
Cached-input pricing applies only to cache-qualified tokens (see above).
Prices were current on the access date; always re-check the vendor pricing
page before extrapolating to your workload.
git clone <this repo> && cd token_optimization_benchmark
python3 -m venv .venv && source .venv/bin/activate # optional, recommended
pip install -r requirements.txt # tiktoken + requests
# Zero-cost default: auto-detects Groq -> Gemini -> local Ollama
python benchmark.py
# Force the local model (recommended for a fully offline, 100% free run)
ollama serve & # if not already running
ollama pull qwen2.5-coder:3b # ~1.9 GB download, local+free
python benchmark.py --provider ollama
# Use a free-tier API instead
export GROQ_API_KEY=gsk_... # or GEMINI_API_KEY=...
python benchmark.py --provider groqUseful flags: --model, --summary-threshold, --cache-min-prefix,
--results-dir, --encoding.
Outputs:
- a results table on stdout,
results/results.json(full detail incl. per-step records),results/results.csv(variants summary),results/results_steps.csv(per-step token/cache/correctness rows).
token_optimization_benchmark/
├── benchmark.py # CLI entrypoint, orchestration
├── requirements.txt
├── README.md
├── fake_repo/ # seed codebase copied per variant
├── src/
│ ├── tokenizer.py # tiktoken counting (real, local)
│ ├── pricing.py # published pricing + projections
│ ├── llm.py # groq/gemini/ollama backends + fallback
│ ├── caching.py # exact cache-eligible prefix measurement
│ ├── scenario.py # 6 steps, graders, repo edits
│ ├── context_naive.py # the baseline strategy
│ ├── context_optimized.py # the three techniques
│ ├── runner.py # per-variant agent loop
│ └── report.py # table + JSON/CSV writers
└── results/ # generated outputs
The numbers below come from an actual run of benchmark.py - nothing is
hardcoded. Backend: local Ollama, qwen2.5-coder:3b (free, offline,
$0.00 spend). Tokens counted with tiktoken (o200k_base); output tokens are
the provider-reported counts from the real completions; USD is projected
at published gpt-4o-mini rates (cached-input discount applied only to
cache-qualified tokens, i.e. prefixes ≥ 1024 tokens).
Run date: 2026-08-30. results/results.json (and .csv) contain the same
data plus full per-step detail.
variant input tok output tok total tok cache-elig cache-qual cost(no-cach) cost(cache) steps overall smoke
---------------------------------------------------------------------------------------------------------------------------
naive 14697 171 14868 10545 9830 $0.002307 $0.001570 6/6 PASS yes
optimized 4374 230 4604 2039 0 $0.000794 $0.000794 6/6 PASS yes
SAVINGS (opt vs naive) 70.2% 69.0% 65.6% 49.4%
| metric | naive | optimized | reduction |
|---|---|---|---|
| total input tokens sent | 14,697 | 4,374 | 70.2% |
| total tokens (input + output) | 14,868 | 4,604 | 69.0% |
| projected cost, no cache credit | $0.002307 | $0.000794 | 65.6% |
| projected cost, with cache credit* | $0.001570 | $0.000794 | 49.4% |
| steps completed correctly | 6/6 | 6/6 | equality preserved |
| end-of-run repo smoke test | pass | pass | equality preserved |
* Naive gets a large cache credit here because its append-only transcript
re-sends an ever-longer verbatim prefix that providers match almost exactly.
That is a real property of prompt caching - but the naive agent still pays to
ship ~3.2× more tokens over the wire, carries stale file dumps into every
turn, and would blow past context limits far sooner. The optimized agent
never even has a ≥1024-token prefix to cache at this toy scale
(cache-eligible = 2,039 tokens measured), yet is still ~half the price.
TL;DR: on this 6-step run the optimized agent used 70% fewer input tokens and projected to cost ~49-66% less, while completing every step correctly - same correctness as the naive agent, no paid API involved.