# token_usage_analysis

Estimate the token cost of **cognee persistent memory** versus **full-context
prompting**, from a few representative chunks of any text.

This is a follow-up to the earlier token-cost probe, which measured that cost on
a single synthetic corpus. Here we characterize how it varies with the input:
running the same measurement across a spectrum of real text — fiction, news,
encyclopedic — and across chunk sizes, so the break-even can be read for text
that resembles a given workload rather than one fixed corpus.

The measurement centers on ingestion cost, which is dominated by graph extraction.
Its output-tokens-per-input-token ratio scales with how densely entities and
relations are packed into the text, and that ratio in turn drives the break-even.
Running it over a density spectrum shows the two moving together — a small,
concrete extension of the note's cost model to a wider range of inputs.

## The cost model

Two strategies answer the same repeated queries over a fixed corpus. Each one's
cumulative token cost is a straight line in the number of queries:

```
full-context:  queries × (corpus_tokens + query_overhead)
cognee:        ingestion_tokens + queries × retrieved_context
```

- `ingestion_tokens` is the one-time cost of `cognee.remember()` — summarizing and
  graph-extracting every chunk. We measure it on a few representative chunks and
  scale by the corpus size: `ingestion_tokens = multiplier × corpus_tokens`, where
  `multiplier` is measured ingestion tokens per content token.
- `retrieved_context` is the (roughly constant) context `cognee.recall()` packs
  per query.

Reading off this model, each **reduction milestone** answers: after how many
repeated queries does the cumulative full-context cost become `factor`× the
cumulative cognee cost, including cognee's one-time ingestion cost?

```
queries(factor) =
    factor × ingestion_tokens
    / ((corpus_tokens + query_overhead) − factor × retrieved_context)
```

Parity, the cross-over where both cumulative costs are equal, is `factor = 1`.
For example, the `7` milestone is the query count where full-context has spent
7× as many input/context tokens as cognee, so cognee uses 7× fewer input tokens
at that point. No milestone is privileged — break-even is just one of them.

Token usage is read from the **real LLM responses** (prompt + completion), so the
instruction wrapper and the Pydantic graph schema are included with no estimated
constants.

## Results: the density spectrum

Measured with `openai/gpt-5-mini`, 3 sampled chunks per corpus,
`retrieved_context = 1118`, `query_overhead = 32`. Each corpus is ~10–12k tokens.
We ran the whole spectrum at **two chunk sizes** to show that chunk size, not just
density, moves the numbers: cognee's `8191` default and `4095` (half). Values are
`graph output ÷ input` / `ingestion multiplier` / `parity (queries)`.

| Tier | Source | Corpus tokens | at chunk 8191 | at chunk 4095 |
| --- | --- | ---: | :--- | :--- |
| Fiction | *War and Peace* excerpt | 10,504 | 2.8× / 5.7× / ~6 | 4.5× / 8.0× / ~9 |
| News | Wikinews (23 articles) | 10,161 | 4.2× / 7.3× / ~8 | 5.9× / 9.8× / ~11 |
| Encyclopedic | Wikipedia — *Apollo 11* | 10,037 | 3.4× / 6.5× / ~7 | 5.9× / 10.3× / ~12 |
| Dense synthetic | benchmark `people_raw` | 12,009 | 9.3× / 12.0× / ~13 | 10.1× / 14.0× / ~15 |

Three things stand out:

- **The ingestion multiplier tracks the graph-output ratio**, and the break-even
  in queries tracks the multiplier — exactly as the cost model predicts. The dense
  synthetic corpus produces ~9–10× graph output per input token; ordinary prose
  produces ~3–6×.
- **Realistic text is clustered and lower.** Fiction, news, and encyclopedic prose
  break even in the single digits to low teens; the dense synthetic corpus is the
  outlier. The technical note's ~23–26 queries reflects that dense outlier (and
  larger ingestion chunks), not typical input.
- **Chunk size matters too.** Halving the chunk size raises every ratio and
  multiplier, because smaller chunks carry proportionally more fixed prompt/schema
  overhead per content token. Same corpus, same model — different break-even.

Caveat: these are single runs, and graph extraction is not fully deterministic —
its output length varies between runs (most on the dense corpus, where a repeat of
the 8191 run moved the multiplier by several points). Treat the numbers as
representative, not exact; the ordering is the robust result.

