Filter and surface results
Raw catalog rows are not useful to a traveler. The propose_options node takes the candidates, multiplies price by passengers, and formats a short reply with numbered choices. The LLM is not involved here because formatting is deterministic.
async def propose_options(state: BookingState) -> BookingState:
candidates = state.get("candidates") or []
if not candidates:
return state
pax = int(state.get("passengers") or 1)
lines = []
for i, c in enumerate(candidates, start=1):
total = c["price_usd"] * max(1, pax)
lines.append(
f" {i}. {c['carrier']} {c['id']} - depart {c['depart_time']}, "
f"arrive {c['arrive_time']} ({c['duration_minutes']} min) - "
f"${c['price_usd']}/pax (total ${total})"
)
body = "\n".join(lines)
depart = state.get("depart_date") or "the requested date"
state["assistant_reply"] = (
f"Here are 3 flight options from {state.get('origin')} to {state.get('destination')} "
f"on {depart} for {pax} passenger(s):\n{body}\n"
"Reply with the option number (1, 2, or 3) to confirm, or say 'cancel' to start over."
)
state["stage"] = "proposing"
return stateThe node computes totals, builds numbered lines, and sets stage to "proposing" so the router knows the next turn is a choice or a change, not a new booking.
Quiz: Quiz
Loading practice…
Checkpoint: Catalog and options checkpoint
Loading practice…