fix(hotel_receptionist): guest-services tool fixes the carve-outs left behind - #6808
fix(hotel_receptionist): guest-services tool fixes the carve-outs left behind#6808u9g wants to merge 10 commits into
Conversation
β¦name A followup is a note for a human about a person; a name derived from the room number identifies the room instead.
β¦ list amend_florist_order puts the note where the florist reads it; record_followup routes it to nobody.
The tool tells the agent to record first and answer after; nothing brought it back to the deferred questions, so they died with the call.
Both are in the catalog the agent already looked up, and the caller agreeing to a booking they haven't been quoted isn't agreement.
The reference is how the caller reaches the order later, so it belongs in the confirmation the way every other booking's does.
β¦ set Free text let the agent write the destination back as a handling note, or promise a delivery time the florist never guaranteed. The read-back has to speak the chosen value, not its stored form.
resend_confirmation suspends into booking verification and resumes; the model re-issues it in the same turn and the run gets two emails_sent rows for a single caller request.
β¦eled Two references come out of one call and the caller has no way to tell which is which; the stored one drops the caller's spacing and case so it matches what the carrier holds.
The pickup margin book_airport_car computes needs a departure on file, and this is the only call that captures one - an optional argument the model may omit leaves the later car booking unable to check anything.
"I'll note that for you" left no row anywhere; a followup is the only thing that carries a preference to the next stay, and it has to run before a booking flow takes over the tool set.
| import os | ||
| import sys | ||
| from datetime import date, time | ||
| from enum import StrEnum |
There was a problem hiding this comment.
π΄ Example crashes on Python 3.10 because of a newer-only enum type
The example imports a string-enum type that only exists in Python 3.11+ (from enum import StrEnum at examples/hotel_receptionist/tools_services.py:7) even though the project declares support back to Python 3.10, so the whole receptionist example fails to start on 3.10.
Impact: Anyone running the example on Python 3.10 gets an immediate startup failure instead of a working agent.
Version floor vs. StrEnum availability
enum.StrEnum was added in CPython 3.11. examples/hotel_receptionist/pyproject.toml declares requires-python = ">=3.10" and livekit-agents/pyproject.toml:11 declares >=3.10,<3.15; AGENTS.md's Code Style section states "Python 3.10+ compatibility required". Importing tools_services on 3.10 raises ImportError: cannot import name 'StrEnum' from 'enum'. A portable equivalent is class DeliveryPreference(str, Enum).
Prompt for agents
tools_services.py imports enum.StrEnum, which is only available on Python 3.11+, while the hotel_receptionist example and livekit-agents both declare support for Python 3.10 (and AGENTS.md requires 3.10 compatibility). On 3.10 the module import fails outright. Replace DeliveryPreference's base with a 3.10-compatible string enum (e.g. class DeliveryPreference(str, Enum)) and verify the generated tool schema still resolves the enum values the same way.
Was this helpful? React with π or π to provide feedback.
| if ( | ||
| ctx.userdata.caller_turns_at_last_resend >= 0 | ||
| and _count_caller_turns(self.session.history) | ||
| <= ctx.userdata.caller_turns_at_last_resend | ||
| ): | ||
| return ( | ||
| "that document already went out moments ago - do NOT send it again. " | ||
| f"Relay the outcome to the caller: {ctx.userdata.last_resend_message}" | ||
| ) | ||
| booking = await self._verified_booking(ctx) | ||
| await ctx.userdata.db.send_email(recipient=booking.email, kind=kind) | ||
| return f"Sent to the address on file, {booking.email.strip().lower()}." | ||
| msg = f"Sent to the address on file, {booking.email.strip().lower()}." | ||
| ctx.userdata.last_resend_message = msg | ||
| ctx.userdata.caller_turns_at_last_resend = _count_caller_turns(self.session.history) |
There was a problem hiding this comment.
π‘ Second requested document is never emailed when a caller asks for two at once
The re-send guard blocks any repeat send based only on whether the caller has spoken since the previous send (caller_turns_at_last_resend check at examples/hotel_receptionist/tools_services.py:482-490) without looking at which document was asked for, so a caller who asks for both documents in one breath only ever receives the first one.
Impact: A guest asking for their confirmation and folio together gets only one of them, while being told the other was sent.
Guard ignores the `kind` argument
resend_confirmation takes kind: Literal["booking_confirmation", "folio"]. The idempotency check copied from cancel_room_booking (examples/hotel_receptionist/tools_rooms.py:316-324) is parameterless there, so turn-count alone is a sufficient key. Here a single caller turn can legitimately require two distinct sends ("can you email me the confirmation and the folio?"); the second invocation is short-circuited and returns "that document already went out moments ago", referencing last_resend_message from the first document. Tracking the last-sent kind (e.g. a per-kind turn map) would fix it.
Prompt for agents
resend_confirmation's new idempotency guard keys only on the caller-turn count, but unlike cancel_room_booking the tool has a `kind` argument (booking_confirmation vs folio). When a caller asks for both documents in one turn, the second call is suppressed and the agent claims it was already sent. Consider storing the last-sent kind (or a mapping kind -> caller-turn count / message) in Userdata (examples/hotel_receptionist/common.py) and only short-circuiting when the same kind is re-requested with no caller turn since.
Was this helpful? React with π or π to provide feedback.
Stacked on #6805 β four of these refine code that only exists on that branch (
amend_florist_order,speak_room, the florist/flight db signatures). Review the last 10 commits.The guest-services tool fixes from #6567 that the earlier carve-outs left behind, one commit each:
record_followuptakes the caller's real name. A followup is a note for a human about a person; "guest in 402" identifies the room. The room number stays valid as the callback number.record_followup's own docstring never named the exception, so notes routed there and reached nobody.resend_confirmationwon't email the same document twice for one ask. It suspends into booking verification and resumes; the model re-issues it in the same turn and the run lands twoemails_sentrows for a single request. Same idempotency shapecancel_room_bookingalready uses.book_airport_carcompute the pickup margin from a departure on file, and this is the only call that captures one β an optional argument the model may omit leaves the car booking with nothing to check.Three things from #6567 deliberately not carried:
_florist_destination's no-destination raise βhotel_db.order_flowersalready raisesUnavailable("no delivery destinationβ¦"), surfaced as aToolError.Roomas aRootModelunion β fix(hotel_receptionist): hotel_db stored-state and read-back fixesΒ #6805's discriminatedBaseModelunion is the later formulation of the same thing.book_airport_car's requiredflight_departure_timeargument β fix(hotel_receptionist): hotel_db stored-state and read-back fixesΒ #6805 replaced it with thelatest_flight_departurelookup. The departure-time commit here is what makes that lookup find something.Testing
.venv/bin/python -m pytest tests/test_tools.py --unit(114 passed)departure_timeis required-nullable,DeliveryPreferenceresolves as a$refenumruff format --check/ruff checkclean overexamples/hotel_receptionist/Follow-up
benchmark.pystill listsdelivery_instructionsunder "free text written by the agent" and drops it from expected-state grading. It's a closed enum now, so it's gradable β left alone here because #6567 didn't touch it either.