By Sagar Shankaran, Founder of CallSphere
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.
Key takeaways
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.
Define a consistent shipment model that normalizes data across carriers:
Hear it before you finish reading
Talk to a live CallSphere AI voice agent for logistics in your browser — 60 seconds, no signup.
flowchart LR
INPUT(["User intent"])
PARSE["Parse plus<br/>classify"]
PLAN["Plan and tool<br/>selection"]
AGENT["Agent loop<br/>LLM plus tools"]
GUARD{"Guardrails<br/>and policy"}
EXEC["Execute and<br/>verify result"]
OBS[("Trace and metrics")]
OUT(["Outcome plus<br/>next action"])
INPUT --> PARSE --> PLAN --> AGENT --> GUARD
GUARD -->|Pass| EXEC --> OUT
GUARD -->|Fail| AGENT
AGENT --> OBS
style AGENT fill:#4f46e5,stroke:#4338ca,color:#fff
style GUARD fill:#f59e0b,stroke:#d97706,color:#1f2937
style OBS fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
style OUT fill:#059669,stroke:#047857,color:#fff
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
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)
Customers often know their order ID but not the tracking number. This tool bridges that gap:
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)
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."
)
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.
Still reading? Stop comparing — try CallSphere live.
See the logistics AI agent handle a real call — complete, industry-specific, and live in your browser. No signup.
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.
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.
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

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
Freight forwarders, clearing agents, and logistics firms around the Port of Djibouti and Doraleh use CallSphere AI agents to answer French, Somali, Arabic, and English calls 24/7 and never miss a shipment enquiry.
The lifeline between Funafuti and Tuvalu's outer islands runs on ships, seafarer wages and remittances. See how CallSphere AI voice and chat agents handle sailing queries, family calls and money-transfer questions 24/7.
Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.
Smolagents lets agents write Python instead of JSON. Why code-as-action reduces tool errors and where the security trade-offs are for production deployments.
Logistics agents handle dispatch, routing, and driver comms in 2026 production deployments. The integrations, the OS&D math, and what's next.
Modal turns a Python function into autoscaling serverless compute with optional GPU. Deploy a LiveKit Agent with one command and get pay-per-second billing.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.