To get started, we’ll implement a chatbot that returns a structured
ChatResponse rather than a plain string. This gives your checks access to the
internal ConversationContext — so you can assert that the bot stored a name,
detected a conversation type, or suggested an action, not just that it produced
some text.
With the chatbot in place, we can now write our first scenario. This three-turn
exchange tests greeting, name introduction, and recall in a single run — each
.interact() builds on the previous one so the test reads like the actual
conversation it simulates.
Test a simple greeting and name exchange:
from giskard.checks import Scenario, FnCheck, StringMatching
inputs="What is my name?",outputs=lambdainputs: bot.chat(inputs)
)
.check(
StringMatching(
name="recalls_name",
keyword="Alice",
target_key="trace.last.outputs.message",
)
)
)
import asyncio
asyncdeftest_basic_conversation():
result =await test_scenario.run()
assert result.passed
print("✓ Basic conversation flow test passed")
asyncio.run(test_basic_conversation())
Output
✓ Basic conversation flow test passed
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 chatbot correctly reclassifies the
conversation as it evolves. The Equals check on context.conversation_type is
more precise than checking the response text — it tests the bot’s internal state
directly, catching regressions in context-detection logic even if the response
wording changes.
Verify the chatbot handles different conversation types:
from giskard.checks import Scenario, LLMJudge, Equals, set_default_generator
Next, we’ll evaluate properties that can’t be captured by pattern matching —
specifically, whether the bot sounds professional and whether its response is
actually complete. Two separate LLMJudge checks are used here rather than one
combined prompt so that each dimension reports its result independently, making
it easier to diagnose which aspect failed.
Now we’ll verify that information shared across turns is actually persisted in
the context. This test is intentionally structured to introduce name and email
in separate turns — the final interaction then confirms both fields are still
intact, ruling out extraction bugs that clear previous data.
Test the chatbot’s ability to extract and remember user information:
from giskard.checks import Scenario, FnCheck, Equals
bot =SimpleChatbot()
test_scenario =(
Scenario("information_collection")
# Collect name
.interact(
inputs="Hi, I'm Bob Johnson",outputs=lambdainputs: bot.chat(inputs)
With normal flows verified, we now stress-test the boundaries. Each of the three
scenarios below targets a different failure mode — empty input, excessive
length, and nonsense — so you can confirm the bot handles all of them gracefully
before shipping.
Test how the chatbot handles unusual inputs:
from giskard.checks import Scenario, FnCheck, LLMJudge
bot =SimpleChatbot()
# Test empty input
tc_empty =(
Scenario("empty_input_handling")
.interact(
inputs="",
outputs=lambdainputs:(
bot.chat(inputs)
if inputs
elseChatResponse(
message="I didn't receive a message. Could you try again?",
context=bot.context,
)
),
)
.check(
FnCheck(fn=
lambdatrace:len(trace.last.outputs.message)>0,
name="provides_response",
success_message="Bot provided a response to empty input",
Next, we’ll test a confirmation flow — a pattern common in support bots where
destructive actions require explicit user approval. The checks verify both
directions: that confirmation is requested when it should be, and that the state
is correctly cleared when the user cancels.
Test complex stateful interactions:
from giskard.checks import Scenario, FnCheck, LLMJudge, StringMatching
classStatefulChatbot(SimpleChatbot):
def__init__(self):
super().__init__()
self.awaiting_confirmation =False
self.pending_action =None
defchat(self,user_message:str)-> ChatResponse:
# Handle confirmations
ifself.awaiting_confirmation:
if user_message.lower()in["yes","confirm","ok","sure"]:
response_text =(
f"Great! I'll proceed with {self.pending_action}."
Now we’ll bring all the individual scenarios and test cases together into a
suite class. The add_scenario and add_test methods let you build the suite
incrementally, and run_all executes both categories in sequence so the report
shows the complete picture.