Skip to content

Add flight-booking community ability#209

Open
hassan1731996 wants to merge 8 commits intoopenhome-dev:devfrom
hassan1731996:add-flight-booking
Open

Add flight-booking community ability#209
hassan1731996 wants to merge 8 commits intoopenhome-dev:devfrom
hassan1731996:add-flight-booking

Conversation

@hassan1731996
Copy link
Contributor

What This Ability Does

Search and book flights entirely by voice using the Duffel API. Supports one-way and round-trip searches, reads back up to 3 options, collects passenger details, and creates a hold booking (pay-later — no payment by voice).

Checklist

  • Files in community/flight-booking/
  • Extends MatchingCapability with #{{register capability}} tag
  • resume_normal_flow() called on every exit path
  • No print() statements (uses editor_logging_handler)
  • No hardcoded API keys (placeholder duffel_test_YOUR_KEY_HERE)
  • Error handling on all Duffel API calls
  • README with setup instructions, trigger words, and example conversation

Setup Required

  1. Sign up at duffel.com → create a test API token
  2. Replace duffel_test_YOUR_KEY_HERE in main.py with your token

Testing

Tested end-to-end in OpenHome Live Editor:

  • One-way & round-trip search
  • Hold booking with passenger details (name, DOB, email, phone)
  • Spoken phone number via LLM (_parse_phone)
  • City name normalization (_normalize_city_name)
  • Single-offer fast-path (yes/no instead of numbered selection)
  • Search-retry loop without session restart

@hassan1731996 hassan1731996 requested a review from a team as a code owner March 14, 2026 17:06
@github-actions
Copy link
Contributor

github-actions bot commented Mar 14, 2026

🔀 Branch Merge Check

PR direction: add-flight-bookingdev

Passedadd-flight-bookingdev is a valid merge direction

@github-actions
Copy link
Contributor

github-actions bot commented Mar 14, 2026

✅ Community PR Path Check — Passed

All changed files are inside the community/ folder. Looks good!

@github-actions github-actions bot added the community-ability Community-contributed ability label Mar 14, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 14, 2026

✅ Ability Validation Passed

📋 Validating: community/flight-booking
  ✅ All checks passed!

@github-actions
Copy link
Contributor

github-actions bot commented Mar 14, 2026

🔍 Lint Results

__init__.py — Empty as expected

Files linted: community/flight-booking/main.py

✅ Flake8 — Passed

✅ All checks passed!

@uzair401
Copy link
Contributor

Really good work — the Duffel integration is clean, the retry loops are well-structured, and using LLM extraction on the trigger utterance is exactly the right pattern. Notes below are about hardening the voice layer; the core logic is in good shape.


Please review this and apply the changes so the conversation flow looks natural.

Replace hardcoded word lists with LLM classifiers

Several any(w in lower for w in [...]) checks will silently misroute native speakers. The fix is the same in each case — swap for a one-line LLM classifier with a YES/NO or named return.

Location Problem Fix
Affirmative after no results Missing: yep, absolutely, go for it, why not LLM: "Did the user agree to change their search? YES or NO."
Single-offer confirm Missing: do it, let's go, alright, book it Same YES/NO pattern
Multi-offer rejection Missing: nope, nah, never mind, start over Same YES/NO pattern
Round-trip detection "I'm coming back" / "there and back" silently books one-way LLM: "ROUND-TRIP or ONE-WAY?"
Change-routing (dest/date/cabin) "to" false-positives on "I want to change the date"; "dest" never appears in natural speech Single 3-way LLM classifier → DATE, DESTINATION, or CABIN
Title detection Silent default to mr/male for anything unrecognised LLM: "Which title: mr, mrs, miss, ms, or dr?"

24-hour times in _format_offer

dep_raw[11:16] produces "14:30" — TTS reads "fourteen thirty". US speakers expect 12-hour AM/PM. → datetime.strptime(t, "%H:%M").strftime("%-I:%M %p")

Raw date string spoken aloud

f"{date_str} is in the past."date_str is YYYY-MM-DD, reads as "two thousand twenty four dash zero three dash zero one." → Wrap with _format_date_natural(date_str).


