Building a Shipping and Tracking Agent: Real-Time Package Status and Delivery Coordination
Create an AI agent that aggregates tracking data from multiple carriers, predicts ETAs, handles delivery exceptions, and provides real-time package status through a conversational interface.
Why Shipping Needs Intelligent Tracking Agents
E-commerce businesses ship through multiple carriers: FedEx for overnight, UPS for ground, USPS for lightweight parcels, and regional carriers for last-mile. Customers ask "where is my package?" constantly, and support teams spend hours copying tracking numbers between carrier websites.
An AI shipping agent aggregates tracking data from every carrier into a single interface. It normalizes status updates into a consistent format, predicts delivery windows based on historical transit data, and proactively flags exceptions like weather delays or failed delivery attempts before customers even ask.
Modeling Shipment Data
Define a consistent shipment model that normalizes data across carriers:
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from typing import Optional
class ShipmentStatus(str, Enum):
LABEL_CREATED = "label_created"
PICKED_UP = "picked_up"
IN_TRANSIT = "in_transit"
OUT_FOR_DELIVERY = "out_for_delivery"
DELIVERED = "delivered"
EXCEPTION = "exception"
RETURNED = "returned"
class Carrier(str, Enum):
FEDEX = "fedex"
UPS = "ups"
USPS = "usps"
DHL = "dhl"
@dataclass
class TrackingEvent:
timestamp: datetime
location: str
description: str
status: ShipmentStatus
@dataclass
class Shipment:
tracking_number: str
carrier: Carrier
origin: str
destination: str
status: ShipmentStatus
estimated_delivery: Optional[datetime]
events: list[TrackingEvent]
order_id: Optional[str] = None
weight_lbs: Optional[float] = None
Building the Multi-Carrier Tracking Tool
The tracking tool queries the appropriate carrier API based on the tracking number format:
from agents import function_tool
SHIPMENTS_DB: dict[str, Shipment] = {
"1Z999AA10123456784": Shipment(
tracking_number="1Z999AA10123456784",
carrier=Carrier.UPS,
origin="Los Angeles, CA",
destination="New York, NY",
status=ShipmentStatus.IN_TRANSIT,
estimated_delivery=datetime(2026, 3, 19, 17, 0),
events=[
TrackingEvent(datetime(2026, 3, 15, 8, 30), "Los Angeles, CA",
"Picked up", ShipmentStatus.PICKED_UP),
TrackingEvent(datetime(2026, 3, 16, 14, 0), "Phoenix, AZ",
"In transit - departed facility", ShipmentStatus.IN_TRANSIT),
TrackingEvent(datetime(2026, 3, 17, 6, 15), "Dallas, TX",
"In transit - arrived at hub", ShipmentStatus.IN_TRANSIT),
],
),
"9400111899223100012": Shipment(
tracking_number="9400111899223100012",
carrier=Carrier.USPS,
origin="Seattle, WA",
destination="Portland, OR",
status=ShipmentStatus.EXCEPTION,
estimated_delivery=None,
events=[
TrackingEvent(datetime(2026, 3, 14, 10, 0), "Seattle, WA",
"Accepted at USPS origin", ShipmentStatus.PICKED_UP),
TrackingEvent(datetime(2026, 3, 16, 9, 0), "Portland, OR",
"Delivery attempted - no access", ShipmentStatus.EXCEPTION),
],
),
}
@function_tool
def track_package(tracking_number: str) -> str:
"""Track a package by tracking number across all supported carriers."""
shipment = SHIPMENTS_DB.get(tracking_number)
if not shipment:
return f"No shipment found for tracking number {tracking_number}."
eta = (shipment.estimated_delivery.strftime("%B %d, %Y %I:%M %p")
if shipment.estimated_delivery else "Unknown")
lines = [
f"Carrier: {shipment.carrier.value.upper()}",
f"Route: {shipment.origin} -> {shipment.destination}",
f"Status: {shipment.status.value.replace('_', ' ').title()}",
f"Estimated Delivery: {eta}",
"",
"Tracking History:",
]
for event in reversed(shipment.events):
lines.append(
f" {event.timestamp.strftime('%m/%d %H:%M')} | "
f"{event.location} | {event.description}"
)
return "\n".join(lines)
Order Lookup Tool
Customers often know their order ID but not the tracking number. This tool bridges that gap:
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
ORDER_TO_TRACKING = {
"ORD-50001": ["1Z999AA10123456784"],
"ORD-50002": ["9400111899223100012"],
"ORD-50003": ["1Z999AA10123456784", "9400111899223100012"],
}
@function_tool
def lookup_order_shipments(order_id: str) -> str:
"""Look up all tracking numbers associated with an order ID."""
tracking_numbers = ORDER_TO_TRACKING.get(order_id.upper())
if not tracking_numbers:
return f"No shipments found for order {order_id}."
lines = [f"Order {order_id} has {len(tracking_numbers)} shipment(s):"]
for tn in tracking_numbers:
shipment = SHIPMENTS_DB.get(tn)
if shipment:
lines.append(
f" {tn} ({shipment.carrier.value.upper()}) - "
f"{shipment.status.value.replace('_', ' ').title()}"
)
return "\n".join(lines)
Exception Handling Tool
When a shipment hits an exception, the agent needs tools to resolve it:
@function_tool
def report_delivery_exception(
tracking_number: str,
resolution: str,
new_delivery_instructions: Optional[str] = None,
) -> str:
"""Report or resolve a delivery exception for a shipment."""
shipment = SHIPMENTS_DB.get(tracking_number)
if not shipment:
return "Shipment not found."
if shipment.status != ShipmentStatus.EXCEPTION:
return f"Shipment is not in exception status. Current: {shipment.status.value}"
valid_resolutions = ["reattempt", "hold_at_facility", "redirect", "return_to_sender"]
if resolution not in valid_resolutions:
return f"Invalid resolution. Choose from: {', '.join(valid_resolutions)}"
# In production, this calls the carrier API
return (
f"Exception resolution submitted for {tracking_number}: {resolution}. "
f"{'Instructions: ' + new_delivery_instructions if new_delivery_instructions else ''}"
f"Carrier will process within 2 hours."
)
Assembling the Shipping Agent
from agents import Agent, Runner
shipping_agent = Agent(
name="Shipping Tracker",
instructions="""You are a shipping and delivery assistant. Help customers:
1. Track packages by tracking number or order ID
2. Explain delivery status and estimated arrival times
3. Handle delivery exceptions by offering resolution options
Always explain carrier-specific terminology in plain language.""",
tools=[track_package, lookup_order_shipments, report_delivery_exception],
)
result = Runner.run_sync(
shipping_agent,
"My order ORD-50002 was supposed to arrive yesterday. What happened?"
)
print(result.final_output)
The agent will look up the order, find the USPS tracking number, see the exception status, and proactively offer resolution options.
FAQ
How do I integrate with real carrier APIs?
Use carrier-specific SDKs or aggregate APIs like ShipEngine, EasyPost, or Shippo. These services normalize tracking data across carriers into a single API. Authenticate with API keys, poll for updates every 15 minutes, or use webhooks for real-time push notifications when status changes occur.
How do I predict accurate delivery times beyond the carrier estimate?
Build a simple model that tracks actual transit times for common origin-destination pairs. Store historical delivery data and compute rolling averages. Factor in day-of-week patterns (Monday shipments take longer), weather disruptions, and holiday slowdowns. Even a basic lookup table outperforms carrier estimates for repeat lanes.
What about international shipments with customs delays?
Add customs status as a tracking event type. International carriers like DHL and FedEx International provide customs clearance milestones through their APIs. The agent should explain common hold reasons (incomplete commercial invoice, restricted items, duties owed) and guide customers on providing required documentation.
#Shipping #PackageTracking #LogisticsAI #CarrierAPIs #Python #AgenticAI #LearnAI #AIEngineering
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.