Freight operations still run on unstructured emails and spreadsheets. In this tutorial we will build a Logistics Request Parser that reads raw shipment descriptions and returns structured execution plans with cost estimates, carrier modes, and delivery timelines. The agent runs entirely on Oxlo.ai, so you pay a flat rate per request and never worry about token costs on long manifests.
What you'll need
- Python 3.10 or newer
- An Oxlo.ai API key from https://portal.oxlo.ai
- The OpenAI SDK:
pip install openai
Step 1: Configure the Oxlo.ai client
We instantiate the OpenAI-compatible client pointing at Oxlo.ai. Because Oxlo.ai uses flat per-request pricing, we can pass full manifest context without ballooning costs.
from openai import OpenAI
import json
import os
client = OpenAI(
base_url="https://api.oxlo.ai/v1",
api_key=os.getenv("OXLO_API_KEY", "YOUR_OXLO_API_KEY")
)
Step 2: Define the system prompt
The system prompt constrains the model to act as a logistics planner and emit only valid JSON. This keeps the output predictable and easy to integrate into a TMS.
SYSTEM_PROMPT = """You are a logistics planning engine.
A user will describe a shipment in plain language.
Extract the details and return a single JSON object with these keys:
- origin: string
- destination: string
- cargo_type: string
- weight_kg: number
- special_instructions: string or null
- recommended_mode: one of LTL, FTL, AIR, OCEAN, RAIL
- estimated_cost_usd: number
- transit_days: integer
- risks: array of strings
Rules:
1. If a dimension is missing, infer a reasonable default and note it in risks.
2. Return ONLY the JSON object, no markdown fences."""
Step 3: Build the planner function
This function sends the unstructured request to Oxlo.ai and parses the JSON response. We use Llama 3.3 70B for reliable instruction following.
def plan_shipment(user_message: str) -> dict:
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_message},
],
)
content = response.choices[0].message.content
return json.loads(content)
Step 4: Harden output with retries
LLMs occasionally return markdown fences or extra text. We add a thin guard that strips fences and retries once on JSON failure. Because Oxlo.ai has no cold starts, the retry is fast.
def parse_json_safe(raw: str) -> dict:
raw = raw.strip()
if raw.startswith("
```"):
raw = raw.split("\n", 1)[1].rsplit("```
", 1)[0].strip()
return json.loads(raw)
def plan_shipment(user_message: str) -> dict:
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_message},
],
)
content = response.choices[0].message.content
try:
return parse_json_safe(content)
except json.JSONDecodeError:
retry_msg = f"Fix this so it is valid JSON only:\n\n{content}"
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_message},
{"role": "assistant", "content": content},
{"role": "user", "content": retry_msg},
],
)
return parse_json_safe(response.choices[0].message.content)
Step 5: Batch process multiple requests
Logistics teams receive multiple requests at once. This loop processes a list of raw strings and returns a list of structured plans.
def batch_plan(shipments: list[str]) -> list[dict]:
results = []
for idx, req in enumerate(shipments, 1):
print(f"Processing request {idx}/{len(shipments)}...")
results.append(plan_shipment(req))
return results
Run it
We feed the agent three real-world style requests and print the results. You can replace these with emails from your own inbox.
if __name__ == "__main__":
requests = [
"Need to move 12 pallets of refrigerated pharmaceuticals from Rotterdam to Chicago. Must stay under 5C. Weight around 3400 kg.",
"Rush shipment: 200 kg of auto parts from Detroit to Toronto. Customer wants it tomorrow.",
"Full truckload of furniture, 18,000 kg, Shanghai to Los Angeles. No special handling."
]
plans = batch_plan(requests)
for p in plans:
print(json.dumps(p, indent=2))
print("-" * 40)
Example output:
Processing request 1/3...
Processing request 2/3...
Processing request 3/3...
{
"origin": "Rotterdam",
"destination": "Chicago",
"cargo_type": "refrigerated pharmaceuticals",
"weight_kg": 3400,
"special_instructions": "Must stay under 5C",
"recommended_mode": "AIR",
"estimated_cost_usd": 18500,
"transit_days": 2,
"risks": [
"Temperature compliance required",
"Customs clearance for pharmaceuticals"
]
}
----------------------------------------
{
"origin": "Detroit",
"destination": "Toronto",
"cargo_type": "auto parts",
"weight_kg": 200,
"special_instructions": null,
"recommended_mode": "FTL",
"estimated_cost_usd": 850,
"transit_days": 1,
"risks": [
"Next-day delivery may require expedited border clearance"
]
}
----------------------------------------
{
"origin": "Shanghai",
"destination": "Los Angeles",
"cargo_type": "furniture",
"weight_kg": 18000,
"special_instructions": null,
"recommended_mode": "OCEAN",
"estimated_cost_usd": 4200,
"transit_days": 18,
"risks": [
"Long transit time",
"Port congestion possible"
]
}
Wrap-up and next steps
That is the core agent. From here, you can add function calling to pull live rate data from freight APIs, or switch to kimi-k2.6 on Oxlo.ai for multi-leg route reasoning with deeper chain-of-thought.
Top comments (0)