基于多 Agent 协作的对话式旅行规划应用。
部署与运行说明请直接看:
当前仓库包含两部分:
src/: Next.js App Router 前端,页面与交互遵循docs/和stitch/backend/: FastAPI backend,接口、WebSocket 事件和字段结构对齐技术规格,聊天链路已接入 LangGraph 编排入口,并会把对话里的日期/航班/酒店/行程调整持久化到/planLLM 配置现在以加密形式存储在后端,设置页和会话创建弹窗只返回脱敏后的 API key;运行时会在服务端解密后再发起真实模型调用。 当会话或用户配置了可用的 OpenAI 兼容 endpoint,或 Anthropic Messages API endpoint + API key 时,coordinator 会优先尝试用真实模型做轻量意图判路由,助手文案也会优先走真实模型生成,失败后自动回退本地规则 / mock。设置页和新建对话弹窗现在都支持显式选择API 格式,第三方 Anthropic Messages 网关不再依赖域名猜测。 在 PostgreSQL 环境下会自动启用 LangGraph checkpointer;flight agent 配置好 Kiwi MCP 后会优先发起真实工具查询,hotel / attraction / dining agent 配置好 Tavily 后会优先发起实时搜索,任一外部工具失败都会自动回退本地规则。 鉴权现已按 JWT 闭环接入:REST 和 WebSocket 都要求 access token,会话 / 设置 / 计划读取按当前登录用户隔离,Next.js 页面通过 cookie + middleware 保护;设置页已支持当前登录用户修改密码,改密后会轮转一组新 token 并作废旧 access / refresh token。对话列表页也已经接通真实删除链路。计划页的 PDF 导出也已经接成真实下载链路,会生成包含预算、航班、酒店、日程、景点和餐饮内容的多页 PDF。
npm install
npm run dev默认会请求 http://127.0.0.1:8001。如果后端未启动,页面会自动回退到本地 mock 数据。
如果你要在本地直接调试 Kiwi MCP + Tavily + Live LLM,优先使用:
bash -c 'cp -n config.env.example config.env || true'
bash ./scripts/run-backend-live-memory.sh本地敏感配置现在统一放在仓库根目录的 config.env 中。推荐先复制:
cp config.env.example config.env然后把下面这些值填进去,而不是写死在代码里:
TAVILY_API_KEYLANGTRAVELLER_DEFAULT_LLM_API_KEY
提交到 GitHub 前,确认你提交的是样例文件 config.env.example,不是本地真实配置文件 config.env。仓库已经通过 .gitignore 忽略了 config.env 和 .env.local,但提交前仍建议执行一次:
git status --short如果输出里出现 config.env、.env.local 或其他本地密钥文件,先移出暂存区再提交。
这个脚本会读取上一级或仓库根目录的 config.env,并强制把后端启动成 memory + live providers enabled 模式,避免当前 shell 里残留的 LANGTRAVELLER_ENABLE_* = false 或旧 DATABASE_URL 影响启动。
默认不会开启 --reload,目的是避免本地联调时因为文件改动或 reloader 进程切换,导致聊天流式结果中途断掉。
如果聊天页顶部明确提示“当前后端运行在降级模式”,那说明你现在连到的 8001 后端没有启用 MCP / Tavily。这种状态下,新对话只会返回文本更新,不会出现实时航班或酒店选择卡片。
浏览器 E2E:
npx playwright install chromium
npm run test:e2etest:e2e 会自动拉起:
npm run backend:e2e:内存模式 FastAPI,端口8002npm run dev:e2e:Next.js dev server,端口3001
uv sync --project backend
export DATABASE_URL='postgresql+asyncpg://postgres:postgres@127.0.0.1:5432/langtraveller'
uv run --project backend alembic -c backend/alembic.ini upgrade head
uv run --project backend uvicorn app.main:app --app-dir backend --reload --port 8001数据层支持两种模式:
LANGTRAVELLER_STORE_MODE=memory: 强制使用内存 storeLANGTRAVELLER_STORE_MODE=database: 强制使用数据库 store- 默认
auto: 检测到DATABASE_URL时走数据库,否则回退内存
推荐的 PostgreSQL 配置:
export DATABASE_URL='postgresql+asyncpg://postgres:postgres@127.0.0.1:5432/langtraveller'
export LANGTRAVELLER_ENABLE_GRAPH_CHECKPOINTER=true
export LANGTRAVELLER_ENABLE_MCP=true
export LANGTRAVELLER_KIWI_MCP_URL='https://mcp.kiwi.com'
export LANGTRAVELLER_ENABLE_TAVILY_SEARCH=true
# secrets live in config.env
uv run --project backend alembic -c backend/alembic.ini upgrade head
uv run --project backend uvicorn app.main:app --app-dir backend --reload --port 8001本地 smoke test 也可以先用 SQLite:
export DATABASE_URL='sqlite+aiosqlite:///./backend/langtraveller.db'
uv run --project backend alembic -c backend/alembic.ini upgrade head
uv run --project backend uvicorn app.main:app --app-dir backend --reload --port 8001数据库模式下,后端启动前必须先执行迁移;运行时不再自动 create_all()。
后端回归测试:
uv run --project backend python -m unittest discover -s backend/tests -vPostgreSQL + checkpointer 集成测试:
# 方式一:本机 Docker daemon 可用时直接运行
uv run --project backend python -m unittest -v backend/tests/test_postgres_checkpointer.py
# 方式二:显式指定一套可丢弃的 PostgreSQL 测试库
export LANGTRAVELLER_TEST_DATABASE_URL='postgresql+asyncpg://postgres:postgres@127.0.0.1:5432/langtraveller_test'
uv run --project backend python -m unittest -v backend/tests/test_postgres_checkpointer.py已验证通过:
npm run lintnpm run buildpython3 -m compileall backend/appuv run --project backend python -m unittest discover -s backend/tests -vuv run --project backend alembic -c backend/alembic.ini upgrade headnpm run test:e2eGET /api/healthGET /api/conversationsGET /api/settingsPOST /api/settings/llm/testGET /api/conversations/{id}/planGET /api/health返回 store / graph checkpointer / MCP / Tavily runtime 能力摘要- 使用
https://mcp.kiwi.com成功发现并调用真实 Kiwisearch-flight工具,返回结果已可写入/plan.flights ws://127.0.0.1:8001/ws/chat/{conversation_id}WebSocket round-trip- 无效 Kiwi MCP 地址 + 无效 Tavily key 下,hotel / attraction / dining / flight agent 仍可正常回退并返回 assistant 消息
- 伪造 live MCP / Tavily payload 下,flight / hotel / attraction / dining 会生成结构化
module_patches和对应聊天 UI block - 59 个后端
unittest用例覆盖 JWT 鉴权保护、跨用户会话隔离、会话删除、WebSocket owner 校验、密码修改、改密后的 access / refresh / WebSocket 失效、LLM API key 加密存储与 masked-key 复用、真实 PDF 导出、PostgreSQL checkpointer 集成、Kiwi MCP schema 适配与 payload 归一化、Tavily answer/entity 提取、specialist 结构化解析、module/ui merge、itinerary 合成、PlannerRuntime fallback / live-tool 路径,以及自然语言旅行需求提取 / 缺信息追问 / live LLM 路由判定 DATABASE_URL=sqlite+aiosqlite:///./backend/langtraveller.db下的建表、seed、REST、WebSocket 持久化DATABASE_URL=sqlite+aiosqlite:///tmp/...db下的 Alembicupgrade head、LangGraph WebSocket round-trip、消息与 plan 持久化,以及内联 UI 交互写回- 新建对话中输入“从上海去东京、4 月 1 日到 4 月 4 日、1 人、预算 1 万”这类自由文本后,
/plan会正确写入出发地、目的地、日期、人数和预算目标,而不是回退到默认日期 - PostgreSQL 集成测试中,LangGraph checkpointer 会跨 runner 重启持久化 checkpoint,并且不会把上一轮的 specialist module updates 泄漏到下一轮酒店查询
- 8 条 Playwright E2E 用例覆盖“日期选择写回计划页”“酒店更换写回计划页”“SSR access token 过期后的静默刷新”“设置页修改密码并保持当前会话”“计划页导出 PDF 下载”“对话列表删除会话”“新建对话加载默认 LLM 配置”以及“降级后端状态提示”的核心前后端联动链路
下一步更值得做的是补强 live provider 的字段归一化质量,尤其是 Tavily 返回里的价格、评分、来源域名可信度和更细的餐饮/酒店去重策略;真实 provider 通路本身已经验证通过。