Drop-in token & prompt optimization for OpenAI, Anthropic, and local LLM clients
Project description
TokenOpt SDK
Token and prompt optimization for LLM clients — a drop-in replacement for OpenAI/Anthropic (plus local models via Ollama/vLLM/llama.cpp) that automatically reduces token usage and cost.
- Cost reduction — prompt compression, summarization, model routing
- Context management — semantic caching, RAG chunk optimization
- Latency — cheap-model routing for simple queries, cache hits avoid API calls
- Quality preservation — quality-aware routing and similarity-based few-shot selection
All optimization is best-effort and fails open: an optimization error never blocks the underlying request.
5-Minute Quick Start
-
Clone the repository
git clone https://github.com/rohit-naik36/TokenOpt.git cd TokenOpt
-
Create a virtual environment (Python ≥ 3.10)
python -m venv .venv
Then activate it — macOS/Linux:
source .venv/bin/activate· Windows:.venv\Scripts\activate -
Install the package
pip install -e .
-
Install optional extras if needed — e.g. native Ollama support:
pip install -e ".[local]"
-
Set your API key (OpenAI for this quick start)
export OPENAI_API_KEY=sk-... # macOS/Linux # PowerShell: $env:OPENAI_API_KEY = "sk-..."
-
Run the example
python examples/quickstart.py -
Expected output — a readable metrics block (not raw JSON):
Request metrics: Model: gpt-4o-mini Cache hit: No Compression: attempted / no reduction (0 tokens, 0.0%) Tokens: 27 -> 27 (+7 output) Latency: total 566.4 ms | model 393.8 ms | TokenOpt overhead 172.6 ms Estimated cost: $0.000008 Response: TokenOpt is a drop-in SDK that optimizes LLM prompts... Aggregated metrics: Requests: 1 Cache hit rate: 0.0% ...What a successful run looks like: the script prints the response plus a per-request metrics block showing the model actually used (note the router may pick
gpt-4o-minifor simple queries), cache status, compression outcome, latency split, and estimated cost. If you see aRequest metrics:block and a response, TokenOpt is working.See
examples/for runnable value demonstrations — compression OFF vs ON, cache miss → hit, conversation summarization, model routing with reasons, and observability — plusdocs/UAT.mdfor the full acceptance checklist.
Installation
# From PyPI (regular use)
pip install tokenopt
Or clone + editable install (recommended for development):
git clone https://github.com/rohit-naik36/TokenOpt.git
cd TokenOpt
pip install -e .
Core install (OpenAI + Anthropic providers, routing, compression, summarization, in-memory caching, RAG, few-shot, metrics):
| Extras | Enables | Command |
|---|---|---|
| (none) | core as above | pip install tokenopt |
[local] |
native Ollama support | pip install "tokenopt[local]" |
[cache] |
Redis-backed semantic cache | pip install "tokenopt[cache]" |
[semantic] |
sentence-transformers embeddings | pip install "tokenopt[semantic]" |
[compression] |
LLMLingua compression | pip install "tokenopt[compression]" |
[all] |
everything above | pip install "tokenopt[all]" |
[dev] |
test/lint/type/audit tools | pip install -e ".[dev]" |
Requires Python ≥ 3.10.
Quick Start
The simplest possible drop-in. With OPENAI_API_KEY set in your environment:
# Before
from openai import OpenAI
client = OpenAI()
# After (drop-in replacement)
from tokenopt import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Long prompt..."}],
)
# Check savings
print(client.get_metrics_summary())
Run it end-to-end: python examples/quickstart.py.
Examples by provider
OpenAI
from tokenopt import OpenAI, TokenOptConfig
config = TokenOptConfig(
compression_ratio=0.5,
cache_enabled=True, # in-memory semantic cache
enable_routing=True, # route simple queries to cheaper models
)
client = OpenAI(config=config)
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Explain semantic caching in one sentence."}],
)
print(response.choices[0].message.content)
Full script: examples/openai_basic.py — sends the same long prompt through
a plain client vs a compressed client (~50% fewer tokens), then repeats the
call to show the cache miss → hit behavior.
Anthropic
from tokenopt import Anthropic
client = Anthropic() # reads ANTHROPIC_API_KEY
response = client.messages.create(
model="claude-3-5-haiku",
max_tokens=1024,
messages=[{"role": "user", "content": "Write a one-line haiku about caching."}],
)
print("".join(block.text for block in response.content))
Full script: examples/anthropic_basic.py — a 5-turn conversation that
exceeds the summarization threshold; older turns are condensed into a
summary instead of being sent verbatim.
Local models (Ollama, vLLM, llama.cpp, LM Studio)
from tokenopt import LocalClient
# Ollama (default URL uses the native `ollama` package; needs `[local]` extra)
client = LocalClient(model="llama3.1")
# Any OpenAI-compatible server: vLLM, llama.cpp, LM Studio (no extra needed)
client = LocalClient(model="qwen2.5", base_url="http://localhost:8000/v1")
response = client.chat.completions.create(
messages=[{"role": "user", "content": "Hello! Who are you?"}],
)
print(response.choices[0].message.content)
Full script: examples/local_basic.py — a multi-paragraph code-review prompt
compressed before the local model sees it, then a repeat request served from
the cache (no inference call). Cloud routing rules are auto-skipped for
local backends.
One client, any provider (factory)
from tokenopt import create_client, create_client_from_model
# Auto-detect provider from the model name
client = create_client_from_model("claude-3-5-haiku", api_key=...)
client = create_client_from_model("llama3.1", base_url="http://localhost:11434")
# Explicit provider + endpoint
local = create_client(
provider="local",
model="qwen2.5",
base_url="http://localhost:8000/v1",
)
Configuration
TokenOptConfig controls every optimization stage:
from tokenopt import OpenAI, RoutingRule, TokenOptConfig
config = TokenOptConfig(
compression_ratio=0.5, # target prompt size reduction
cache_enabled=True,
cache_ttl=3600,
enable_routing=True,
routing_rules=[ # custom routing (checked by priority)
RoutingRule(
name="math_tasks",
condition=lambda q, m: "equation" in q.lower(),
model="o1-mini",
priority=10,
),
],
enable_summarization=True,
summarization_threshold=8000, # token count that triggers summarization
rag_max_chunks=5,
fewshot_max_examples=3,
metrics_callback=my_callback, # per-request hook (see observability)
)
client = OpenAI(config=config)
Routing precedence (least surprise, Decision 24):
- an explicitly passed
model=is always honored — never overridden; - a matching rule picks its model (highest priority wins);
- custom rules that match nothing preserve the requested model
(
routing_reason = "preserved (no rule matched)"); - with no custom rules configured (the SDK's built-in default rules
don't count), the complexity heuristic picks the model
(
routing_reason = "complexity-based (low|medium|high)").
Run it: examples/pipeline_config.py — matching rules (o1-mini,
gpt-4o), preserved no-match requests on the default model, complexity
routing without custom rules, each with its routing_reason, plus
routing OFF vs ON; and examples/metrics_observability.py — every metric
annotated, the latency split explained, and the callback hook for your own
monitoring.
Supported providers & features
| Provider | Models | Backend |
|---|---|---|
| OpenAI | gpt-*, o1-*, o3-* |
official openai SDK |
| Anthropic | claude-* |
official anthropic SDK |
| Local | Ollama (http://localhost:11434), vLLM, llama.cpp, LM Studio (/v1) |
ollama package or openai SDK |
Features (all configurable via TokenOptConfig): model routing, prompt
compression, conversation summarization, semantic caching (in-memory or
Redis), RAG chunk optimization, few-shot selection, metrics + cost
estimation.
Project structure
tokenopt/
├── clients/ # OpenAI, Anthropic, LocalClient, base drop-in wrappers
├── pipeline/ # routing, compression, summarization, cache, RAG, few-shot
├── observability/ # metrics collection, cost estimation, structured logging
├── utils/ # token counting, truncation, embeddings
├── config.py # TokenOptConfig, RoutingRule, default config
└── factory.py # create_client / create_client_from_model / detect_provider
examples/ # runnable scripts for every primary workflow
tests/ # unit + integration suite (offline, deterministic)
See .ai/ARCHITECTURE.md for a compact overview and
.ai/KNOWLEDGE_BASE/ for the full Architecture Knowledge Base (system
design, request lifecycle, architectural contracts, and extension guide).
Troubleshooting / FAQ
ModuleNotFoundError: No module named 'ollama'
The Ollama backend needs the ollama package: pip install -e ".[local]".
Or point base_url at an OpenAI-compatible server (vLLM, llama.cpp, LM
Studio) — no extra needed.
AuthenticationError / 401 on OpenAI or Anthropic
Set the API key as an environment variable (OPENAI_API_KEY /
ANTHROPIC_API_KEY) or pass api_key= to the client. The SDK itself never
stores or logs keys.
My prompts aren't being compressed / saved tokens
- Optimization is fails open and conservative by default: on short
prompts the compressor only removes fillers/whitespace (truncation kicks
in above the
compression_ratiotoken budget), so savings are small — there is simply little to save. - Routing only applies when
enable_routing=Trueand (for Anthropic/Local) custom rules targeting that provider's models exist. - Summarization only triggers on multi-turn conversations above
summarization_thresholdtokens. - Verify what happened:
client.get_metrics_summary()showsoptimization_usageandavg_token_reduction_pctper request.
Second identical call didn't hit the cache
Cache keys include the model and conversation; different model values are
separate entries. In-memory cache lives on the client instance — create the
client once and reuse it.
I want to disable all optimization
TokenOptConfig(enable_compression=False, cache_enabled=False, enable_routing=False, enable_summarization=False, observability_enabled=False)
— the SDK then behaves as a thin passthrough.
Does TokenOpt work with streaming?
Local clients accept stream=True passthrough; the base OpenAI/Anthropic
flows are non-streaming in v0.1.0 (async/streaming is on the roadmap).
Do I need sentence-transformers for caching?
No — the in-memory cache falls back to deterministic hashing when the
sentence-transformers extra isn't installed. Install [semantic] for
near-duplicate (semantic) cache hits.
Python version support? Python ≥ 3.10; CI tests 3.10, 3.11, 3.12.
Development
make dev # or: pip install -e ".[dev]"
make test # pytest tests/
make lint # ruff check tokenopt tests
make typecheck # mypy tokenopt
make build # python -m build
make audit # pip-audit --path . (security scan)
See CONTRIBUTING.md for the Definition of Done gates, CI pipeline, and
branch protection recommendations.
Continuous Integration
Every push to main and every pull request runs the CI pipeline
(.github/workflows/ci.yml) — the project's single source of truth for
release readiness. It executes the DoD gates:
- Lint —
ruff check tokenopt tests+mypy tokenopt - Test —
pytest tests/on Python 3.10, 3.11, and 3.12, with the ≥80% coverage gate enforced by pytest itself - Package —
python -m build(sdist + wheel) plus a fresh-venv install andimport tokenoptsmoke test - Security —
pip-audit(dependency vulnerabilities) +gitleaks(secret scan of full git history) — seeSECURITY.md
Status
Pre-1.0 (v0.1.0): the public API is stabilizing but may still evolve. Optimization is best-effort and always fails open — an optimization error never blocks the underlying request. Feedback and contributions welcome via GitHub issues.
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 tokenopt-0.1.0.tar.gz.
File metadata
- Download URL: tokenopt-0.1.0.tar.gz
- Upload date:
- Size: 43.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
126422be4566e3bbb82cbdef2a758d259d5a1c490232c0705f38561188852d49
|
|
| MD5 |
427f2a8a005a811a919fcb94bc90a2fc
|
|
| BLAKE2b-256 |
75cc63bf91e2e7316c4b90599fac08e68b8e2f6617d5f2c3ec001b3500ae487b
|
Provenance
The following attestation bundles were made for tokenopt-0.1.0.tar.gz:
Publisher:
publish.yml on rohit-naik36/TokenOpt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tokenopt-0.1.0.tar.gz -
Subject digest:
126422be4566e3bbb82cbdef2a758d259d5a1c490232c0705f38561188852d49 - Sigstore transparency entry: 2318684871
- Sigstore integration time:
-
Permalink:
rohit-naik36/TokenOpt@bc4ccbd8e9778f68ee7ebe87761a3ca92b26cfe1 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/rohit-naik36
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bc4ccbd8e9778f68ee7ebe87761a3ca92b26cfe1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tokenopt-0.1.0-py3-none-any.whl.
File metadata
- Download URL: tokenopt-0.1.0-py3-none-any.whl
- Upload date:
- Size: 36.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4ac36f1e04d82910cdb181e79d0ec7cc97c83c754bf3541fe0eb4181955bd400
|
|
| MD5 |
b2cd8b210ac4de5130d3229e97ed35f2
|
|
| BLAKE2b-256 |
0d2eeeb876545d9c5696bb6ba1191989936c98c73aa032cfb49c5bf55d486d71
|
Provenance
The following attestation bundles were made for tokenopt-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on rohit-naik36/TokenOpt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tokenopt-0.1.0-py3-none-any.whl -
Subject digest:
4ac36f1e04d82910cdb181e79d0ec7cc97c83c754bf3541fe0eb4181955bd400 - Sigstore transparency entry: 2318684978
- Sigstore integration time:
-
Permalink:
rohit-naik36/TokenOpt@bc4ccbd8e9778f68ee7ebe87761a3ca92b26cfe1 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/rohit-naik36
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bc4ccbd8e9778f68ee7ebe87761a3ca92b26cfe1 -
Trigger Event:
push
-
Statement type: