← Back to DevBytes

Building AI Agents for Restaurants: Ordering and Reservations

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

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

— Ad —

Google AdSense will appear here after approval

← Back to all articles