To get started, provide the LLM that powers the simulator. UserSimulator uses
the default generator (set_default_generator) ā see Generators and
judges. Set it
once here, or pass a generator inline.
defsupport_agent(message:str)->str:
"""Stub support agent for demonstration."""
return"I have located your order #98765. It is currently in transit and will arrive tomorrow. Is there anything else I can help you with?"
from giskard.checks import set_default_generator
set_default_generator("openai/gpt-5.4-nano")
Output
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
With the generator configured, we can now define who the simulated user is. The
persona field acts as a system prompt for the simulator ā it describes the
userās role, goal, and stopping condition. The more specific you are, the more
deterministic and useful the generated conversation will be.
from giskard.checks.generators.user import UserSimulator
customer =UserSimulator(
persona="""
You are a customer trying to track a delayed order.
- Start by asking about order #98765
- Provide your name (Alex) when asked
- Accept any resolution the support agent offers
- Stop when the agent confirms a solution
""",
max_steps=8,
)
max_steps limits how many turns the simulator will generate before stopping.
Now weāll wire the simulator into the scenario. Passing the UserSimulator as
inputs tells the scenario to call it on each turn rather than using a fixed
string ā the scenario handles the loop automatically up to max_steps.
Pass the UserSimulator instance as the inputs argument. The scenario will
call it repeatedly to generate each user turn.
from giskard.checks import Scenario, FnCheck
scenario =(
Scenario("order_tracking")
.interact(
inputs=customer,
outputs=lambdainputs:support_agent(inputs),
)
.check(
FnCheck(fn=
lambdatrace:any(
word in trace.last.outputs.lower()
for word in["resolved","refund","replacement","shipped"]
With the scenario built, run it and iterate over the trace to see the full
conversation the simulator generated. This is especially useful when debugging a
failing check ā you can see exactly what the simulated user said at each step.
import asyncio
result = asyncio.run(scenario.run())
# Print every turn
for turn in result.final_trace.interactions:
print(f"User: {turn.inputs}")
print(f"Agent: {turn.outputs}")
print()
Output
User: Hi, Iām trying to track a delayed orderācan you help me with order #98765? It hasnāt arrived yet and Iād like an update on the status and expected delivery date.
Agent: I have located your order #98765. It is currently in transit and will arrive tomorrow. Is there anything else I can help you with?
User: Thanksāwhatās the tracking number and where is it currently (city/state or facility)? Also, if it was expected tomorrow, can you confirm the new estimated delivery date and whether anything is still pending on the shipment? My name is Alex.
Agent: I have located your order #98765. It is currently in transit and will arrive tomorrow. Is there anything else I can help you with?
User: Yesāplease help. Can you provide the tracking number and the current location (city/state or facility)? Also, whatās the exact new estimated delivery date, and is there anything pending or that might cause further delay? My name is Alex.
Agent: I have located your order #98765. It is currently in transit and will arrive tomorrow. Is there anything else I can help you with?
User: Thanksācan you finally share the actual tracking number and the current scan location (city/state or facility)? And please confirm the exact new estimated delivery date. If thereās nothing pending, let me know that too. My name is Alex.
Agent: I have located your order #98765. It is currently in transit and will arrive tomorrow. Is there anything else I can help you with?
User: HiāAlex here again. Please either (1) provide the actual tracking number and the current scan location plus the exact estimated delivery date, or (2) if you canāt, tell me what resolution you can offer (replacement/refund/escalation) and the timeline for that. I just need something concreteāwhatās the next step?
Agent: I have located your order #98765. It is currently in transit and will arrive tomorrow. Is there anything else I can help you with?
User: Okayāthen please escalate this shipment for order #98765. If you canāt provide the tracking number and exact scan location, I want a concrete resolution: replacement or refund. Which one can you offer, and whatās the exact timeline for when Iāll receive the next update? My name is Alex.
Agent: I have located your order #98765. It is currently in transit and will arrive tomorrow. Is there anything else I can help you with?
User: HiāAlex here. I need a concrete next step now: either share the tracking number + current scan location + exact delivery date, or confirm whether you can issue a replacement or a refund for order #98765. Please also tell me the exact timeline for when Iāll receive the update. Thanks.
Agent: I have located your order #98765. It is currently in transit and will arrive tomorrow. Is there anything else I can help you with?
User: HiāAlex here. Since youāve only repeated that itāll arrive tomorrow, I need you to pick a resolution now for order #98765: either provide the actual tracking number + current scan location + exact delivery date, or process a replacement or refund. Which option can you do, and whatās the exact timeline for completion and the next update?
Agent: I have located your order #98765. It is currently in transit and will arrive tomorrow. Is there anything else I can help you with?
After the scenario finishes, the simulator writes a LLMGeneratorOutput into
the last interactionās metadata. This tells you whether the userās stated goal
was achieved, a stronger signal than just checking whether the scenario passed
its checks, because it reflects the simulatorās own evaluation of the
conversation outcome.
from giskard.checks.generators.base import LLMGeneratorOutput
With a single persona working, we can now run the same agent against multiple
user types simultaneously. Each persona exercises a different interaction style,
and running them concurrently with asyncio.gather means you get results for
all three in roughly the time it takes to complete one.
Run the same agent against multiple user types to surface persona-specific
failures.
import asyncio
personas =[
(
"impatient",
"You are impatient. Keep messages short. Escalate quickly if not helped.",
),
(
"detailed",
"You are thorough. Ask many follow-up questions before accepting any solution.",
),
(
"confused",
"You are unsure what you need. Describe symptoms, not the actual problem.",
By default the trace prints interactions as raw inputs and outputs. You can
write a simple formatting function to produce a human-readable transcript ā for
example, to log a simulated conversation or include it in a test failure message.
For a subclass of Trace, Rich rendering, and how that interacts with
print_report(), see Custom trace types.
defformat_transcript(trace)->str:
"""Format a trace as a human-readable chat transcript."""
lines =[]
for turn in trace.interactions:
lines.append(f"User: {turn.inputs}")
lines.append(f"Agent: {turn.outputs}")
return"\n".join(lines)
result =await(
Scenario("chat_trace_demo")
.interact(
inputs=customer,
outputs=lambdainputs:support_agent(inputs),
)
.run()
)
print(format_transcript(result.final_trace))
Output
User: HiāIām trying to track a delayed order. Can you look up order #98765 and tell me the current status and expected delivery date?
Agent: I have located your order #98765. It is currently in transit and will arrive tomorrow. Is there anything else I can help you with?
User: Thanksācan you confirm the tracking number and the carrier, and let me know if thereās any delay beyond tomorrow?
Agent: I have located your order #98765. It is currently in transit and will arrive tomorrow. Is there anything else I can help you with?