tc-ai-experiments

RAG evaluation with RAGAS

A follow-up to rag-langchain. That project evaluates its pipeline with metrics reimplemented in TypeScript, following the ideas in RAGAS. This project runs the real RAGAS library (Python, v0.4.3) on the same pipeline and the same music data, and writes a page that shows every judge step.

It’s a separate project because the data changed: the artist profiles here label begin_date differently (see the first finding under “This project’s own run”), so its runs can’t be compared one-to-one with rag-langchain’s published runs.

Key findings

Read the comparison report: rag-langchain’s evaluation and this one side by side, covering what each measures, what each found and missed, how far the judges can be trusted, and which to use.

Two judges, the same claims

rag-langchain’s 4 evaluation runs hold 161 claims that gemini-2.5-flash already judged. RAGAS’s verdict step (gpt-4o-mini with RAGAS’s prompt) judged the same claims against the same chunks on 2026-10-09.

This project’s own run

From the run on 2026-10-09: 12 questions, answers by gpt-5-mini, judged by gpt-4o-mini. 12 of 12 answered or declined as expected. Mean faithfulness 0.96 over the 7 answers.

See the results

Open the comparison report: the two evaluation processes compared, with a recommendation.

Open the two-judge comparison: the agreement grid, what each judge does to the scores, the mistakes grouped by kind, and every disagreement with its evidence and decision.

Open the walkthrough, the page from the latest run, committed in output/ and published with GitHub Pages. It follows one answer through each judge step: the refusal check, splitting the answer into claims, each claim’s verdict with the closest line in the retrieved chunks (to check the judge yourself), and the score. Then it lists every question and what to check by hand. Each judge prompt can be expanded to see exactly what was sent.

The raw data is in output/eval/: samples.jsonl (the questions, retrieved chunks and answers, in RAGAS’s field names) and results.json (scores, claims, verdicts and the judge prompts).

How it works

Step Runs in What it does
src/1-export.ts TypeScript Runs each question in eval/questions.json through the pipeline (top 6 chunks, gpt-5-mini) and writes output/eval/samples.jsonl.
eval/2_score.py Python Scores each sample. Refusal: a rule for plain “I don’t know based on the data.”, otherwise a RAGAS DiscreteMetric (answered or refused) compared with the question’s label. Faithfulness, for answers only: RAGAS splits the answer into claims, then checks each claim against the chunks. Score = supported ÷ all. Writes output/eval/results.json.
src/3-walkthrough.ts TypeScript Writes output/eval-walkthrough.html from the results. No API calls.
eval/4_cross_judge.py Python Gives the claims Gemini judged in rag-langchain’s runs (copied to data/rag-langchain-runs/) to RAGAS’s verdict step, with the same numbered chunks. RAGAS’s claim splitting is skipped, so every claim gets exactly two verdicts. Writes output/cross-judge/verdicts.json.
eval/sample_agreements.py Python (standard library) Draws a seeded sample of the claims both judges agreed on into eval/agreement-sample.json, for checking by hand. Keeps decisions already made.
src/5-cross-judge-page.ts TypeScript Writes output/cross-judge.html from the verdicts, the decisions in eval/cross-judge-labels.json and the checked sample. No API calls.
src/6-report-page.ts TypeScript Writes output/evaluation-comparison.html, the report comparing the two evaluation processes. The text is fixed; every number is read from the saved runs. No API calls.

The pipeline is split across two languages because RAGAS is Python-only and the pipeline is TypeScript. One file sits between them.

The script calls RAGAS’s two faithfulness steps itself instead of metric.ascore(), so it can keep the claims and verdicts. The score still comes from RAGAS’s own _compute_score. RAGAS sends usage analytics by default; the script turns that off.

The 12 questions were written with Claude from the dataset. Each is labelled answer (7) or refuse (5), with a note on why it’s in the set.

Setup

Then:

npm run eval          # answer the 12 questions, then score them (embeds the data on the first run)
npm run walkthrough   # build the page; add -- 6 to follow question 6
npm run score         # rescore the same answers, to see how much the judge moves
npm run cross-judge        # RAGAS's verdicts on rag-langchain's claims
npm run cross-judge-page   # build the comparison page; rerun after editing a decision
npm run report             # build the comparison report

eval/judge.py holds the judge setup that both Python scripts share. Each disagreement’s decision in eval/cross-judge-labels.json records who made it: decisions made from the chunk text quote the evidence line, and judgement calls are marked as such. A rerun of npm run cross-judge can produce new disagreements; the page shows any without a decision as undecided.

A full run costs a few cents. After changing anything in src/lib/pipeline.ts that affects the chunks, run npm run reindex.

The data is in data/; see data/README.md for its source and licence.