What You'll Build
If you've been searching for a real way to build a chatbot with Claude that actually does something useful, this is it. You're going to build a production-ready multi-agent restaurant system that handles food orders, books reservations, and confirms everything automatically — all in about 200 lines of Python. By the end, you'll have three specialized Claude agents talking to each other, routed by a central orchestrator that decides which agent handles which customer request.
This isn't a toy demo. The pattern you'll learn here is the same one we use at Naples AI to automate real restaurant operations for clients in Southwest Florida.
Prerequisites
- Python 3.10 or higher
- An Anthropic API key — grab one at console.anthropic.com
- Basic Python knowledge (classes, functions, dictionaries)
anthropicSDK installed:pip install anthropic- A
.envfile or environment variable set forANTHROPIC_API_KEY
Step 1: Set Up Claude API and Dependencies
Start by installing the Anthropic SDK and setting up your environment. This one command gets you everything you need.
terminalpip install anthropic python-dotenv
Now create a .env file in your project root with your API key.
ANTHROPIC_API_KEY=sk-ant-your-key-here
Next, set up your base configuration file. This loads the key and gives you a reusable client you'll import in every agent module.
config.pyimport os
from anthropic import Anthropic
from dotenv import load_dotenv
load_dotenv()
# Single client instance shared across all agents
client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
MODEL = "claude-sonnet-4-6"Step 2: Define the Order-Taking Agent
The order-taking agent is the most complex one because it uses Claude's tool use feature to structure the order data. Instead of parsing freeform text, we give Claude a tool definition and let it fill in the fields automatically. This is what makes the output reliable enough to feed into a real POS system.
agents/order_agent.pyimport json
from config import client, MODEL
# Tool definition tells Claude exactly what fields to extract from the conversation
ORDER_TOOLS = [
{
"name": "submit_order",
"description": "Submit a structured food order after collecting all required details from the customer.",
"input_schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"description": "List of ordered menu items",
"items": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Menu item name"},
"quantity": {"type": "integer", "description": "Number of this item"},
"special_instructions": {"type": "string", "description": "Any modifications or allergies"}
},
"required": ["name", "quantity"]
}
},
"customer_name": {"type": "string", "description": "Customer's name for the order"},
"order_type": {"type": "string", "enum": ["dine-in", "takeout", "delivery"], "description": "How the customer wants their order"},
"estimated_total": {"type": "number", "description": "Estimated total in USD based on menu pricing"}
},
"required": ["items", "customer_name", "order_type"]
}
}
]
ORDER_SYSTEM_PROMPT = """You are a friendly restaurant order-taking assistant for Bella Vista, an Italian restaurant in Naples, Florida.
Menu items and prices:
- Margherita Pizza: $16
- Pasta Carbonara: $18
- Caesar Salad: $12
- Chicken Parmesan: $22
- Tiramisu: $8
- House Wine (glass): $10
Your job is to:
1. Greet the customer warmly
2. Take their complete order including any special instructions or dietary needs
3. Confirm the order back to them
4. Use the submit_order tool to finalize the order
Always be friendly and confirm the order before submitting it."""
def run_order_agent(user_message: str, conversation_history: list) -> dict:
"""
Runs one turn of the order-taking agent.
Returns a dict with 'response' (text to show user) and 'order_data' (if order was submitted).
"""
# Append the new user message to ongoing conversation
conversation_history.append({"role": "user", "content": user_message})
response = client.messages.create(
model=MODEL,
max_tokens=1024,
system=ORDER_SYSTEM_PROMPT,
tools=ORDER_TOOLS,
messages=conversation_history
)
result = {"response": "", "order_data": None, "history": conversation_history}
# Check if Claude decided to call a tool
if response.stop_reason == "tool_use":
for block in response.content:
if block.type == "tool_use" and block.name == "submit_order":
order_data = block.input
result["order_data"] = order_data
# Add assistant response and tool result back to history
conversation_history.append({"role": "assistant", "content": response.content})
conversation_history.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps({"status": "success", "order_id": "ORD-2847"})
}]
})
# Get Claude's final confirmation message
confirmation_response = client.messages.create(
model=MODEL,
max_tokens=512,
system=ORDER_SYSTEM_PROMPT,
tools=ORDER_TOOLS,
messages=conversation_history
)
result["response"] = confirmation_response.content[0].text
else:
# Claude is still gathering information — just return its message
for block in response.content:
if hasattr(block, "text"):
result["response"] = block.text
conversation_history.append({"role": "assistant", "content": response.content})
result["history"] = conversation_history
return resultStep 3: Create the Reservation-Booking Agent
The reservation agent works the same way — it uses a tool to capture structured reservation data. Notice it has its own system prompt and tool definition, which means it behaves completely independently from the order agent. That's the whole point of multi-agent design: each agent is focused and doesn't have to know what the others are doing.
agents/reservation_agent.pyimport json
from config import client, MODEL
RESERVATION_TOOLS = [
{
"name": "book_reservation",
"description": "Book a table reservation after collecting all required details.",
"input_schema": {
"type": "object",
"properties": {
"guest_name": {"type": "string", "description": "Name for the reservation"},
"party_size": {"type": "integer", "description": "Number of guests"},
"date": {"type": "string", "description": "Reservation date in YYYY-MM-DD format"},
"time": {"type": "string", "description": "Reservation time in HH:MM format (24-hour)"},
"special_requests": {"type": "string", "description": "Any special requests like birthday setup, accessibility needs, etc."},
"contact_phone": {"type": "string", "description": "Phone number for confirmation"}
},
"required": ["guest_name", "party_size", "date", "time", "contact_phone"]
}
}
]
RESERVATION_SYSTEM_PROMPT = """You are a reservation specialist for Bella Vista Italian Restaurant in Naples, Florida.
Restaurant hours: Tuesday-Sunday, 5:00 PM - 10:00 PM. Closed Mondays.
Available reservation times: 5:00 PM, 5:30 PM, 6:00 PM, 6:30 PM, 7:00 PM, 7:30 PM, 8:00 PM, 8:30 PM, 9:00 PM
Your job is to:
1. Collect the guest's name, party size, preferred date and time, and phone number
2. Check if the requested time is within available slots
3. Ask about any special requests (birthdays, anniversaries, dietary needs)
4. Confirm all details with the guest
5. Use the book_reservation tool to finalize the booking
Today's date for reference: 2026-05-29. Always confirm the day of the week when repeating a date back to the guest."""
def run_reservation_agent(user_message: str, conversation_history: list) -> dict:
"""
Runs one turn of the reservation-booking agent.
Returns a dict with 'response' (text) and 'reservation_data' (if booking was completed).
"""
conversation_history.append({"role": "user", "content": user_message})
response = client.messages.create(
model=MODEL,
max_tokens=1024,
system=RESERVATION_SYSTEM_PROMPT,
tools=RESERVATION_TOOLS,
messages=conversation_history
)
result = {"response": "", "reservation_data": None, "history": conversation_history}
if response.stop_reason == "tool_use":
for block in response.content:
if block.type == "tool_use" and block.name == "book_reservation":
reservation_data = block.input
result["reservation_data"] = reservation_data
conversation_history.append({"role": "assistant", "content": response.content})
conversation_history.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps({"status": "confirmed", "confirmation_number": "RES-4491"})
}]
})
confirmation_response = client.messages.create(
model=MODEL,
max_tokens=512,
system=RESERVATION_SYSTEM_PROMPT,
tools=RESERVATION_TOOLS,
messages=conversation_history
)
result["response"] = confirmation_response.content[0].text
else:
for block in response.content:
if hasattr(block, "text"):
result["response"] = block.text
conversation_history.append({"role": "assistant", "content": response.content})
result["history"] = conversation_history
return resultStep 4: Build the Order Confirmation Agent
This lightweight agent handles follow-up questions after an order or reservation is placed. It doesn't need tools — it just needs context about what was already confirmed. This is a great example of how not every agent in a system needs to be complex.
agents/confirmation_agent.pyfrom config import client, MODEL
CONFIRMATION_SYSTEM_PROMPT = """You are a helpful follow-up assistant for Bella Vista Italian Restaurant in Naples, Florida.
Your role is to:
1. Answer questions about existing orders or reservations
2. Provide estimated wait times (kitchen orders: 20-30 min, reservations: confirmed immediately)
3. Handle simple modifications if mentioned (note: major changes require calling the restaurant)
4. Thank the customer warmly and encourage them to enjoy their experience
Restaurant phone for major changes: (239) 555-0192
Restaurant address: 1247 Gulf Shore Blvd, Naples, FL 34102
Keep responses brief and friendly."""
def run_confirmation_agent(user_message: str, context: dict, conversation_history: list) -> dict:
"""
Runs the confirmation agent with context from a prior order or reservation.
'context' should contain the order_data or reservation_data dict from a previous agent.
"""
conversation_history.append({"role": "user", "content": user_message})
# Inject the confirmed booking context directly into the system prompt
context_str = f"\n\nCurrent confirmed context:\n{context}" if context else ""
response = client.messages.create(
model=MODEL,
max_tokens=512,
system=CONFIRMATION_SYSTEM_PROMPT + context_str,
messages=conversation_history
)
reply = response.content[0].text
conversation_history.append({"role": "assistant", "content": reply})
return {"response": reply, "history": conversation_history}Step 5: Connect Agents with Routing Logic
Here's where it all comes together. The main routing agent uses Claude to classify the user's intent and then hands off to the right specialist agent. This is the orchestrator — it doesn't handle orders or reservations itself, it just decides who should. This separation is what makes the system scalable: you can add new agents without rewriting the router.
main.pyimport json
from config import client, MODEL
from agents.order_agent import run_order_agent
from agents.reservation_agent import run_reservation_agent
from agents.confirmation_agent import run_confirmation_agent
# ─── Main Routing Agent ────────────────────────────────────────────────────────
ROUTER_SYSTEM_PROMPT = """You are a routing assistant for Bella Vista Italian Restaurant.
Classify the customer's intent into exactly one of these categories:
- "order" — customer wants to place a food or drink order
- "reservation" — customer wants to book a table or check availability
- "confirmation" — customer has a follow-up question about an existing order or reservation
- "general" — any other question (hours, location, menu info)
Respond with ONLY a JSON object like: {"intent": "order"}
No explanation. No extra text. Just the JSON."""
class RestaurantOrchestrator:
"""
Central routing class that manages agent selection and session state.
Maintains separate conversation histories per agent so context isn't mixed.
"""
def __init__(self):
self.current_agent = None # Tracks which agent is active
self.confirmed_context = {} # Stores completed order/reservation data
self.order_history = [] # Isolated history for order agent
self.reservation_history = [] # Isolated history for reservation agent
self.confirmation_history = [] # Isolated history for confirmation agent
def classify_intent(self, user_message: str) -> str:
"""Uses Claude to classify what the user wants."""
response = client.messages.create(
model=MODEL,
max_tokens=64,
system=ROUTER_SYSTEM_PROMPT,
messages=[{"role": "user", "content": user_message}]
)
raw = response.content[0].text.strip()
try:
return json.loads(raw).get("intent", "general")
except json.JSONDecodeError:
return "general"
def handle_general(self, user_message: str) -> str:
"""Handles basic restaurant questions without a specialist agent."""
response = client.messages.create(
model=MODEL,
max_tokens=512,
system="""You are a helpful assistant for Bella Vista Italian Restaurant in Naples, FL.
Hours: Tue-Sun 5PM-10PM. Address: 1247 Gulf Shore Blvd, Naples, FL 34102.
Phone: (239) 555-0192. Answer briefly and warmly.""",
messages=[{"role": "user", "content": user_message}]
)
return response.content[0].text
def run(self, user_message: str) -> str:
"""
Main entry point. Routes each message to the correct agent
or continues with the currently active agent if a session is ongoing.
"""
# If we're mid-conversation with a specific agent, keep going with it
if self.current_agent == "order":
result = run_order_agent(user_message, self.order_history)
self.order_history = result["history"]
if result.get("order_data"):
self.confirmed_context = {"order": result["order_data"]}
self.current_agent = "confirmation" # Switch to follow-up mode
return result["response"]
if self.current_agent == "reservation":
result = run_reservation_agent(user_message, self.reservation_history)
self.reservation_history = result["history"]
if result.get("reservation_data"):
self.confirmed_context = {"reservation": result["reservation_data"]}
self.current_agent = "confirmation"
return result["response"]
if self.current_agent == "confirmation":
result = run_confirmation_agent(user_message, self.confirmed_context, self.confirmation_history)
self.confirmation_history = result["history"]
return result["response"]
# No active agent — classify intent and route
intent = self.classify_intent(user_message)
print(f" [Router] Detected intent: {intent}")
if intent == "order":
self.current_agent = "order"
result = run_order_agent(user_message, self.order_history)
self.order_history = result["history"]
return result["response"]
elif intent == "reservation":
self.current_agent = "reservation"
result = run_reservation_agent(user_message, self.reservation_history)
self.reservation_history = result["history"]
return result["response"]
elif intent == "confirmation":
self.current_agent = "confirmation"
result = run_confirmation_agent(user_message, self.confirmed_context, self.confirmation_history)
self.confirmation_history = result["history"]
return result["response"]
else:
return self.handle_general(user_message)
# ─── Agent Orchestration Run Loop ─────────────────────────────────────────────
def main():
print("=" * 60)
print(" Bella Vista Restaurant — AI Assistant")
print(" Powered by Naples AI | naplesai.agency")
print("=" * 60)
print("Type 'quit' to exit.\n")
orchestrator = RestaurantOrchestrator()
while True:
user_input = input("You: ").strip()
if not user_input:
continue
if user_input.lower() in ["quit", "exit", "bye"]:
print("Assistant: Thanks for choosing Bella Vista. See you soon!")
break
response = orchestrator.run(user_input)
print(f"\nAssistant: {response}\n")
if __name__ == "__main__":
main()Example Conversation Output with JSON Responses
Here's what a real session looks like when you run the system. I've included the router's internal intent labels so you can see what's happening under the hood.
sample_output.txt============================================================
Bella Vista Restaurant — AI Assistant
Powered by Naples AI | naplesai.agency
============================================================
Type 'quit' to exit.
You: I'd like to order some food for pickup
[Router] Detected intent: order