Per-tier cumulative-cost plots and full per-chunk JSON are in
`results/chunk_8191/` and `results/chunk_4095/`.

## Usage

Install the project with the eval dependencies, then run the commands from this
directory:

```bash
uv sync --dev --all-extras
cd cognee/eval_framework/token_usage_analysis
```

The script loads the repo-root `.env`, so make sure it contains a working
`LLM_PROVIDER`, `LLM_MODEL`, and API key. If `--llm-models` is omitted, the
configured `LLM_MODEL` is used.

```bash
# one representative file (the input is treated as the corpus)
uv run python analyze.py --file data/wikipedia_article.txt --plot

# a folder of .txt files, pooled then sampled
uv run python analyze.py --dir some_corpus/ --out report.json

# a single representative chunk (corpus size must be given explicitly)
uv run python analyze.py --text "$(cat one_chunk.txt)" --corpus-tokens 200000
```

The script chunks the input with cognee's `TextChunker`, samples a few chunks
(`--samples`, default 3), measures each through `cognee.remember()`'s summary and
graph-extraction calls, and writes a JSON report. `--plot` additionally writes the
cumulative-cost figure (`matplotlib` is included in the `evals` extra).

Key options:

| Flag | Default | Meaning |
| --- | --- | --- |
| `--file` / `--dir` / `--text` | — | input form (exactly one) |
| `--samples` | 3 | chunks to measure |
| `--llm-models` | the `.env` model | comma list; runs each, switching cognee's config |
| `--corpus-tokens` | token count of the input | corpus size for the comparison (required with `--text`) |
| `--retrieved-context` | 1118 | recall context per query |
| `--query-overhead` | 32 | instruction + question tokens per query |
| `--reduction-factors` | `1,2,7,10` | milestones to report (1 = parity) |
| `--plot` / `--plot-dir` | off / `.` | also write the cross-over figure |

## Reproducing the spectrum

The spectrum is four single-input runs per chunk size, collated by hand. Each
chunk size gets its own results folder:

```bash
for size in 8191 4095; do
  for tier in war_and_peace wikinews wikipedia dense; do
    uv run python analyze.py --file "data/${tier}"*.txt --max-chunk-size "$size" \
      --out "results/chunk_${size}/${tier}.json" --plot --plot-dir "results/chunk_${size}"
  done
done
```

`--max-chunk-size` defaults to `4095`; pass `8191` for cognee's default.

`--llm-models` runs only models you have keys for. To compare providers, add
`OPENAI_API_KEY` / `ANTHROPIC_API_KEY` to `.env`; the script switches
`LLM_PROVIDER`, `LLM_MODEL`, and `LLM_API_KEY` before each measurement. The runs
above used the configured `openai/gpt-5-mini`.

## Modules

`analyze.py` is pure orchestration; each step is one call into a focused module:

- `cli.py` — argument definitions and default resolution.
- `corpus.py` — input → sampled chunks (cognee `TextChunker`). No LLM, no math.
- `measure.py` — the only LLM/IO surface: run a chunk through cognee and record
  real token usage per model.
- `cost_model.py` — pure arithmetic: the two strategy cost objects, the average
  chunk, ingestion scaling, and reduction milestones.
- `report.py` — per-model orchestration and JSON assembly.
- `plot.py` — optional cumulative-cost figure (matplotlib, imported lazily).

## Assumptions and caveats

- **Constant retrieved context.** Recall context is treated as fixed per query
  (it grows only modestly with corpus size under a fixed `top_k`).
- **Embeddings excluded.** Only language-model tokens are counted; embeddings are
  computed locally.
- **Representative-chunk extrapolation.** Ingestion is scaled from the average of
  the sampled chunks, assuming the rest of the corpus has similar density.

## Data and attribution

The `data/` excerpts are the raw text that actually gets ingested:

- `war_and_peace_excerpt.txt` — *War and Peace*, Project Gutenberg eBook #2600
  (public domain).
- `wikipedia_article.txt` — *Apollo 11*, English Wikipedia
  (CC BY-SA 4.0). https://en.wikipedia.org/wiki/Apollo_11
- `wikinews_article.txt` — 23 recent English Wikinews articles, concatenated to a
  comparable corpus size (CC BY 2.5). Titles and URLs in `data/wikinews_sources.json`.
- `dense_synthetic.txt` — an excerpt of the benchmark `people_raw` corpus shipped
  in this repo.
