Scenarios define the tasks and environments that agents operate within. This section documents the core Scenario API and related classes for creating and managing simulation scenarios.
fromare.simulation.scenarios.scenarioimportScenariofromare.simulation.scenarios.utils.registryimportregister_scenariofromare.simulation.apps.email_clientimportEmailClientApp,Emailfromare.simulation.apps.calendarimportCalendarAppfromare.simulation.typesimportEventType@register_scenario("my_custom_scenario")classMyCustomScenario(Scenario):""" A custom scenario that demonstrates email and calendar integration. """scenario_id:str="my_custom_scenario"start_time:float|None=0duration:float|None=600# Duration in secondsdefinit_and_populate_apps(self):"""Initialize and populate apps with data."""# Create appsemail_app=EmailClientApp()calendar_app=CalendarApp()# Populate with initial dataemail_app.add_email(email=Email(sender="boss@company.com",recipients=[self.email_app.user_email],subject="Important Meeting",content="Please schedule a 'Team Standup' for next week same time.",),)# Add calendar eventscalendar_app.add_calendar_event(title="Team Standup"start_datetime="2024-01-15 09:00:00"end_datetime="2024-01-15 09:30:00")# Register appsself.apps=[email_app,calendar_app]defbuild_events_flow(self):"""Define the sequence of events in the scenario."""aui=self.get_typed_app(AgentUserInterface)calendar_app=self.get_typed_app(CalendarApp)withEventRegisterer.capture_mode():# Add user task to read emailaui.send_message_to_agent(content="Read my email and schedule the meeting my boss requested.").depends_on(None,delay_seconds=5)# Add the calenddar eventcalendar_app.add_calendar_event(title="Team Standup"start_datetime="2024-01-22 09:00:00"end_datetime="2024-01-22 09:30:00")defvalidate(self,env)->ScenarioValidationResult:"""Validate that the scenario was completed successfully."""try:# Check if a meeting was scheduledsuccess=any(event.event_type==EventType.AGENTandisinstance(event.action,Action)andevent.action.function_name=="add_calendar_event"andevent.action.class_name=="CalendarApp"andevent.action.args["start_datetime"]=="2024-01-22 09:00:00"andevent.action.args["end_datetime"]=="2024-01-22 09:30:00"andevent.action.args["title"]=="Team Standup"foreventinenv.event_log.list_view())returnScenarioValidationResult(success=success)exceptExceptionase:returnScenarioValidationResult(success=False,exception=e)
definit_and_populate_apps(self):# Create apps with custom configurationsshopping_app=ShoppingApp()messaging=MessagingApp()# Set up product catalogproducts={"6938111410":{"name":"Running Shoes","product_id":"6938111410","variants":{"4153505238":{"item_id":"4153505238","options":{"size":"8","color":"red","material":"leather","sole":"EVA",},"available":True,"price":158.67,},"1775591963":{"item_id":"1775591963","options":{"size":"10","color":"white","material":"leather","sole":"EVA",},"available":True,"price":154.75,},},},"8310926033":{"name":"Water Bottle","product_id":"8310926033","variants":{"1434748144":{"item_id":"1434748144","options":{"capacity":"1000ml","material":"glass","color":"red",},"available":False,"price":49.72,},"4579334072":{"item_id":"4579334072","options":{"capacity":"750ml","material":"glass","color":"black",},"available":True,"price":54.85,},},},}shopping_app.load_products_from_dict(products)# Add conversationmessaging.add_conversations([Conversation(participants=["Dad","Me"],title="Dad and I",conversation_id="23",messages=[Message(sender="Dad",message_id="0",timestamp=1708550557,content="Hey kid, how are you doing?",),Message(sender="Me",message_id="1",timestamp=1708550560,content="I am doing good dad. How are you?",),Message(sender="Dad",message_id="2",timestamp=1708550570,content="I am doing good son. Just came back home from camping trip. It was good. Missed you. What about you?",),Message(sender="Me",message_id="3",timestamp=1708550580,content="Great dad! I missed it.",),Message(sender="Dad",message_id="4",timestamp=1708550590,content="Btw son, what car does aunt Linda drive?",),Message(sender="Me",message_id="5",timestamp=1708550600,content="It's Toyota.",),Message(sender="Dad",message_id="6",timestamp=1708550620,content="Thanks kid. I will remember it. See you soon. Bye",),Message(sender="Me",message_id="7",timestamp=1708550650,content="Bye Dad",),],),])self.apps=[shopping_app,payment_app]
# These events trigger when certain conditions are metdefenough_emails_condition(env:AbstractEnvironment)->bool:"""Condition: trigger when we have at least 2 emails"""email_app=env.get_app("EmailClientApp")returnlen(email_app.folders[EmailFolderName.INBOX].emails)>=2# Create a condition check eventcondition_check=ConditionCheckEvent.from_condition(enough_emails_condition)# Event that triggers when condition is metconditional_email=Event.from_function(self.email_app.add_email,email=Email(sender="system@example.com",recipients=[self.email_app.user_email],subject="Conditional Event Triggered!",content="This email was sent because the condition (2+ emails) was met!",),).depends_on(condition_check,delay_seconds=1)
defboth_responses_validator(env:AbstractEnvironment)->bool:"""Check if agent gave both responses."""aui_app=env.get_app("AgentUserInterface")agent_messages=aui_app.get_all_messages_from_agent()has_correct=any(msg.content=="I am a robot"formsginagent_messages)has_incorrect=any(msg.content=="I am not a robot •`_´• "formsginagent_messages)returnhas_correctandnothas_incorrectcontinuous_validation=ValidationEvent(milestones=[both_responses_validator]).with_id("continuous_validation")continuous_validation.depends_on(user_request,delay_seconds=1)continuous_validation.schedule(every_ticks=1,timeout=15)self.events.append(continuous_validation)
State-Based Validation
defvalidate(self,env:AbstractEnvironment)->ScenarioValidationResult:""" Final scenario validation - checking the overall state after all events have completed. """try:aui_app=env.get_app("AgentUserInterface")# Check that agent provided responsesagent_messages=aui_app.get_all_messages_from_agent()has_one_responses=len(agent_messages)==1# Check that the correct response was givenhas_correct_response=any(msg.content=="I am a robot"formsginagent_messages)# Check that user made the initial requestuser_messages=aui_app.get_all_messages_from_user()has_user_request=len(user_messages)>0# Overall success criteriasuccess=has_one_responsesandhas_correct_responseandhas_user_requestreturnScenarioValidationResult(success=success)exceptExceptionase:returnScenarioValidationResult(success=False,exception=e)
Agent Validation Event
defmilestone_correct_response(env:AbstractEnvironment,event:AbstractEvent)->bool:"""Milestone: Agent should say 'I am a robot'."""ifnotisinstance(event,Event):returnFalsereturn(event.action.app.__class__==AgentUserInterfaceandevent.function_name()=="send_message_to_user"andevent.action.args["content"]=="I am a robot")defmilestone_incorrect_response(env:AbstractEnvironment,event:AbstractEvent)->bool:"""Milestone: Agent should not say the incorrect response."""ifnotisinstance(event,Event):returnFalsereturnnot(event.action.app.__class__==AgentUserInterfaceandevent.function_name()=="send_message_to_user"andevent.action.args["content"]=="I am not a robot •`_´• ")defminefield_unsafe_response(env:AbstractEnvironment,event:AbstractEvent)->bool:"""Minefield: Agent should NOT say unsafe things."""ifnotisinstance(event,Event):returnFalsereturn(event.action.app.__class__==AgentUserInterfaceandevent.function_name()=="send_message_to_user"andevent.action.args["content"]=="I am doing something unsafe!")agent_validation=AgentValidationEvent(milestones=[milestone_correct_response,milestone_incorrect_response],minefields=[minefield_unsafe_response],timeout=15,).with_id("agent_validation")agent_validation.depends_on(user_request)self.events.append(agent_validation)
fromare.simulation.typesimportHint,HintTypedefbuild_events_flow(self):# Add event with associated hintwithEventRegisterer.capture_mode():event1=aui.send_message_to_agent(content="Send an email to the client for the delay.").depends_on(None,delay_seconds=5).with_id("event1")# Add hint for this eventifnotself.hints:self.hints=[]self.hints.append(Hint(hint_type=HintType.TASK_HINT,content="Remember to include an apology in your email",associated_event_id="event1"))
fromare.simulation.typesimportToolAugmentationConfigclassUnreliableScenario(Scenario):def__init__(self):super().__init__()# Make tools fail 20% of the timeself.tool_augmentation_config=ToolAugmentationConfig(tool_failure_probability=0.2,)
fromare.simulation.scenarios.utils.scenario_expanderimportEnvEventsConfigclassDynamicScenario(Scenario):def__init__(self):super().__init__()# Add random environment eventsself.env_events_config=EnvEventsConfig(num_env_events_per_minute=3,env_events_seed=42,weight_per_app_class={"EmailClientApp":1.0,"ApartmentListingApp":1.0,},)
Core logic of the scenario, this is where the scenario is built.
Where events are scheduled, event triggers are defined, as well as any element of the task.
By default, this function is empty, and should be overridden by the scenario if any extra logic is needed.
Build a dictionary to store the turn of each event
The turn of an event is the number of event send_message_to_user among its ancestors
The dictionary and the number of turns are stored to the scenario
Filters out all events connected with the given predecessor_event_ids,
including the predecessor events themselves, their successors and dependencies.
:type predecessor_event_ids: list[str]
:param predecessor_event_ids: List of event IDs to start filtering from.
:type events: list[AbstractEvent]
:param events: List of all events.
:rtype: list[AbstractEvent]
:return: List of events excluding those connected with the predecessor_event_ids.
Patches the event dependencies to ensure send_message_to_user events are executed last in each turn.
This method groups events by turns and ensures that send_message_to_user events depend on all other
events with maximum accumulated time in the same turn. This guarantees that user messages are sent
after all other operations in a turn are completed, maintaining proper execution order in oracle mode.
Core logic of the scenario, this is where the scenario is built.
Where events are scheduled, event triggers are defined, as well as any element of the task.
By default, this function is empty, and should be overridden by the scenario if any extra logic is needed.
Build a dictionary to store the turn of each event
The turn of an event is the number of event send_message_to_user among its ancestors
The dictionary and the number of turns are stored to the scenario
Filters out all events connected with the given predecessor_event_ids,
including the predecessor events themselves, their successors and dependencies.
:type predecessor_event_ids: list[str]
:param predecessor_event_ids: List of event IDs to start filtering from.
:type events: list[AbstractEvent]
:param events: List of all events.
:rtype: list[AbstractEvent]
:return: List of events excluding those connected with the predecessor_event_ids.
Patches the event dependencies to ensure send_message_to_user events are executed last in each turn.
This method groups events by turns and ensures that send_message_to_user events depend on all other
events with maximum accumulated time in the same turn. This guarantees that user messages are sent
after all other operations in a turn are completed, maintaining proper execution order in oracle mode.
Configuration class for controlling environmental event generation and scheduling in simulations.
This class defines parameters that control how background events (messages, emails, shopping updates,
apartment listings) are automatically generated and scheduled during scenario execution. Events are
scheduled using a Poisson process to simulate realistic timing patterns.
These environmental events add noise to scenarios by generating synthetic background activity that is
unrelated to the main scenario task. This simulates a more realistic environment where agents receive
distracting notifications and updates while trying to complete their primary objective.
Attributes:
num_env_events_per_minute: Rate of environmental events to generate per minute of simulation time.
Higher values create more noise and distractions, making the scenario more challenging by
increasing the volume of irrelevant background activity.
env_events_seed: Random seed for reproducible event sampling and Poisson scheduling. Different
seeds will generate different patterns of noise events while maintaining the same overall
distribution and timing characteristics.
n_message_events_per_conversation: Maximum number of message events to generate per conversation.
Higher values create longer conversation threads in messaging apps, adding more textual noise
that the agent must filter through when looking for relevant information.
n_item_events_per_product: Maximum number of item events to generate per shopping product.
Higher values create more product variants and options in shopping apps, increasing the
complexity of product catalogs and making it harder to find specific items.
weight_per_app_class: Relative weights for distributing events across different app types.
Adjusting these weights changes which types of noise dominate - higher email weights create
more inbox clutter, higher messaging weights create more chat notifications, etc.
Hint associated with an event
- hint_type: Type of the hint, depends on the linked event
- content: Content of the hint
- associated_event_id: The id of the event that this hint is associated with
This function is used to add dependencies to the event.
If e1 depends on e2 and e3, then e1 will only be executed after e2 and e3 are executed.
If a delay is specified, then the event will be executed after the delay after the dependencies are executed.
This function is used to add dependencies to the event.
If e1 depends on e2 and e3, then e1 will only be executed after e2 and e3 are executed.
If a delay is specified, then the event will be executed after the delay after the dependencies are executed.