Skip to content
Learn Agentic AI14 min read0 views

Building a Food Delivery Support Agent: Order Tracking and Issue Resolution

Build an AI support agent for food delivery platforms that tracks orders in real time, provides accurate ETAs, categorizes issues, and processes refunds through structured workflows.

The Delivery Support Challenge

Food delivery platforms handle thousands of support inquiries per hour: "Where is my order?", "I received the wrong item," "My food arrived cold," "The driver cannot find my address." Each inquiry category requires a different resolution workflow, and customers expect instant responses during an experience that is already time-sensitive.

An AI support agent resolves the majority of these inquiries automatically while knowing exactly when to escalate to a human agent — and handing off with full context when it does.

Order State Model

The foundation of a delivery support agent is a comprehensive order state model that the agent can query in real time.

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

from dataclasses import dataclass, field
from datetime import datetime, timedelta
from enum import Enum
from typing import Optional

class OrderStatus(Enum):
    PLACED = "placed"
    CONFIRMED = "confirmed"
    PREPARING = "preparing"
    READY_FOR_PICKUP = "ready_for_pickup"
    DRIVER_ASSIGNED = "driver_assigned"
    PICKED_UP = "picked_up"
    EN_ROUTE = "en_route"
    ARRIVING = "arriving"
    DELIVERED = "delivered"
    CANCELLED = "cancelled"

class IssueCategory(Enum):
    MISSING_ITEM = "missing_item"
    WRONG_ITEM = "wrong_item"
    COLD_FOOD = "cold_food"
    LATE_DELIVERY = "late_delivery"
    DRIVER_ISSUE = "driver_issue"
    QUALITY_ISSUE = "quality_issue"
    NEVER_DELIVERED = "never_delivered"
    SPILLED = "spilled"

@dataclass
class DeliveryOrder:
    order_id: str
    customer_name: str
    customer_phone: str
    restaurant_name: str
    items: list[dict]
    status: OrderStatus
    placed_at: datetime
    estimated_delivery: datetime
    driver_name: Optional[str] = None
    driver_phone: Optional[str] = None
    driver_location: Optional[dict] = None
    actual_delivery: Optional[datetime] = None
    total: float = 0.0
    delivery_fee: float = 0.0

    @property
    def is_late(self) -> bool:
        now = datetime.now()
        return now > self.estimated_delivery and self.status != OrderStatus.DELIVERED

    @property
    def minutes_until_delivery(self) -> int:
        delta = self.estimated_delivery - datetime.now()
        return max(0, int(delta.total_seconds() / 60))

@dataclass
class SupportTicket:
    ticket_id: str
    order_id: str
    category: IssueCategory
    description: str
    resolution: str = ""
    refund_amount: float = 0.0
    created_at: datetime = field(default_factory=datetime.now)
    resolved: bool = False

Building the Support Agent Tools

from agents import Agent, function_tool

# Simulated order database
orders_db: dict[str, DeliveryOrder] = {}
tickets_db: list[SupportTicket] = []

@function_tool
def track_order(order_id: str) -> str:
    order = orders_db.get(order_id)
    if not order:
        return f"Order {order_id} not found. Please verify the order ID."
    status_messages = {
        OrderStatus.PLACED: "Your order has been placed and is awaiting restaurant confirmation.",
        OrderStatus.CONFIRMED: "The restaurant has confirmed your order.",
        OrderStatus.PREPARING: "Your food is being prepared.",
        OrderStatus.READY_FOR_PICKUP: "Your order is ready and waiting for a driver.",
        OrderStatus.DRIVER_ASSIGNED: f"Driver {order.driver_name} has been assigned.",
        OrderStatus.PICKED_UP: f"Driver {order.driver_name} has picked up your order.",
        OrderStatus.EN_ROUTE: f"Your order is on the way. ETA: {order.minutes_until_delivery} minutes.",
        OrderStatus.ARRIVING: "Your driver is arriving now!",
        OrderStatus.DELIVERED: f"Your order was delivered at {order.actual_delivery}.",
    }
    message = status_messages.get(order.status, f"Status: {order.status.value}")
    if order.is_late:
        message += " We apologize for the delay."
    return message

