Files
yvgude--lean-ctx/bench/compress/benchmark.py
T
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:35:30 +08:00

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())