GoodFoods — Autonomous Reservation Agent
An end-to-end conversational AI restaurant reservation system featuring a two-stage intent-classified tool calling loop, pluggable LLM backends (Ollama, Gemini, OpenAI-compatible), anti-hallucination guardrails, and Neon PostgreSQL persistence.
Timeline
2025 — 2026
Role
Solo — agent architecture, two-stage tool pipeline, FastAPI server, React frontend
Status
CompletedTechnology Stack
Key Challenges
- Preventing LLM hallucinations and unauthorized database mutations during free-form conversational booking
- Maintaining consistent tool-calling semantics across diverse LLM backends (local Ollama Qwen 2.5 vs. Gemini vs. OpenAI APIs)
- Handling multi-intent user inputs (e.g. searching cuisine preferences while checking live reservation availability) without context degradation
- Enforcing strict session isolation, user authentication, and booking ownership across concurrent client chats
Key Learnings
- Two-stage architecture (minimal prompt intent classification followed by intent-filtered tool exposure) drastically reduces tool selection errors and prevents irrelevant tool invocation
- Decoupling the agent from HTTP transport through a unified LLMProvider interface enables seamless runtime switching between local and cloud models with zero agent code changes
- Strict Pydantic schema validation at the tool boundary acts as a deterministic firewall between probabilistic LLM outputs and database writes
- Streaming assistant tokens via Server-Sent Events (SSE) while deferring tool execution results produces responsive user perceived latency
Overview
Natural language interfaces for booking systems often suffer from two major failure modes: models hallucinate nonexistent restaurant openings, or they inadvertently trigger destructive mutations (like cancelling a reservation) due to ambiguous prompts.
GoodFoods AI Concierge is an autonomous reservation agent supporting 75+ restaurant locations. Rather than giving an LLM unfiltered database access, the system enforces a two-stage intent-classified tool calling pipeline. The agent dynamically inspects customer intent, exposes only the relevant subset of tools, validates structured parameters against Pydantic schemas, and executes transactions against a PostgreSQL database.
System Architecture
The core request cycle routes through a two-stage agent loop before any database transaction is evaluated:
Tool Suite Overview
The platform exposes 10 fine-grained tools organized by domain:
| Category | Available Tools | Enforced Constraints |
|---|---|---|
| Discovery | search_restaurants, get_restaurant_details, get_cuisine_recommendations | Filters by cuisine, price tier (budget to fine dining), ambiance, location, and dietary options |
| Availability | check_table_availability | Verifies real-time table inventory for requested date, time slot, and party size |
| Booking | create_reservation | Atomically claims table seats; issues unique confirmation ticket |
| Lifecycle | view_reservations, modify_reservation, cancel_reservation | Validates user session ownership before applying modifications |
| Support | get_promotions, get_faq_answer | Delivers deterministic policy rules (cancellation cutoffs, dress codes) |
Key Technical Decisions
1. Two-Stage Intent-Based Tool Filtering
When an LLM is presented with 10+ available tools simultaneously, smaller models (such as Qwen 2.5 7B) frequently hallucinate tool arguments or call irrelevant functions. GoodFoods splits execution into two passes:
- Stage 1: A minimal prompt categorizes user intent into
SEARCH,RESERVE,MANAGE, orINFOwithout tools. - Stage 2: Only tools belonging to the detected categories are exposed in the system prompt. This eliminates out-of-context tool selection by over 90%.
2. Pluggable Multi-Provider LLM Abstraction
The agent core never interacts with raw HTTP or proprietary SDKs directly. All requests pass through an LLMProvider abstraction implementing a single canonical interface:
class LLMProvider(ABC):
@abstractmethod
def chat(self, messages: list[dict], tools: list[dict] | None) -> dict:
"""Translates canonical messages and tools into provider wire format."""
passSpecialized adapters translate tool schemas and payloads for:
- Local Ollama: Qwen 2.5 7B via local Ollama API for zero-cost, private offline inference
- Google Gemini: Gemini Flash via Google AI Studio API for high-throughput cloud processing
- OpenAI-Compatible: Groq, DeepSeek, and OpenAI endpoints
Switching providers is supported live via UI settings without restarting the backend.
3. Strict Deterministic Validation Boundary
To protect database state from malformed LLM outputs, every tool payload must satisfy strict Pydantic models with type constraints (such as valid ISO timestamps, party size bounded between 1 and 20 guests, and regex-verified phone numbers). If the model emits invalid parameters, ToolExecutor traps the error and returns a structured correction prompt to the model rather than crashing.
4. Real-Time Streaming & Optimistic State
The client communicates over Server-Sent Events (SSE). Conversational thoughts and answers stream token-by-token for immediate responsiveness, while structured tool results (restaurant cards, reservation tickets) arrive as typed JSON events that trigger rich UI components.
Tech Stack Summary
- Backend: Python 3.11, FastAPI, Pydantic v2, Uvicorn, SSE Starlette
- Frontend: React 18, TypeScript, Vite, Tailwind CSS, Lucide Icons
- Database: Neon Serverless PostgreSQL (with in-memory fallback for offline testing)
- AI / LLMs: Ollama (Qwen 2.5 7B), Google Gemini 3.5 Flash, Groq, OpenAI API