@function_tool
def get_driver_location(order_id: str) -> str:
    order = orders_db.get(order_id)
    if not order or not order.driver_location:
        return "Driver location is not available at this time."
    loc = order.driver_location
    return (
        f"Driver {order.driver_name} is at {loc.get('street', 'unknown location')}, "
        f"approximately {loc.get('distance_km', '?')} km away. "
        f"ETA: {order.minutes_until_delivery} minutes."
    )

@function_tool
def report_issue(order_id: str, category: str, description: str) -> str:
    order = orders_db.get(order_id)
    if not order:
        return f"Order {order_id} not found."

    try:
        issue_cat = IssueCategory(category)
    except ValueError:
        valid = ", ".join(c.value for c in IssueCategory)
        return f"Invalid category. Valid options: {valid}"

    # Determine refund eligibility and amount
    refund_rules = {
        IssueCategory.MISSING_ITEM: ("partial", 0.0),
        IssueCategory.WRONG_ITEM: ("full_item", 0.0),
        IssueCategory.COLD_FOOD: ("partial", 0.30),
        IssueCategory.LATE_DELIVERY: ("delivery_fee", order.delivery_fee),
        IssueCategory.NEVER_DELIVERED: ("full", order.total),
        IssueCategory.SPILLED: ("full", order.total),
        IssueCategory.QUALITY_ISSUE: ("partial", 0.25),
        IssueCategory.DRIVER_ISSUE: ("escalate", 0.0),
    }

    rule_type, amount = refund_rules.get(issue_cat, ("escalate", 0.0))

    ticket = SupportTicket(
        ticket_id=f"TKT-{len(tickets_db)+1:04d}",
        order_id=order_id,
        category=issue_cat,
        description=description,
    )

    if rule_type == "full":
        ticket.refund_amount = order.total
        ticket.resolution = f"Full refund of ${order.total:.2f} issued."
        ticket.resolved = True
    elif rule_type == "delivery_fee":
        ticket.refund_amount = order.delivery_fee
        ticket.resolution = f"Delivery fee refund of ${order.delivery_fee:.2f} issued."
        ticket.resolved = True
    elif rule_type == "partial":
        refund = round(order.total * amount, 2) if amount < 1 else amount
        ticket.refund_amount = refund
        ticket.resolution = f"Partial credit of ${refund:.2f} issued."
        ticket.resolved = True
    else:
        ticket.resolution = "Escalated to senior support team."
        ticket.resolved = False

    tickets_db.append(ticket)
    return f"Ticket {ticket.ticket_id} created. {ticket.resolution}"

@function_tool
def request_redelivery(order_id: str) -> str:
    order = orders_db.get(order_id)
    if not order:
        return f"Order {order_id} not found."
    if order.status != OrderStatus.DELIVERED:
        return "Redelivery is only available for delivered orders with issues."
    return (
        f"Redelivery requested for order {order_id}. "
        f"A new driver will pick up a fresh order from {order.restaurant_name}. "
        f"Estimated delivery: 35-45 minutes."
    )

delivery_support_agent = Agent(
    name="Delivery Support Agent",
    instructions="""You are a customer support agent for a food delivery platform.
    Help customers track orders, report issues, and resolve problems quickly.
    Always check the order status first before addressing concerns. Be empathetic
    about delays and food quality issues. Offer refunds or redelivery when
    appropriate based on the issue type.""",
    tools=[track_order, get_driver_location, report_issue, request_redelivery],
)

FAQ

How does the agent determine whether to offer a refund or redelivery?

The agent uses a rules engine that maps issue categories to resolution actions. Missing items and quality issues trigger partial refunds. Never-delivered and spilled orders qualify for full refunds. For wrong items, the agent offers both a refund for the incorrect item and optional redelivery of the correct one. The customer can choose their preferred resolution.

What prevents customers from abusing the refund system?

The agent integrates with a customer risk score calculated from historical claims. Customers with a high frequency of refund requests are flagged, and the agent escalates their tickets to a human reviewer instead of auto-approving refunds. The escalation is transparent — the agent tells the customer their issue is being reviewed by a specialist.

How does the agent handle real-time ETA updates when a driver is stuck in traffic?

The order tracking system receives GPS updates from the driver's app every 30 seconds. The agent's track_order tool reads the latest estimated delivery time, which the routing engine recalculates dynamically based on current traffic conditions. If the ETA changes significantly, the system can proactively notify the customer without waiting for them to ask.


#FoodDelivery #CustomerSupportAI #OrderTracking #AgenticAI #Python #LearnAI #AIEngineering

Share this article
C

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.