Skip to content

glennliew/cafe-order-assistant

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Café Order Assistant

A Python implementation of an LLM-powered café order processing system using OpenAI function calling to handle natural language orders, inventory management, pricing, customer allergies, loyalty points, and customer communication.

Key Features

  • Natural Language Processing: Parse customer orders from free-text input
  • Inventory Management: Real-time availability checking for all ingredients
  • Allergy Protection: Automatic customer allergy checking with clear messaging
  • Loyalty Points System: Redeem points in blocks of 100 for $5 off (minimum $5 subtotal)
  • Dynamic Pricing: Size multipliers, modifier pricing, location-based tax calculation
  • Smart Substitutions: Suggest alternatives when items are unavailable
  • Customer Communication: Send messages for substitutions and clarifications

System Prompt

You are CafeOrderAssistant. Parse customer orders and MUST call appropriate functions. DO NOT provide explanations - only call functions and return final JSON.

Available menu items with BASE PRICES:
- DRIP: Drip Coffee (S/M/L) - BASE: $2.50 - modifiers: milk (none/whole/skim/oat/almond), shots (0-3)
- LATTE: Latte (S/M/L) - BASE: $4.00 - modifiers: milk (whole/skim/oat/almond), syrup (vanilla/caramel)
- MOCHA: Mocha (S/M/L) - BASE: $4.50 - modifiers: milk (whole/skim/oat/almond), choc (dark/milk)
- COOKIE: Cookie (no sizes) - BASE: $2.00 - allergens: gluten, nuts

PRICING RULES:
- Size multipliers: S ×1.0, M ×1.2, L ×1.4
- Modifiers: extra shot +$0.75 each; alt milks (oat/almond) +$0.50; syrups +$0.50
- Tax: 8.875% if location=NYC, else 0%
- ALWAYS use correct base prices in pricing_quote calls

STRICT WORKFLOW:
1. Parse order into normalized items with ALL modifiers
2. ALWAYS call menu_and_inventory_lookup with customer_id and ALL modifiers
3. If missing information (size, milk type), return ask_for_info action
4. If inventory issues or allergies, call messaging_send then return substitute_and_confirm
5. If everything OK, call pricing_quote then return fulfill action
6. Return ONLY final JSON decision - no explanations or markdown

CRITICAL RULES:
- For "latte" without size/milk → MUST ask_for_info
- For incomplete orders → MUST ask_for_info (combine missing fields)
- ALWAYS call menu_and_inventory_lookup with ALL modifiers before any decision
- ALWAYS include customer_id parameter for allergy checking
- If substitutions needed → call messaging_send first
- Default size: M if not specified (except when asking for info)
- ALWAYS use the exact location provided in the order context for pricing_quote
- For loyalty points: redeem in blocks of 100 for $5 off, cannot exceed subtotal
- When customer asks to "pay with points", suggest appropriate redemption amount

MESSAGE CLARITY RULES:
- If "cookie_gluten" in missing: say "cannot serve cookies due to your gluten allergy"
- If "cookie" in missing: say "out of cookies"
- If "almond_milk" in missing: say "out of almond milk"
- Be specific about WHY items cannot be served (allergy vs inventory)

FUNCTION CALL REQUIREMENTS:
- Every order MUST call menu_and_inventory_lookup with customer_id and complete modifiers first
- Include ALL modifiers in menu_and_inventory_lookup calls
- Use pricing_quote for fulfill actions - MUST include ALL modifiers in pricing_quote calls
- Use messaging_send for substitutions
- Return final decision as clean JSON only

FINAL RESPONSE FORMATS:
ask_for_info: {"action": "ask_for_info", "ask": {"missing": ["size", "milk"], "message": "..."}}
fulfill: {"action": "fulfill", "items": [...], "pricing": {...}}
substitute_and_confirm: {"action": "substitute_and_confirm", "substitutions": {...}, "message": "..."}
reject: {"action": "reject", "reason": "..."}

DO NOT explain your reasoning or provide markdown. Call functions then return JSON.

Few-Shot Examples

Example 1: Successful Order

USER: "large mocha, dark chocolate, oat milk, 2 extra shots, and a cookie"
ASSISTANT → tool call: menu_and_inventory_lookup([...])
TOOL → {ok:true}
ASSISTANT → tool call: pricing_quote([...])
TOOL → {...}
ASSISTANT (final):
{
  "order_id":"O1",
  "action":"fulfill",
  "items":[...],
  "pricing":{...},
  "notes":["LLM processed order successfully"]
}

Example 2: Missing Information

USER: "latte"
ASSISTANT → tool call: menu_and_inventory_lookup([{"sku":"LATTE","size":"M","qty":1}], "CUST-002")
TOOL → {ok:false, missing:["milk_type"]}
ASSISTANT (final):
{
  "action":"ask_for_info",
  "ask": {"missing":["milk"],["size"], "message":"Please specify size and milk type"}
}

Example 3: Allergy Protection with Clear Messaging

USER: "small latte with almond milk and a cookie" (Customer has gluten allergy)
ASSISTANT → tool call: menu_and_inventory_lookup([...], "CUST-002")
TOOL → {ok:false, missing:["almond_milk","cookie_gluten"], suggestions:{...}}
ASSISTANT → tool call: messaging_send("CUST-002", "We are out of almond milk and cannot serve cookies due to your gluten allergy. Would you like to substitute almond milk with oat milk and remove the cookie from your order?")
ASSISTANT (final):
{
  "action":"substitute_and_confirm",
  "substitutions":{"almond_milk":"oat_milk","cookie_gluten":"remove_cookie"},
  "message":"We are out of almond milk and cannot serve cookies due to your gluten allergy..."
}

LLM Integration

Function Calling Architecture

The system uses OpenAI-style function calling with these tool definitions:

TOOL_FUNCTIONS = [
    {
        "name": "menu_and_inventory_lookup",
        "description": "Check menu items and inventory availability",
        "parameters": {
            "type": "object",
            "properties": {
                "items": {
                    "type": "array",
                    "description": "List of normalized items to check",
                    "items": {
                        "type": "object",
                        "properties": {
                            "sku": {"type": "string"},
                            "size": {"type": "string"},
                            "qty": {"type": "integer"},
                            "modifiers": {"type": "object"}
                        },
                        "required": ["sku"]
                    }
                },
                "customer_id": {
                    "type": "string",
                    "description": "Customer ID for allergy checking"
                }
            },
            "required": ["items"]
        }
    },
    {
        "name": "pricing_quote",
        "description": "Calculate order pricing with taxes and discounts",
        "parameters": {
            "type": "object",
            "properties": {
                "order": {
                    "type": "array",
                    "description": "List of items for pricing",
                    "items": {
                        "type": "object",
                        "properties": {
                            "sku": {"type": "string"},
                            "base_price": {"type": "number"},
                            "size": {"type": "string"},
                            "qty": {"type": "integer"},
                            "modifiers": {"type": "object"}
                        },
                        "required": ["sku", "base_price"]
                    }
                },
                "opts": {
                    "type": "object",
                    "properties": {
                        "location": {"type": "string"},
                        "loyalty_points_to_redeem": {"type": "integer"},
                        "customer_id": {"type": "string"}
                    },
                    "required": ["location", "customer_id"]
                }
            },
            "required": ["order", "opts"]
        }
    },
    {
        "name": "messaging_send",
        "description": "Send a message to customer",
        "parameters": {
            "type": "object",
            "properties": {
                "to": {"type": "string", "description": "Customer ID"},
                "text": {"type": "string", "description": "Message text"}
            },
            "required": ["to", "text"]
        }
    }
]

LLM Decision Flow

  1. Parse Order: LLM extracts items, sizes, modifiers from natural language
  2. Tool Selection: LLM decides which tools to call based on parsed information
  3. Function Execution: System executes tool calls and returns results to LLM
  4. Decision Making: LLM uses tool results to make final action decision
  5. Output Generation: LLM formats decision as required JSON structure

Project Structure

