Skip to content
Learn Agentic AI13 min read0 views

Building a Freight Quote Agent: Multi-Carrier Pricing and Booking

Learn how to build an AI agent that fetches freight rates from multiple carriers, compares pricing based on transit time and service level, books shipments, and generates required documentation.

Why Freight Quoting Needs Automation

Shipping managers spend hours every day requesting quotes from multiple freight carriers, comparing rates, and booking the best option. A single LTL (Less Than Truckload) shipment might require checking five different carriers, each with their own rate structure, accessorial charges, and transit time estimates. The process is repetitive, error-prone, and time-sensitive since rates can change daily.

An AI freight quote agent automates the entire workflow: it collects shipment details, fetches quotes from multiple carriers simultaneously, presents a ranked comparison, books the selected carrier, and generates the Bill of Lading.

Shipment and Rate Data Models

from dataclasses import dataclass
from typing import Optional

@dataclass
class ShipmentDetails:
    origin_zip: str
    destination_zip: str
    weight_lbs: float
    freight_class: int
    pieces: int
    length_in: float
    width_in: float
    height_in: float
    is_hazmat: bool = False
    liftgate_required: bool = False
    residential: bool = False

@dataclass
class FreightQuote:
    carrier: str
    service_level: str
    rate: float
    fuel_surcharge: float
    accessorials: float
    total_cost: float
    transit_days: int
    guaranteed: bool
    quote_id: str
    valid_until: str

Multi-Carrier Rate Fetching Tool

The rate tool simulates calling multiple carrier APIs and returns normalized quotes:

from agents import function_tool
import hashlib
from datetime import date, timedelta

def _generate_quote_id(carrier: str, origin: str, dest: str) -> str:
    raw = f"{carrier}-{origin}-{dest}-{date.today()}"
    return f"Q-{hashlib.md5(raw.encode()).hexdigest()[:8].upper()}"

CARRIER_RATES = {
    "FedEx Freight": {"base_per_cwt": 28.50, "fuel_pct": 0.32, "transit_base": 3},
    "XPO Logistics": {"base_per_cwt": 24.75, "fuel_pct": 0.29, "transit_base": 4},
    "Old Dominion": {"base_per_cwt": 31.00, "fuel_pct": 0.30, "transit_base": 2},
    "Estes Express": {"base_per_cwt": 22.50, "fuel_pct": 0.35, "transit_base": 5},
    "SAIA": {"base_per_cwt": 26.00, "fuel_pct": 0.31, "transit_base": 3},
}

@function_tool
def get_freight_quotes(
    origin_zip: str,
    destination_zip: str,
    weight_lbs: float,
    freight_class: int = 70,
    liftgate: bool = False,
    residential: bool = False,
) -> str:
    """Get freight quotes from multiple carriers for an LTL shipment."""
    quotes = []
    cwt = weight_lbs / 100

    for carrier, rates in CARRIER_RATES.items():
        base_rate = cwt * rates["base_per_cwt"]

        # Adjust for freight class
        class_multiplier = 1.0 + (freight_class - 70) * 0.008
        base_rate *= class_multiplier

        fuel = base_rate * rates["fuel_pct"]
        accessorials = 0.0
        if liftgate:
            accessorials += 75.00
        if residential:
            accessorials += 85.00

        total = base_rate + fuel + accessorials
        valid_date = (date.today() + timedelta(days=3)).isoformat()

        quotes.append(FreightQuote(
            carrier=carrier,
            service_level="LTL Standard",
            rate=round(base_rate, 2),
            fuel_surcharge=round(fuel, 2),
            accessorials=round(accessorials, 2),
            total_cost=round(total, 2),
            transit_days=rates["transit_base"],
            guaranteed=carrier in ("Old Dominion", "FedEx Freight"),
            quote_id=_generate_quote_id(carrier, origin_zip, destination_zip),
            valid_until=valid_date,
        ))

    quotes.sort(key=lambda q: q.total_cost)

    lines = [f"Freight quotes for {weight_lbs} lbs, Class {freight_class}:"]
    lines.append(f"Route: {origin_zip} -> {destination_zip}\n")

    for i, q in enumerate(quotes, 1):
        guaranteed_tag = " [GUARANTEED]" if q.guaranteed else ""
        lines.append(
            f"{i}. {q.carrier}{guaranteed_tag}\n"
            f"   Base: ${q.rate:.2f} | Fuel: ${q.fuel_surcharge:.2f} | "
            f"Accessorials: ${q.accessorials:.2f}\n"
            f"   Total: ${q.total_cost:.2f} | Transit: {q.transit_days} days\n"
            f"   Quote ID: {q.quote_id} (valid until {q.valid_until})"
        )
    return "\n".join(lines)

Booking Tool

Once the shipper selects a quote, the agent books the shipment:

See AI Voice Agents Handle Real Calls

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

BOOKED_SHIPMENTS = {}

@function_tool
def book_freight_shipment(
    quote_id: str,
    shipper_name: str,
    shipper_address: str,
    consignee_name: str,
    consignee_address: str,
    pickup_date: str,
    special_instructions: Optional[str] = None,
) -> str:
    """Book a freight shipment using a previously generated quote ID."""
    # In production, validate quote_id against cached quotes
    booking_ref = f"BK-{quote_id[2:]}"

    BOOKED_SHIPMENTS[booking_ref] = {
        "quote_id": quote_id,
        "shipper": shipper_name,
        "consignee": consignee_name,
        "pickup_date": pickup_date,
        "status": "confirmed",
    }

    return (
        f"Shipment booked successfully!\n"
        f"Booking Reference: {booking_ref}\n"
        f"Pickup Date: {pickup_date}\n"
        f"Shipper: {shipper_name} ({shipper_address})\n"
        f"Consignee: {consignee_name} ({consignee_address})\n"
        f"{'Special Instructions: ' + special_instructions if special_instructions else ''}"
        f"\nBill of Lading will be emailed to the shipper."
    )

Documentation Generation Tool

@function_tool
def generate_bol(booking_reference: str) -> str:
    """Generate a Bill of Lading summary for a booked shipment."""
    booking = BOOKED_SHIPMENTS.get(booking_reference)
    if not booking:
        return f"Booking {booking_reference} not found."

    return (
        f"=== BILL OF LADING ===\n"
        f"BOL Number: BOL-{booking_reference[3:]}\n"
        f"Date: {date.today().isoformat()}\n"
        f"Shipper: {booking['shipper']}\n"
        f"Consignee: {booking['consignee']}\n"
        f"Pickup Date: {booking['pickup_date']}\n"
        f"Carrier Quote: {booking['quote_id']}\n"
        f"Status: {booking['status'].upper()}\n"
        f"========================\n"
        f"This BOL is ready for printing and driver signature."
    )

Assembling the Freight Agent

from agents import Agent, Runner

freight_agent = Agent(
    name="Freight Broker",
    instructions="""You are a freight quoting and booking assistant. Help shippers:
    1. Get competitive quotes from multiple LTL carriers
    2. Compare rates by cost, transit time, and service guarantees
    3. Book shipments with the selected carrier
    4. Generate Bills of Lading for booked shipments
    Always recommend the best value option and note guaranteed service when relevant.""",
    tools=[get_freight_quotes, book_freight_shipment, generate_bol],
)

result = Runner.run_sync(
    freight_agent,
    "I need to ship 1200 lbs of Class 85 freight from 90210 to 10001 with liftgate. "
    "Show me the cheapest options."
)
print(result.final_output)

FAQ

How do I connect to real carrier rate APIs?

Use aggregate APIs like ShipEngine, Freightview, or SMC3 which provide a single interface to multiple LTL carriers. Each requires a shipper account and API credentials. Rate requests typically need origin/destination zips, weight, freight class, and dimensions. Cache quotes with a TTL matching each carrier's validity window (usually 3-7 days).

What about FTL (Full Truckload) quotes?

FTL pricing is lane-based rather than per-hundredweight. Add a separate tool that queries load boards or FTL rate APIs. FTL quotes depend on origin-destination lane, equipment type (dry van, reefer, flatbed), and market conditions. The agent should ask the user about equipment needs before fetching FTL rates.

How do I handle accessorial charges that vary by carrier?

Build an accessorial fee table per carrier. Common accessorials include liftgate delivery, residential delivery, inside delivery, notify before delivery, and hazmat surcharges. When the user mentions special requirements, the agent should include relevant accessorial codes in the rate request and show them as separate line items in the comparison.


#Freight #CarrierPricing #ShippingQuotes #BookingAutomation #Python #AgenticAI #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.