Skip to content
Learn Agentic AI
Learn Agentic AI14 min read3 views

Building a Dispatch Agent: Intelligent Route Planning and Driver Assignment

Learn how to build an AI dispatch agent that optimizes delivery routes, matches drivers to orders based on constraints like location and capacity, and handles real-time changes to the delivery schedule.

The Dispatch Optimization Problem

Dispatch is one of the hardest problems in logistics. Given a set of delivery orders with time windows, a fleet of drivers with different locations and capacities, and real-time traffic conditions, a dispatcher must assign orders to drivers and sequence stops to minimize total distance while meeting every delivery window.

Human dispatchers juggle this with experience and intuition, but they struggle as order volume grows. An AI dispatch agent combines route optimization algorithms with conversational tools, letting dispatchers interact naturally while the agent handles the computational heavy lifting.

Modeling Orders, Drivers, and Routes

Start with data models that capture the dispatch domain:

flowchart TD
    START["Building a Dispatch Agent: Intelligent Route Plan…"] --> A
    A["The Dispatch Optimization Problem"]
    A --> B
    B["Modeling Orders, Drivers, and Routes"]
    B --> C
    C["Driver Matching Tool"]
    C --> D
    D["Route Optimization Tool"]
    D --> E
    E["Order Assignment Tool"]
    E --> F
    F["Assembling the Dispatch Agent"]
    F --> G
    G["FAQ"]
    G --> DONE["Key Takeaways"]
    style START fill:#4f46e5,stroke:#4338ca,color:#fff
    style DONE fill:#059669,stroke:#047857,color:#fff
from dataclasses import dataclass, field
from datetime import datetime, time
from typing import Optional
import math

@dataclass
class DeliveryOrder:
    order_id: str
    pickup_address: str
    delivery_address: str
    pickup_lat: float
    pickup_lng: float
    delivery_lat: float
    delivery_lng: float
    weight_lbs: float
    window_start: time
    window_end: time
    priority: str = "standard"
    status: str = "pending"

@dataclass
class Driver:
    driver_id: str
    name: str
    current_lat: float
    current_lng: float
    vehicle_capacity_lbs: float
    current_load_lbs: float = 0.0
    active_orders: list[str] = field(default_factory=list)
    status: str = "available"
    shift_end: time = time(18, 0)

def haversine_miles(lat1: float, lng1: float, lat2: float, lng2: float) -> float:
    """Calculate distance between two coordinates in miles."""
    R = 3959
    dlat = math.radians(lat2 - lat1)
    dlng = math.radians(lng2 - lng1)
    a = (math.sin(dlat / 2) ** 2 +
         math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) *
         math.sin(dlng / 2) ** 2)
    return R * 2 * math.asin(math.sqrt(a))

Driver Matching Tool

The matching algorithm scores each driver against an order based on proximity, remaining capacity, and schedule fit:

See AI Voice Agents Handle Real Calls

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

from agents import function_tool

DRIVERS = [
    Driver("DRV-01", "Alex Rivera", 37.7749, -122.4194, 2000.0, 350.0,
           ["ORD-101"], "active"),
    Driver("DRV-02", "Priya Sharma", 37.3382, -121.8863, 1500.0, 0.0,
           [], "available"),
    Driver("DRV-03", "Carlos Mendez", 37.5485, -122.0574, 3000.0, 1200.0,
           ["ORD-102", "ORD-103"], "active"),
]

ORDERS = [
    DeliveryOrder("ORD-201", "Warehouse A", "123 Market St", 37.5600, -122.0700,
                  37.7850, -122.4100, 250.0, time(10, 0), time(14, 0)),
    DeliveryOrder("ORD-202", "Warehouse B", "456 Mission St", 37.3400, -121.8900,
                  37.7600, -122.4300, 180.0, time(9, 0), time(12, 0), "express"),
    DeliveryOrder("ORD-203", "Warehouse A", "789 Howard St", 37.5600, -122.0700,
                  37.7820, -122.3950, 500.0, time(11, 0), time(16, 0)),
]