Sequential prompts in _collect_passenger_details

5 back-to-back run_io_loop calls. A user who answers multiple fields at once ("John Smith, born March 5th 1990") has the extra data silently discarded. → Open with one prompt: "I need your full name, date of birth, email, and phone — go ahead." Extract via LLM (same pattern as _extract_flight_details_from_utterance), re-ask only for nulls.

Setup waterfall when trigger utterance is short

Up to 4 sequential clarification prompts fire on a short trigger like "Book me a flight." Users can't answer multiple fields at once. → If 2+ fields are null after initial extraction, ask one catch-all first: "Where are you flying, and when?" Fall through to individual prompts only for fields still missing.

Three offers read as one 90-word block

No way to pause or replay on a smart speaker. → Speak each offer individually, prompt after the second: "Want the third option, or go with one of those?"

Confirmation prompt is 40–50 words

"Confirm: {carrier}, {origin} to {dest}, {date}, {currency} {price} for {pax_name}. This is a hold — you'll pay later. Shall I book it?" (~30 words)


  • Final speak() reads the raw email address aloud — privacy-sensitive and unnecessary. Replace f"sent to {passenger_details['email']}" with "sent to your email address". Also trims the response from 45 to ~20 words.
  • "What's your title? Mr, Mrs, Miss, Ms, or Dr?" — five comma-separated labels sound robotic through TTS. → "Are you Mister, Missus, Miss, or Doctor?"
  • Phone example is a UK number (plus 44 7700 900 123) in a US-targeted ability. → "For example, plus 1 and then your ten-digit number." Also add "If no country code was spoken, assume +1" to _parse_phone prompt.
  • premium_economy missing from _extract_flight_details_from_utterance cabin enum — it's accepted elsewhere in the code but can never be extracted from the trigger utterance.
  • Error message is speculative"The flight may no longer be available" alarms the user when the real cause could be a timeout. → "Sorry, I couldn't complete that booking. Want me to try again?"
  • LLM prompts for _resolve_airport and number extraction have no examples of noisy STT input or natural selection phrasing. Small additions would make both more robust — happy to suggest exact wording if useful.

@hassan1731996
Copy link
Contributor Author

hassan1731996 commented Mar 17, 2026

Hi @uzair401 - thank you for the thorough review! I've pushed a new commit addressing all the points:

  • Replaced all hardcoded wordlists with LLM classifiers (YES/NO and 3-way DATE/DESTINATION/CABIN) — covers "nah", "do it", "I'm coming back", etc.
  • 24-hour times in offers → 12-hour AM/PM (e.g. "2:30 PM")
  • Past-date error now uses natural date format instead of raw ISO string
  • Setup waterfall: single "Where are you flying, and when?" when 2+ fields are missing
  • Passenger details: one combined prompt, re-asks only for null fields
  • Progressive offer reveal: speak 1 → speak 2 → ask if user wants 3rd
  • _ask_and_apply_change() helper eliminates the 3 duplicate change-routing blocks
  • Confirmation prompt shortened to ~28 words
  • Email address no longer read aloud in final message
  • Title prompt is now TTS-friendly ("Are you Mister, Missus, Miss, or Doctor?")
  • Phone example updated to US format with +1 fallback assumption
  • premium_economy added to extraction cabin enum
  • _resolve_airport and _parse_phone prompts hardened with STT noise context
  • Booking error → retry loop with LLM-classified response

Let me know if anything else needs adjusting!

Copy link
Contributor

@uzair401 uzair401 left a comment

Choose a reason for hiding this comment

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

Hey, great work on this — the flight selection flow is much improved and delivers a solid voice experience overall!

A couple of things still need attention before this can be approved:

  1. Passenger details collection — the fallback chain can hit up to 6 sequential one-by-one prompts if the combined extraction misses fields. On a voice device this feels robotic and form-like. Consider collapsing the fallbacks into a maximum of 2 prompts (name + DOB in one, email + phone in another) and letting the LLM extract from each. The current approach works when STT is clean but breaks down quickly when it isn't.

  2. No exit path in the flow — if the user says "never mind", "forget it", or "cancel" at any point during passenger details or the search loop, the ability has no way to detect it and exits uncleanly. Add an LLM-based exit check after each run_io_loop response.

  3. Confirmation step needs a change path — right now run_confirmation_loop only gives the user yes or no. If the user wants to change something at confirmation (e.g. wrong date, wrong passenger name), there's no way to do that — it either books or cancels. Consider replacing run_confirmation_loop with a run_io_loop and routing the response through the LLM so the user can confirm, cancel, or request a specific change.

