flow2 / freight /scripts /omniroute_freight_synthesizer.py
AndrianBalanescu
fix: require auth only when FLOW_API_KEY is explicitly set
434c049
Raw
History Blame Contribute Delete
9.78 kB
#!/usr/bin/env python3
"""
scripts/omniroute_freight_synthesizer.py — OmniRoute 'paid-premium' Multi-Agent Freight Dataset Synthesizer.
Generates hyper-realistic multi-turn freight negotiation training dialogues using OmniRoute's
high-reasoning 'paid-premium' combo model.
Output complies with:
- OpenAI tool-calling chat completions format
- LoadETA tool schemas (fmcsa_verify_mc, calculate_rate_floor, get_truck_route_distance, book_load_offer)
- LiveKit voice conversational requirements (phonetic currencies, spoken cadence, no markdown)
Usage:
python3 scripts/omniroute_freight_synthesizer.py --count 10 --output data/freight_negotiation_omniroute.jsonl
"""
import os
import sys
import json
import random
import time
import argparse
import urllib.request
import urllib.error
from typing import Dict, Any, List
OMNIROUTE_URL = "http://100.70.158.21:20128/v1/chat/completions"
OMNIROUTE_MODEL = "paid-premium"
SEED_SCENARIOS = [
{
"lane": "Atlanta, GA to Chicago, IL",
"miles": 715,
"equipment": "53ft Reefer",
"temp": "-10F continuous",
"commodity": "Frozen Poultry",
"weight": "43,000 lbs",
"broker_persona": "Aggressive lowball broker trying to anchor rate at $1,800 on a $2,400 lane",
"special_condition": "Requires strict 2 hours free detention agreement and temp recorder verification",
},
{
"lane": "Laredo, TX to Dallas, TX",
"miles": 430,
"equipment": "53ft Dry Van",
"temp": "ambient",
"commodity": "Cross-border Automotive Parts",
"weight": "42,500 lbs",
"broker_persona": "Urgent hot-load broker with auto assembly line shutdown risk if not picked up in 90 mins",
"special_condition": "High rate elasticity, dispatcher pushes for premium rate ($1,650)",
},
{
"lane": "Allentown, PA to Richmond, VA",
"miles": 260,
"equipment": "48ft Flatbed",
"temp": "ambient",
"commodity": "Structural Steel & Beams with 8ft Tarp",
"weight": "46,000 lbs",
"broker_persona": "Mid-tier broker trying to avoid paying $150 tarp fee",
"special_condition": "Dispatcher stands firm on tarping surcharge and Northeast toll reimbursement",
},
{
"lane": "Los Angeles, CA to Phoenix, AZ",
"miles": 375,
"equipment": "53ft Reefer",
"temp": "+34F pre-cooled",
"commodity": "Fresh Organic Berries",
"weight": "38,000 lbs",
"broker_persona": "Double-broker suspect with newly registered MC (42 days old)",
"special_condition": "FMCSA tool flags new authority and lack of credit score; dispatcher politely refuses load",
},
{
"lane": "Houston, TX to Savannah, GA",
"miles": 840,
"equipment": "53ft Dry Van",
"temp": "ambient",
"commodity": "Retail Consumer Goods",
"weight": "36,000 lbs",
"broker_persona": "Standard C.H. Robinson corporate broker negotiating multi-stop delivery",
"special_condition": "2 pick-up locations and 2 drop-offs; dispatcher negotiates $100 extra per stop",
},
]
def get_omniroute_api_key() -> str:
key = os.environ.get("OMNIROUTE_API_KEY")
if key:
return key
secrets_path = os.path.expanduser("~/.mcp/secrets/omniroute.env")
if os.path.exists(secrets_path):
with open(secrets_path, "r", encoding="utf-8") as f:
for line in f:
if line.startswith("OMNIROUTE_MCP_KEY="):
return line.strip().split("=", 1)[1]
return ""
def call_omniroute(prompt: str, system: str = "") -> str:
key = get_omniroute_api_key()
headers = {
"Authorization": f"Bearer {key}",
"Content-Type": "application/json",
}
payload = {
"model": OMNIROUTE_MODEL,
"messages": [
{"role": "system", "content": system or "You are an expert synthetic dataset engineer for autonomous freight negotiation voice agents."},
{"role": "user", "content": prompt},
],
"temperature": 0.7,
"max_tokens": 2500,
}
req = urllib.request.Request(
OMNIROUTE_URL,
data=json.dumps(payload).encode("utf-8"),
headers=headers,
)
with urllib.request.urlopen(req, timeout=120) as resp:
res = json.loads(resp.read().decode("utf-8"))
return res["choices"][0]["message"]["content"]
def extract_json(raw_text: str) -> Dict[str, Any]:
import re
# 1. Try markdown code blocks first (often the cleanest JSON)
blocks = re.findall(r"```(?:json)?\s*([\s\S]*?)\s*```", raw_text)
for block in reversed(blocks):
try:
val = json.loads(block.strip())
if isinstance(val, dict) and ("messages" in val or "turns" in val or "id" in val):
return val
except Exception:
pass
for block in blocks:
try:
val = json.loads(block.strip())
if isinstance(val, dict):
return val
except Exception:
pass
# 2. Try whole string directly
try:
return json.loads(raw_text.strip())
except Exception:
pass
# 3. Scan from first { to last }
s = raw_text.find("{")
e = raw_text.rfind("}")
if s != -1 and e != -1 and e > s:
try:
return json.loads(raw_text[s:e+1])
except Exception:
pass
raise ValueError(f"No valid JSON object could be extracted from: {raw_text[:120]}")
def synthesize_conversation(scenario: Dict[str, Any], index: int) -> Dict[str, Any]:
prompt = f"""
Generate an authentic, complete multi-turn freight negotiation training conversation between a US Freight Broker and LoadETA (an autonomous dispatcher voice agent).
Scenario Parameters:
- Lane: {scenario['lane']} ({scenario['miles']} miles)
- Equipment: {scenario['equipment']}
- Commodity & Weight: {scenario['commodity']} ({scenario['weight']})
- Temperature: {scenario['temp']}
- Broker Profile: {scenario['broker_persona']}
- Special Case: {scenario['special_condition']}
Strict Rules for LoadETA Spoken Output:
1. Format exclusively for human voice synthesis (LiveKit TTS): No markdown bolding, no bullet points, no emojis, no robotic phrasing.
2. Spoken numbers and currencies: use natural spoken words for money (e.g. "twenty-four hundred", "eighteen fifty all in", "seventy-five dollars an hour").
3. Tool Calling: Assistant MUST include tool calls with exact arguments:
- 'fmcsa_verify_mc' with arguments {{"mc_number": "...", "broker_name": "..."}}
- 'calculate_rate_floor' with arguments {{"origin": "...", "destination": "...", "equipment_type": "...", "mileage": {scenario['miles']}, "weight": 42000}}
- 'book_load_offer' with arguments {{"broker_name": "...", "mc_number": "...", "rate_agreed": 2400, "detention_rate_per_hr": 75}}
You must output a JSON object with this exact key structure:
{{
"id": "omniroute_freight_{index:05d}",
"messages": [
{{"role": "system", "content": "You are LoadETA, an expert freight dispatcher and live negotiation voice agent for US commercial trucking. Protect driver margins, verify broker authority, and negotiate rates naturally for voice without markdown."}},
{{"role": "user", "content": "Broker opening pitch..."}},
{{"role": "assistant", "content": null, "tool_calls": [{{"id": "call_fmcsa_{index:04d}", "type": "function", "function": {{"name": "fmcsa_verify_mc", "arguments": "{{\\"mc_number\\": \\"782914\\", \\"broker_name\\": \\"Apex Logistics\\"}}"}}}}]}},
{{"role": "tool", "tool_call_id": "call_fmcsa_{index:04d}", "content": "{{\\"status\\": \\"CLEAN\\", \\"active\\": true, \\"credit_score\\": 94}}"}}
{{"role": "assistant", "content": "Natural counter-offer in spoken voice..."}},
{{"role": "user", "content": "Broker counter..."}},
{{"role": "assistant", "content": null, "tool_calls": [{{"id": "call_book_{index:04d}", "type": "function", "function": {{"name": "book_load_offer", "arguments": "{{\\"rate_agreed\\": 2400}}"}}}}]}},
{{"role": "tool", "tool_call_id": "call_book_{index:04d}", "content": "{{\\"status\\": \\"BOOKED\\"}}"}},
{{"role": "assistant", "content": "Final confirmation..."}}
]
}}
OUTPUT JSON ONLY. Do not write explanations or notes.
"""
raw_response = call_omniroute(prompt)
try:
data = extract_json(raw_response)
return data
except Exception as e:
print(f"JSON decode failed for scenario {index}: {e}. Raw head: {repr(raw_response[:100])}")
return None
def main():
parser = argparse.ArgumentParser(description="OmniRoute Paid-Premium Freight Dataset Synthesizer")
parser.add_argument("--count", type=int, default=5, help="Number of dialogues to synthesize")
parser.add_argument("--output", default="freight/data/freight_negotiation_omniroute.jsonl", help="Output JSONL file")
args = parser.parse_args()
os.makedirs(os.path.dirname(args.output), exist_ok=True)
print(f"Connecting to OmniRoute ({OMNIROUTE_URL}) with model '{OMNIROUTE_MODEL}'...")
results = []
for i in range(1, args.count + 1):
scenario = random.choice(SEED_SCENARIOS)
print(f"[{i}/{args.count}] Synthesizing: {scenario['lane']} ({scenario['equipment']})...")
conv = synthesize_conversation(scenario, i)
if conv:
results.append(conv)
with open(args.output, "a" if i > 1 else "w", encoding="utf-8") as f:
f.write(json.dumps(conv) + "\n")
print(f" -> Generated {len(conv.get('messages', []))} turns.")
time.sleep(1)
print(f"\nSuccessfully generated {len(results)} high-reasoning dialogues in {args.output}")
if __name__ == "__main__":
main()