What Are AI Agents for Restaurant Ordering and Reservations?
AI agents for restaurants are conversational systems that can autonomously handle customer interactions—specifically taking food orders and managing table reservations—through natural language. Unlike simple chatbots that follow rigid decision trees, these agents use large language models (LLMs), intent classification, and slot-filling techniques to understand what a customer wants, ask clarifying questions when needed, and execute actions against real restaurant systems like reservation databases or point-of-sale APIs.
At their core, these agents combine several capabilities: understanding natural speech or text, extracting structured data from unstructured conversation, maintaining context across multiple turns of dialogue, and calling external APIs to complete tasks. A well-built agent can handle a phone call or chat conversation end-to-end—checking availability, suggesting time slots, collecting party size and dietary preferences, confirming details, and creating a record in the restaurant's system.
Core Components of a Restaurant AI Agent
- Intent Classifier — Determines whether the customer wants to make a reservation, place an order, ask about menu items, or cancel a booking
- Slot Filler / Entity Extractor — Pulls structured data from utterances: dates, times, party size, menu items, quantities, dietary restrictions
- Dialogue Manager — Tracks conversation state and decides when enough information has been gathered versus when to ask follow-up questions
- API Integration Layer — Connects to reservation systems (like OpenTable, Resy, or a custom database) and ordering systems (POS APIs, kitchen display systems)
- Natural Language Generator — Crafts human-like responses confirming bookings, suggesting alternatives, or summarizing orders
Why Building AI Agents for Restaurants Matters
Restaurants face chronic staffing shortages, especially during peak hours. Phone lines get overwhelmed, front-of-house staff juggle multiple responsibilities, and manual order entry leads to errors. An AI agent that handles reservations 24/7 and takes accurate orders can reduce labor costs, eliminate hold times, capture late-night booking requests, and dramatically improve order accuracy. For restaurant groups with multiple locations, a centralized AI agent can route calls intelligently and provide consistent service across all venues. The technology is mature enough today—with LLMs and speech-to-text APIs—that developers can build production-grade agents in weeks, not months.
Building an AI Reservation Agent: Step by Step
Let's walk through building a complete reservation agent in Python. We'll use a combination of an LLM for understanding, a simple state machine for dialogue management, and a mock reservation API that you would replace with your real system.
Project Structure and Dependencies
Create a new project and install the required packages. We'll use FastAPI for the web server, Pydantic for data validation, and the OpenAI SDK for the language model. You can swap OpenAI for any LLM provider.
# requirements.txt
fastapi==0.104.1
uvicorn==0.24.0
openai==1.6.1
pydantic==2.5.0
python-dateutil==2.8.2
Here's the project layout:
restaurant_agent/
├── main.py # FastAPI server and webhook endpoint
├── agent.py # Core agent logic and dialogue management
├── intent_extractor.py # LLM-based intent and slot extraction
├── reservation_api.py # Mock reservation database (replace with real API)
├── models.py # Pydantic models for structured data
└── config.py # API keys and configuration
Defining the Data Models
Start by creating clear Pydantic models. These represent the structured reservation data we want to extract from conversation.
# models.py
from pydantic import BaseModel, Field
from datetime import datetime, date, time
from typing import Optional, List
from enum import Enum
class Intent(str, Enum):
MAKE_RESERVATION = "make_reservation"
CANCEL_RESERVATION = "cancel_reservation"
CHECK_AVAILABILITY = "check_availability"
MODIFY_RESERVATION = "modify_reservation"
GENERAL_INQUIRY = "general_inquiry"
class Reservation(BaseModel):
restaurant_id: str
date: date
time: time
party_size: int
customer_name: str
customer_phone: str
customer_email: Optional[str] = None
special_requests: Optional[str] = None
dietary_restrictions: Optional[List[str]] = None
booth_preference: bool = False
confirmed: bool = False
class DialogueState(BaseModel):
intent: Optional[Intent] = None
collected_slots: dict = Field(default_factory=dict)
missing_slots: List[str] = Field(default_factory=list)
conversation_history: List[dict] = Field(default_factory=list)
reservation_id: Optional[str] = None
requires_clarification: bool = False
Intent and Slot Extraction with an LLM
The heart of the agent is a function that sends the user's utterance to an LLM and gets back structured intent and extracted slots. We craft a system prompt carefully to return JSON we can parse.
# intent_extractor.py
import json
from openai import OpenAI
from models import Intent, DialogueState
from config import OPENAI_API_KEY
client = OpenAI(api_key=OPENAI_API_KEY)
SYSTEM_PROMPT = """You are an intent and slot extraction system for a restaurant reservation agent.
Given a user message and the current dialogue state, extract the following and return ONLY valid JSON:
{
"intent": "make_reservation" | "cancel_reservation" | "check_availability" | "modify_reservation" | "general_inquiry",
"slots": {
"date": "YYYY-MM-DD or null",
"time": "HH:MM in 24-hour format or null",
"party_size": integer or null,
"customer_name": "string or null",
"customer_phone": "string or null",
"customer_email": "string or null",
"special_requests": "string or null",
"dietary_restrictions": ["list"] or [],
"booth_preference": true/false,
"reservation_id": "string or null"
},
"needs_clarification": true/false,
"clarification_question": "question to ask if slots are missing, otherwise null"
}
Rules:
- Only extract information explicitly stated. Do not infer.
- For dates like "next Friday", resolve relative to the current date provided.
- For times like "7pm", convert to 24-hour format: "19:00".
- If the user says "two people" or "a table for 4", extract party_size accordingly.
- If critical slots are missing (date, time, party_size), set needs_clarification to true.
- Do not hallucinate phone numbers or emails.
"""
def extract_intent_and_slots(
user_message: str,
current_state: DialogueState,
current_date: str = "2024-01-15" # You'd pass the actual current date
) -> dict:
"""Send user message to LLM and get structured intent + slots back."""
# Build context from conversation history
context_messages = [
{"role": "system", "content": SYSTEM_PROMPT}
]
# Add previous turns for context
for turn in current_state.conversation_history[-6:]:
context_messages.append(turn)
# Add current user message with date context
context_messages.append({
"role": "user",
"content": f"Current date: {current_date}\nUser message: {user_message}"
})
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=context_messages,
response_format={"type": "json_object"},
temperature=0.1,
max_tokens=500
)
result = json.loads(response.choices[0].message.content)
return result
def merge_slots(state: DialogueState, extracted_slots: dict) -> DialogueState:
"""Merge newly extracted slots into the dialogue state, keeping existing values."""
for key, value in extracted_slots.items():
if value is not None and value != [] and value != "":
state.collected_slots[key] = value
return state
The Dialogue Manager and Reservation Agent
The dialogue manager orchestrates the flow: it checks what slots are still missing, decides whether to ask for more information or proceed to book, and handles the confirmation step. Here's the complete agent class:
# agent.py
from typing import Generator, Dict, Any
from models import Intent, DialogueState, Reservation
from intent_extractor import extract_intent_and_slots, merge_slots
from reservation_api import check_availability, create_reservation, cancel_reservation_by_id
from datetime import datetime, date, time
import re
REQUIRED_SLOTS = ["date", "time", "party_size", "customer_name", "customer_phone"]
SLOT_QUESTIONS = {
"date": "What date would you like to reserve?",
"time": "What time works best for you?",
"party_size": "How many people will be in your party?",
"customer_name": "May I have the name for the reservation?",
"customer_phone": "What's a good phone number to reach you at?",
"customer_email": "Would you like to add an email for the confirmation? (optional)",
"special_requests": "Any special requests or occasions we should note?",
}
class ReservationAgent:
def __init__(self, restaurant_id: str = "bistro_north_202"):
self.restaurant_id = restaurant_id
self.state = DialogueState()
self.current_date = date.today().isoformat()
def reset(self):
"""Reset agent state for a new conversation."""
self.state = DialogueState()
def process_message(self, user_message: str) -> Dict[str, Any]:
"""Process a user message and return the agent's response."""
# Add user message to history
self.state.conversation_history.append({
"role": "user", "content": user_message
})
# Step 1: Extract intent and slots
extracted = extract_intent_and_slots(
user_message,
self.state,
current_date=self.current_date
)
# Step 2: Merge extracted slots into state
self.state = merge_slots(self.state, extracted.get("slots", {}))
# Step 3: Handle intent
intent = extracted.get("intent", "general_inquiry")
self.state.intent = Intent(intent) if intent else None
# Step 4: Route based on intent
if self.state.intent == Intent.CANCEL_RESERVATION:
return self._handle_cancellation()
elif self.state.intent == Intent.CHECK_AVAILABILITY:
return self._handle_availability_check()
elif self.state.intent == Intent.MAKE_RESERVATION:
return self._handle_reservation_flow(extracted)
else:
return self._handle_general_inquiry()
def _handle_reservation_flow(self, extracted: dict) -> Dict[str, Any]:
"""Handle the multi-step reservation flow."""
# Check which required slots are missing
missing = [slot for slot in REQUIRED_SLOTS if slot not in self.state.collected_slots]
self.state.missing_slots = missing
# If LLM flagged that clarification is needed, or we have missing slots
if extracted.get("needs_clarification") or missing:
# Ask for the first missing slot
next_slot = missing[0] if missing else "date"
question = SLOT_QUESTIONS.get(next_slot, f"Could you provide your {next_slot}?")
return self._respond(question, waiting_for=next_slot)
# All required slots present — check availability before confirming
availability = check_availability(
restaurant_id=self.restaurant_id,
reservation_date=self.state.collected_slots["date"],
reservation_time=self.state.collected_slots["time"],
party_size=self.state.collected_slots["party_size"]
)
if not availability["available"]:
# Suggest alternatives
alternatives = availability.get("alternatives", [])
alt_text = ", ".join(alternatives[:3]) if alternatives else "no alternatives available"
return self._respond(
f"I'm sorry, we don't have availability for {self.state.collected_slots['party_size']} "
f"on {self.state.collected_slots['date']} at {self.state.collected_slots['time']}. "
f"Here are some alternative times: {alt_text}. Would any of these work?",
waiting_for="time"
)
# Available — confirm details and ask for confirmation
details = self.state.collected_slots
confirm_msg = (
f"Great, I can confirm a table for {details['party_size']} on "
f"{details['date']} at {details['time']} under {details['customer_name']}. "
f"Shall I go ahead and book this?"
)
self.state.requires_clarification = True
return self._respond(confirm_msg, awaiting_confirmation=True)
def _handle_cancellation(self) -> Dict[str, Any]:
"""Handle reservation cancellation."""
reservation_id = self.state.collected_slots.get("reservation_id")
customer_name = self.state.collected_slots.get("customer_name")
if reservation_id:
result = cancel_reservation_by_id(reservation_id, self.restaurant_id)
if result["success"]:
return self._respond(f"Your reservation {reservation_id} has been cancelled. We're sorry to see you go!")
else:
return self._respond(f"I couldn't find that reservation. Could you provide your name or confirmation number?")
elif customer_name:
# Search by name
return self._respond(
f"I'll look up reservations under {customer_name}. "
f"Can you confirm which date the reservation was for?"
)
else:
return self._respond(
"I'd be happy to help cancel your reservation. Could you provide your "
"reservation confirmation number or the name on the booking?"
)
def _handle_availability_check(self) -> Dict[str, Any]:
"""Check availability for given parameters."""
date_val = self.state.collected_slots.get("date")
time_val = self.state.collected_slots.get("time")
party_val = self.state.collected_slots.get("party_size")
if not all([date_val, time_val, party_val]):
missing = []
if not date_val: missing.append("date")
if not time_val: missing.append("time")
if not party_val: missing.append("party_size")
return self._respond(
f"To check availability, I'll need: {', '.join(missing)}. "
f"{SLOT_QUESTIONS.get(missing[0], 'Can you provide more details?')}",
waiting_for=missing[0] if missing else None
)
availability = check_availability(
self.restaurant_id, date_val, time_val, party_val
)
if availability["available"]:
return self._respond(
f"Yes! We have tables available for {party_val} on {date_val} at {time_val}. "
f"Would you like me to book it?"
)
else:
return self._respond(
f"Unfortunately, we're fully booked for {party_val} at {time_val} on {date_val}. "
f"Would you like me to check nearby times?"
)
def _handle_general_inquiry(self) -> Dict[str, Any]:
"""Handle general questions about the restaurant."""
return self._respond(
"I'm here to help with reservations! You can ask about availability, "
"make a new booking, or cancel an existing reservation. How can I assist?"
)
def _respond(self, text: str, **kwargs) -> Dict[str, Any]:
"""Create a response and store it in conversation history."""
self.state.conversation_history.append({
"role": "assistant", "content": text
})
return {
"response": text,
"intent": self.state.intent.value if self.state.intent else None,
"collected_slots": self.state.collected_slots,
"is_complete": self._is_booking_complete(),
**kwargs
}
def _is_booking_complete(self) -> bool:
"""Check if we have confirmed and booked the reservation."""
return self.state.collected_slots.get("confirmed", False)
def confirm_booking(self) -> Dict[str, Any]:
"""Called when user confirms they want to proceed with the booking."""
if not self.state.collected_slots:
return self._respond("I don't have enough details to book yet. Let me ask you a few questions.")
reservation = Reservation(
restaurant_id=self.restaurant_id,
date=date.fromisoformat(self.state.collected_slots["date"]),
time=time.fromisoformat(self.state.collected_slots["time"]),
party_size=self.state.collected_slots["party_size"],
customer_name=self.state.collected_slots["customer_name"],
customer_phone=self.state.collected_slots["customer_phone"],
customer_email=self.state.collected_slots.get("customer_email"),
special_requests=self.state.collected_slots.get("special_requests"),
dietary_restrictions=self.state.collected_slots.get("dietary_restrictions", []),
booth_preference=self.state.collected_slots.get("booth_preference", False),
confirmed=True
)
result = create_reservation(reservation)
self.state.collected_slots["confirmed"] = True
self.state.reservation_id = result["reservation_id"]
return self._respond(
f"Confirmed! Your reservation is booked. Your confirmation number is "
f"{result['reservation_id']}. We look forward to serving you, {reservation.customer_name}!"
)
Mock Reservation API
In production, this would connect to your actual reservation system. For development, here's a realistic mock:
# reservation_api.py
from datetime import date, time
from typing import Dict, List, Optional
from models import Reservation
import uuid
# In-memory store for demo purposes
_reservations_db: Dict[str, dict] = {}
_availability_cache: Dict[str, List[str]] = {}
def _generate_id() -> str:
return f"RES-{uuid.uuid4().hex[:8].upper()}"
def check_availability(
restaurant_id: str,
reservation_date: str,
reservation_time: str,
party_size: int
) -> Dict:
"""
Check table availability for a given date, time, and party size.
Returns available status and alternative times if unavailable.
"""
# Mock: simulate a restaurant with limited capacity
hour = int(reservation_time.split(":")[0])
# Simulate: busy during peak hours (18-21)
if 18 <= hour <= 21 and party_size > 4:
return {
"available": False,
"alternatives": [
f"{reservation_date}T17:00",
f"{reservation_date}T17:30",
f"{reservation_date}T21:30",
f"{reservation_date}T22:00"
]
}
# Simulate: small tables always available, large need checking
if party_size <= 2:
return {"available": True, "alternatives": []}
elif party_size <= 6:
return {"available": True, "alternatives": []}
else:
# Large parties need manual review
return {
"available": False,
"alternatives": ["Call for large party arrangements"],
"requires_manager": True
}
def create_reservation(reservation: Reservation) -> Dict:
"""Create a new reservation in the system."""
reservation_id = _generate_id()
reservation_data = reservation.model_dump()
reservation_data["reservation_id"] = reservation_id
reservation_data["created_at"] = date.today().isoformat()
_reservations_db[reservation_id] = reservation_data
return {
"success": True,
"reservation_id": reservation_id,
"details": reservation_data
}
def cancel_reservation_by_id(reservation_id: str, restaurant_id: str) -> Dict:
"""Cancel a reservation by its ID."""
if reservation_id in _reservations_db:
del _reservations_db[reservation_id]
return {"success": True, "message": "Reservation cancelled"}
return {"success": False, "message": "Reservation not found"}
def get_reservation(reservation_id: str) -> Optional[Dict]:
"""Retrieve a reservation by ID."""
return _reservations_db.get(reservation_id)
FastAPI Webhook Endpoint
Expose the agent as a REST endpoint so it can be called from a chat widget, voice assistant, or messaging platform:
# main.py
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from agent import ReservationAgent
from typing import Optional
import uvicorn
app = FastAPI(title="Restaurant Reservation Agent")
# Store active sessions
sessions: dict = {}
class MessageRequest(BaseModel):
session_id: str
message: str
confirm_booking: Optional[bool] = False
class MessageResponse(BaseModel):
response: str
intent: Optional[str]
collected_slots: dict
is_complete: bool
session_id: str
@app.post("/api/agent/message")
async def handle_message(request: MessageRequest):
"""Process a message from a user and return the agent's response."""
# Get or create session
if request.session_id not in sessions:
sessions[request.session_id] = ReservationAgent(
restaurant_id="bistro_north_202"
)
agent = sessions[request.session_id]
# If user is confirming a booking
if request.confirm_booking:
result = agent.confirm_booking()
else:
result = agent.process_message(request.message)
return JSONResponse({
"response": result["response"],
"intent": result.get("intent"),
"collected_slots": result.get("collected_slots", {}),
"is_complete": result.get("is_complete", False),
"session_id": request.session_id
})
@app.post("/api/agent/reset")
async def reset_session(request: Request):
"""Reset a conversation session."""
data = await request.json()
session_id = data.get("session_id")
if session_id in sessions:
sessions[session_id].reset()
return JSONResponse({"status": "reset", "session_id": session_id})
@app.get("/api/health")
async def health():
return {"status": "healthy", "active_sessions": len(sessions)}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8080)
Testing the Reservation Agent
Here's a test script that simulates a full reservation conversation:
# test_reservation_flow.py
import requests
import uuid
BASE_URL = "http://localhost:8080/api/agent"
session_id = str(uuid.uuid4())
def send_message(msg, confirm=False):
resp = requests.post(f"{BASE_URL}/message", json={
"session_id": session_id,
"message": msg,
"confirm_booking": confirm
})
return resp.json()
# Simulate a conversation
print("User: I'd like to make a dinner reservation for Friday at 7pm for 4 people")
r = send_message("I'd like to make a dinner reservation for Friday at 7pm for 4 people")
print(f"Agent: {r['response']}")
print(f"Slots collected: {r['collected_slots']}")
print("\nUser: My name is Sarah Chen, phone is 555-0123")
r = send_message("My name is Sarah Chen, phone is 555-0123")
print(f"Agent: {r['response']}")
print("\nUser: Yes, please book it!")
r = send_message("Yes, please book it!", confirm=True)
print(f"Agent: {r['response']}")
print(f"Complete: {r['is_complete']}")
print(f"Final slots: {r['collected_slots']}")
Building an AI Ordering Agent
Ordering is more complex than reservations because it involves a dynamic menu, item customization, quantities, and often dietary constraints. The agent needs to understand menu items, handle modifications (like "no onions" or "extra cheese"), track an order cart across multiple turns, and calculate totals. Here's how to approach it.
Menu Representation and Embedding Search
First, represent your menu as structured data and create embeddings for semantic search. This allows customers to say "I want something spicy with chicken" and have the agent find relevant items.
# menu_service.py
from pydantic import BaseModel
from typing import List, Optional
import json
import numpy as np
class MenuItem(BaseModel):
id: str
name: str
description: str
price: float
category: str # appetizer, entree, dessert, beverage
dietary_tags: List[str] # ["vegetarian", "gluten-free", "spicy", etc.]
available_modifications: List[str] # ["extra_cheese", "no_onions", "substitute_protein"]
ingredients: List[str]
class MenuCategory(BaseModel):
name: str
items: List[MenuItem]
class OrderCart(BaseModel):
items: List[dict] = []
subtotal: float = 0.0
tax: float = 0.0
tip: float = 0.0
total: float = 0.0
# Sample menu - in production, load from a database or CMS
SAMPLE_MENU = {
"appetizers": [
MenuItem(
id="app_001", name="Crispy Spring Rolls",
description="Golden fried spring rolls with pork, glass noodles, and vegetables. Served with sweet chili dipping sauce.",
price=8.95, category="appetizer",
dietary_tags=["contains_meat", "contains_gluten"],
available_modifications=["make_vegetarian", "extra_sauce"],
ingredients=["pork", "glass_noodles", "carrots", "cabbage", "wrappers"]
),
MenuItem(
id="app_002", name="Edamame with Sea Salt",
description="Steamed young soybeans in the pod, finished with flaky sea salt.",
price=6.50, category="appetizer",
dietary_tags=["vegan", "gluten-free"],
available_modifications=["spicy_version", "no_salt"],
ingredients=["edamame", "sea_salt"]
),
],
"entrees": [
MenuItem(
id="ent_001", name="Pad Thai",
description="Stir-fried rice noodles with shrimp, tofu, peanuts, bean sprouts, and tamarind sauce.",
price=15.95, category="entree",
dietary_tags=["contains_shellfish", "contains_peanuts", "gluten_free_option"],
available_modifications=["no_peanuts", "extra_spicy", "mild", "no_shrimp", "add_extra_protein"],
ingredients=["rice_noodles", "shrimp", "tofu", "peanuts", "bean_sprouts", "tamarind"]
),
MenuItem(
id="ent_002", name="Green Curry",
description="Aromatic green curry with chicken, bamboo shoots, bell peppers, and fresh basil in coconut milk.",
price=14.50, category="entree",
dietary_tags=["contains_meat", "gluten-free", "spicy"],
available_modifications=["mild", "extra_spicy", "no_chicken", "substitute_tofu", "extra_vegetables"],
ingredients=["chicken", "bamboo_shoots", "bell_peppers", "basil", "coconut_milk", "green_curry_paste"]
),
],
"beverages": [
MenuItem(id="bev_001", name="Thai Iced Tea", description="Sweetened black tea with condensed milk over ice.", price=4.50, category="beverage", dietary_tags=["contains_dairy"], available_modifications=["no_milk", "less_sweet"], ingredients=["black_tea", "condensed_milk"]),
]
}
def search_menu_by_semantic(query: str, top_k: int = 5) -> List[MenuItem]:
"""
Search menu items using keyword matching on name, description, and tags.
In production, replace with embedding-based vector search (e.g., using OpenAI embeddings + Pinecone).
"""
all_items = []
for category, items in SAMPLE_MENU.items():
all_items.extend(items)
query_lower = query.lower()
scored = []
for item in all_items:
score = 0
# Match in name
if query_lower in item.name.lower():
score += 10
# Match in description
if query_lower in item.description.lower():
score += 5
# Match in tags
for tag in item.dietary_tags:
if query_lower in tag.lower():
score += 3
# Match ingredients
for ingredient in item.ingredients:
if query_lower in ingredient.lower():
score += 3
if score > 0:
scored.append((score, item))
scored.sort(key=lambda x: x[0], reverse=True)
return [item for _, item in scored[:top_k]]
def get_menu_item_by_id(item_id: str) -> Optional[MenuItem]:
"""Retrieve a specific menu item by ID."""
for category, items in SAMPLE_MENU.items():
for item in items:
if item.id == item_id:
return item
return None
def get_full_menu_text() -> str:
"""Generate a text representation of the menu for the LLM context."""
lines = []
for category, items in SAMPLE_MENU.items():
lines.append(f"\n=== {category.upper()} ===")
for item in items:
mods = ", ".join(item.available_modifications)
tags = ", ".join(item.dietary_tags)
lines.append(
f"[{item.id}] {item.name} - ${item.price:.2f}\n"
f" {item.description}\n"
f" Tags: {tags} | Modifications: {mods}"
)
return "\n".join(lines)
Order Extraction LLM Prompt
The ordering agent needs a specialized prompt that can extract order items, quantities, and modifications from natural language:
# order_extractor.py
import json
from openai import OpenAI
from config import OPENAI_API_KEY
client = OpenAI(api_key=OPENAI_API_KEY)
ORDER_SYSTEM_PROMPT = """You are an order extraction system for a restaurant.
Given the user's message, the current cart state, and the full menu below, extract ordering actions.
{MENU_TEXT}
Return ONLY valid JSON in this format:
{
"action": "add_item" | "remove_item" | "modify_item" | "check_cart" | "checkout" | "ask_menu_question" | "none",
"items": [
{
"menu_item_id": "string matching menu IDs above",
"quantity": integer (default 1),
"modifications": ["list of modifications from the menu's available_modifications"],
"special_instructions": "any custom request not in standard modifications"
}
],
"needs_clarification": true/false,
"clarification_question": "what to ask if the order is unclear",
"cart_summary": "brief summary of what's in the cart now"
}
Rules:
- Match menu_item_id precisely to IDs shown in the menu.
- For phrases like "I'll have the Pad Thai, no peanuts, extra spicy", extract the item and modifications.
- If user says "and also two spring rolls", set quantity to 2.
- If the user references an item vaguely ("that noodle dish"), use context from previous turns.
- Only suggest modifications that exist in available_modifications.
- For dietary restrictions (e.g., "I'm gluten-free"), flag items that are compatible.
"""
The Complete Ordering Agent
# ordering_agent.py
from typing import Dict, Any, List
from models import DialogueState
from menu_service import (
SAMPLE_MENU, search_menu_by_semantic, get_menu_item_by_id,
get_full_menu_text, MenuItem, OrderCart
)
from order_extractor import ORDER_SYSTEM_PROMPT
from openai import OpenAI
from config import OPENAI_API_KEY
import json
client = OpenAI(api_key=OPENAI_API_KEY)
class OrderingAgent:
def __init__(self, restaurant_id: str = "bistro_north_202"):
self.restaurant_id = restaurant_id
self.cart = OrderCart()
self.conversation_history: List[dict] = []
self.menu_text = get_full_menu_text()
def reset(self):
self.cart = OrderCart()
self.conversation_history = []
def process_message(self, user_message: str) -> Dict[str, Any]:
"""Process an ordering-related message."""
self.conversation_history.append({"role": "user", "content": user_message})
# Build the system prompt with menu injected
system_prompt = ORDER_SYSTEM_PROMPT.replace("{MENU_TEXT}", self.menu_text)
messages = [
{"role": "system", "content": system_prompt}
]
# Include conversation history (last 8 turns for context)
for turn in self.conversation_history[-8:]:
messages.append(turn)
# Add cart state context
cart_context = f"\nCurrent cart: {len(self.cart.items)} items, subtotal ${self.cart.subtotal:.2f}"
messages.append({"role": "system", "content": cart_context})
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
response_format={"type": "json_object