cafe_order_assistant/
├── data/
│   ├── menu.json          # Menu items (DRIP, LATTE, MOCHA, COOKIE)
│   ├── inventory.json     # Inventory levels (oat_milk, almond_milk, etc.)
│   ├── customers.json     # Customer data (CUST-001, CUST-002)
│   └── orders.jsonl       # Test orders (O1-O5)
├── src/
│   ├── agent.py           # LLM agent with function calling
│   ├── tools.py           # Tool implementations  
│   ├── pricing.py         # Exact pricing rules
│   └── logger.py          # Logging and replay support
├── evaluate.py            # Evaluation script
├── README.md              # This file
├── replay.json            # Generated by evaluate.py
└── outbox.log             # Generated by messaging.send

Tools Implementation

1. menu_and_inventory_lookup(items, customer_id)

  • Input: List of normalized items with sku, size, modifiers, qty + customer_id for allergy checking
  • Output: {ok, missing, suggestions} with availability status and allergy conflicts
  • Checks inventory for components (oat_milk, almond_milk, cookie, etc.)
  • Checks customer allergies and returns specific missing codes (e.g., "cookie_gluten" vs "cookie")
  • Provides clear suggestions for substitutions

2. pricing_quote(order, opts)

  • Input: Order items and options (location, loyalty_points_to_redeem, customer_id)
  • Output: {subtotal, discounts, tax, total, redeemed_points}
  • Applies exact pricing rules:
    • Size multipliers: S ×1.0, M ×1.2, L ×1.4 (cookies don't get size multipliers)
    • Modifiers: extra shot +$0.75; alt milks (oat/almond) +$0.50; syrups +$0.50
    • Tax: 8.875% if location=NYC, else 0%
    • Loyalty: 100 points = $5 off (requires subtotal ≥ $5 for redemption)
  • Updates customer loyalty points in customers.json when points are redeemed

3. messaging_send(to, text)

  • Input: Customer ID and message text
  • Output: {"status": "ok"}
  • Appends timestamped message to outbox.log

API Contract

Input Format

{
  "id": "O1",
  "customer_id": "CUST-001", 
  "text": "large mocha, dark chocolate, oat milk, 2 extra shots, and a cookie",
  "meta": {"location": "NYC"}
}

Output Format

{
  "order_id": "O1",
  "action": "fulfill",
  "items": [...],
  "pricing": {...},
  "notes": ["LLM processed order successfully"]
}

Expected Evaluation Results

Order Expected Action Rationale Key Features Tested
O1 fulfill Large mocha + cookie with loyalty points Loyalty redemption (100 pts = $5 off), size multipliers, modifier pricing
O2 ask_for_info Latte missing milk type specification Information gathering, clear messaging
O3 substitute_and_confirm Almond milk OOS + cookie allergy protection Clear messaging: "out of almond milk" vs "cannot serve cookies due to gluten allergy"
O4 fulfill Drip coffee, no point redemption Subtotal < $5 prevents loyalty redemption
O5 substitute_and_confirm Milk chocolate OOS → dark chocolate Inventory management with alternatives

Usage

Setup

  1. Install dependencies:
pip install -r requirements.txt

### Dependencies
- `openai>=1.0.0` - OpenAI API client
- `python-dotenv>=1.0.0` - Environment variable management
  1. Create .env file with your OpenAI API key:
OPENAI_API_KEY=your_actual_openai_api_key_here
OPENAI_MODEL=gpt-4
OPENAI_MAX_TOKENS=5000
OPENAI_TEMPERATURE=0.1

Run Live Evaluation (with LLM calls)

cd cafe-order-assistant-authentic
python evaluate.py --live

Run Replay Mode (no LLM calls)

cd cafe-order-assistant-authentic
python evaluate.py --replay replay.json

Evaluation Output:

  • Tool call traces for each order showing function calls and results
  • Final decisions with action types (fulfill/ask_for_info/substitute_and_confirm)
  • Success/failure status against expected actions
  • Message logs saved to outbox.log
  • Complete replay data saved to replay.json for future testing

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages