26382a7ac6
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled
180 lines
6.1 KiB
Python
180 lines
6.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Head-to-head compression benchmark: lean-ctx `/v1/compress` vs Headroom.
|
|
|
|
Runs both libraries over the *same* real corpus with the *same* tokenizer and
|
|
reports compression ratio + latency as JSON. Numbers are always measured, never
|
|
fabricated: a tool that is not installed/reachable is reported as
|
|
``available: false`` rather than guessed.
|
|
|
|
Prerequisites
|
|
-------------
|
|
* lean-ctx daemon with ``POST /v1/compress`` running (``lean-ctx dev-install``).
|
|
* Optional head-to-head: ``pip install headroom-ai``.
|
|
* Optional accurate token counts: ``pip install tiktoken`` (else char counts).
|
|
|
|
Usage
|
|
-----
|
|
python bench/compress/benchmark.py # JSON to stdout
|
|
python bench/compress/benchmark.py --corpus docs/ # custom corpus
|
|
python bench/compress/benchmark.py --out report.json --model gpt-4o
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
from typing import Any, Callable, Dict, List, Optional
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
PY_SDK = REPO_ROOT / "packages" / "python-lean-ctx"
|
|
if PY_SDK.is_dir():
|
|
sys.path.insert(0, str(PY_SDK))
|
|
|
|
Message = Dict[str, Any]
|
|
|
|
|
|
def build_tokenizer(model: str) -> tuple[Callable[[str], int], str]:
|
|
"""Return ``(count_fn, name)``. Prefers tiktoken; falls back to chars."""
|
|
try:
|
|
import tiktoken
|
|
|
|
try:
|
|
enc = tiktoken.encoding_for_model(model)
|
|
except KeyError:
|
|
enc = tiktoken.get_encoding("o200k_base")
|
|
return (lambda text: len(enc.encode(text)), enc.name)
|
|
except Exception:
|
|
return (len, "chars")
|
|
|
|
|
|
def iter_text(content: Any):
|
|
"""Yield every text payload inside an OpenAI/Anthropic message content."""
|
|
if isinstance(content, str):
|
|
yield content
|
|
elif isinstance(content, list):
|
|
for block in content:
|
|
if not isinstance(block, dict):
|
|
continue
|
|
if block.get("type") == "text" and isinstance(block.get("text"), str):
|
|
yield block["text"]
|
|
elif block.get("type") == "tool_result":
|
|
yield from iter_text(block.get("content"))
|
|
|
|
|
|
def total_tokens(messages: List[Message], count: Callable[[str], int]) -> int:
|
|
return sum(count(text) for msg in messages for text in iter_text(msg.get("content")))
|
|
|
|
|
|
def load_corpus(path: Path, max_files: int, max_bytes: int) -> List[Message]:
|
|
"""Build one user message per real text file under ``path`` (no fixtures)."""
|
|
if not path.exists():
|
|
raise SystemExit(f"corpus path does not exist: {path}")
|
|
suffixes = {".md", ".rs", ".py", ".ts", ".txt", ".json", ".log", ".yaml", ".yml"}
|
|
files = sorted(p for p in path.rglob("*") if p.is_file() and p.suffix in suffixes)
|
|
messages: List[Message] = []
|
|
for file in files:
|
|
if len(messages) >= max_files:
|
|
break
|
|
try:
|
|
text = file.read_text(encoding="utf-8")
|
|
except (UnicodeDecodeError, OSError):
|
|
continue
|
|
if len(text) > max_bytes:
|
|
text = text[:max_bytes]
|
|
if text.strip():
|
|
messages.append({"role": "user", "content": text})
|
|
if not messages:
|
|
raise SystemExit(f"no readable text files found under {path}")
|
|
return messages
|
|
|
|
|
|
def measure(
|
|
label: str,
|
|
compress: Callable[[List[Message]], List[Message]],
|
|
messages: List[Message],
|
|
count: Callable[[str], int],
|
|
) -> Dict[str, Any]:
|
|
"""Run one compressor once, returning measured tokens + latency."""
|
|
original = total_tokens(messages, count)
|
|
started = time.perf_counter()
|
|
try:
|
|
out = compress(messages)
|
|
except Exception as exc: # noqa: BLE001 - any failure is reported, not raised
|
|
return {"available": False, "error": f"{type(exc).__name__}: {exc}"}
|
|
latency_ms = round((time.perf_counter() - started) * 1000, 2)
|
|
compressed = total_tokens(out, count)
|
|
ratio = round(1 - compressed / original, 4) if original else 0.0
|
|
return {
|
|
"available": True,
|
|
"original_tokens": original,
|
|
"compressed_tokens": compressed,
|
|
"tokens_saved": original - compressed,
|
|
"ratio": ratio,
|
|
"latency_ms": latency_ms,
|
|
}
|
|
|
|
|
|
def lean_ctx_compressor(model: str) -> Optional[Callable[[List[Message]], List[Message]]]:
|
|
try:
|
|
from lean_ctx import compress as lc_compress
|
|
except ImportError:
|
|
return None
|
|
return lambda messages: lc_compress(messages, model=model)
|
|
|
|
|
|
def headroom_compressor(model: str) -> Optional[Callable[[List[Message]], List[Message]]]:
|
|
try:
|
|
from headroom import compress as hr_compress
|
|
except ImportError:
|
|
return None
|
|
return lambda messages: hr_compress(messages, model=model).messages
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--corpus", default=str(REPO_ROOT / "docs" / "reference"))
|
|
parser.add_argument("--model", default="gpt-4o")
|
|
parser.add_argument("--max-files", type=int, default=50)
|
|
parser.add_argument("--max-bytes", type=int, default=200_000)
|
|
parser.add_argument("--out", help="write the JSON report to this file")
|
|
args = parser.parse_args()
|
|
|
|
count, tokenizer = build_tokenizer(args.model)
|
|
messages = load_corpus(Path(args.corpus), args.max_files, args.max_bytes)
|
|
|
|
report: Dict[str, Any] = {
|
|
"corpus": {
|
|
"path": str(Path(args.corpus)),
|
|
"messages": len(messages),
|
|
"model": args.model,
|
|
"tokenizer": tokenizer,
|
|
},
|
|
}
|
|
|
|
lc = lean_ctx_compressor(args.model)
|
|
report["lean_ctx"] = (
|
|
measure("lean-ctx", lc, messages, count)
|
|
if lc
|
|
else {"available": False, "install": "pip install lean-ctx-sdk (and run the daemon)"}
|
|
)
|
|
|
|
hr = headroom_compressor(args.model)
|
|
report["headroom"] = (
|
|
measure("headroom", hr, messages, count)
|
|
if hr
|
|
else {"available": False, "install": "pip install headroom-ai"}
|
|
)
|
|
|
|
payload = json.dumps(report, indent=2)
|
|
print(payload)
|
|
if args.out:
|
|
Path(args.out).write_text(payload + "\n", encoding="utf-8")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|