@function_tool
def find_best_driver(order_id: str) -> str:
    """Find the best available driver for a delivery order based on proximity and capacity."""
    order = next((o for o in ORDERS if o.order_id == order_id), None)
    if not order:
        return f"Order {order_id} not found."

    candidates = []
    for driver in DRIVERS:
        remaining_capacity = driver.vehicle_capacity_lbs - driver.current_load_lbs
        if remaining_capacity < order.weight_lbs:
            continue

        distance = haversine_miles(
            driver.current_lat, driver.current_lng,
            order.pickup_lat, order.pickup_lng,
        )
        load_ratio = driver.current_load_lbs / driver.vehicle_capacity_lbs
        order_count_penalty = len(driver.active_orders) * 2.0

        score = distance + (load_ratio * 10) + order_count_penalty
        if order.priority == "express":
            score *= 0.8 if driver.status == "available" else 1.2

        candidates.append((driver, distance, remaining_capacity, score))

    candidates.sort(key=lambda x: x[3])

    if not candidates:
        return f"No drivers available for order {order_id} ({order.weight_lbs} lbs)."

    lines = [f"Driver rankings for {order_id} ({order.weight_lbs} lbs):"]
    for driver, dist, cap, score in candidates:
        lines.append(
            f"  {driver.name} | {dist:.1f} mi away | "
            f"Capacity: {cap:.0f} lbs remaining | "
            f"Active orders: {len(driver.active_orders)} | Score: {score:.1f}"
        )
    return "\n".join(lines)

Route Optimization Tool

Once orders are assigned, the agent optimizes the stop sequence using a nearest-neighbor heuristic:

@function_tool
def optimize_route(driver_id: str, order_ids: list[str]) -> str:
    """Optimize delivery sequence for a driver using nearest-neighbor routing."""
    driver = next((d for d in DRIVERS if d.driver_id == driver_id), None)
    if not driver:
        return f"Driver {driver_id} not found."

    orders = [o for o in ORDERS if o.order_id in order_ids]
    if not orders:
        return "No valid orders provided."

    # Nearest-neighbor heuristic
    route = []
    current_lat, current_lng = driver.current_lat, driver.current_lng
    remaining = list(orders)
    total_distance = 0.0

    while remaining:
        nearest = min(
            remaining,
            key=lambda o: haversine_miles(
                current_lat, current_lng, o.pickup_lat, o.pickup_lng
            ),
        )
        pickup_dist = haversine_miles(
            current_lat, current_lng, nearest.pickup_lat, nearest.pickup_lng
        )
        delivery_dist = haversine_miles(
            nearest.pickup_lat, nearest.pickup_lng,
            nearest.delivery_lat, nearest.delivery_lng,
        )
        total_distance += pickup_dist + delivery_dist

        route.append(
            f"  {len(route)+1}. Pickup {nearest.order_id} at {nearest.pickup_address} "
            f"({pickup_dist:.1f} mi) -> Deliver to {nearest.delivery_address} "
            f"({delivery_dist:.1f} mi)"
        )
        current_lat, current_lng = nearest.delivery_lat, nearest.delivery_lng
        remaining.remove(nearest)

    lines = [
        f"Optimized route for {driver.name}:",
        *route,
        f"\nTotal distance: {total_distance:.1f} miles",
        f"Estimated time: {total_distance / 25 * 60:.0f} minutes (avg 25 mph city)",
    ]
    return "\n".join(lines)

Order Assignment Tool

