On this article, you’ll discover ways to consider LLM functions utilizing the three dominant open-source frameworks — RAGAS, DeepEval, and Promptfoo — and why the LLM-as-a-judge mechanism all of them depend on has measurable biases you have to actively design round.
Subjects we are going to cowl embrace:
- How RAGAS, DeepEval, and Promptfoo differ in goal and when to make use of each, together with which pairings skilled groups converge on.
- implement a faithfulness verify and a CI-gated high quality analysis with working code you’ll be able to run instantly.
- What place bias, self-preference bias, and verbosity bias are, easy methods to detect them with an audit harness, and easy methods to mitigate them in manufacturing.
There’s lots to get by, so let’s get proper into it.

Introduction
You ship an LLM function after seeing a few outputs and resolve it appears to be like good. Three weeks later, a immediate tweak silently breaks one thing no person was testing for, and no person notices till a person complains. That is the default failure mode for LLM functions, and it’s completely different from a typical software program bug. Conventional code fails with a stack hint. LLM outputs fail by being confidently, plausibly unsuitable, which is precisely the sort of failure a fast handbook look received’t catch.
Three open-source instruments dominate the sensible aspect of this drawback in 2026: Promptfoo, DeepEval, and RAGAS. Every is constructed for a special form of drawback, not competing for a similar job. Layered above them are production-monitoring platforms like LangSmith and Braintrust, which choose up the place offline analysis leaves off. None of those instruments wins outright; most mature GenAI QA applications run two of them in parallel: a light-weight framework for blocking unhealthy deploys plus a platform for ongoing monitoring and human evaluate.
This text compares the frameworks that truly matter, walks by examined code for the 2 commonest analysis jobs, and covers the half most comparability items skip totally: the truth that “LLM-as-a-judge“, the mechanism practically each framework right here depends on, has measurable, printed biases you have to design round, not simply belief.
What “Evaluating an LLM” Truly Means
Earlier than evaluating instruments, it helps to separate three issues folks conflate after they say “LLM analysis.” Choosing the unsuitable class right here is the only commonest mistake groups make.
- Mannequin benchmarking compares uncooked mannequin capabilities on standardized educational duties, similar to MMLU, GSM8K, and HumanEval. lm-evaluation-harness is the usual right here, with no actual substitute when the requirement is a standardized educational benchmark. In case you’re selecting between GPT-5 and Claude for a brand new mission, that is the class you need, however it tells you nearly nothing about whether or not your particular utility works.
- Utility analysis asks a narrower, extra helpful query: does your RAG pipeline, chatbot, or agent produce appropriate, grounded, secure outputs in your knowledge and your prompts? That is the place RAGAS, DeepEval, and Promptfoo stay, and it’s the place this text spends most of its time.
- Manufacturing monitoring tracks stay site visitors after deployment, catching regressions and drift that offline check units by no means anticipated. That is LangSmith, Braintrust, and Arize Phoenix territory.
Most individuals asking “which eval framework ought to I take advantage of” really want the second class, usually paired with the third. The remainder of this text focuses on that.
The Metrics Beneath the Frameworks
Earlier than the framework comparability is sensible, it’s value understanding what’s truly being scored, as a result of each instrument under implements some model of the identical handful of metrics.
- Faithfulness (or groundedness) checks whether or not a solution comprises solely claims supported by the retrieved context — the core mechanism for catching RAG hallucinations.
- Context precision and recall verify whether or not retrieval pulled the precise paperwork, and solely the precise ones, earlier than era even occurs.
- Reply relevancy checks whether or not the response truly addresses the query requested, impartial of whether or not it’s factually grounded.
- G-Eval, launched by Liu et al., makes use of chain-of-thought prompting mixed with form-filling to information an LLM decide by an express rubric, and has been proven to align with human desire extra intently than naive “fee this 1-10” prompting. Past these, most frameworks add task-specific checks for toxicity, bias, and PII leakage.
The true differentiator between frameworks isn’t metric novelty; they largely implement the identical handful of concepts. It’s workflow match: how the metric will get triggered, the place the end result goes, and whether or not it blocks a deploy or simply generates a report.
RAGAS vs. DeepEval vs. Promptfoo, Head to Head
RAGAS is research-backed, with academic-grade methodology behind metrics like faithfulness, context precision, and context recall, however it’s scoped to retrieval and era scoring, with no manufacturing monitoring or collaboration layer inbuilt. Choose it when your structure is retrieval-heavy and also you need metrics with a broadcast paper behind their definition, not only a vendor’s inner heuristic.
- DeepEval is Python-native and pytest-based, with 14-plus metrics spanning hallucination, bias, toxicity, and RAG-specific checks — constructed explicitly to perform as a CI/CD high quality gate that may block a deploy. Choose it when analysis must stay inside your present check suite quite than as a separate offline report somebody has to recollect to run.
- Promptfoo is CLI-first and YAML-config-driven, strongest at multi-model immediate comparability and adversarial red-teaming, with 500-plus built-in assault vectors in its security-testing suite. Choose it for immediate engineering iteration throughout a number of fashions, or when red-teaming and safety testing are the precise requirement.
The framing that issues most: DeepEval and RAGAS aren’t actually rivals. DeepEval covers broad LLM utility testing, RAGAS specializes particularly in RAG, and a significant share of manufacturing groups run each collectively — with RAGAS scoring the retrieval-specific dimensions and DeepEval dealing with all the pieces else inside the identical CI pipeline.
| Class | RAGAS | DeepEval | Promptfoo |
|---|---|---|---|
| Finest for | RAG-specific scoring | CI/CD high quality gates | Multi-model comparability, red-teaming |
| Integration fashion | Python library | pytest-native | YAML + CLI |
| Strongest metric set | Faithfulness, context precision/recall | 14+ metrics incl. bias, toxicity | Safety/assault vectors (500+) |
| Manufacturing monitoring | No | No | No |
| Pairs effectively with | DeepEval (broader protection) | RAGAS (RAG-specific depth) | Both for prompt-side testing |
Code Walkthrough: Catching Hallucination with a Faithfulness Examine
Right here’s the mechanism behind RAGAS’s faithfulness metric, demonstrated immediately: decompose a solution into atomic claims, then verify every declare towards the retrieved context. A declare with no help within the context is a hallucination — precisely the failure mode {that a} fast handbook learn tends to overlook, as a result of the unsupported element usually sounds fully believable.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# faithfulness_check.py # Stipulations: none past Python’s customary library (re) # Run: python faithfulness_check.py # # Notice: this demonstrates the faithfulness-checking MECHANISM that RAGAS’s # actual Faithfulness metric implements with an LLM decide. The keyword-overlap # verify under is a simplified, absolutely offline-testable stand-in for that # LLM-based declare verification — swap in RAGAS’s precise metric for manufacturing use.
import re
def decompose_claims(reply: str) -> checklist[str]: “”“Cut up a solution into atomic, independently-checkable statements.”“” sentences = re.cut up(r‘(?<=[.!?])s+’, reply.strip()) return [s.strip() for s in sentences if s.strip()]
def claim_supported_by_context(declare: str, context: str) -> bool: “”“ Examine whether or not a declare has lexical help within the retrieved context. RAGAS does this with an LLM decide; this overlap verify demonstrates the identical supported/unsupported resolution in a deterministic approach. ““” claim_words = set(re.findall(r‘b[a-zA-Z]{4,}b’, declare.decrease())) context_words = set(re.findall(r‘b[a-zA-Z]{4,}b’, context.decrease())) if not claim_words: return True overlap = len(claim_words & context_words) / len(claim_words) return overlap >= 0.5
def compute_faithfulness(reply: str, context: str) -> dict: “”“ Faithfulness rating = fraction of claims within the reply supported by context. This mirrors RAGAS’s precise metric definition: supported claims / complete claims. ““” claims = decompose_claims(reply) supported = [c for c in claims if claim_supported_by_context(c, context)] unsupported = [c for c in claims if c not in supported] rating = len(supported) / len(claims) if claims else 1.0 return { “rating”: spherical(rating, 3), “total_claims”: len(claims), “unsupported_claims”: unsupported, }
if __name__ == “__main__”: context = “Abuja turned the capital of Nigeria in 1991, changing Lagos because the seat of presidency.”
# Case 1: absolutely grounded reply — each declare traces again to the context grounded_answer = “The capital of Nigeria is Abuja. It turned the capital in 1991.” result_1 = compute_faithfulness(grounded_answer, context) print(“Grounded reply:”) print(f” Faithfulness rating: {result_1[‘score’]}”) print(f” Unsupported claims: {result_1[‘unsupported_claims’]}n”)
# Case 2: the mannequin provides a plausible-sounding element the context by no means talked about hallucinated_answer = ( “The capital of Nigeria is Abuja. It turned the capital in 1991. “ “The town has a inhabitants of over 3 million folks.” ) result_2 = compute_faithfulness(hallucinated_answer, context) print(“Reply with a hallucinated element:”) print(f” Faithfulness rating: {result_2[‘score’]}”) print(f” Unsupported claims: {result_2[‘unsupported_claims’]}”) |
run (no dependencies required):
|
python faithfulness_check.py |
Output:
|
Grounded reply: Faithfulness rating: 1.0 Unsupported claims: []
Reply with a hallucinated element: Faithfulness rating: 0.667 Unsupported claims: [‘The city has a population of over 3 million people.’] |
That inhabitants determine sounds totally affordable, which is precisely why a handbook evaluate would doubtless let it by. The faithfulness verify catches it as a result of it’s checking towards the precise retrieved context, not towards basic plausibility. That is the mechanism working beneath RAGAS’s actual Faithfulness metric, which makes use of an LLM to do the declare decomposition and support-checking as a substitute of key phrase overlap — extra correct, similar underlying logic.
To run this with the precise RAGAS library towards a stay mannequin:
|
# Manufacturing sample utilizing the actual RAGAS library # pip set up ragas
from ragas import SingleTurnSample, EvaluationDataset from ragas.metrics import Faithfulness from ragas import consider
pattern = SingleTurnSample( user_input=“What’s the capital of Nigeria?”, response=“The capital of Nigeria is Abuja. It turned the capital in 1991. The town has a inhabitants of over 3 million folks.”, retrieved_contexts=[“Abuja became the capital of Nigeria in 1991, replacing Lagos as the seat of government.”], ) dataset = EvaluationDataset(samples=[sample]) outcomes = consider(dataset, metrics=[Faithfulness()]) print(outcomes) |
Code Walkthrough: CI-Gated Analysis with DeepEval
The sample that makes DeepEval distinct from a standalone analysis script is that it runs as an actual pytest check, which means a top quality regression fails the construct the identical approach a damaged unit check would — as a substitute of producing a report somebody has to recollect to learn.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# test_response_quality.py # Stipulations: pip set up deepeval pytest # Set your decide mannequin’s API key as an surroundings variable earlier than working # Run: deepeval check run test_response_quality.py
import pytest from deepeval import assert_test from deepeval.test_case import LLMTestCase from deepeval.metrics import GEval
# G-Eval allows you to outline a customized rubric in plain language — the LLM decide # makes use of chain-of-thought reasoning towards this rubric quite than a generic # “fee this 1-10” immediate, which is what provides G-Eval higher alignment # with human judgment than naive scoring prompts. correctness_metric = GEval( identify=“Coverage Accuracy”, standards=( “Decide whether or not the precise output precisely displays firm coverage “ “with out including unspoken situations or omitting required disclosures.” ), evaluation_params=[“input”, “actual_output”], threshold=0.7, # Minimal rating to go — tune based mostly in your threat tolerance )
def test_refund_policy_response(): “”“ This check fails the construct if the mannequin’s refund coverage clarification drops under the correctness threshold — the identical approach a damaged assertion would fail another pytest check. ““” test_case = LLMTestCase( enter=“What’s the refund coverage?”, actual_output=“You’ll be able to request a refund inside 30 days of buy, no questions requested.”, ) assert_test(test_case, [correctness_metric])
def test_refund_policy_response_with_unstated_condition(): “”“ This case demonstrates what a FAILING check appears to be like like: the response provides a situation (“solely for unopened objects“) that wasn’t a part of the precise coverage being examined towards, which ought to drag the rating down. ““” test_case = LLMTestCase( enter=“What’s the refund coverage?”, actual_output=( “You’ll be able to request a refund inside 30 days, however just for unopened objects “ “and solely when you’ve got the unique receipt and packaging.” ), ) assert_test(test_case, [correctness_metric]) |
Stipulations:
|
pip set up deepeval pytest export OPENAI_API_KEY=your_key # DeepEval makes use of an LLM decide below the hood |
run:
|
deepeval check run test_response_quality.py |
The primary check ought to go; the response matches a believable, unembellished coverage assertion. The second is written to show a probable failure: it provides situations that weren’t a part of the unique enter, which a well-configured G-Eval rubric ought to catch and rating under the 0.7 threshold. In an actual CI pipeline, that failure blocks the merge — precisely the conduct that turns analysis from “one thing we must always verify on” into “one thing the pipeline enforces.”
The Downside No one Mentions: LLM-as-a-Choose Is Biased
Each framework above depends on the identical underlying mechanism for the metrics that matter most: an LLM judging one other LLM’s output. That decide just isn’t impartial, and the analysis on that is extra developed than most groups understand.
- Place bias is the best-documented of those. A systematic research at IJCNLP 2025 evaluated 15 LLM judges throughout roughly 150,000 analysis situations and located that judges systematically favor whichever response sits in a selected slot of the immediate, and that this impact just isn’t attributable to random probability. Swap the order of two responses being in contrast, and the identical decide can flip its verdict purely due to the place every response now sits — not as a result of both response modified.
- Self-preference bias compounds this. Fashions disproportionately favor outputs generated by themselves or by fashions in their very own household, which means that for those who use GPT-4o to evaluate GPT-4o’s personal outputs, the ensuing rating is inflated above what an impartial decide would assign. Analysis distinguishes this from real high quality variations: not each self-preference sign is biased, however the dangerous model is particularly when a decide fails to penalize its personal mannequin household’s errors.
- Verbosity bias is the third main one: longer responses get rated greater impartial of whether or not the added size comprises something helpful. A decide evaluating a concise, appropriate reply towards a padded, partially redundant one will usually favor the longer one.
The usually-cited statistic that LLM judges attain roughly 80% settlement with human evaluators comes from the unique MT-Bench research and is correct as an mixture determine, however it describes common efficiency throughout a broad benchmark — not reliability in your particular activity together with your particular decide mannequin. Treating that quantity as a production-readiness assure is the error.
Code: A Place-Bias Detection Harness
The only highest-leverage verify most groups skip: run the identical pairwise comparability twice, with the response order swapped, and see whether or not the decision flips.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# position_bias_audit.py # Stipulations: none past Python’s customary library (random, dataclasses) # Run: python position_bias_audit.py
import random from dataclasses import dataclass
@dataclass class PairwiseResult: question: str verdict_original_order: str verdict_swapped_order: str position_consistent: bool # False means the decision flipped purely on slot place
def run_position_bias_check(question: str, response_x: str, response_y: str, judge_fn) -> PairwiseResult: “”“ Run the identical comparability twice with positions swapped. An unbiased decide ought to choose the identical underlying response each instances no matter which slot it occupies. A flip signifies place bias, not a real high quality sign. ““” # Spherical 1: response_x in slot A, response_y in slot B verdict_1 = judge_fn(response_x, response_y) winner_1 = response_x if verdict_1 == “A” else response_y
# Spherical 2: swap — response_y now in slot A, response_x in slot B verdict_2 = judge_fn(response_y, response_x) winner_2 = response_y if verdict_2 == “A” else response_x
return PairwiseResult( question=question, verdict_original_order=verdict_1, verdict_swapped_order=verdict_2, position_consistent=(winner_1 == winner_2), )
def audit_position_bias(test_pairs: checklist[tuple], judge_fn, n_trials: int = 50) -> dict: “”“ Run many position-swapped comparisons and report the speed of inconsistent verdicts. A excessive fee means your decide is responding to fit place, not response high quality — and any rating it produces ought to be handled with actual skepticism till that is addressed. ““” outcomes = [] for question, resp_x, resp_y in test_pairs: for _ in vary(n_trials // len(test_pairs)): outcomes.append(run_position_bias_check(question, resp_x, resp_y, judge_fn))
inconsistent = [r for r in results if not r.position_consistent] return { “total_trials”: len(outcomes), “inconsistent_count”: len(inconsistent), “inconsistency_rate”: spherical(len(inconsistent) / len(outcomes), 3), }
if __name__ == “__main__”: # In manufacturing, change this with an actual name to your decide LLM evaluating # response_1 vs response_2 and returning “A” or “B”. def your_judge_function(response_1: str, response_2: str) -> str: # Placeholder — wire this as much as your precise decide mannequin name. elevate NotImplementedError(“Exchange together with your actual LLM decide name”)
test_pairs = [ (“Summarize the quarterly report”, “Response variant A”, “Response variant B”), ]
# Demo with a simulated 70%-position-A-biased decide, for illustration random.seed(7) def demo_biased_judge(r1, r2): return “A” if random.random() < 0.7 else “B”
report = audit_position_bias(test_pairs, demo_biased_judge, n_trials=200) print(f“Inconsistency fee: {report[‘inconsistency_rate’] * 100:.1f}%”) print(f“({report[‘inconsistent_count’]}/{report[‘total_trials’]} trials flipped purely on place swap)”) print(“nA fee meaningfully above 0% signifies place bias in your decide setup.”) print(“Mitigation: common scores throughout each orderings, or use a separate decide”) print(“mannequin from a special household than the mannequin being evaluated.”) |
run (no dependencies required):
|
python position_bias_audit.py |
Output (with the simulated biased decide):
|
Inconsistency fee: 55.5% (111/200 trials flipped purely on place swap)
A fee meaningfully above 0% signifies place bias in your decide setup. Mitigation: common scores throughout each orderings, or use a separate decide mannequin from a completely different household than the mannequin being evaluated |
A 55% flip fee is a extreme case used right here for readability; most actual judges aren’t this biased, however even a flip fee within the 10–15% vary — which exhibits up repeatedly in manufacturing decide setups — is sufficient to make a borderline go/fail resolution unreliable. The repair prices one additional LLM name per analysis: run each orderings and common, or, extra robustly, use a decide mannequin from a special household than whichever mannequin produced the responses being scored, which immediately addresses the self-preference situation on the similar time.
Selecting Your Stack
There’s no common reply right here, however the resolution tree is pretty clear as soon as what you’re constructing:
- RAG utility: begin with RAGAS for the retrieval-specific metrics (faithfulness, context precision, context recall). Add DeepEval alongside it for those who want broader protection of toxicity, bias, and basic correctness in the identical check suite.
- Basic chatbot or content-generation function with CI necessities: DeepEval, run as a part of your present pytest suite, gating merges on a top quality threshold.
- Immediate iteration throughout a number of fashions, or safety/red-teaming: Promptfoo. Its YAML-driven config makes multi-model comparability quick, and its built-in assault suite is essentially the most full open-source possibility for adversarial testing.
- Manufacturing tracing and human evaluate after deployment: LangSmith in case your stack is already constructed on LangChain or LangGraph; Braintrust in case your stack is extra heterogeneous and also you desire a platform that isn’t coupled to 1 framework.
The sample skilled groups converge on is 2 instruments, not one: a light-weight framework for CI-time gating, paired with a platform for ongoing monitoring, regression monitoring, and the human annotation that no automated metric absolutely replaces.
Conclusion
No framework right here wins outright, as a result of they’re not fixing the identical drawback. RAGAS, DeepEval, and Promptfoo every match a special form of analysis want, and the actual architectural resolution is normally “which two of those do I run collectively,” not “which one is greatest.”
The larger threat isn’t choosing the unsuitable instrument from this checklist; it’s trusting no matter rating an LLM decide produces with out checking it for the biases documented above. Place bias, self-preference, and verbosity bias aren’t edge instances buried in a tutorial paper; they’re measurable results that present up in strange decide setups, together with those contained in the very frameworks this text simply walked by. The frameworks provide the scoring mechanism. The audit behavior within the position-bias part is what makes the ensuing quantity value trusting.








