Cache-aware prompt structure optimizer + LLM-as-judge eval + local cost/usage logger.
Stop paying for the same tokens twice. ContextOps reorders your prompt sections so stable content (system prompt, tools) sits at the top β and variable content (query, history) sits at the bottom β maximizing cache hit rate on Anthropic / OpenAI / DeepSeek / any provider that does prefix caching.
No cloud, no SaaS, no SDK lock-in. Just pip install contextops-tool and go.
pip install contextops-toolfrom contextops import optimize, Prompt
p = Prompt(
query="What's the weather in Berlin?",
history=[{"role": "user", "content": "Hi!"}],
documents="Berlin weather API docs...",
system="You are a helpful weather assistant.",
tools='[{"name": "get_weather"}]',
model="gpt-4o",
)
result = optimize(p)
print(result.diff()) # history β documents β ... β query
print(f"Cache hit: {result.estimated_cache_hit_rate:.1%}")
print(f"Saves ~${result.estimated_cost_savings_usd:.4f} per 1k calls")Output:
Section order: history β documents β ... β system β ... β query
Cache hit: 71.0%
Saves ~$0.1006 per 1k calls
That's it. Same prompt, same tokens, ~70% cache hit rate instead of ~5%.
LLM providers (Anthropic, OpenAI, DeepSeek, Google) cache the prefix of your prompt. If the prefix is stable across calls, you pay 10% of the cached-token price instead of the full price.
The trick: keep the prefix stable by putting variable content (query, history) at the end.
ContextOps knows the canonical ordering by stability:
system β tools β role β context β documents β history β query
β stable β variable
Estimated impact on a typical workload:
| Setup | Cache hit rate | Effective $/1M input |
|---|---|---|
| Random order | ~5% | $X (full price) |
| ContextOps optimized | ~78% | ~$0.3Β·X (10% on cached prefix) |
| Feature | Description |
|---|---|
| Cache-aware reordering | Moves stable sections to the top, variable to the bottom. Same total tokens, much higher cache hit rate. |
| Token counting | tiktoken-based, model-aware (gpt-4o, claude-*, qwen*, fallback to cl100k_base). |
| Cost estimation | Per-model pricing baked in; estimates $/1k calls before vs after reorder. |
| LLM-as-judge eval | Built-in metrics: faithfulness, relevance, completeness, conciseness. |
| A/B testing | Run two prompts over a golden dataset, get structural + quality deltas. |
| Local SQLite logger | Every LLM call goes to ~/.contextops/calls.db. Zero cloud. |
| Dataset loaders | .json, .jsonl, .csv golden QA datasets. |
| Rich CLI | optimize / stats / recent / compare / eval / reset with tables and progress bars. |
| LiteLLM auto-log (opt) | One line to auto-log every litellm call. pip install "contextops[integrations]" |
| Bench harness | 1000+ prompts through Ollama, LM Studio, OpenRouter, or direct APIs (Anthropic / OpenAI / Gemini / OpenCode-ZEN). |
# Core (optimizer + logger + eval + CLI)
pip install contextops-tool
# With LiteLLM auto-callback for real LLM logging
pip install "contextops-tool[integrations]"
# With dev tooling (pytest, ruff, mypy)
pip install "contextops-tool[dev]"
# Everything
pip install "contextops-tool[all]"From source:
git clone https://github.com/QuickLeopard/contextops.git
cd contextops
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev,integrations]"
pytest # 53 tests
python -m contextops_bench smoke # offline smokeRequires Python 3.10+.
If you see zsh: command not found: python, use python3 instead. Or install
Python 3.12 via Homebrew and add it to PATH:
brew install python@3.12
export PATH="/opt/homebrew/opt/python@3.12/bin:$PATH"
python --version # should print 3.12.xIf you want a fully automated setup, run the bootstrap script after cloning:
git clone https://github.com/QuickLeopard/contextops.git
cd contextops
./scripts/bootstrap.sh # installs Python, creates venv, runs tests + smokeSome Linux distros ship Python without venv:
# Debian / Ubuntu
sudo apt install python3.12-venv
# Fedora
sudo dnf install python3.12-venv
# Or use virtualenv instead
pip install virtualenv
virtualenv .venvfrom contextops import optimize, Prompt
p = Prompt(
query="What's the weather in Berlin?",
documents="API docs...",
system="You are a helpful weather assistant.",
tools="[]",
model="gpt-4o",
)
result = optimize(p)
print(result.diff()) # before β after
print(result.optimized_sections) # [(Section, content), ...]from contextops.eval import compare
report = compare(baseline=bad_prompt, optimized=good_prompt)
print(report["delta"])
# {"tokens": 0, "cache_hit_rate": 0.65, "cost_savings_per_1k_usd": 4.21}from contextops import evaluate_ab, load_dataset, Prompt, LiteLLMJudge
dataset = load_dataset("evals/sample_dataset.jsonl")
baseline = Prompt(system="...", query="", documents="{ctx}", model="gpt-4o-mini")
optimized = Prompt(system="...", documents="{ctx}", query="", model="gpt-4o-mini")
def my_llm(prompt_str: str) -> str:
return call_my_llm(prompt_str)
report = evaluate_ab(
baseline, optimized,
run_fn=my_llm,
dataset=dataset,
metrics=["faithfulness", "relevance", "completeness"],
judge=LiteLLMJudge(),
on_render=lambda p, item: p.system.replace("{ctx}", item.context),
)
print(report["structural"]) # tokens / cache / cost deltas
print(report["quality"]) # per-metric judge deltas# Optimize a prompt inline
contextops optimize \
--system "You are a weather assistant." \
--query "What's the weather in Berlin?" \
--documents "API docs..." \
--model gpt-4o
# Load a prompt from a JSON file
contextops optimize --from-json my_prompt.json
# Side-by-side comparison
contextops compare baseline.json optimized.json
# A/B eval with offline echo judge
contextops eval \
--baseline evals/baseline_prompt.json \
--optimized evals/optimized_prompt.json \
--dataset evals/sample_dataset.jsonl \
--metrics relevance,completeness,faithfulness \
--echo --run-fn echo \
--output report.json
# Real LLM-as-judge
pip install "contextops[integrations]"
contextops eval \
--baseline evals/baseline_prompt.json \
--dataset evals/sample_dataset.jsonl \
--judge-model gpt-4o-mini \
--metrics relevance,completeness,faithfulness,conciseness
# Local call stats
contextops stats
contextops recent --limit 50
# Reset the local database
contextops resetfrom contextops.integrations import install_callback
install_callback()
import litellm
litellm.completion(model="gpt-4o", messages=[{"role": "user", "content": "hi"}])
# β automatically logged to ~/.contextops/calls.db| Tool | What it does | Where ContextOps is different |
|---|---|---|
| DSPy | Auto-rewrites prompt text using a dataset | We reorder sections β no dataset, no model rewrite |
| RAGAS / DeepEval | Evaluate answer quality via LLM-judge | We measure structure + cost, complementary not competing |
| Langfuse | Cloud LLM observability | We stay local-first: SQLite, no signup |
| prompt-cache / token-optimizer | Cache responses, compress tokens | We focus on provider cache (Anthropic / OpenAI), not response cache |
| vaibkumr/prompt-optimizer | Compress text (LLMLingua-style) | We reorder, never change tokens or text |
1000+ prompts through Ollama, LM Studio, OpenRouter, or direct APIs:
# Smoke (10 prompts, <30s, no LLM, for CI)
python -m contextops_bench smoke
# Local (100 prompts via Ollama)
python -m contextops_bench local --provider ollama --model llama3.1:8b --n 100
# Cloud via OpenRouter, multi-model, parallel
export OPENROUTER_API_KEY=sk-or-v1-...
python -m contextops_bench cloud --provider openrouter \
--model openai/gpt-4o-mini,anthropic/claude-3.5-haiku,meta-llama/llama-3.1-8b-instruct \
--n 1000 --parallel 4
# Direct APIs (bypasses OpenRouter translation β definitive cache signal).
# Pick the provider that matches your target's cache mechanics:
export ZEN_API_KEY=... # or ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_API_KEY
python -m contextops_bench cloud --provider direct_anthropic \
--model claude-sonnet-4-6 --preset-agent realistic --n 30 # explicit cache_control markers
python -m contextops_bench cloud --provider direct_openai \
--model gpt-4o-mini --preset-agent realistic --n 30 # automatic caching, 50% off
python -m contextops_bench cloud --provider direct_google \
--model google/gemini-2.5-flash --preset-agent realistic --n 30 # implicit caching, 10% offAbout --preset-agent: the bench needs a stable system prompt + tool schema + role across all calls for cache hits to be non-zero. --preset-agent realistic pins all three. On cache-bearing providers (OpenRouter + the four direct_*), the bench auto-applies realistic if you don't pass a preset β with a loud warning explaining why. Use --preset-agent none to opt out and use randomized prompts.
Each run writes:
bench/results/<label>.csvβ every observation (prompt_id, model, tokens, cache hit, cost, latency, error, section order)bench/results/<label>.summary.jsonβ aggregated stats with optimized vs baseline deltas
Troubleshooting cache reads showing 0? Read docs/POSTMORTEM_realistic_cache.md β it covers the realistic-preset cache key regression, why OpenRouter drops cache_control markers during translation, and why EchoClient (used in unit tests) hides the bug.
See docs/ACCEPTANCE.md for the formal pass criteria.
- β v0.1 β reorder, token count, SQLite logger, CLI
- β v0.2 β LLM-as-judge eval + A/B testing + dataset loaders
- β
v0.3 β realistic-preset cache-key regression fix + direct providers (Anthropic / Zen / OpenAI / Gemini) + CI bench regression gate + safety-net auto-default on cache-bearing providers. See
docs/POSTMORTEM_realistic_cache.md. - π v0.4 β RAG curator (multi-signal retrieval + strict threshold)
- π v1.0 β Access-aware context + audit trail (on-prem / enterprise)
docs/ACCEPTANCE.mdβ formal pass/fail criteriaCHANGELOG.mdβ version historyCONTRIBUTING.mdβ how to contributeSECURITY.mdβ how to report vulnerabilitiesevals/β sample datasets and prompts
PRs welcome. See CONTRIBUTING.md for workflow, conventions, and release process.
Good first contributions:
- New
metricincontextops/judge.py(e.g.safety,format_compliance) - New
providerincontextops_bench/clients.py(e.g.vllm,tgi) - Better pricing tables for non-USD regions
- Translations of
docs/andREADME.md
MIT.
Built with: