A multi-agent system built with LangGraph that takes any sports query and automatically researches the web, then writes a clean article — all powered by Groq's Llama 3.1 and served through a FastAPI backend.
LIVE DEMO: https://tiny-griffin-dfc226.netlify.app
API: https://sports-agent-2k73.onrender.com/
Ask it anything sports-related. The system spins up a pipeline of AI agents that work together:
- A Supervisor decides who should act next
- A Researcher searches the web for real, up-to-date sports data
- A Writer turns that research into a clean, readable article
- The Supervisor reviews the result and either loops back or finishes
The whole pipeline is stateful — it remembers conversation history per session.
User Query
│
▼
┌─────────────┐
│ Supervisor │ ◄─────────────────────────┐
│ (routes) │ │
└──────┬───────┘ │
│ │
┌───┴───┐ │
│ │ │
▼ ▼ │
Researcher Writer ──────────────────────────┘
│
▼
Tavily Search Tool
│
└──► back to Researcher ──► Supervisor
The Supervisor uses structured output (Pydantic) to make routing decisions — it always returns one of: researcher, writer, or FINISH.
See ARCHITECTURE.md for a full breakdown of each file.
| Layer | Technology |
|---|---|
| Agent Framework | LangGraph |
| LLM | Groq — llama-3.3-70b-versatile |
| Web Search | Tavily Search API |
| API Server | FastAPI |
| Memory | LangGraph MemorySaver (in-memory) |
| Validation | Pydantic |
SPORTS_AGENT/
├── state.py # Shared state definition (messages + routing key)
├── supervisor.py # Routing agent — decides who acts next
├── agents.py # Researcher and Writer agents
├── graph.py # LangGraph graph — wires everything together
├── api.py # FastAPI server — exposes /chat endpoint
├── requirements.txt
└── runtime.txt
git clone https://github.com/MAGUIRE-GOATED/SPORTS_AGENT.git
cd SPORTS_AGENTpip install -r requirements.txtCreate a .env file in the root:
GROQ_API_KEY=your_groq_api_key
TAVILY_API_KEY=your_tavily_api_keyuvicorn api:app --reloadServer starts at http://localhost:8000
Health check.
{ "message": "Sports Agent API is running" }Send a sports query and get a researched article back.
Request body:
{
"message": "What happened in the Champions League final?",
"session_id": "user-123"
}Response:
{
"response": "The Champions League final..."
}The session_id maintains conversation memory — each unique ID gets its own isolated context.
Supervisor (supervisor.py)
Uses llm.with_structured_output() to always return a clean routing decision. No ambiguity — it's either researcher, writer, or FINISH.
Researcher (agents.py)
Bound to the Tavily search tool. When it needs to look something up, it emits tool calls which get handled by the ToolNode and routed back to it automatically.
Writer (agents.py)
Receives the full message history (including research results) and synthesizes a clean article. No tools — just pure generation.
Graph (graph.py)
Built with StateGraph, compiled with MemorySaver for per-session conversation tracking via thread_id.
| Variable | Description |
|---|---|
GROQ_API_KEY |
API key from console.groq.com |
TAVILY_API_KEY |
API key from tavily.com |
- Backend — Render (free tier).
runtime.txtpins the Python version. Auto-deploys on push tomain. Note: free instances sleep after inactivity — first request after idle takes ~50s. - Frontend — Netlify (drag and drop
index.html). CORS is scoped to the Netlify URL inapi.py.