Compress huge LLM context into dense intermediate representations. Provider-agnostic. Norwegian first-class.
Project description
narratoflow
Tags: llm · prompt-compression · token-optimization · cost-reduction · anthropic · openai · ollama · claude · gpt · pydantic · python · multilingual · spacy · async · rag · narrative-generation · context-window · apache-2.0
Compress huge LLM input context into dense intermediate representations. Pay fewer tokens, keep the meaning.
Docs: https://Mrrobi.github.io/narratoflow/ · PyPI: https://pypi.org/project/narratoflow/ · Source: https://github.com/Mrrobi/narratoflow
narratoflow (PyPI name; import as narrato) is an open-source Python library (Apache-2.0) for shrinking long source text before sending it to an LLM. It targets any workload where the input dwarfs the output and tokens are the dominant cost — RAG retrieval contexts, narrative generation, transcript summarisation, long-document QA.
The library has a generic, language- and domain-neutral core. Common starting points ship as named profiles (rag-en, narrative-no, news-en, …) so you do not have to choose every argument up front.
Highlights
- 43% token reduction on a real Norwegian narrative sample (gpt-4o-mini extractor → gpt-4o target), with 8/10 quality from an LLM judge
- 3 providers out of the box — Anthropic, OpenAI, Ollama (local models, no API key). Bring your own via a 3-method Protocol.
- Async API —
Compressor.acompress(...)runs chunked extraction concurrently viaasyncio.gather - Layered design — pick free deterministic layers, an LLM-backed semantic layer, or both
- Schema-driven — define a Pydantic model, get a dense JSON payload in return; 5 presets built in (
narrative,qa,interview,dialogue,news) - Long-document ready — automatic chunked map-reduce extraction with overlap-aware merging
- Anthropic prompt caching — opt-in via
Compressor(cache=True); OpenAI's automatic cache is reported on the response - 12 bundled languages — stopwords for en, no, de, fr, es, it, pt, nl, sv, da, fi, pl; auto-detect via
source_lang="auto" - Optional spaCy preprocessing — POS-aware token stripping that keeps named entities verbatim
- Typed (PEP 561) —
py.typedmarker; works directly with mypy / pyright / IDEs - Named profiles —
Compressor.from_profile("rag-en")for one-line setup; register your own uvready — pure-PEP-621 hatchling package;uv add narratoflow,uv build,uvxall work
Why
LLM input is priced per token, and a long source document — say a 20-page transcript that feeds a 200-word summary — burns most of the budget before the model has written anything.
narrato lets you trade a tiny bit of fidelity for a large reduction in input tokens by passing your downstream LLM a dense, machine-friendly representation instead of the raw text. The intermediate representation does not need to be human-readable. It just needs to be:
- Cheap to produce.
- Decodable by the downstream LLM into a faithful output.
- Smaller in tokens than the original.
Architecture
┌──────────────────────────────────────────────┐
raw text ──▶ │ L1 preprocess (deterministic, free) │
│ L2 codebook (deterministic, free) │
│ L3 semantic extract (small LLM call) │
│ L4 learned encoder (planned, v0.6+) │
└──────────────────────────────────────────────┘
│
▼
CompressionResult
(payload + legend + stats)
│
▼
┌──────────────────────────────────────────────┐
│ Decoder.unpack_prompt() │
│ → ready-to-send prompt for downstream LLM │
└──────────────────────────────────────────────┘
Pick which layers run for each call. Free layers stack with paid layers.
Install
pip install narratoflow
# or
uv add narratoflow
Optional extras:
pip install "narratoflow[lang]" # langdetect, for source_lang="auto"
pip install "narratoflow[nlp]" # spaCy, for POS-aware preprocessing
pip install "narratoflow[dev]" # pytest, ruff, mypy
pip install "narratoflow[docs]" # mkdocs-material to build the site
pip install "narratoflow[benchmark]" # rich + tabulate for the bench CLI
Set credentials for the provider you use:
export ANTHROPIC_API_KEY=sk-ant-...
export OPENAI_API_KEY=sk-...
# Ollama: no key — just `ollama pull <model>` and start the daemon.
Quick start
Using a named profile (recommended)
from narrato import Compressor
c = Compressor.from_profile("rag-en", provider="anthropic")
result = c.compress(long_source_text)
print(result.stats)
# {'input_tokens': 8421, 'output_tokens': 1102, 'ratio': 0.131, ...}
Run narratoflow profiles to list all built-in profiles, or register your own:
from narrato import Compressor, Profile, register_profile
register_profile(Profile(
name="legal-en",
description="English legal documents — chunked + cached",
source_lang="en",
schema="qa",
chunked=True,
chunk_chars=6000,
extra={"cache": True},
))
c = Compressor.from_profile("legal-en", provider="anthropic")
Async + concurrent chunked extraction
import asyncio
from narrato import Compressor
c = Compressor.from_profile("long-en", provider="openai")
result = asyncio.run(c.acompress(very_long_document, concurrency=8))
Local models via Ollama
from narrato import Compressor
c = Compressor.from_profile(
"rag-en",
provider="ollama",
extractor_model="llama3",
target_model="llama3",
)
result = c.compress(text)
Auto-detect language
c = Compressor.from_profile("rag-en", provider="anthropic")
c.source_lang = "auto" # langdetect if installed, heuristic otherwise
result = c.compress(any_language_document)
print(result.stats["resolved_lang"]) # 'en' / 'no' / 'de' / ...
Explicit construction
from narrato import Compressor, Decoder
c = Compressor(
source_lang="no",
provider="anthropic",
extractor_model="claude-haiku-4-5-20251001",
target_model="claude-opus-4-7",
layers=["preprocess", "codebook", "extract"],
schema="narrative",
)
result = c.compress(long_norwegian_text)
prompt = Decoder.unpack_prompt(
result,
instruction="Skriv en kort fortelling basert på faktene over.",
)
# Send `prompt` to your target LLM.
CLI
narratoflow profiles
narratoflow schemas
narratoflow compress doc.txt --profile rag-en --out compressed.json
narratoflow eval doc.txt --target-task "Write a 200-word summary." --profile rag-en
The eval command reports tokens_in, tokens_out, ratio, estimated cost savings, and an LLM-judge quality score (skip with --skip-quality).
Layer reference
| Layer | What it does | Cost | Loss |
|---|---|---|---|
preprocess |
Whitespace/punct normalize, near-duplicate sentence dedupe, stopword or spaCy-POS stripping | free | tiny |
codebook |
Frequent phrase → short §x code, emit legend; tokenizer-aware savings |
free | none (with legend) |
extract |
Small LLM extracts schema-conformant facts; chunked + concurrent for long docs | cheap LLM call | lossy by design |
learned (v0.6+) |
Fine-tuned encoder produces dense codes | one-time train | tunable |
Schemas
Schemas tell the extractor what to keep. Built-in presets:
| preset | for |
|---|---|
narrative |
story / fiction. Characters, setting, ordered events, themes, tone, verbatim quotes. |
qa |
fact extraction / RAG. Summary, entities, dates, claims. |
interview |
interview / transcript. Interviewer + interviewee, ordered turns, key points, sentiment. |
dialogue |
scripted / fictional dialogue. Participants, setting, ordered lines, arc, notable quotes. |
news |
news article. Headline, lede, 5W1H (who/what/when/where/why/how), sources, quotes. |
Define your own — any Pydantic v2 BaseModel:
from pydantic import BaseModel, Field
from narrato import Compressor
class MyFacts(BaseModel):
summary: str
speakers: list[str] = Field(default_factory=list)
key_dates: list[str] = Field(default_factory=list)
c = Compressor(schema=MyFacts, provider="anthropic")
Providers
| provider | sync | async | JSON mode | prompt cache |
|---|---|---|---|---|
| Anthropic | ✅ | ✅ | tool-use | opt-in (cache=True) |
| OpenAI | ✅ | ✅ | response_format JSON schema |
automatic (≥1024 tok), reported on cached_input_tokens |
| Ollama | ✅ | ✅ | format=json + schema reminder |
n/a |
| Mock (tests) | ✅ | ✅ | canned payloads | n/a |
Bring your own — implement complete + complete_json (and optionally acomplete*):
from narrato.providers import Provider, ProviderResponse
class MyProvider:
name = "myco"
def complete(self, system, user, model, max_tokens=2048, temperature=0.0):
text = call_my_api(system, user, model)
return ProviderResponse(text=text, input_tokens=0, output_tokens=0, model=model)
def complete_json(self, system, user, model, schema=None, max_tokens=2048, temperature=0.0):
...
c = Compressor(provider=MyProvider(), ...)
Documentation
Full docs are at https://Mrrobi.github.io/narratoflow/. Highlights:
- Install — pip / uv / source / build
- Quickstart
- Profiles — bundled presets and custom registration
- Architecture — all layers in depth
- Schemas
- Providers — capability matrix
- Async API
- Language detection —
source_lang="auto" - spaCy integration
- Type checking
- Benchmark
- API reference
- Roadmap
Roadmap
- v0.1 — layered preprocess + codebook + schema extract, Anthropic + OpenAI, CLI, eval harness
- v0.2 — chunked map-reduce extraction, Anthropic prompt caching, 3 new schema presets, MockProvider, tokenizer-aware codebook
- v0.3 — generic-core refactor, named profiles, 12-language stopword bundle, CLI
--profile - v0.4 — Ollama provider, async API, OpenAI prompt-cache reporting,
py.typed,uv buildverified, expanded docs - v0.5 — optional spaCy preprocessing, language auto-detect, mypy CI job
- v0.6 — learned encoder R&D, HF Spaces demo, multilingual benchmark corpora, more language stopword sets
See CHANGELOG.md for full release notes.
Contributing
PRs welcome. Bring your own benchmark.
Local dev:
git clone https://github.com/Mrrobi/narratoflow.git
cd narratoflow
pip install -e ".[dev]"
pytest -q
mypy narrato
ruff check narrato tests
mkdocs serve # http://127.0.0.1:8000
License
Apache-2.0. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file narratoflow-0.5.1.tar.gz.
File metadata
- Download URL: narratoflow-0.5.1.tar.gz
- Upload date:
- Size: 80.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
56725e9c7504942e75e455a654fa7f6f6bb23bc020bde3590eb81e4b247f7b03
|
|
| MD5 |
415afc8315ce55b6bc5b568c079b0379
|
|
| BLAKE2b-256 |
5b0d9b44e138d88971b196007d353a0ceabfe22bbf5d54d145cb99d061a7dc23
|
Provenance
The following attestation bundles were made for narratoflow-0.5.1.tar.gz:
Publisher:
release.yml on Mrrobi/narratoflow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
narratoflow-0.5.1.tar.gz -
Subject digest:
56725e9c7504942e75e455a654fa7f6f6bb23bc020bde3590eb81e4b247f7b03 - Sigstore transparency entry: 1626793065
- Sigstore integration time:
-
Permalink:
Mrrobi/narratoflow@d7d870568df7f3cbb1ebfa2243420e865554b67b -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/Mrrobi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@d7d870568df7f3cbb1ebfa2243420e865554b67b -
Trigger Event:
release
-
Statement type:
File details
Details for the file narratoflow-0.5.1-py3-none-any.whl.
File metadata
- Download URL: narratoflow-0.5.1-py3-none-any.whl
- Upload date:
- Size: 52.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fded9fd753e094c3c278cf1035efd513ea176645e0dec91fa94a700c8af40ea5
|
|
| MD5 |
b6c1952ff20b3929ecfbadf735d17aab
|
|
| BLAKE2b-256 |
0b0f14e153ff1d7b8a260f9f0522c47fc1b008ac480561e581371e9baa908a2e
|
Provenance
The following attestation bundles were made for narratoflow-0.5.1-py3-none-any.whl:
Publisher:
release.yml on Mrrobi/narratoflow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
narratoflow-0.5.1-py3-none-any.whl -
Subject digest:
fded9fd753e094c3c278cf1035efd513ea176645e0dec91fa94a700c8af40ea5 - Sigstore transparency entry: 1626793099
- Sigstore integration time:
-
Permalink:
Mrrobi/narratoflow@d7d870568df7f3cbb1ebfa2243420e865554b67b -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/Mrrobi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@d7d870568df7f3cbb1ebfa2243420e865554b67b -
Trigger Event:
release
-
Statement type: