A bilingual (English / 中文) restaurant booking agent that never books until the guest says yes, and never fills in a value the guest didn't give.
Kaihuan HuangBased on Nalu, the guest assistant I rebuilt at IPOTIndependent reimplementation · all data is synthetic
Try it
Rules, not a model, read the date, time, party size and name. Anything unclear becomes a question. The only action, create_reservation, runs after an explicit yes.
What the rules found
Actions taken
None yet.
How Nalu is built
Nalu is a restaurant group's guest assistant for questions and table bookings in English and Chinese, now in pre-launch testing. The booking path makes no model call at all; the model only handles what's left, and it can never write a booking field.
Answers are grounded read-only on the POS API; bookings go through the Reservation API. The reply says "confirmed" only when the Reservation API returns confirmed; otherwise it says the request is pending.
No model in the booking path
Because
A pilot with the model reading bookings got 17 of 30 messages fully right (57%) and invented values: lisa@example.com became the name "Lisa", "what time do you close?" became the time "close", "I want to book…" became the name "I".
Bonus
The model runs on a local GPU that serves one request at a time (about 3 s each), so every call skipped saves 1–3 s per turn. The cheapest call is the one you don't make.
Own date and time resolvers
Because
A standard date-parsing library returned nothing for 4 of 12 test date phrases and for every time phrase. Dates resolve in the restaurant's time zone, not the server's.
Only a bare, explicit yes books
Because
"ok", "sure", "好" and "嗯" are too vague for an action that holds a table. A negative word inside a question ("wait, what time do you close?") is not a cancel either.
Never claim what isn't true
Because
A real browser session showed the model promising "I will send a confirmation email" three hours after the booking draft had expired. The fix: the model sees only the last 30 minutes, status and contact replies became fixed templates, and "confirmed" is said only when the Reservation API says so.
Behind a flag, default off
Because
The old flow had over a hundred tests and a safety fix depending on it. The new flow shipped beside it; turning the flag off rolls back in about a minute.
Keep personal data short-lived
Because
Booking drafts hold phone numbers and emails, so expired drafts are purged on every save, and logs record which fields were found, never their values.
Measured on Nalu
Model-only pilot (qwen2.5:7b)
17 of 30 messages fully correct (57%); 3 invented values
Rules + confirmation gate, untuned held-out set
33 of 39 fully correct (85%), 0 wrong or invented values; all 6 misses were omissions, so the guest was simply asked again
Development set
35 of 35, gated in CI on every change to the agent. It was tuned against, so it is a regression suite, not an accuracy claim
Live replay
23 conversations × 3 runs, 114 turns, 0 failures; p95 1.27 s on a later 38-turn replay
Local GPU
NVIDIA DGX Spark (GB10): about 47 tokens/s warm, about 10 s cold start, concurrent requests served one after another. Hence at most one model call per turn, none on yes / no
Honest limit
54 non-booking conversations with zero wrong bookings only bound the rate below about 5.5% (rule of three). A safety claim needs 100+ adversarial trials
Compare with an LLM on your machine
The first version let a language model read the booking details. Measured, it filled in values the guest never gave, so the model came out of the booking path. If you run Ollama, this page can ask your local model to read the same conversation and show where it would have guessed. Nothing leaves your computer.
What is inside that model: architecture card for Qwen2.5-7B-Instruct. 7.62B parameters over 28 layers, grouped-query attention (28 heads, 4 KV heads), 14.2 GiB of weights in bf16 and 56 KiB of KV cache per token, so 32K tokens of context cost 1.75 GiB and the GPU budget goes to weights, not conversation history. Generated from the config alone, no weights downloaded.
Chrome may ask to let this page reach your local network; allow it. The model reads only your messages in the chat above.
Sample run on this page's test conversations
qwen2.5:7b on Ollama, temperature 0, 20 conversations, 25 Sep 2026. In 2 it chose AM or PM on its own when the guest hadn't said; in 1 it put "下周五" (next Friday) on a Tuesday. The rules asked instead, or got the date right.
Tests that gate every change
Every bug fix starts as a failing test. These synthetic conversations run in your browser against the code on this page, with a fixed clock (Fri 25 Sep 2026, 10:00). Each one also checks that no reply promises an email, text or call.