Build a supply chain research agent
Mapping a supply chain means stitching together market structure, supplier directories, freight indices, customs data, and a constant stream of disruption news — port congestion, tariff changes, shortages — then turning all of it into something a procurement or operations team can act on. This guide builds a Python agent that runs that loop and produces a structured supply chain brief. It chains all four Superhighway endpoints — /research for a multi-source synthesis of a commodity or trade route, /search to find supplier directories and freight data, /scrape to pull pricing, lead times, and capabilities off supplier pages, and /news for recent disruptions — then uses an LLM to emit a clear brief: market overview, key suppliers, pricing trends, supply risks, alternative sources, lead-time estimates, recent disruptions, recommended actions, and an overall risk level.
1. What you'll build
A Python agent that takes a commodity, supplier category, trade route, or supply chain topic and produces a structured intelligence brief:
- Synthesizes the market structure, key players, and pricing landscape
- Finds supplier directories, trade data, and industry pricing
- Scrapes supplier pages for specific pricing, lead times, and capabilities
- Pulls recent disruption news — shipping delays, tariff changes, shortages
- Uses an LLM to generate a structured supply chain brief with a risk level
2. Setup
pip install openai requests python-dotenv
Create a .env file with your two keys:
SUPERHIGHWAY_API_KEY=your_key_here
OPENAI_API_KEY=your_key_here
3. Research the supply chain landscape
Start with /research. One call pulls multiple sources into a synthesis of the market — its structure, scale, key players, where production is concentrated, and the pricing and geopolitical dynamics — so the LLM has grounded context instead of guessing.
import requests, os, json
SUPERHIGHWAY_KEY = os.getenv("SUPERHIGHWAY_API_KEY")
BASE = "https://superhighway.walls.sh"
def research_supply_landscape(commodity: str, pages: int = 6) -> str:
"""Deep synthesis of market structure, key players, and pricing landscape."""
r = requests.get(
f"{BASE}/research",
params={
"q": f"{commodity} supply chain market suppliers pricing logistics",
"pages": pages
},
headers={"Authorization": f"Bearer {SUPERHIGHWAY_KEY}"}
)
data = r.json()
return data.get("synthesis", data.get("markdown", ""))[:3000]
4. Find suppliers and trade data
Two narrow /search calls: one hunts for supplier directories, manufacturers, and pricing/lead-time pages — the people who actually make the thing — and one looks for trade data, freight indices, and shipping-cost sources that tell you how it moves.
def find_suppliers(commodity: str, region: str = "global") -> list[dict]:
"""Find supplier directories, manufacturers, pricing, and lead times."""
r = requests.get(
f"{BASE}/search",
params={
"q": f"{commodity} {region} suppliers manufacturers pricing lead time",
"limit": 8
},
headers={"Authorization": f"Bearer {SUPERHIGHWAY_KEY}"}
)
return r.json().get("results", [])
def find_trade_data(commodity: str) -> list[dict]:
"""Find trade data, freight indices, and shipping cost sources."""
r = requests.get(
f"{BASE}/search",
params={
"q": f"{commodity} import export trade data freight index shipping cost",
"limit": 5
},
headers={"Authorization": f"Bearer {SUPERHIGHWAY_KEY}"}
)
return r.json().get("results", [])
5. Scrape supplier and industry pages
/scrape turns each supplier or trade page into clean, LLM-ready markdown — pricing schedules, lead times, product specs, capabilities — so the LLM summarizes real content, not just a title.
def scrape_page(url: str) -> dict:
"""Scrape a supplier or industry page for pricing, lead times, and specs."""
r = requests.get(
f"{BASE}/scrape",
params={"url": url},
headers={"Authorization": f"Bearer {SUPERHIGHWAY_KEY}"}
)
data = r.json()
return {
"url": url,
"title": data.get("title", ""),
"content": data.get("markdown", "")[:2500],
}
6. Get supply chain disruption news
/news surfaces the time-sensitive layer a market report can't give you — fresh port congestion, tariff changes, strikes, shortages, and commodity price moves.
def get_disruption_news(commodity: str) -> list[dict]:
"""Get recent disruptions — congestion, tariffs, shortages, delays."""
r = requests.get(
f"{BASE}/news",
params={
"q": f"{commodity} supply chain disruption shortage shipping delay tariff",
"count": 6
},
headers={"Authorization": f"Bearer {SUPERHIGHWAY_KEY}"}
)
return r.json().get("articles", [])
7. Generate the supply chain brief with an LLM
Now hand everything to the LLM. The system prompt pins the output to a procurement/operations reader, forces every claim back to the sources, and asks it to flag stale data on fast-moving topics. The output is structured JSON so it slots straight into a sourcing doc, BOM tracker, or risk dashboard.
from openai import OpenAI
llm = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def generate_supply_brief(
topic: str,
landscape: str,
suppliers: list[dict],
trade_data: list[dict],
news: list[dict],
context: dict
) -> dict | None:
"""Generate a structured supply chain intelligence brief."""
supplier_text = "\n".join(
f"- {s['title'][:80]}: {s['content'][:400]}"
for s in suppliers[:5]
if s.get("content")
)
trade_text = "\n".join(
f"- {t['title'][:80]}: {t['content'][:300]}"
for t in trade_data[:3]
if t.get("content")
)
news_text = "\n".join(
f"- {n.get('title', '')} ({n.get('source', '')}, {n.get('date', '')})"
for n in news[:5]
)
commodity_type = context.get("commodity_type", "component")
region = context.get("region", "global")
industry = context.get("industry", "general")
response = llm.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": f"""You are a supply chain research analyst helping a procurement and operations team.
Write a clear, structured supply chain brief. Only report information supported by the provided sources.
Be concrete about suppliers, countries of origin, pricing direction, and lead times.
Assess risk soberly — do not overstate or understate. If the topic is fast-moving and the data may be stale, flag it."""
},
{
"role": "user",
"content": f"""Supply chain brief on: {topic}
Commodity type: {commodity_type} | Region focus: {region} | Industry: {industry}
Market Landscape:
{landscape[:2000]}
Suppliers & Trade Data Found:
{supplier_text}
Freight & Trade Data:
{trade_text}
Recent Disruption News:
{news_text}
Return JSON with:
- commodity_or_topic: string
- market_overview: string (2-3 sentences on market structure, scale, key dynamics)
- key_suppliers: list of 3-5 strings (major suppliers/manufacturers with country of origin)
- geographic_concentration: string (where production is concentrated + what that means for risk)
- pricing_trends: string (current pricing environment, recent moves, outlook)
- lead_time_estimates: string (typical lead times and what's affecting them)
- supply_risks: list of 3-4 strings (geopolitical, weather, capacity, regulatory risks)
- alternative_sources: list of 2-3 strings (alternative suppliers/regions if primary disrupted)
- recent_disruptions: list of 3 strings (from the news — congestion, tariffs, shortages, strikes)
- recommended_actions: list of 3-4 strings (what the procurement/ops team should do)
- risk_level: "low" | "medium" | "high" | "critical"
- data_freshness: "current" | "may-be-dated" (flag if the topic is fast-moving and data may be stale)"""
}
],
response_format={"type": "json_object"}
)
try:
return json.loads(response.choices[0].message.content)
except (json.JSONDecodeError, KeyError):
return None
8. Wire up the full pipeline
The orchestrator runs research, search, scrape, and news, then hands the lot to the LLM. A context dict tunes the brief to a commodity type, region, and industry.
def research_supply_chain(
topic: str,
context: dict | None = None,
max_pages: int = 5
) -> dict | None:
"""
Research a commodity, supplier category, trade route, or supply chain topic.
topic: e.g. "lithium carbonate supply chain", "PCB manufacturers Taiwan",
"Trans-Pacific shipping rates", "rare earth magnets China"
context: {
"commodity_type": "raw-material" | "component" | "finished-good" | "service",
"region": "US" | "EU" | "APAC" | "global",
"industry": "electronics" | "automotive" | "pharma" | "food" | "general"
}
"""
if context is None:
context = {
"commodity_type": "component",
"region": "global",
"industry": "general"
}
region = context.get("region", "global")
print(f"Researching: {topic} ({region})")
# Deep market research
print("Synthesizing supply chain landscape...")
landscape = research_supply_landscape(topic, pages=6)
# Find suppliers and trade data
print("Finding suppliers and trade data...")
suppliers_raw = find_suppliers(topic, region)
trade_raw = find_trade_data(topic)
# Scrape key pages
print(f"Scraping {min(len(suppliers_raw), max_pages)} supplier pages...")
suppliers = []
for result in suppliers_raw[:max_pages]:
page = scrape_page(result["url"])
if page["content"]:
page["title"] = page["title"] or result.get("title", "")
suppliers.append(page)
trade_data = []
for result in trade_raw[:2]:
page = scrape_page(result["url"])
if page["content"]:
trade_data.append(page)
# Recent disruption news
print("Fetching recent disruptions...")
news = get_disruption_news(topic)
# Generate brief
print("Generating supply chain brief...")
return generate_supply_brief(
topic, landscape, suppliers, trade_data, news, context
)
def print_brief(brief: dict):
if not brief:
print("Could not generate brief.")
return
risk_marks = {"low": "[LOW]", "medium": "[MED]", "high": "[HIGH]", "critical": "[CRIT]"}
risk = brief.get("risk_level", "medium")
print(f"\n{'='*60}")
print(f"Supply Chain Brief: {brief.get('commodity_or_topic', 'Topic')}")
print(f"Risk Level: {risk_marks.get(risk, '[?]')} {risk.upper()}")
if brief.get("data_freshness") == "may-be-dated":
print("! Note: fast-moving topic — verify current data before acting.")
print(f"{'='*60}")
print(f"\nMarket Overview: {brief.get('market_overview', '')}\n")
suppliers = brief.get("key_suppliers", [])
if suppliers:
print("Key Suppliers:")
for s in suppliers:
print(f" * {s}")
geo = brief.get("geographic_concentration", "")
if geo:
print(f"\nGeographic Concentration: {geo}")
pricing = brief.get("pricing_trends", "")
if pricing:
print(f"\nPricing Trends: {pricing}")
lead = brief.get("lead_time_estimates", "")
if lead:
print(f"\nLead Times: {lead}")
risks = brief.get("supply_risks", [])
if risks:
print("\nSupply Risks:")
for r in risks:
print(f" ! {r}")
alts = brief.get("alternative_sources", [])
if alts:
print("\nAlternative Sources:")
for a in alts:
print(f" -> {a}")
disruptions = brief.get("recent_disruptions", [])
if disruptions:
print("\nRecent Disruptions:")
for d in disruptions:
print(f" > {d}")
actions = brief.get("recommended_actions", [])
if actions:
print("\nRecommended Actions:")
for a in actions:
print(f" [ ] {a}")
if __name__ == "__main__":
import sys
topic = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else "lithium carbonate supply chain"
CONTEXT = {
"commodity_type": "raw-material",
"region": "global",
"industry": "automotive"
}
brief = research_supply_chain(topic, CONTEXT, max_pages=5)
if brief:
print_brief(brief)
9. Common topics to research
- Component sourcing — "TSMC semiconductor supply", "rare earth magnets China", "PCB manufacturers Taiwan".
- Raw materials — "lithium carbonate supply chain", "steel plate pricing", "cobalt DRC supply".
- Logistics — "Trans-Pacific shipping rates", "US port congestion Los Angeles", "air freight rates".
- Trade policy — "tariffs on EV batteries", "USMCA compliance electronics", "Section 301 tariffs".
- Supplier risk — "Foxconn China operations risk", "automotive chip shortage update".
10. Use cases
- Procurement manager — research a new commodity category before issuing an RFQ: pricing, lead times, top suppliers, risks.
- Supply chain analyst — weekly disruption monitoring: scan for port congestion, weather events, and tariff changes affecting your categories.
- Startup founder — before launching a hardware product, understand the sourcing landscape for key components.
- Operations team — identify alternative sources when a primary supplier fails or a country-level disruption hits.
- Import/export team — research tariff exposure and compliance requirements for a new trade route.
11. Extending the agent
- Multi-commodity comparison — run
research_supply_chain()for 5 components in your BOM and comparerisk_levelto flag the highest-exposure items. - Disruption alerts — schedule weekly with
/news?q={commodity}+supply+chain+disruptionand alert whenrisk_levelhits "high" or "critical". - Supplier watchlist — run monthly on your top 10 suppliers to track news and pricing changes.
- Tariff tracker — parameterize by HS code or trade route to monitor specific regulatory exposure.
12. Getting your API key
Grab a free Superhighway key at /pricing (1,000 calls/month, no credit card). For an agent that provisions its own access, skip the key entirely with x402: it pays $0.002 per call in USDC on Base — no signup, no key management. See the x402 pay-per-call guide for the wallet setup.
For related builds, the financial research agent uses the same four-endpoint pattern on companies and markets, and the regulatory research agent covers compliance and trade rules.