The rest of the flow is clean — nice job on the progressive offer reveal and the retry logic!

@hassan1731996
Copy link
Contributor Author

hassan1731996 commented Mar 18, 2026

Hi — thanks for the detailed feedback! All three points addressed plus a few improvements in the same commit:

Reviewer-requested:

  • Passenger details — collapsed to 2 grouped prompts (name+DOB, email+phone) with _extract_passenger_fields() LLM extraction. Individual fallback asks only fire if a field wasn't extracted. Ideal path is now 3 prompts total (group 1, title, group 2) instead of 6
  • Exit intent — added _is_exit_intent() LLM helper (YES/NO classifier) called after every run_io_loop throughout the entire flow. Detected exit → "No problem. Let me know if you need anything else." + resume_normal_flow()
  • Confirmation change path — replaced run_confirmation_loop with run_io_loop + LLM intent classifier returning CONFIRM / CANCEL / CHANGE_DATE / CHANGE_DESTINATION / CHANGE_PASSENGER / CHANGE_CABIN. Each routes appropriately: passenger change re-collects details without re-searching; route/date/cabin changes trigger a new search

In anticipation of this:

  • 12-hour times — added _fmt_time_12h(), offers now read "Departs at 1:09 PM, arrives at 3:17 PM"
  • Extracted _ask_and_apply_change() helper — the date/destination/cabin change block was copy-pasted in 3 places; now a single reusable async method
  • Empty time fallbackdep/arr fall back to "TBD" if Duffel returns a segment without timestamps

Let me know if anything else needs adjusting!

@hassan1731996 hassan1731996 requested a review from uzair401 March 18, 2026 09:21
@hassan1731996
Copy link
Contributor Author

hassan1731996 commented Mar 18, 2026

Another round of improvements pushed:

Correctness fixes:

  • Phone E.164 validation_extract_passenger_fields result now always passed through _parse_phone + _validate_phone(r"^\+\d{7,15}$") before use. LLM-extracted phones that slip through malformed no longer hit Duffel raw
  • Origin city change_ask_and_apply_change now classifies ORIGIN as a distinct intent (was silently falling through to OTHER before). Saying "change where I'm flying from" at any point now works correctly
  • Round-trip return date on date change — when the user changes the departure date (either via _ask_and_apply_change or at confirmation step CHANGE_DATE), the return date is also re-asked for round-trip bookings, preventing a departure-after-return invalid state
  • Conditional payment sentence — final success message no longer says "complete payment before the deadline" when no deadline was returned by Duffel. Two clean variants: one with deadline date, one without

Performance:

  • Exit intent fast pathEXIT_KEYWORDS ClassVar checked with string matching before calling LLM. Obvious exits ("cancel", "stop", "nevermind", "forget it", etc.) skip the LLM call entirely, saving ~1s per turn

Voice UX:

  • Trip type confirmed before search — ability now says "Searching one-way economy flights from London to Dubai on March 20th. One moment." so the user hears what was understood before waiting
  • Offer count intro — "I found two options." spoken before reading the list
  • Round-trip return date in confirmation — "Booking … on March 20th, returning April 3rd for John Smith…"

Reliability:

  • Duffel 429/5xx retry_duffel_request_with_retry() wraps all Duffel calls with one automatic retry after 2s on rate-limit or server errors. Both _search_flights and _book_flight use it
  • _exit() helper — extracted repeated exit-speak pattern into one method to reduce duplication

README:

  • Added Known Limitations table (sandbox flights, hold-only, 1 passenger, hold availability, regional coverage)
  • Updated example conversation to match current flow (grouped prompts, 12h times, trip type confirmation)
  • Added API key security note

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-ability Community-contributed ability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants