Content
# ROUTE_SYSTEM —— Travel Route Planning Agent PWA
AI-native, conversation-first travel route planning PWA. Frontend: React + Vite + TypeScript, Backend: FastAPI + LangGraph, utilizing **official Amap MCP Server** for geocoding / POI / route planning / weather. LLM uses domestic models (Tongyi Qianwen / DeepSeek / GLM, OpenAI compatible).
Design style follows `DESIGN.md` (Terminal-Luxe). Product requirements see `route_system.md`.
## Architecture
```
Frontend PWA ──HTTP/SSE──> FastAPI ──> LangGraph Agent
Session/Plan/Map/Account ├─ LLM(Domestic, OpenAI compatible)
├─ Amap MCP Tools
└─ emit_itinerary structured itinerary
```
Core closed-loop: Session input → Agent calls Amap tools → Streaming conversation + structured itinerary JSON → Automatic filling of "Plan" timeline and "Map" visualization. Itinerary cover and node images from real Amap POI photos (not AI-generated).
## Directory
```
backend/ FastAPI + LangGraph + Amap MCP
frontend/ React + Vite + TS PWA
DESIGN.md Design System · route_system.md Product Brief
```
## Environment Preparation
Required: Node ≥ 18, Python ≥ 3.11.
### Backend
```bash
cd backend
python -m venv .venv
.venv/Scripts/activate # Windows; Linux/Mac: source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # Fill in real key
```
`.env` key items:
| Variable | Description |
|------|------|
| `LLM_API_KEY` / `LLM_BASE_URL` / `LLM_MODEL` | Domestic models (default DeepSeek `deepseek-reasoner` reasoning model, displays "Deep Thinking" thought chain in conversation; for faster speed, switch to `deepseek-chat`) |
| `AMAP_MAPS_API_KEY` | Amap **Web Service** key (for MCP) |
| `AMAP_MCP_TRANSPORT` | `streamable_http` (remote, recommended) / `sse` (old) / `stdio` (local npx) |
| `PROXY_URL` | Outbound proxy. Fill in if local machine needs to access external network through proxy (e.g., Clash `http://127.0.0.1:7897`), leave blank for direct connection |
> **Proxy Instructions**: If all external network traffic on the local machine goes through a local proxy (Clash/V2Ray, etc.), fill in `PROXY_URL`, otherwise the backend cannot connect to Amap/LLM. After setting, `HTTP(S)_PROXY` will be automatically injected, and LLM and Amap MCP requests will go through it (`localhost` is directly connected).
>
> **Weak Network Reinforcement**: Remote Amap MCP endpoint occasionally drops connections under concurrency. The code has added retries for tool loading and each call (see `app/agent/mcp_tools.py`). Individual failures will automatically retry without interrupting the entire planning.
Start:
```bash
uvicorn app.main:app --reload --port 8000
# Health check: curl http://localhost:8000/health
```
### Frontend
```bash
cd frontend
npm install
cp .env.example .env # Fill in Amap JS key
npm run dev # http://localhost:5173
```
`frontend/.env`:
| Variable | Description |
|------|------|
| `VITE_AMAP_JS_KEY` | Amap **JS API** key (different from backend Web Service key) |
| `VITE_AMAP_JS_SECURITY_CODE` | Amap JS security key |
> During development, `vite.config.ts` has proxied `/api` to `localhost:8000`, no need to handle CORS.
## Switching LLM Providers
Only modify three items in `backend/.env`, no code changes needed:
```ini
# DeepSeek
LLM_BASE_URL=https://api.deepseek.com/v1
LLM_MODEL=deepseek-chat
# Zhipu GLM
LLM_BASE_URL=https://open.bigmodel.cn/api/paas/v4
LLM_MODEL=glm-4-flash
```
> Selected model must support function/tool calling.
>
> **Deep Thinking (Thought Chain)**: DeepSeek is accessed through `langchain-deepseek`'s `ChatDeepSeek`. `deepseek-reasoner`'s reasoning content is streamed as `thinking` events, and the frontend displays it as "Deep Thinking" in conversation bubbles. `ChatOpenAI` base class does not expose reasoning, so only DeepSeek automatically enables it; switching to `deepseek-chat` removes thought chain but is faster. Reasoning models use more tools, and the backend has increased LangGraph's `recursion_limit` to 50.
## Real Scenic Images & Image Proxy (works in development and production)
Itinerary cover images (`cover_image`) and node images (`node.image`) come from real Amap POI photo URLs, **not AI-generated** (prompt words strictly prohibit model-generated image links).
Amap images have two issues: some use `http://` (which will be blocked by mixed content in production HTTPS pages) and have anti-hotlinking protection. The backend provides a same-origin image proxy, and the frontend uses it for all images (see `frontend/src/lib/img.ts`'s `proxiedImage()`):
```
GET /api/img?url=<Amap image URL> # Only allows *.amap.com / *.autonavi.com, prevents SSRF
```
As long as `/api` routes to the backend, **development and production can display images normally**:
- **Development**: `vite.config.ts` has proxied `/api` to `localhost:8000`, works out of the box.
- **Production**: see "Production Deployment" below. The key is to reverse proxy frontend's `/api/*` to the backend, making image proxy and `/api/chat` accessible from the same origin.
## Production Deployment
Frontend is a pure static product, and backend is FastAPI. **The core is to make both have the same origin** (so `/api/chat` and `/api/img` are accessible, and images and SSE work):
```bash
# 1) Build frontend static resources
cd frontend && npm run build # Output in frontend/dist
# 2) Backend resident (production uses --workers, not --reload)
cd backend && .venv/Scripts/python.exe -m uvicorn app.main:app --host 0.0.0.0 --port 8000
```
Use Nginx to reverse proxy static resources and `/api` together (example):
```nginx
server {
listen 80;
root /path/to/frontend/dist;
location / { try_files $uri /index.html; } # SPA fallback
location /api/ {
proxy_pass http://127.0.0.1:8000; # Forward to uvicorn
proxy_http_version 1.1;
proxy_set_header Connection ''; # SSE requires: disable connection reuse buffering
proxy_buffering off; # SSE requires: disable buffering to ensure streaming
}
}
```
> If the server where the backend is located can directly connect to Amap/LLM (like domestic servers), leave `backend/.env`'s `PROXY_URL` blank.
## End-to-End Verification
1. Start backend and frontend.
2. In "Session" Tab, input "Help me plan a 2-day weekend trip to Shanghai".
3. Observe: conversation outputs streaming, telemetry bar shows current tool (e.g., `maps_text_search`).
4. After itinerary generation, automatically switch to "Plan" Tab → timeline nodes (coordinates/time/booking ID).
5. "Map" Tab → Amap markers and route connections.
6. In the same session, ask "Add a museum on the second day" → Agent modifies itinerary incrementally based on existing plan (thread-level memory).
## Known Boundaries (MVP)
- Session memory is in-process (`MemorySaver`), resets on restart.
- Account, real-time weather/logistics cards, bilingual support, and offline enhancements are for subsequent iterations (see `DESIGN.md` plan document).
- Amap JS security key is currently injected in the frontend; **production environment** should use server-side proxy signature.
## Security Tips
- Amap keys are not interchangeable: use Web Service key on the server-side and JS API key + security key on the frontend.
- `.env` is ignored by `.gitignore`, do not commit real credentials.