To get started, we’ll implement a minimal RAG pipeline whose public interface
mirrors what a production system would expose — a single answer() method that
returns both the generated answer and the retrieved documents. Returning this
rich RAGResponse object lets our checks inspect retrieval quality
independently of answer quality.
With the RAG system defined, we need a controlled knowledge base to test
against. Using a small, deterministic set of documents means we know exactly
which facts should be retrievable — making it straightforward to assert on both
retrieval hits and misses.
Create a knowledge base for testing:
knowledge_base =[
Document(
content=(
"Paris is the capital and largest city of France. "
"It is known for the Eiffel Tower."
),
metadata={"source":"geography","topic":"France"},
),
Document(
content=(
"The Eiffel Tower is a wrought-iron lattice tower in Paris. "
With the test data in place, we can now write our first scenario. This test
stacks three checks on a single interaction — content, retrieval presence, and
confidence — so a single run tells you whether the pipeline is working
end-to-end.
Test passed: True
pass
pass
pass
Thank you for using Giskard open-source! 🐢 🙏
Giskard Enterprise adds deeper agent scans, audit reports
with remediation guidance, test review interfaces
for root-cause analysis & human feedback integration,
and team collaboration — with flexible pricing.
Learn more: https://giskard.ai
Building on Test 1, we now verify that the answer doesn’t introduce facts absent
from the retrieved documents. The Groundedness check uses an LLM to compare
the answer against the context, catching hallucinations that StringMatching
would miss.
Verify that answers are grounded in retrieved context:
from giskard.checks import Scenario, Groundedness, StringMatching
Next, we’ll isolate retrieval from generation and verify that the documents
returned for a query are topically relevant. This separation matters because a
failure in retrieval will silently produce a low-confidence or hallucinated
answer — and this test lets you catch that upstream.
Test that the right documents are retrieved:
from giskard.checks import Scenario, FnCheck
defcheck_retrieved_topics(trace)->bool:
"""Verify retrieved docs are about the right topic."""
docs = trace.last.outputs.retrieved_docs
topics =[doc.metadata.get("topic")for doc in docs]
Now we’ll verify the system’s failure mode. A well-behaved RAG pipeline should
return zero documents and a graceful fallback message when no relevant content
exists — not a hallucinated answer that sounds plausible.
Test how the system handles questions it can’t answer:
from giskard.checks import Scenario, LLMJudge, FnCheck
With structural and retrieval checks in place, we can now add a holistic quality
evaluation. LLMJudge is the right tool here because “answer quality” is a
composite signal — accuracy, completeness, clarity, and relevance — that no
single keyword or numeric threshold can capture.
Use an LLM to evaluate answer quality comprehensively:
from giskard.checks import Scenario, LLMJudge
tc =(
Scenario("comprehensive_quality_check")
.interact(
inputs="What is machine learning?",
outputs=lambdainputs: rag.answer(inputs),
)
.check(
LLMJudge(
name="answer_quality",
prompt="""
Evaluate the answer quality based on these criteria:
Next, we’ll extend the RAG system to handle conversational follow-ups. This test
uses two .interact() calls in the same scenario so the trace records both
turns, letting the LLMJudge check verify that the second answer correctly
resolves the pronoun reference from the first.
Test a conversational RAG that handles follow-up questions:
success_message="Follow-up answer discusses Paris / Eiffel Tower",
failure_message="Follow-up answer did not resolve the reference",
)
)
)
asyncdeftest_conversational_rag():
result =await test_scenario.run()
result.print_report()
print(f"Conversational RAG test passed: {result.passed}")
asyncio.run(test_conversational_rag())
Output
──────────────────────────────────────────────────── ✅ PASSED ────────────────────────────────────────────────────first_answer_groundedPASSfirst_mentions_parisPASSfollowup_groundedPASSresolves_referencePASS────────────────────────────────────────────────────── Trace ──────────────────────────────────────────────────────────────────────────────────────────────────────── Interaction 1 ──────────────────────────────────────────────────
Inputs: 'What is the capital of France?'
Outputs: RAGResponse(question='What is the capital of France?', answer='Based on the available information: Paris is the capital and largest city of France. It is known for the Eiffel Tower.\nThe Eiffel Tower is a wrought-iron lattice tower in Paris. It was completed in 1889.\nFrance is a country in Western E...',
retrieved_docs=[Document(content='Paris is the capital and largest city of France. It is known for the Eiffel Tower.', metadata={'source': 'geography', 'topic': 'France'}), Document(content='The Eiffel Tower is a wrought-ironlattice tower in Paris. It was completed in 1889.', metadata={'source': 'landmarks', 'topic': 'Eiffel Tower'}),
Document(content='France is a country in Western Europe. It has a population of about 67 million.',
metadata={'source': 'geography', 'topic': 'France'})], confidence=1.0)────────────────────────────────────────────────── Interaction 2 ──────────────────────────────────────────────────
Inputs: 'What is it known for?'
Outputs: RAGResponse(question='What is it known for? (referring to: What is the capital of France?)', answer='Basedon the available information: Paris is the capital and largest city of France. It is known for the Eiffel Tower.\nThe Eiffel Tower is a wrought-iron lattice tower in Paris. It was completed in 1889.\nFrance is a country in Western E...', retrieved_docs=[Document(content='Paris is the capital and largest city of France. It is known for the Eiffel Tower.', metadata={'source': 'geography', 'topic': 'France'}), Document(content='The Eiffel Tower isa wrought-iron lattice tower in Paris. It was completed in 1889.', metadata={'source': 'landmarks', 'topic':
'Eiffel Tower'}), Document(content='France is a country in Western Europe. It has a population of about 67 million.', metadata={'source': 'geography', 'topic': 'France'})], confidence=1.0)────────────────────────────────────────── 2 steps in 4564ms | runs: 1/1 ──────────────────────────────────────────
Now we’ll bring all the individual tests together into a single suite class.
Organizing tests into _create_qa_tests, _create_groundedness_tests, and
_create_edge_case_tests methods keeps each concern separate and makes it easy
to run only one category during development.
The answer does not contain the keyword ‘programming language’
✓ groundedness_What is the capital
✓ groundedness_Tell me about the Ei
✓ groundedness_What is machine lear
✓ edge_case_empty_query
✓ edge_case_whitespace_query
✓ edge_case_out_of_scope
✓ edge_case_gibberish