Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions examples/hotel_receptionist/book_restaurant.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@
Never speak the same question twice in a row. If a field was just captured ("name recorded", "time recorded"), it is DONE - asking for it again stalls the call; the only valid next move is the directive in the last tool return.
"""

_RESTAURANT_PHONE_INSTRUCTIONS = """\
Caller cannot provide the phone number or does not have it handy: call
`decline_phone_number_capture` immediately. Do not say or imply that the table or
reservation is booked.
"""


class RestaurantReservationNotCreatedError(ToolError):
"""The restaurant flow ended before a reservation was written."""


class BookRestaurantTask(AgentTask[RestaurantReservation]):
"""Restaurant booking as one focused task, mirroring BookRoomTask: `set_party`
Expand Down Expand Up @@ -137,9 +147,18 @@ async def open_name_dialog(self) -> str:
@function_tool()
async def open_phone_dialog(self) -> str:
"""Open the phone dialog. It collects the guest's phone number (read back and confirmed) from the caller."""
r = await beta.workflows.GetPhoneNumberTask(
chat_ctx=speech_only(self.chat_ctx), extra_instructions=COMMON_INSTRUCTIONS
)
try:
r = await beta.workflows.GetPhoneNumberTask(
chat_ctx=speech_only(self.chat_ctx),
extra_instructions=f"{COMMON_INSTRUCTIONS}\n\n{_RESTAURANT_PHONE_INSTRUCTIONS}",
)
except beta.workflows.PhoneNumberCaptureDeclinedError:
error = RestaurantReservationNotCreatedError(
"reservation not created: a phone number is required"
)
if not self.done():
self.complete(error)
return f"{error} | never tell the caller the table is reserved"
self._phone = r.phone_number
return f"phone recorded: {self._phone} | {self._status()}"

Expand Down
17 changes: 13 additions & 4 deletions examples/hotel_receptionist/tools_restaurant.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

sys.path.append(os.path.dirname(os.path.abspath(__file__)))

from book_restaurant import BookRestaurantTask
from book_restaurant import BookRestaurantTask, RestaurantReservationNotCreatedError
from common import Userdata, _speak_code
from context import speech_only
from hotel_db import (
Expand Down Expand Up @@ -49,9 +49,18 @@ async def check_restaurant_availability(
@function_tool
async def start_restaurant_booking(self, ctx: RunContext[Userdata]) -> str | None:
"""Start the restaurant-reservation flow. Call it the moment the caller wants a table - the flow collects date, party size, time, name, and phone itself. Its return is the FINAL result of the reservation: relay it and move on - nothing further to confirm or call afterwards."""
reservation = await BookRestaurantTask(
db=ctx.userdata.db, chat_ctx=speech_only(self.chat_ctx)
)
try:
reservation = await BookRestaurantTask(
db=ctx.userdata.db, chat_ctx=speech_only(self.chat_ctx)
)
except RestaurantReservationNotCreatedError:
return (
"No reservation was created because a phone number is required. "
"| tell the caller the table is not reserved; do not use success wording. "
"If they ask to hold the table or make an exception without a number: say "
"no table is held and invite them to call back with one - do not offer to "
"connect or transfer them to the restaurant."
)
return (
f"You're set for {speak_time(reservation.time)} on "
f"{reservation.date.strftime('%A, %B %-d')} for "
Expand Down
7 changes: 6 additions & 1 deletion livekit-agents/livekit/agents/beta/workflows/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
from .dtmf_inputs import GetDtmfResult, GetDtmfTask
from .email_address import GetEmailResult, GetEmailTask
from .name import GetNameResult, GetNameTask
from .phone_number import GetPhoneNumberResult, GetPhoneNumberTask
from .phone_number import (
GetPhoneNumberResult,
GetPhoneNumberTask,
PhoneNumberCaptureDeclinedError,
)
from .task_group import TaskCompletedEvent, TaskGroup, TaskGroupResult
from .utils import WorkflowInstructions
from .warm_transfer import WarmTransferResult, WarmTransferTask
Expand All @@ -25,6 +29,7 @@
"GetNameResult",
"GetPhoneNumberTask",
"GetPhoneNumberResult",
"PhoneNumberCaptureDeclinedError",
"TaskCompletedEvent",
"TaskGroup",
"TaskGroupResult",
Expand Down
12 changes: 11 additions & 1 deletion livekit-agents/livekit/agents/beta/workflows/phone_number.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ class GetPhoneNumberResult:
phone_number: str


class PhoneNumberCaptureDeclinedError(ToolError):
def __init__(self, reason: str) -> None:
super().__init__(f"couldn't get the phone number: {reason}")
self._reason = reason

@property
def reason(self) -> str:
return self._reason
Comment on lines +62 to +69

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 New public error class ships without documentation

The newly added public error type is exported in the package's public API (PhoneNumberCaptureDeclinedError at livekit-agents/livekit/agents/beta/workflows/phone_number.py:62) without any docstring, so it appears undocumented in the generated API docs.
Impact: Users browsing the published API reference see an undocumented error type and cannot tell when it is raised.

CONTRIBUTING.md documentation requirement for new classes

CONTRIBUTING.md states: "If writing new methods/enums/classes, document them. This project uses pdoc3 for automatic API documentation generation, and every new addition has to be properly documented." The class and its reason property (livekit-agents/livekit/agents/beta/workflows/phone_number.py:67-69) have no docstrings, and the class is exported in livekit-agents/livekit/agents/beta/workflows/__init__.py:32. By contrast, the base ToolError documents itself (livekit-agents/livekit/agents/llm/tool_context.py:124-131), and the example-side counterpart RestaurantReservationNotCreatedError does carry a docstring.

Suggested change
class PhoneNumberCaptureDeclinedError(ToolError):
def __init__(self, reason: str) -> None:
super().__init__(f"couldn't get the phone number: {reason}")
self._reason = reason
@property
def reason(self) -> str:
return self._reason
class PhoneNumberCaptureDeclinedError(ToolError):
"""Raised when the user explicitly declines to provide a phone number.
`GetPhoneNumberTask` completes with this error (instead of a bare `ToolError`)
so callers can distinguish a deliberate refusal from other failures.
"""
def __init__(self, reason: str) -> None:
super().__init__(f"couldn't get the phone number: {reason}")
self._reason = reason
@property
def reason(self) -> str:
"""Short explanation of why the user declined to provide the phone number."""
return self._reason
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.



class GetPhoneNumberTask(AgentTask[GetPhoneNumberResult]):
def __init__(
self,
Expand Down Expand Up @@ -179,7 +189,7 @@ async def decline_phone_number_capture(self, reason: str) -> None:
reason: A short explanation of why the user declined to provide the phone number
"""
if not self.done():
self.complete(ToolError(f"couldn't get the phone number: {reason}"))
self.complete(PhoneNumberCaptureDeclinedError(reason))

def _confirmation_required(self, ctx: RunContext) -> bool:
if is_given(self._require_confirmation):
Expand Down
Loading