Simulation Testing: AI Agent Testing SDK Module

Test AI agents at scale with simulated customer personas. Run multi-turn conversations against your agent and score the results automatically.

📝
TL;DR
  • pip install agent-simulate — separate package from the core SDK
  • Simulate multi-turn conversations with configurable customer personas
  • Cloud mode needs only the base install; local voice mode is an optional [livekit] extra

Simulation testing lets you run automated conversations against your AI agents using synthetic customer personas. For the full platform guide, see Simulation docs. Each simulation produces a transcript and evaluation scores, plus audio recordings when you run the local voice mode.

Note

Requires pip install agent-simulate and FI_API_KEY + FI_SECRET_KEY in your environment. The local voice mode is not installed by default; it needs pip install agent-simulate[livekit] and a LiveKit deployment your agent is already connected to.

Two modes

TestRunner.run_test() is one method that picks its mode from the arguments you pass.

ModeSelected byWhat happensRequires
Cloudrun_id or run_test_nameFuture AGI orchestrates the simulated customer and calls your agent_callback each turnFI_API_KEY and FI_SECRET_KEY
Local voiceagent_definitionA simulated customer joins your agent’s LiveKit room over WebRTCagent-simulate[livekit] and a running LiveKit deployment

Passing neither raises ValueError. The two argument sets do not combine: if you pass both, run_id or run_test_name wins and the local-mode arguments are ignored.

Quick Example

import asyncio
from fi.simulate import TestRunner, AgentInput, AgentResponse

runner = TestRunner()

async def my_agent(input: AgentInput) -> str:
    """Your agent logic — receives conversation history, returns a response."""
    user_message = (input.new_message or {}).get("content", "")
    return f"I can help with that: {user_message}"

asyncio.run(runner.run_test(
    run_test_name="basic-test",
    agent_callback=my_agent,
))

run_test_name must match the name of a run test you created on the platform. Pass run_id instead if you already have its ID.

TestRunner

The main entry point for running simulations.

from fi.simulate import TestRunner

runner = TestRunner(
    api_key="...",       # or FI_API_KEY env var
    secret_key="...",    # or FI_SECRET_KEY env var
)

run_test()

Cloud mode:

await runner.run_test(
    run_test_name="my-test",   # or run_id="<uuid>"
    agent_callback=my_agent,
    concurrency=5,
)

Local voice mode, which needs the [livekit] extra:

await runner.run_test(
    agent_definition=agent,    # your deployed agent's LiveKit room
    scenario=scenario,
    record_audio=True,
    max_seconds=45.0,
)
ParameterTypeDefaultModeDescription
run_idstr or NoneNoneCloudID of the run test to execute
run_test_namestr or NoneNoneCloudName of the run test, resolved to an ID for you; use instead of run_id
agent_callbackcallable or AgentWrapperNoneCloudYour agent function or wrapper instance
concurrencyint5CloudHow many calls run in parallel
agent_definitionAgentDefinitionNoneLocalThe deployed voice agent to dial into
scenarioScenario or NoneNoneLocalPre-defined scenario with personas
simulatorSimulatorAgentDefinition or NoneNoneLocalOverrides the simulated customer’s LLM, TTS, STT, and VAD settings
num_scenariosint1LocalScenarios to generate when scenario is omitted
topicstr or NoneNoneLocalTopic for auto-generated scenarios
record_audioboolFalseLocalCapture per-speaker and combined WAV files
min_turn_messagesint8LocalMinimum messages per conversation
max_secondsfloat45.0LocalMaximum duration per conversation

Note

run_test returns a TestReport in both modes, but in cloud mode its results field is always empty. Transcripts, metrics, and evaluations live on the platform; read them from the dashboard. A cloud conversation also stops after 50 turns if the platform hasn’t ended it first.

Agent Callback

Your agent receives an AgentInput and returns either a string or an AgentResponse.

from fi.simulate import AgentInput, AgentResponse

# Simple — return a string
async def simple_agent(input: AgentInput) -> str:
    user_msg = (input.new_message or {}).get("content", "")
    # Call your LLM here
    return "Your response"

# Advanced — return AgentResponse with tool calls
async def advanced_agent(input: AgentInput) -> AgentResponse:
    return AgentResponse(
        content="Let me check that for you.",
        tool_calls=[{"name": "lookup_order", "arguments": {"order_id": "12345"}}],
        metadata={"intent": "order_lookup"},
    )

AgentInput

FieldTypeDescription
thread_idstrConversation ID
messageslistFull conversation history
new_messagedict or NoneLatest user message ({"role": "user", "content": "..."})
execution_idstr or NoneExecution tracking ID

AgentResponse

FieldTypeDescription
contentstrAgent’s text response
tool_callslist or NoneTool/function calls made
tool_responseslist or NoneResults from tool calls
metadatadict or NoneCustom metadata

Scenarios and Personas

A Scenario is a named list of personas. It is a local voice mode argument: in cloud mode the personas come from the run test you configured on the platform, and a scenario passed to run_test is ignored.

from fi.simulate import AgentDefinition, Scenario, Persona

agent = AgentDefinition(
    name="billing-support-agent",
    url="wss://your-livekit-server.com",
    room_name="support-room",
    system_prompt="You are a helpful billing support agent.",
)

scenario = Scenario(
    name="billing-complaints",
    description="Customers with billing issues",
    dataset=[
        Persona(
            persona={"name": "Sarah", "age": 35, "communication_style": "frustrated"},
            situation="Charged twice for the same order",
            outcome="Get a refund and confirmation email",
        ),
        Persona(
            persona={"name": "Mike", "age": 62, "communication_style": "confused"},
            situation="Doesn't understand a charge on the statement",
            outcome="Get a clear explanation of the charge",
        ),
    ],
)

asyncio.run(runner.run_test(
    agent_definition=agent,
    scenario=scenario,
))
Was this page helpful?

Questions & Discussion