Latency measured while another job hogs the cores is not comparable: the scale curve moved from 1.315s to 0.809s at 50,000 sources between two runs of the same code. - bench/systems.py: machine_facts() reports cpus plus the 1/5/15 minute load average. - bench/run.py, bench/run_external.py: both reports carry it, and BASELINE.md labels the latency table a ceiling when the machine was busy. Verification: pytest bench/tests -q -> 60 passed; ruff check bench/ -> all checks passed; mypy (fresh cache) -> no issues in 25 source files.
168 lines
7.4 KiB
Python
168 lines
7.4 KiB
Python
"""Run the Mem0- and Zep-style counterparts over the frozen sets (b-4), same queries, same metrics.
|
|
|
|
The point of this runner is an external reference point measured on our own corpus: a flat
|
|
memory store with holder scoping and no invalidation (Mem0-style), and the same items with
|
|
time-bounded validity (Zep-style). Whatever ely-eye scores, it must beat these two on the
|
|
same queries, or the memory story is not real.
|
|
|
|
Run: python -m bench.run_external # after bench.ingest_baseline has merged the corpus
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from datetime import UTC, datetime
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from bench.counterpart import (
|
|
Counterpart,
|
|
build_counterparts,
|
|
build_items,
|
|
load_item_atoms,
|
|
load_source_index,
|
|
)
|
|
from bench.ingest_baseline import RUNS, database_path
|
|
from bench.metrics import aggregate, score_query
|
|
from bench.run import Query, git_commit, holder_index, load_queries
|
|
from bench.sets_common import ROOT
|
|
from bench.systems import machine_facts
|
|
|
|
REPORTS = ROOT / "reports"
|
|
TOP_K = 10
|
|
|
|
|
|
def score_store(store: Counterpart, queries: list[Query], holder_of: dict[str, str]) -> dict[str, Any]:
|
|
"""Rank every query against one counterpart store and aggregate the same metrics the arms use."""
|
|
scores = []
|
|
for query in queries:
|
|
ranked = store.search(query.text, query.holder, query.as_of, TOP_K)
|
|
scores.append(score_query(query.query_id, ranked, set(query.gold), holder=query.holder, holder_of=holder_of))
|
|
summary: dict[str, Any] = {
|
|
"store": store.name,
|
|
"items": len(store.items),
|
|
"closed_items": sum(1 for item in store.items if item.valid_to is not None),
|
|
"overall": aggregate(scores),
|
|
"by_modality": {
|
|
modality: aggregate([s for s, q in zip(scores, queries, strict=True) if q.modality == modality])
|
|
for modality in sorted({q.modality for q in queries})
|
|
},
|
|
"latency_seconds": {
|
|
"queries": len(store.timings),
|
|
"mean": sum(store.timings) / len(store.timings) if store.timings else 0.0,
|
|
"p50": store.percentile(0.50) if store.timings else 0.0,
|
|
"p95": store.percentile(0.95) if store.timings else 0.0,
|
|
"max": max(store.timings) if store.timings else 0.0,
|
|
},
|
|
}
|
|
return summary
|
|
|
|
|
|
def write_markdown(report: dict[str, Any], path: Path) -> None:
|
|
"""A table a reader can check: every number carries its n and its 95% lower bound."""
|
|
meta = report["meta"]
|
|
lines = [
|
|
"# BASELINE_EXTERNAL: memory counterparts on the same corpus",
|
|
"",
|
|
f"- generated {meta['generated_at']} at commit {meta['git_commit'][:12]}",
|
|
f"- {meta['queries']} queries ({meta['answerable']} answerable) against {meta['sources']} sources",
|
|
(
|
|
f"- machine: {meta['machine']['cpus']} cores, load average "
|
|
f"{meta['machine']['load_average_1_5_15'][0]:.1f} when the report was written"
|
|
),
|
|
(
|
|
"- extraction is lexical for both stores; Mem0's real add() and Zep's graph builder call a "
|
|
"generator model, which this round does not have"
|
|
),
|
|
"",
|
|
(
|
|
"| store | items | closed | recall@10 | hit@10 | hit@10 Wilson 95% lower | MRR | "
|
|
"abstain (answerable) | abstain (unanswerable) | p50 s | p95 s |"
|
|
),
|
|
"| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |",
|
|
]
|
|
for store in report["stores"]:
|
|
overall = store["overall"]
|
|
latency = store["latency_seconds"]
|
|
lines.append(
|
|
f"| {store['store']} | {store['items']} | {store['closed_items']} | {overall['recall_at_10']:.3f} "
|
|
f"| {overall['hit_at_10']:.3f} | {overall['hit_at_10_wilson_lower']:.3f} | {overall['mrr']:.3f} "
|
|
f"| {overall['abstention_rate_answerable']:.3f} | {overall['abstention_rate_unanswerable']:.3f} "
|
|
f"| {latency['p50']:.3f} | {latency['p95']:.3f} |"
|
|
)
|
|
lines += ["", f"n per modality: {json.dumps(meta['queries_by_modality'], sort_keys=True)}", ""]
|
|
for store in report["stores"]:
|
|
lines.append(f"## {store['store']} by modality")
|
|
lines.append("")
|
|
lines.append("| modality | n answerable | recall@10 | hit@10 | Wilson lower | MRR |")
|
|
lines.append("| --- | --- | --- | --- | --- | --- |")
|
|
for modality, row in sorted(store["by_modality"].items()):
|
|
lines.append(
|
|
f"| {modality} | {row['answerable']} | {row['recall_at_10']:.3f} | {row['hit_at_10']:.3f} "
|
|
f"| {row['hit_at_10_wilson_lower']:.3f} | {row['mrr']:.3f} |"
|
|
)
|
|
lines.append("")
|
|
path.write_text("\n".join(lines), encoding="utf-8")
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--limit", type=int, help="score at most this many queries per modality (smoke runs)")
|
|
parser.add_argument("--modality", action="append", choices=None)
|
|
parser.add_argument("--out", type=Path, default=REPORTS / "baseline_external.json")
|
|
parser.add_argument("--db", type=Path, default=database_path(RUNS), help="atoms to build items from (smoke runs use a shard)")
|
|
args = parser.parse_args()
|
|
|
|
modalities = args.modality or None
|
|
queries = load_queries(modalities) if modalities else load_queries()
|
|
if args.limit:
|
|
kept: list[Query] = []
|
|
for modality in sorted({q.modality for q in queries}):
|
|
kept.extend([q for q in queries if q.modality == modality][: args.limit])
|
|
queries = kept
|
|
if not queries:
|
|
raise SystemExit("no queries selected")
|
|
|
|
index = load_source_index()
|
|
atoms = load_item_atoms(args.db)
|
|
items = build_items(atoms, index)
|
|
if not items:
|
|
raise SystemExit("no memory items built; the corpus produced no text")
|
|
print(f"== {len(items)} memory items from {len(atoms)} atoms and {len(index)} indexed sources")
|
|
|
|
holder_of = holder_index()
|
|
stores = build_counterparts(items)
|
|
report: dict[str, Any] = {
|
|
"meta": {
|
|
"generated_at": datetime.now(UTC).isoformat(timespec="seconds"),
|
|
"git_commit": git_commit(),
|
|
"queries": len(queries),
|
|
"answerable": sum(1 for q in queries if q.answerable),
|
|
"limit_per_modality": args.limit,
|
|
"queries_by_modality": {m: sum(1 for q in queries if q.modality == m) for m in sorted({q.modality for q in queries})},
|
|
"sources": len(index),
|
|
"top_k": TOP_K,
|
|
"extraction": "lexical (see bench/counterpart.py); LLM extraction deferred with the generator",
|
|
"machine": machine_facts(),
|
|
},
|
|
"stores": [score_store(store, queries, holder_of) for store in stores],
|
|
}
|
|
REPORTS.mkdir(parents=True, exist_ok=True)
|
|
args.out.write_text(json.dumps(report, indent=1, sort_keys=True), encoding="utf-8")
|
|
markdown = args.out.with_name("BASELINE_EXTERNAL.md")
|
|
write_markdown(report, markdown)
|
|
for store in report["stores"]:
|
|
overall = store["overall"]
|
|
print(
|
|
f"{store['store']:14s} recall@10 {overall['recall_at_10']:.3f} hit@10 {overall['hit_at_10']:.3f} "
|
|
f"(Wilson lower {overall['hit_at_10_wilson_lower']:.3f}) mrr {overall['mrr']:.3f} "
|
|
f"p50 {store['latency_seconds']['p50']:.3f}s"
|
|
)
|
|
print(f"== wrote {args.out} and {markdown}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|