Files
2026-07-13 12:42:18 +08:00

126 lines
4.0 KiB
Python

"""Token reduction benchmark -- measures graph query efficiency vs naive file reading."""
from __future__ import annotations
import logging
import sqlite3
from pathlib import Path
from typing import Any
from .graph import GraphStore
from .search import hybrid_search
logger = logging.getLogger(__name__)
# Sample questions for benchmarking
_SAMPLE_QUESTIONS = [
"how does authentication work",
"what is the main entry point",
"how are database connections managed",
"what error handling patterns are used",
"how do tests verify core functionality",
]
def estimate_tokens(text: str) -> int:
"""Rough token estimate: ~4 chars per token."""
return max(1, len(text) // 4)
def compute_naive_tokens(repo_root: Path) -> int:
"""Count tokens in all parseable source files."""
total = 0
exts = (
".py", ".js", ".ts", ".go", ".rs", ".java",
".c", ".cpp", ".rb", ".php", ".swift", ".kt",
)
for ext in exts:
for f in repo_root.rglob(f"*{ext}"):
try:
total += estimate_tokens(
f.read_text(errors="replace")
)
except OSError:
continue
return total
def run_token_benchmark(
store: GraphStore,
repo_root: Path,
questions: list[str] | None = None,
) -> dict[str, Any]:
"""Run token reduction benchmark.
Compares naive full-corpus token cost vs graph query token
cost for a set of sample questions.
The default sample questions are natural language and require semantic
search to match. If no embeddings are present in the graph, ``hybrid_search``
falls back to FTS5/LIKE matching on node names, which produces no hits for
questions like "how does authentication work" — every per-question ratio
becomes 0 and the benchmark silently appears to fail. We log a clear
warning when that is the case so callers know to run ``embed_graph`` first
(or to pass keyword-matching questions).
"""
if questions is None:
questions = _SAMPLE_QUESTIONS
using_default_questions = questions is _SAMPLE_QUESTIONS
try:
cur = store._conn.execute("SELECT count(*) FROM embeddings")
embedding_count = cur.fetchone()[0]
except sqlite3.OperationalError:
embedding_count = 0
if embedding_count == 0 and using_default_questions:
logger.warning(
"No embeddings found in this graph. The default sample questions "
"are natural language and will not match via FTS5/LIKE alone — "
"every reduction ratio is likely to be 0. Run "
"`code-review-graph embed` first, or pass keyword-matching `questions=`."
)
naive_total = compute_naive_tokens(repo_root)
results = []
for q in questions:
search_results = hybrid_search(store, q, limit=5)
# Simulate graph context: search results + neighbors
graph_tokens = 0
for r in search_results:
graph_tokens += estimate_tokens(str(r))
# Add approximate neighbor context
qn = r.get("qualified_name", "")
edges = store.get_edges_by_source(qn)[:5]
for e in edges:
graph_tokens += estimate_tokens(str(e))
if graph_tokens > 0:
ratio = naive_total / graph_tokens
else:
ratio = 0
results.append({
"question": q,
"naive_tokens": naive_total,
"graph_tokens": graph_tokens,
"reduction_ratio": round(ratio, 1),
})
if results:
total = sum(
r["reduction_ratio"] for r in results # type: ignore[misc]
)
avg_ratio = float(total) / len(results) # type: ignore[arg-type]
else:
avg_ratio = 0.0
return {
"naive_corpus_tokens": naive_total,
"per_question": results,
"average_reduction_ratio": round(avg_ratio, 1),
"summary": (
f"Graph queries use ~{avg_ratio:.0f}x fewer tokens "
f"than reading all source files"
),
}