An agentic AI travel planner that researches, reasons, and builds fully personalized travel itineraries β using live web data, Reddit, climate APIs, and rich media.
ZAI is not a chatbot that answers travel questions from memory. It is an autonomous agent that:
- Searches the live web for events, logistics, and destination intel
- Digs into Reddit for first-hand traveler experiences and insider tips
- Fetches real climate data for your travel dates
- Verifies event dates before recommending them
- Assesses safety conditions, local laws, and visa requirements
- Surfaces curated images and a travel video for every itinerary
- Streams its entire reasoning process to the UI in real time
Every itinerary is generated fresh from live data β no templates, no cached responses.
Prompt: "I want to travel somewhere in December."
ZAI classifies the query, resolves the date range, plans parallel searches across web, Reddit, and climate APIs, fires all tool calls simultaneously, synthesizes results, and returns a structured Markdown itinerary β with images, an embedded YouTube video, safety briefings, visa notes, and a day-by-day plan.
User β FastAPI (main.py)
β
βββ SSE stream (tool progress events)
β
βββ travel_agent.py (agentic loop)
β
βββ LLM Gateway (client.py)
β
βββ MCP Tool Server (mcp_server.py)
β
βββ search_web β DuckDuckGo
βββ read_webpage β httpx + readability
βββ search_reddit β Reddit public API / DDG fallback
βββ search_media β images + YouTube
βββ get_climate_data β Open-Meteo (forecast + ERA5)
βββ calendar_math β date resolution pipeline
- Parallel tool execution β
asyncio.TaskGroupfires independent tool calls simultaneously;tool_startevents stream to the UI before results return - Fallback chains β every tool has a degradation path; the agent never hard-crashes on a failed search
- Date grounding β the LLM is explicitly prohibited from computing dates itself; it must call
calendar_math_toolfor all temporal expressions - No LLM token streaming β SSE progress comes from tool events, not token streaming; LLM calls are blocking and cheap
- MCP registration β tools are registered on the FastMCP server and fetched by the agent at startup via
streamable_http_client
| Tool | What it does |
|---|---|
search_web |
DuckDuckGo web search |
read_webpage |
Extracts clean text from any URL (readability + BS4) |
search_reddit |
Searches travel subreddits for first-hand experiences |
search_media |
Returns images and YouTube videos for a destination |
get_climate_data |
Open-Meteo Forecast API (β€16 days) or ERA5 archive (historical averages) |
calendar_math |
Resolves fuzzy date expressions to concrete ranges |
The /chat/stream endpoint emits server-sent events. Clients must handle all types:
type |
Fields | When |
|---|---|---|
session |
session_id |
First event β carry forward for multi-turn |
thinking |
label |
Before each LLM call |
tool_start |
tool, label |
When a tool call begins |
tool_end |
tool, label |
When a tool call completes |
response |
text |
Final Markdown itinerary |
error |
message |
Agent-level exception |
done |
β | Stream complete |
- Python 3.14+
uv- An LLM gateway running at
http://localhost:8100(or override via env)
git clone https://github.com/swapniel99/zai
cd zai
uv syncCreate .env at the repo root:
LLM_GATEWAY_V2_URL=http://localhost:8100 # LLM gateway endpoint
LLM_PROVIDER=cerebras # or groq, or omit for auto-select
MCP_SERVER_URL=http://127.0.0.1:8000/mcp # optional override
MCP_PORT=8000 # optional override
REDDIT_CLIENT_ID= # optional β enables full post content
REDDIT_CLIENT_SECRET= # optional β enables full post contentStart both servers (two terminals):
# Terminal 1 β MCP tool server
uv run python mcp_server.py
# Terminal 2 β API + UI server
uv run python main.pyOpen http://localhost:8080 in your browser.
uv run python travel_agent.pySSE stream. Send a JSON body:
{
"message": "Plan 10 days in Japan in October β I love hiking and street food.",
"session_id": "optional-for-multi-turn"
}Non-streaming JSON fallback. Same request body, returns { "response": "..." }.
Returns { "status": "ok" }.
# Run all 21 tests
uv run pytest
# Run a specific test class
uv run pytest tests/test_tools.py::TestCalendarMath
# Run a single test
uv run pytest tests/test_tools.py::TestCalendarMath::test_tomorrowTests cover primary paths, fallback chains, and MCP tool registration. All tests run against live APIs β no mocking.
zai/
βββ main.py # FastAPI server (port 8080)
βββ travel_agent.py # Core agentic loop (MAX_TURNS = 12)
βββ mcp_server.py # FastMCP tool registration
βββ client.py # LLM gateway client
βββ prompt.md # System prompt
βββ tools/
β βββ search_web.py
β βββ read_webpage.py
β βββ search_reddit.py
β βββ search_media.py
β βββ get_climate_data.py
β βββ calendar_math.py
βββ static/
β βββ index.html # Single-file chat UI
βββ tests/
β βββ test_tools.py
βββ docs/
βββ PRD.md
βββ architecture.md
| Layer | Technology |
|---|---|
| Agent framework | Custom agentic loop + FastMCP |
| API server | FastAPI |
| LLM | Configurable via gateway (Cerebras / Groq / any OpenAI-compatible) |
| Web search | ddgs (DuckDuckGo) |
| Webpage extraction | readability-lxml + BeautifulSoup4 |
| Climate data | Open-Meteo (no API key required) |
| Date parsing | parsedatetime + dateparser |
| HTTP client | httpx |
| Package manager | uv |
ZAI follows a strict reasoning protocol on every turn:
- Turn classification β new request, refinement, or clarification answer
- Input analysis β city, country/region, timeframe, or combination
- Planning β what to search, what to verify
- Self-check β resolve fuzzy dates, verify event dates before recommending
- Safety assessment β advisories, local laws, solo female safety if applicable
- Tool execution β parallel searches across all relevant sources
- Synthesis β structured Markdown itinerary with media, safety, logistics, and day-by-day plan
For country/region queries, ZAI builds a multi-city route (2β4 stops in geographic order) rather than picking one city. For timeframe-only queries, ZAI returns 3 destination options β at least 1 domestic, at least 2 international across different regions β and waits for the user to choose before generating the full itinerary.
- Requires an external LLM gateway β no built-in model
- Reddit search is restricted to travel subreddits; general Reddit not supported
- Agent traces are stored locally in
logs/with no rotation policy - No persistent memory across sessions (in-memory session store only)
