Hallucination Detection
Catch LLM hallucinations in RAG outputs using faithfulness (local NLI) and groundedness (Turing), individually or combined in one evaluate() call.
Catch LLM hallucinations in RAG outputs using two complementary metrics: faithfulness (local NLI, catches contradictions) and groundedness (Turing model, catches unsourced claims), then combine both in a single evaluate() call.
| Time | Difficulty | Package |
|---|---|---|
| 10 min | Beginner | ai-evaluation |
- Future AGI account → app.futureagi.com
- API keys:
FI_API_KEYandFI_SECRET_KEY(see Get your API keys) - Python 3.11+
Install
pip install 'ai-evaluation[nli]'
The [nli] extra installs the local NLI model that faithfulness runs on. Without it, the metric falls back to a less accurate word-overlap heuristic.
export FI_API_KEY="your-api-key"
export FI_SECRET_KEY="your-secret-key"
Tutorial
Score a faithful response
faithfulness checks whether a response is consistent with its retrieved context, no contradictions allowed. It runs entirely on a local NLI model, no API key required.
from fi.evals import evaluate
context = (
"Pro plan subscriptions can be canceled anytime from Account Settings > Billing. "
"Cancellation takes effect at the end of the current billing cycle, and no partial "
"refunds are issued for unused time. Annual plans can be downgraded to monthly "
"only at renewal, not mid-cycle."
)
question = "How do I cancel my Pro plan, and will I get a refund for the unused time?"
# A response that faithfully reflects the context
response = (
"You can cancel your Pro plan anytime from Account Settings > Billing. "
"Cancellation takes effect at the end of your current billing cycle, and you won't "
"get a refund for the unused time."
)
result = evaluate(
"faithfulness",
output=response,
context=context,
input=question,
)
print(f"Faithfulness score : {result.score:.2f}")
print(f"Passed : {result.passed}")
print(f"Reason : {result.reason}")Expected output (illustrative: exact wording depends on the installed NLI model):
Faithfulness score : 1.00
Passed : True
Reason : 2/2 claims supportedEvery claim in the response traces back to the context, so the score is a perfect 1.00.
Detect a hallucinated response
Run the same check against a response that contradicts the context on both cancellation timing and the refund.
from fi.evals import evaluate
hallucinated_response = (
"You can cancel your Pro plan anytime, and it takes effect immediately. "
"You'll receive a prorated refund for the unused portion of the billing cycle."
)
result = evaluate(
"faithfulness",
output=hallucinated_response,
context=context,
input=question,
)
print(f"Faithfulness score : {result.score:.2f}")
print(f"Passed : {result.passed}")
print(f"Reason : {result.reason}")Expected output (illustrative: exact wording depends on the installed NLI model):
Faithfulness score : 0.00
Passed : False
Reason : 0/2 claims supportedNeither claim matches the context, so faithfulness fails the response and reason names both mismatches.
Check groundedness on an ungrounded response
groundedness catches something faithfulness doesn’t: plausible-sounding additions that have no basis in the context at all, rather than direct contradictions. Run it through a Turing model.
from fi.evals import evaluate
# A response that adds a fact not present in the context
ungrounded_response = (
"You can cancel your Pro plan anytime from Account Settings > Billing. "
"Cancellation takes effect at the end of your current billing cycle, with no refund "
"for unused time. Canceling also removes you from the referral rewards program."
)
result = evaluate(
"groundedness",
output=ungrounded_response,
context=context,
input=question,
model="turing_small",
)
print(f"Passed : {result.passed}")
print(f"Reason : {result.reason}")Expected output (illustrative: Turing’s judgment is model-based, so exact wording varies):
Passed : False
Reason : The response includes a claim that is not supported by the provided context.The referral-program claim isn’t in the context, so groundedness fails the response even though the other two sentences are accurate.
Check groundedness on a clean response
Run the same check on a response that stays entirely within the context, to confirm the metric passes clean output.
from fi.evals import evaluate
clean_response = (
"You can cancel your Pro plan anytime from Account Settings > Billing. "
"Cancellation takes effect at the end of your current billing cycle, and there's no "
"refund for unused time."
)
result = evaluate(
"groundedness",
output=clean_response,
context=context,
input=question,
model="turing_small",
)
print(f"Passed : {result.passed}")
print(f"Reason : {result.reason}")Expected output (illustrative: Turing’s judgment is model-based, so exact wording varies):
Passed : True
Reason : All claims are traceable to the provided context.Tip
groundedness can also run locally by omitting model=. For the Turing engine, use turing_flash for lowest latency, turing_small for a balanced default, or turing_large for highest accuracy.
Combine both metrics in one evaluate() call
Pass a list of metric names to run faithfulness and groundedness together on the same output. evaluate() returns a BatchResult you can iterate or index by name.
Warning
This call omits model=. Per evaluate()’s engine routing, no model means both metrics run on the local engine here, not Turing, so groundedness behaves differently than it did in steps 3 and 4. If you add model="turing_small" back to route groundedness to Turing, you’ll hit the mixed-engine trap: faithfulness is local-only, so it silently returns score=None in that batch instead of erroring. Run local and cloud metrics in separate evaluate() calls when you need both.
from fi.evals import evaluate
context = (
"Standard shipping takes 5-7 business days within the continental US. Expedited "
"shipping is available for an additional $12.99 and delivers in 2-3 business days. "
"International shipping is not currently supported. Orders ship Monday through "
"Friday, excluding federal holidays."
)
question = "What shipping options are available and how long does each take?"
response = (
"Standard shipping takes 5-7 business days within the continental US. For an "
"additional $12.99, expedited shipping delivers in 2-3 business days. Orders ship "
"Monday through Friday, excluding federal holidays."
)
results = evaluate(
["faithfulness", "groundedness"],
output=response,
context=context,
input=question,
)
# Iterate over both results
for result in results:
status = "PASS" if result.passed else "FAIL"
if result.eval_name == "groundedness":
# groundedness is a pass/fail check here; see steps 3-4
print(f"{result.eval_name:<15} {status}")
else:
print(f"{result.eval_name:<15} score={result.score:.2f} {status}")
print(f" Reason: {result.reason}")
print()
# Or look up by name directly
faith_result = results.get("faithfulness")
ground_result = results.get("groundedness")
print(f"Both metrics passed: {faith_result.passed and ground_result.passed}")Expected output (illustrative):
faithfulness score=1.00 PASS
Reason: 3/3 claims supported
groundedness PASS
Reason: All claims traceable to context
Both metrics passed: TrueBoth metrics pass because every claim in the response is both consistent with and traceable to the context.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
faithfulness runs slowly or gives a low-confidence reason | The [nli] extra wasn’t installed, so a word-overlap fallback is running instead of the local NLI model | pip install 'ai-evaluation[nli]' and rerun |
evaluate() raises an authentication error on groundedness | FI_API_KEY or FI_SECRET_KEY isn’t set, or is set to a placeholder string | export FI_API_KEY=... and export FI_SECRET_KEY=... with your real keys from app.futureagi.com |
groundedness result flips on a rerun with the same inputs | Turing’s judgment is model-based, not a fixed rule, so wording and edge-case verdicts can vary | Read reason for the actual unsupported claim rather than asserting exact pass/fail in a test suite |
faithfulness returns 1.00 on a response that clearly adds unsourced facts | faithfulness only checks for contradictions, not additions with no basis in the context | Use groundedness alongside faithfulness, as in step 3 |
faithfulness or groundedness returns a low score unexpectedly | context is missing or is a summary that doesn’t actually contain the claims being checked | Pass the full source text as context, not a paraphrase or unrelated passage |
results.get("groundedness") returns None on a batch call | The metric name in the evaluate([...]) list is misspelled, so it’s silently skipped | Check the name against the hallucination metrics reference or the built-in evals catalog |
See RAG Evaluation to score retrieval quality alongside output faithfulness.
Questions & Discussion