
Rag Eval
- 118 installs
- 339 repo stars
- Updated August 4, 2026
- glebis/claude-skills
For integrating A developer tool for AI integration and automation
About
A developer tool for AI integration and automation. This is a developer tool for building and integrating AI-powered features.
- AI
- Developer tool
Rag Eval by the numbers
- 118 all-time installs (skills.sh)
- Ranked #3,851 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/glebis/claude-skills --skill rag-evalAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 118 |
|---|---|
| repo stars | ★ 339 |
| Last updated | August 4, 2026 |
| Repository | glebis/claude-skills ↗ |
What it does
For integrating A developer tool for AI integration and automation
Files
rag-eval
Purpose
Replace the "tweak → squint → swap model → burn credits" loop with a single command that runs a grid of eval variants on the user's gold-set, ranks them by a cost-aware score, and returns structured feedback on architecture, stack, and likely-issues. Draws on evidence-based RAG practices and learns from the user's past runs.
When to use
Trigger on: "help me test a RAG", "tune my RAG", "my RAG is bad", "compare retrieval prompts", "how do I eval this", "what's the best embedding model for X", "my RAG eval is expensive". Also trigger when the user reports burning OpenRouter / OpenAI credits with no clear signal of improvement.
Prerequisites — gather before running
Collect these from the user before the first sweep. Many are optional with sensible defaults; always confirm the ones that gate cost.
1. RAG codebase root — path to the repo/module under test. 2. Gold-set — at least 10 Q&A pairs. If missing, offer to generate a starter gold-set from the user's dataset (LLM-synthesized, human-reviewed). See references/best-practices.md. 3. Dataset — the corpus the RAG retrieves over. 4. Budget cap — hard dollar limit per run (default: $2 if user doesn't specify). Always confirm before any sweep. 5. Provider keys — OPENROUTER_API_KEY or OPENAI_API_KEY (read from env). 6. Vector-store config — collection name, embedding model, chunk size (read from repo; confirm if ambiguous). 7. Eval history path (optional) — defaults to .rag-eval/history.jsonl in the repo root.
Workflow
Follow this order. Refer to references/best-practices.md for the canonical checklist and references/evidence-base.md for the research-backed defaults.
Step 0 — (Optional) Ingest a prior iteration session
When the user provides a session ID (Claude Code transcript, skill-studio session, or a Fathom meeting), run the deterministic ingest first — no LLM calls. This extracts only the useful signals (models tried, prompt variants, cost events, eval results) as compact JSON, so the rest of the skill works off a tiny structured bundle instead of a long raw transcript.
python scripts/session_ingest.py <session_id> > /tmp/rag-eval-bundle.json
# or with a direct path:
python scripts/session_ingest.py --path /path/to/transcript.jsonl > /tmp/rag-eval-bundle.jsonThe bundle includes: models_tried, prompts_tried (hashes only), iterations, total_cost_usd, summary_stats. Feed this into Step 1 — do not paste the raw transcript.
Why this matters: transcripts can be 100k+ tokens of noise. The ingest script does regex extraction only, keeping the LLM budget for the actual audit + sweep planning. This is a hard requirement, not an optimization.
Step 1 — Audit the stack
Read references/best-practices.md and inspect the user's repo + vector-store config. Produce a structured report covering:
- Architecture (retrieval type: dense / hybrid / rerank; chunking strategy; prompt structure)
- Tech stack (embedding model, LLM, vector store)
- Resources (dataset size, gold-set size, prior eval runs)
- Risks (known anti-patterns, missing pieces)
Present the report to the user and ask which issues to address first.
Step 2 — Propose a sweep plan
Based on the audit, propose 3–8 variants to test. Keep the grid small on the first run (default: 2 prompts × 2 models × 1 retrieval variant = 4 cells). Estimate cost using gold-set size × variants × avg tokens × provider pricing. Present the cost estimate and wait for user confirmation before running.
Step 3 — Run the sweep
Use scripts/eval_sweep.py (see the script header for invocation). It reads a config YAML, runs each variant against the gold-set, records per-variant cost and answer quality, and appends to history.jsonl.
Guardrails:
- Never exceed the budget cap — halt mid-sweep if reached.
- Never mutate the user's repo. Write all artifacts under
.rag-eval/(gitignore it). - Confirm before any sweep estimated to exceed the user's cap.
Step 4 — Rank and report
After the sweep, rank variants by a cost-aware score: quality × (1 / log(1 + cost)). Present:
- Top 3 variants with quality metrics and cost
- What changed vs the previous best
- Concrete next experiment to try
Write the full report to .rag-eval/reports/<timestamp>.md.
Step 5 — Self-improve
Before each subsequent run, read history.jsonl and factor in what the user has already tried. Avoid re-testing rejected variants. Surface patterns ("models A, B, C all underperformed on multi-hop queries — next try a reranker").
Reusable resources
scripts/eval_sweep.py— grid-search runner. Readseval_config.yaml, writes results tohistory.jsonl.references/best-practices.md— evidence-based RAG checklist the agent uses as an anchor.references/evidence-base.md— pointers to recent RAG research and when each technique helps.assets/eval_config.template.yaml— starter config to copy into the user's repo.assets/gold_set.template.jsonl— 3 example Q&A pairs to show the gold-set format.
Notes
- Cost is the main failure mode. Never run without a confirmed budget. Err on the side of smaller sweeps; users can always run again.
- No repo mutation. All outputs go under
.rag-eval/in the target repo. - When uncertain about best practices, do web research. Use
tavily-searchorfirecrawl-researchto pull current evidence, then synthesize into the audit report. - Defer to the user. Before changing any file in the target repo, always confirm.
#!/usr/bin/env python3
"""Deterministic session ingest — takes a session ID, emits a compact JSON
signal bundle ready to feed into rag-eval. No LLM calls; pure regex + parse.
Supported session sources (auto-detected by ID prefix and path):
- Claude Code session transcript (~/.claude/projects/*/<uuid>.jsonl)
- skill-studio session (~/.skill-studio/sessions/<uuid>/transcript.md)
- Fathom meeting transcript path (passed explicitly)
Emits to stdout (JSON):
{
"session_id": "...",
"source": "claude-code" | "skill-studio" | "fathom",
"iterations": [
{"action": "prompt_change", "text": "...", "ts": "..."},
{"action": "model_swap", "from": "gpt-4o", "to": "claude-3-5", "ts": "..."},
{"action": "eval_result", "query": "...", "answer_excerpt": "...", "ts": "..."},
{"action": "cost_event", "amount_usd": 0.42, "provider": "openrouter", "ts": "..."}
],
"models_tried": ["gpt-4o", "claude-3-5-sonnet"],
"prompts_tried": ["...short hashes..."],
"total_cost_usd": 1.73,
"summary_stats": {"turns": 42, "iterations": 8, "failed_queries": 3}
}
Usage:
session_ingest.py <session_id>
session_ingest.py --path <path-to-transcript>
session_ingest.py <session_id> --source claude-code
"""
from __future__ import annotations
import argparse
import hashlib
import json
import re
import sys
from pathlib import Path
from typing import Iterable
MODEL_PATTERNS = [
r"\b(gpt-[0-9][a-z0-9\-\.]*)\b",
r"\b(claude-[0-9a-z\-\.]+)\b",
r"\b(mistral-[a-z0-9\-\.]+)\b",
r"\b(llama-?\d[a-z0-9\-\.]*)\b",
r"\b(gemini-[a-z0-9\-\.]+)\b",
]
COST_PATTERNS = [
re.compile(r"\$(\d+\.\d{2,4})\s*(?:spent|cost|charged|USD)?", re.I),
re.compile(r"cost[:= ]+\$?(\d+\.\d{2,4})", re.I),
re.compile(r"usage[:= ]+\$?(\d+\.\d{2,4})", re.I),
]
PROMPT_CHANGE_MARKERS = [
re.compile(r"\b(retrieval|rag|system)\s+prompt[:= ]", re.I),
re.compile(r"(changed|updated|tweaked)\s+(the\s+)?prompt", re.I),
re.compile(r"new prompt", re.I),
]
def _find_claude_code_session(session_id: str) -> Path | None:
root = Path.home() / ".claude" / "projects"
if not root.exists():
return None
matches = list(root.rglob(f"{session_id}*.jsonl"))
return matches[0] if matches else None
def _find_skill_studio_session(session_id: str) -> Path | None:
root = Path.home() / ".skill-studio" / "sessions"
for d in root.glob(f"{session_id}*"):
t = d / "transcript.md"
if t.exists():
return t
return None
def _iter_text(path: Path) -> Iterable[str]:
if path.suffix == ".jsonl":
for line in path.read_text().splitlines():
try:
obj = json.loads(line)
msg = obj.get("message") or obj
content = msg.get("content") if isinstance(msg, dict) else None
if isinstance(content, list):
for block in content:
if isinstance(block, dict) and block.get("type") == "text":
yield block.get("text", "")
elif isinstance(content, str):
yield content
except json.JSONDecodeError:
continue
else:
yield path.read_text()
def extract_signals(path: Path, session_id: str, source: str) -> dict:
models: set[str] = set()
prompts_seen: list[str] = []
iterations: list[dict] = []
total_cost = 0.0
for chunk in _iter_text(path):
for pattern in MODEL_PATTERNS:
for m in re.findall(pattern, chunk, re.I):
models.add(m.lower())
for rx in COST_PATTERNS:
for m in rx.finditer(chunk):
try:
amt = float(m.group(1))
if 0.001 <= amt <= 100:
total_cost += amt
iterations.append({"action": "cost_event", "amount_usd": amt})
except (ValueError, IndexError):
pass
for rx in PROMPT_CHANGE_MARKERS:
if rx.search(chunk):
h = hashlib.sha1(chunk.encode("utf-8", "ignore")).hexdigest()[:8]
if h not in prompts_seen:
prompts_seen.append(h)
iterations.append({"action": "prompt_change", "hash": h})
break
return {
"session_id": session_id,
"source": source,
"iterations": iterations,
"models_tried": sorted(models),
"prompts_tried": prompts_seen,
"total_cost_usd": round(total_cost, 4),
"summary_stats": {
"iterations": len(iterations),
"models_tried": len(models),
"prompt_variants": len(prompts_seen),
},
}
def main(argv: list[str] | None = None) -> int:
ap = argparse.ArgumentParser()
ap.add_argument("session_id", nargs="?")
ap.add_argument("--path", type=Path, help="Direct path to transcript file")
ap.add_argument(
"--source", choices=["claude-code", "skill-studio", "fathom", "auto"], default="auto"
)
args = ap.parse_args(argv)
if args.path:
path = args.path
source = args.source if args.source != "auto" else "fathom"
session_id = args.session_id or path.stem
elif args.session_id:
path = _find_claude_code_session(args.session_id) or _find_skill_studio_session(args.session_id)
if path is None:
print(f"session not found: {args.session_id}", file=sys.stderr)
return 1
source = "claude-code" if ".claude/projects" in str(path) else "skill-studio"
session_id = args.session_id
else:
ap.error("provide session_id or --path")
return 2
bundle = extract_signals(path, session_id, source)
json.dump(bundle, sys.stdout, indent=2)
sys.stdout.write("\n")
return 0
if __name__ == "__main__":
sys.exit(main())