@function_tool
def assign_order(order_id: str, driver_id: str) -> str:
    """Assign a delivery order to a specific driver."""
    order = next((o for o in ORDERS if o.order_id == order_id), None)
    driver = next((d for d in DRIVERS if d.driver_id == driver_id), None)

    if not order:
        return f"Order {order_id} not found."
    if not driver:
        return f"Driver {driver_id} not found."

    remaining = driver.vehicle_capacity_lbs - driver.current_load_lbs
    if order.weight_lbs > remaining:
        return (
            f"Cannot assign: {order.weight_lbs} lbs exceeds "
            f"{driver.name}'s remaining capacity of {remaining} lbs."
        )

    driver.current_load_lbs += order.weight_lbs
    driver.active_orders.append(order_id)
    driver.status = "active"
    order.status = "assigned"

    return (
        f"Order {order_id} assigned to {driver.name}. "
        f"New load: {driver.current_load_lbs}/{driver.vehicle_capacity_lbs} lbs. "
        f"Active orders: {len(driver.active_orders)}"
    )

Assembling the Dispatch Agent

from agents import Agent, Runner

dispatch_agent = Agent(
    name="Dispatch Coordinator",
    instructions="""You are an intelligent dispatch assistant. You can:
    1. Find the best driver for each order based on proximity and capacity
    2. Assign orders to drivers
    3. Optimize delivery routes for assigned orders
    Prioritize express orders. Always explain your driver recommendations.""",
    tools=[find_best_driver, assign_order, optimize_route],
)

result = Runner.run_sync(
    dispatch_agent,
    "I have three new orders: ORD-201, ORD-202, and ORD-203. Assign them to the best drivers and optimize routes."
)
print(result.final_output)

FAQ

Why use nearest-neighbor instead of a more optimal routing algorithm?

Nearest-neighbor is a greedy heuristic that runs in O(n squared) time and produces routes within 20-25 percent of optimal. For real-time dispatch where decisions must be made in seconds, it strikes a good balance. For batch optimization, use Google OR-Tools or the OSRM Trip API which implement more sophisticated algorithms like branch-and-bound or Lin-Kernighan.

How do I handle real-time order changes (cancellations, additions)?

Build a re-optimization tool that the agent calls whenever the order set changes. The tool takes the driver's current position and remaining orders, re-runs the routing algorithm, and returns an updated sequence. Use webhooks or polling to detect order state changes and trigger re-optimization automatically.

Can the agent handle multi-stop pickups where one warehouse has multiple orders?

Yes. Group orders by pickup location before routing. The tool should recognize when multiple orders share the same warehouse and batch them into a single pickup stop. This significantly reduces total distance by avoiding redundant trips to the same location.


#Dispatch #RouteOptimization #DriverAssignment #LogisticsAI #Python #AgenticAI #LearnAI #AIEngineering

Share
C

Written by

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.

Related Articles You May Like

Use Cases

AI Voice Agents for Last-Mile Delivery: Reducing Where-Is-My-Package Calls by 70% with Proactive Updates

Learn how AI voice agents eliminate WISMO calls by proactively notifying customers about delivery status, exceptions, and rescheduling options.

Use Cases

ETA and Status Calls Overwhelm Dispatch: Chat and Voice Agents Can Absorb the Load

Dispatch teams lose hours to repetitive where-are-you and ETA calls. Learn how AI chat and voice agents deliver live status without tying up dispatchers.

AI Interview Prep

7 AI Coding Interview Questions From Anthropic, Meta & OpenAI (2026 Edition)

Real AI coding interview questions from Anthropic, Meta, and OpenAI in 2026. Includes implementing attention from scratch, Anthropic's progressive coding screens, Meta's AI-assisted round, and vector search — with solution approaches.

Use Cases

Emergency Dispatch Priorities Are Unclear: Use Chat and Voice Agents to Triage Faster

When every urgent request sounds the same, teams struggle to triage. Learn how AI chat and voice agents classify urgency and route the right cases first.

Learn Agentic AI

Building a Multi-Agent Data Pipeline: Ingestion, Transformation, and Analysis Agents

Build a three-agent data pipeline with ingestion, transformation, and analysis agents that process data from APIs, CSVs, and databases using Python.

Use Cases

Same-Day Schedule Changes Create Chaos: Use Chat and Voice Agents to Rebalance Faster

Same-day cancellations and reshuffles can overwhelm schedulers. Learn how AI chat and voice agents help rebalance appointments and crews in real time.