项目文件夹

文件
2026-07-13 13:39:38 +08:00

143 行
6.2 KiB
Python

from datetime import datetime, time, timedelta
from zoneinfo import ZoneInfo
import pytest
from livekit.agents import AgentSession, beta, inference, llm
from .agent import FrontDeskAgent, Userdata
from .calendar_api import AvailableSlot, FakeCalendar
TIMEZONE = "UTC"
def _llm_model() -> llm.LLM:
return inference.LLM(
model="openai/gpt-4.1", extra_kwargs={"parallel_tool_calls": False, "temperature": 0.45}
)
@pytest.mark.asyncio
async def test_slot_scheduling() -> None:
tz = tz = ZoneInfo(TIMEZONE)
today = datetime.now(tz).date()
# fmt: off
slots = [
AvailableSlot(start_time=datetime.combine(today, time(9, 0), tzinfo=tz), duration_min=30),
AvailableSlot(start_time=datetime.combine(today, time(9, 30), tzinfo=tz), duration_min=30),
AvailableSlot(start_time=datetime.combine(today, time(10, 0), tzinfo=tz), duration_min=30),
AvailableSlot(start_time=datetime.combine(today + timedelta(days=1), time(14, 0), tzinfo=tz), duration_min=30),
AvailableSlot(start_time=datetime.combine(today + timedelta(days=1), time(14, 30), tzinfo=tz), duration_min=30),
AvailableSlot(start_time=datetime.combine(today + timedelta(days=1), time(15, 0), tzinfo=tz), duration_min=30),
AvailableSlot(start_time=datetime.combine(today + timedelta(days=2), time(11, 0), tzinfo=tz), duration_min=30),
AvailableSlot(start_time=datetime.combine(today + timedelta(days=2), time(11, 30), tzinfo=tz), duration_min=30),
]
# fmt: on
userdata = Userdata(cal=FakeCalendar(timezone=TIMEZONE, slots=slots))
async with _llm_model() as llm, AgentSession(llm=llm, userdata=userdata) as sess:
await sess.start(FrontDeskAgent(timezone=TIMEZONE))
result = await sess.run(user_input="Can I get an appointment tomorrow?")
result.expect.skip_next_event_if(type="message", role="assistant")
# the agent may first check the current date to resolve "tomorrow"
if result.expect.skip_next_event_if(type="function_call", name="get_current_time"):
result.expect.next_event(type="function_call_output")
result.expect.skip_next_event_if(type="message", role="assistant")
result.expect.next_event().is_function_call(name="list_available_slots")
result.expect.next_event().is_function_call_output()
tomorrow = today + timedelta(days=1)
expected_tomorrow_slots = [slot for slot in slots if slot.start_time.date() == tomorrow]
expected_times_text = ", ".join(
slot.start_time.strftime("%-I:%M %p") for slot in expected_tomorrow_slots
)
await (
result.expect.next_event()
.is_message(role="assistant")
.judge(
llm,
intent=(
"must suggest one or more available appointment time slots for tomorrow "
f"({today + timedelta(days=1):%B %-d, %Y}). For reference, today is {today:%B %-d, %Y}"
f"Must only suggest times that are present in the calendar slots for tomorrow, "
f"which are: {expected_times_text}."
),
)
)
result = await sess.run(user_input="2 in the afternoon sounds good")
result.expect.skip_next_event_if(type="message", role="assistant")
slot_id = next(
s.unique_hash
for s in slots
if s.start_time == datetime.combine(today + timedelta(days=1), time(14, 0), tzinfo=tz)
)
result.expect.next_event().is_function_call(
name="schedule_appointment", arguments={"slot_id": slot_id}
)
result.expect.next_event().is_agent_handoff(new_agent_type=beta.workflows.GetEmailTask)
await (
result.expect.next_event()
.is_message(role="assistant")
.judge(llm, intent="must ask for the email address")
)
result = await sess.run(
user_input="My email address is theo@livekit.io",
input_modality="audio", # simulate audio input
)
result.expect.next_event().is_function_call(
name="update_email_address", arguments={"email": "theo@livekit.io"}
)
result.expect.next_event().is_function_call_output()
await (
result.expect.next_event()
.is_message(role="assistant")
.judge(llm, intent="must ask for the email address confirmation/validation")
)
result = await sess.run(user_input="Yes, it's valid")
result.expect.next_event().is_function_call(name="confirm_email_address")
result.expect.next_event().is_function_call_output()
result.expect.next_event().is_agent_handoff(new_agent_type=FrontDeskAgent)
result.expect.next_event().is_function_call_output() # output of the schedule_appointment
await (
result.expect.next_event()
.is_message(role="assistant")
.judge(llm, intent="must confirm the appointment was scheduled")
)
@pytest.mark.asyncio
async def test_no_availability() -> None:
userdata = Userdata(cal=FakeCalendar(timezone=TIMEZONE, slots=[])) # no slots
async with _llm_model() as llm, AgentSession(llm=llm, userdata=userdata) as sess:
await sess.start(FrontDeskAgent(timezone=TIMEZONE))
result = await sess.run(
user_input="Hello, can I need an appointment, what's your availability for the next 2 weeks?"
)
result.expect.skip_next_event_if(type="message", role="assistant")
# the agent may first check the current date before listing availability
if result.expect.skip_next_event_if(type="function_call", name="get_current_time"):
result.expect.next_event(type="function_call_output")
result.expect.skip_next_event_if(type="message", role="assistant")
result.expect.next_event().is_function_call(name="list_available_slots")
result.expect.next_event().is_function_call_output()
await (
result.expect.next_event()
.is_message(role="assistant")
.judge(
llm,
intent="must say that there is no availability, especially in the requested time range. optionally, it can offer to look at other times",
)
)