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
164 lines
5.5 KiB
Python
164 lines
5.5 KiB
Python
"""Run the context-engine benchmark and print a comparison table.
|
|
|
|
Usage (from the plugin directory)::
|
|
|
|
python benchmarks/run.py --turns 400 --needles 16 --context-length 8000
|
|
|
|
Only lean-ctx runs out of the box; install Hermes / hermes-lcm to include the
|
|
competitors (they are auto-detected). Results are written to
|
|
``benchmarks/results/`` (gitignored).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import importlib.util
|
|
import json
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
# Allow direct execution (`python benchmarks/run.py`) without installation.
|
|
_PLUGIN_DIR = Path(__file__).resolve().parent.parent
|
|
_REPO_ROOT = _PLUGIN_DIR.parent.parent
|
|
_PKG = "hermes_lean_ctx"
|
|
|
|
|
|
def _bootstrap_package() -> None:
|
|
"""Make the hyphenated plugin dir importable as ``hermes_lean_ctx``.
|
|
|
|
Mirrors the test conftest so the runner works standalone, and wires the
|
|
monorepo ``leanctx`` SDK onto the path when it is checked out alongside.
|
|
"""
|
|
clients_python = _REPO_ROOT / "clients" / "python"
|
|
if clients_python.is_dir() and str(clients_python) not in sys.path:
|
|
sys.path.insert(0, str(clients_python))
|
|
if _PKG in sys.modules:
|
|
return
|
|
spec = importlib.util.spec_from_file_location(
|
|
_PKG,
|
|
str(_PLUGIN_DIR / "__init__.py"),
|
|
submodule_search_locations=[str(_PLUGIN_DIR)],
|
|
)
|
|
if not spec or not spec.loader: # pragma: no cover - packaging accident
|
|
raise ImportError("cannot locate hermes_lean_ctx package")
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[_PKG] = module
|
|
spec.loader.exec_module(module)
|
|
|
|
|
|
_bootstrap_package()
|
|
|
|
from hermes_lean_ctx.benchmarks import corpus as _corpus # noqa: E402
|
|
from hermes_lean_ctx.benchmarks import engines as _engines # noqa: E402
|
|
from hermes_lean_ctx.benchmarks import metrics as _metrics # noqa: E402
|
|
|
|
_COLUMNS = [
|
|
("engine", "engine", 18),
|
|
("note", "mode", 14),
|
|
("savings_pct", "savings%", 9),
|
|
("saved_tokens", "saved", 10),
|
|
("verbatim_recall", "recall", 7),
|
|
("compress_latency_ms", "ms", 9),
|
|
("output_messages", "out_msgs", 9),
|
|
]
|
|
|
|
|
|
def run_benchmark(
|
|
*,
|
|
turns: int = 200,
|
|
needles: int = 12,
|
|
words_per_msg: int = 60,
|
|
context_length: int = 8_000,
|
|
repeats: int = 1,
|
|
base_url: Optional[str] = None,
|
|
token: Optional[str] = None,
|
|
transcript_path: Optional[str] = None,
|
|
) -> Dict[str, Any]:
|
|
"""Run all available engines over the corpus; return a results dict."""
|
|
if transcript_path:
|
|
messages = _corpus.load_transcript(transcript_path)
|
|
needle_facts: List[str] = []
|
|
else:
|
|
messages, needle_facts = _corpus.build_corpus(
|
|
turns=turns, needles=needles, words_per_msg=words_per_msg
|
|
)
|
|
|
|
rows: List[Dict[str, Any]] = []
|
|
skipped: List[Dict[str, str]] = []
|
|
for adapter in _engines.discover_adapters(
|
|
context_length=context_length, base_url=base_url, token=token
|
|
):
|
|
if not adapter.available:
|
|
skipped.append({"engine": adapter.name, "reason": adapter.note})
|
|
continue
|
|
rows.append(_metrics.measure(adapter, messages, needle_facts, repeats=repeats))
|
|
|
|
return {
|
|
"params": {
|
|
"turns": turns,
|
|
"needles": len(needle_facts),
|
|
"words_per_msg": words_per_msg,
|
|
"context_length": context_length,
|
|
"repeats": repeats,
|
|
"transcript_path": transcript_path,
|
|
},
|
|
"results": rows,
|
|
"skipped": skipped,
|
|
}
|
|
|
|
|
|
def format_table(report: Dict[str, Any]) -> str:
|
|
header = " ".join(label.ljust(width) for _, label, width in _COLUMNS)
|
|
lines = [header, "-" * len(header)]
|
|
for row in report["results"]:
|
|
lines.append(
|
|
" ".join(str(row.get(key, "")).ljust(width) for key, _, width in _COLUMNS)
|
|
)
|
|
for skip in report["skipped"]:
|
|
lines.append(f"(skipped {skip['engine']}: {skip['reason']})")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def _write_results(report: Dict[str, Any]) -> Path:
|
|
out_dir = _PLUGIN_DIR / "benchmarks" / "results"
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
out_path = out_dir / "latest.json"
|
|
out_path.write_text(json.dumps(report, indent=2, sort_keys=True), encoding="utf-8")
|
|
return out_path
|
|
|
|
|
|
def main(argv: Optional[List[str]] = None) -> int:
|
|
parser = argparse.ArgumentParser(description="hermes-lean-ctx context-engine benchmark")
|
|
parser.add_argument("--turns", type=int, default=200)
|
|
parser.add_argument("--needles", type=int, default=12)
|
|
parser.add_argument("--words-per-msg", type=int, default=60)
|
|
parser.add_argument("--context-length", type=int, default=8_000)
|
|
parser.add_argument("--repeats", type=int, default=1)
|
|
parser.add_argument("--base-url", default=os.environ.get("LEANCTX_BASE_URL"))
|
|
parser.add_argument("--token", default=os.environ.get("LEANCTX_TOKEN"))
|
|
parser.add_argument("--transcript", default=None, help="Path to a real transcript JSON")
|
|
parser.add_argument("--no-write", action="store_true", help="Do not write results JSON")
|
|
args = parser.parse_args(argv)
|
|
|
|
report = run_benchmark(
|
|
turns=args.turns,
|
|
needles=args.needles,
|
|
words_per_msg=args.words_per_msg,
|
|
context_length=args.context_length,
|
|
repeats=args.repeats,
|
|
base_url=args.base_url,
|
|
token=args.token,
|
|
transcript_path=args.transcript,
|
|
)
|
|
print(format_table(report))
|
|
if not args.no_write:
|
|
path = _write_results(report)
|
|
print(f"\nwrote {path}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|