1. 项目介绍
TourAI 是一个基于多智能体(Multi-Agent)架构的智能出行助手平台。它利用先进的大语言模型(LLM)和 LangGraph 框架,构建了一个能够理解用户意图、规划复杂行程、查询实时地理信息并生成可视化方案的智能系统。
核心愿景是通过 AI 改变传统的旅行规划方式,从繁琐的搜索和攻略整理中解放用户,提供一站式的个性化出行解决方案。
1.1 核心价值
- 智能化规划:基于用户偏好自动生成详细的旅行计划。
- 实时数据集成:深度集成高德地图(Amap)数据,提供准确的 POI、路线和天气信息。
- 可视化交互:不仅仅是文字回复,还能生成地图卡片、行程单等富媒体内容。
- 个性化记忆:通过档案系统(Archive)长期记忆用户的偏好和习惯。
2. 功能模块
TourAI 的功能模块主要分为前端交互层和后端智能体层。
2.1 前端功能
- 用户认证 (Auth): 登录、注册、自动认证、密码强度检测。
- 工作区 (Workspace): 多会话管理,支持创建、删除、切换不同的规划任务。
- 智能对话 (Chat):
- 实时流式对话体验 (SSE)。
- 多智能体协作展示(显示当前是哪个 Agent 在工作)。
- 工具调用可视化(展开查看工具输入输出)。
- Markdown 渲染与自定义卡片渲染。
- 个人中心 (User): 用户资料管理、偏好设置。
- 地图交互 (Map): 结合高德地图 API 进行地点展示、路线规划可视化。
2.2 后端功能
- 多智能体编排 (Orchestration): 基于 LangGraph 的 Supervisor 模式,协调多个 Agent 协作。
- MCP 服务 (Model Context Protocol):
- Amap MCP: 提供地理编码、路线规划、周边搜索、天气查询等能力。
- Archive MCP: 管理用户出行档案(读写偏好)。
- Planning MCP: 管理当前的行程规划数据。
- Card MCP: 生成前端可渲染的 UI 卡片代码。
- Handy MCP: 提供时间查询等基础工具。
- 数据持久化:
- 基于 Supabase 的数据存储(用户、消息、档案、工作区)。
- API 服务: 提供 RESTful API 和 SSE (Server-Sent Events) 流式接口。
3. 架构设计
TourAI 采用现代化的 Monorepo 架构,前后端分离,后端采用微服务化的 MCP (Model Context Protocol) 架构与 Agentic Workflow 相结合的设计。
3.1 总体架构
- Monorepo 管理: 使用
pnpm workspaces管理多个包。 - 前端架构: React + Vite + Zustand + SCSS Modules。
- 通信层: 使用
fetch+ReadableStream手动解析 SSE 消息流,支持text/event-stream。 - 状态管理: Zustand 管理全局 UI 状态,React Context 管理局部会话状态。
- 通信层: 使用
- 后端架构: Node.js + LangChain/LangGraph + MCP SDK。
- 服务层: Express 托管 API 和 MCP SSE 端点。
- 智能体层: LangGraph 构建的状态机。
- 通信协议:
- 前后端:HTTP + SSE (Server-Sent Events)。
- Agent 与 Tools:MCP (Model Context Protocol) over SSE。
3.2 目录结构关键点
packages/server: 核心后端逻辑,包含 Agents 和 MCP Servers。packages/website: 主前端应用。packages/website-chat: 独立的聊天 UI 组件库。packages/website-kit: 前端 SDK 和通用工具。packages/schema: 前后端共享的类型定义和 Zod Schema。packages/envconfig: 统一的环境变量配置。
3.3 安全架构
- 鉴权机制: 基于 JWT/Token 的身份验证。
- API 鉴权:
authMiddleware拦截请求,解析 Token 并验证 Supabase 用户身份。 - MCP 鉴权: 敏感操作(如档案更新)通过
runMcpWithAuth包装器,在工具执行上下文中注入并校验用户身份。
- API 鉴权:
- 内容风控 (Guardrails):
- 前置检查 (
preCheckInput): 正则匹配拦截 Prompt 注入、非出行意图(如写代码)、敏感话题。 - 后置检查 (
postCheckOutput): 实时流式检测 AI 输出,发现敏感词立即阻断生成。
- 前置检查 (
- 数据安全: 敏感配置(如 API Key)通过环境变量管理,不硬编码。
3.4 性能与可靠性
- 流式处理 (Streaming):
- 使用
MultiAgentProcessor封装 LangGraph 的streamEvents(v2),将复杂的图事件转换为前端友好的MultiAgentStreamChunk。 - 支持
agent_start(智能体切换)、thought(思考过程)、tool_call(工具调用)、text_delta(文本增量) 等多种事件类型。
- 使用
- 长程记忆与摘要 (Long-term Memory):
- 自动摘要: 当活跃历史消息超过 10 条时,触发后台摘要任务 (
summarizeMessages),将旧消息压缩为 Summary,减少 Token 消耗。 - Upstash Redis 缓存: 用于存储摘要结果 (
summary:${workspaceId}),加速读取。
- 自动摘要: 当活跃历史消息超过 10 条时,触发后台摘要任务 (
- 错误恢复:
- 数据库重试:
operateDatabaseWithRetry封装了指数退避重试逻辑。 - 消息清洗:
sanitizedHistory机制自动剔除“烂尾”的 ToolCall(即有调用请求但无结果的消息),防止 LangChain 崩溃。
- 数据库重试:
3.5 错误处理
- 中断处理: 监听客户端连接断开 (
res.on("close")),立即停止生成并保存[ABORT]标记,防止后台无效计算。 - 全局异常捕获: 在 SSE 循环外层捕获所有未处理异常,向前端发送
error类型 chunk,并尝试持久化已生成的错误信息。
4. 架构图
5. 核心流程图
5.1 多智能体协作流程
6. 功能分配图
| 模块 | 职责描述 | 关键技术/工具 |
|---|---|---|
| TourAI (Supervisor) | 总指挥。负责理解用户意图,将任务分发给具体的 Worker Agent,并汇总结果。负责控制对话节奏,避免死循环。 | LangGraph, Structured Output |
| Archiver Agent | 档案管理员。负责维护用户的 TravelArchive,包括用户的偏好、历史记录等。在规划前确认用户需求。 | Archive MCP, Handy MCP |
| Planner Agent | 规划专家。负责具体的行程规划,查询地图数据,制定路线,安排 POI。 | Amap MCP, Planning MCP, Handy MCP |
| Renderer Agent | UI 渲染师。负责将规划好的数据转换为前端可渲染的 UI 组件代码(如 React 组件或特定 JSON)。 | Card MCP, Planning MCP |
| Amap MCP | 地图能力供给。提供所有与地理位置相关的原子能力。 | 高德 Web 服务 API |
| Data Persistence | 数据持久化。存储用户、消息、档案、工作区数据。 | Supabase (PostgreSQL) |
7. Agent 架构设计
TourAI 采用了 Supervisor (监督者) 模式的多智能体架构,通过 LangGraph 实现状态流转。
7.1 全局状态管理
系统通过 AgentStateAnnotation 维护一个全局共享的状态对象,各 Agent 只能读取或更新该状态。
- 状态定义:
messages(BaseMessage[]): 对话历史。采用 Append (追加) 模式,保留所有交互记录。next(AgentRole): 下一个执行的 Agent。采用 Overwrite (覆盖) 模式。travelArchive(TravelArchive): 用户出行档案。采用 Overwrite (覆盖) 模式,确保始终是最新的。travelPlanning(TravelPlanning): 当前行程规划。采用 Overwrite (覆盖) 模式。cardCode(string): 生成的 UI 卡片代码。采用 Overwrite (覆盖) 模式。reason(string): 路由原因,用于调试和日志。
7.2 Supervisor 节点实现
Supervisor 是一个基于 LLM 结构化输出的特殊节点,它不调用工具,只做决策。
- 实现类:
createSupervisorNode(inpackages/server/src/agents/supervisor/index.ts) - 核心逻辑:
- 快照注入: 在 Prompt 中注入
travelArchive,travelPlanning,cardCode的简要状态,帮助 Supervisor 判断任务进度。 - 结构化输出: 使用
withStructuredOutput(routeSchema)强制 LLM 输出 JSON,包含next(目标Agent),reason(理由),reply(回复)。 - 防死循环 (Dead Loop Prevention):
- 检查目标 Agent (
next) 是否是上一个发言者。 - 如果上一个发言者是子 Agent 且没有进行工具调用(说明它在等待用户回复),则强制将
next改为END,避免无限循环调用同一个 Agent。
- 检查目标 Agent (
- 静默回复 (Silent Reply):
- 如果决定路由到
END,且上一个发言的是子 Agent(如 Archiver 刚问完问题),Supervisor 会清空自己的reply,避免输出多余的废话。
- 如果决定路由到
- 快照注入: 在 Prompt 中注入
7.3 Worker 节点实现 (ReAct Agent)
Worker Agent (Archiver, Planner, Renderer) 采用标准的 ReAct (Reasoning + Acting) 模式。
- 实现类:
createAgentNode(inpackages/server/src/agents/creater.ts) - 核心逻辑:
- 工具绑定: 使用 LangChain 的
bindTools将 MCP 工具绑定到 LLM。 - 循环执行:
- 思考 (Think): LLM 根据历史消息生成回复或工具调用请求。
- 行动 (Act):
ToolNode拦截带有tool_calls的消息,执行工具并返回ToolMessage。 - 观察 (Observe): LLM 接收
ToolMessage,继续思考下一步,直到不再调用工具。
- 最大循环限制: 设置
maxTimes = 20,防止 Agent 陷入无限思考或工具调用死循环。
- 工具绑定: 使用 LangChain 的
7.4 状态图构建
使用 StateGraph 将 Supervisor 和 Worker 连接起来。
- 构建过程:
createAgentGraph(inpackages/server/src/agents/workflow.ts) - 拓扑结构:
- Entry:
START->TourAI(Supervisor) - Branching:
TourAI根据next字段路由到Archiver,Planner,Renderer或END。 - Loopback:
Archiver,Planner,Renderer执行完毕后,无条件流转回TourAI,由 Supervisor 决定下一步。
- Entry:
- 持久化: 使用
MemorySaver作为 Checkpointer,支持跨请求的状态记忆。
8. MCP 架构设计
TourAI 深度实践了 Model Context Protocol (MCP),将工具(Tools)与 Agent 解耦,实现了工具的微服务化。
8.1 传输层实现
MCP 协议的核心是传输层的抽象。TourAI 采用了 SSE (Server-Sent Events) 作为主要的传输方式,实现了全双工通信。
- Server 端 (McpServer):
- 实现: 使用
@modelcontextprotocol/sdk的StreamableHTTPServerTransport。 - 暴露方式: 通过 Express 的
createSseHandler适配器,将 Express 的req/res对象传递给 Transport 处理。 - 路由:
/mcp/sse是聚合所有工具的端点,同时也支持/mcp/amap等独立端点。
- 实现: 使用
- Client 端 (McpClient):
- 实现: 使用
@modelcontextprotocol/sdk的StreamableHTTPClientTransport。 - 连接方式:
McpClient类维护一个单例Client实例,连接到本地的 SSE 端点 (http://localhost:9999/mcp/sse)。 - 生命周期: 懒加载连接 (
connect方法),支持自动重连。
- 实现: 使用
8.2 协议消息流
MCP 定义了标准的消息类型(JSON-RPC 2.0 风格),TourAI 主要使用了以下几种:
- Initialize: Client 向 Server 发送初始化请求,协商协议版本和能力。
- Tools/List: Client 请求获取可用工具列表(包含 Schema)。
- Tools/Call: Client 发起工具调用请求,携带参数。
- Tools/Call/Result: Server 返回执行结果,支持文本、图片等多种内容格式。
8.3 工具定义与注册
- 基类抽象:
McpServer<T>抽象类 (inpackages/server/src/mcps/base.ts) 封装了工具定义的样板代码。schema: 使用 Zod 定义输入参数结构,自动生成 JSON Schema。execute: 抽象方法,实现具体的业务逻辑。formatResult: 辅助方法,将结果包装为 MCP 标准的CallToolResult格式。
- 注册流程:
register方法将工具注册到 SDK 的SdkMcpServer实例中。createMcpServer函数聚合所有工具实例,创建一个完整的 MCP Server。
8.4 扩展性设计
- 微服务拆分: 由于 Transport 层是基于 HTTP/SSE 的,当前的 MCP Server 可以轻松拆分为独立部署的微服务,Client 只需要修改连接 URL 即可。
- 多语言支持: MCP 协议是语言无关的,未来可以使用 Python 或 Go 实现特定的高性能工具(如复杂的路径规划算法),并通过 MCP 协议暴露给 Node.js 的 Agent 使用。
9. Agent 模式详细介绍
9.1 TourAI (Supervisor)
- Prompt 策略:
- 角色设定: 团队总管,只做决策,不干具体活。
- 状态快照: Prompt 中实时注入
travelArchive,travelPlanning,cardCode的状态摘要,确保决策基于最新上下文。 - 路由优先级:
- 自动流转 (Auto Flow): 如果 Planner 刚完成,强制流转给 Renderer(无需用户干预)。
- 等待反馈 (Wait for Feedback): 如果子 Agent 正在提问,必须流转给
END。 - 任务分发 (Task Dispatch): 根据当前状态缺失(缺档案 -> Archiver,缺规划 -> Planner)进行路由。
- 核心能力:
- 意图识别: 区分“闲聊”与“任务执行”。
- 流程控制: 管理子 Agent 之间的交接棒。
- 死循环熔断: 如果同一个 Agent 连续被调用且无工具产出,强制中断。
- 输出格式:
- 强制 JSON 输出:
{ next: string, reason: string, reply: string }。
- 强制 JSON 输出:
9.2 Archiver (出行档案大师)
- Prompt 策略:
- 角色设定: 细心专业的客服,语气热情、专业、礼貌。
- 状态机思维: 每次对话先检查
readee_archive,然后针对缺失字段进行追问。 - 数据校验: 明确要求出发地/目的地必须是中国城市,日期格式必须是
YYYY-MM-DD。
- 核心能力:
- 信息提取: 从自然语言中提取结构化数据(如 "2k" -> 2000)。
- 逻辑推断:
- 时间推断: 将“明天”、“下周五”转换为具体日期(利用
handy_time工具)。 - 人数推断: “一家三口” -> 3人,“带上老婆” -> 2人。
- 时间推断: 将“明天”、“下周五”转换为具体日期(利用
- 增量更新: 识别到新信息后立即调用
update_archive,支持多次补充。
- 行为约束:
- 严禁向用户索要
token或workspaceId。 - 严禁伪造数据,只保存用户明确提供或合理推断的信息。
- 严禁向用户索要
9.3 Planner (出行规划大师)
- Prompt 策略:
- 角色设定: 经验丰富的规划师。
- 规划原则: 顺路性(避免折返)、合理性(考虑交通耗时)、完整性(覆盖所有天数)、个性化(匹配预算和偏好)。
- 核心能力:
- 地图检索: 熟练使用
amap_search_*查询 POI,使用amap_route_*计算距离和路线。 - 多日规划: 将行程拆分为天,安排每日的吃住行游。
- 结构化输出:
- 生成 Markdown 格式的图文行程单(带 Emoji)。
- 生成结构化的
pois数据(包含经纬度、类型、建议游玩时长),用于地图打点。
- 地图检索: 熟练使用
- 工作流:
- 分析: 读取档案,确认需求。
- 搜索: 查询景点、天气、交通。
- 生成: 在“脑海”中构建行程。
- 提交: 调用
update_planning一次性提交完整方案。
9.4 Renderer (卡片渲染大师)
- Prompt 策略:
- 角色设定: 极高审美水准的设计师。
- 设计规范: 现代极简风格,模仿 Airbnb/携程的高端行程单。
- 技术栈: 强制使用 TailwindCSS,不引入外部 CSS/JS。
- 核心能力:
- 代码生成: 将
TravelPlanning数据转化为 HTML 代码片段。 - 视觉设计:
- 布局: 顶部标题图 + 中部时间轴 + 底部统计。
- 配色: 使用柔和的渐变色。
- 图片: 使用 Unsplash Source 生成高质量占位图 (
https://source.unsplash.com/...)。
- 代码生成: 将
- 行为约束:
- 只生成
<div>内部片段,不包含<html>或<body>。 - 确保移动端响应式适配。
- 只生成
10. MCP 工具详细介绍
10.1 Amap MCP (高德地图能力)
1. amap_search_around (周边搜索)
- 功能: 查询指定坐标周边的 POI 信息。
- Schema:
location(Required): 中心坐标[经度, 纬度]。city(Required): 城市名称。keywords(Optional): 关键词列表,如["肯德基"]。types(Optional): POI 类型代码。radius(Optional): 半径,默认 1000米。
- 实现: 调用高德
/place/around接口。
2. amap_search_keyword (关键字搜索)
- 功能: 根据关键字查询 POI 信息。
- Schema:
city(Required): 城市名称。keywords(Optional): 关键词。types(Optional): POI 类型代码。
- 实现: 调用高德
/place/text接口。
3. amap_search_detail (POI 详情)
- 功能: 根据 POI ID 查询详细信息(评分、图片等)。
- Schema:
id(Required): POI 的唯一 ID。
- 实现: 调用高德
/place/detail接口。
4. amap_weather (天气查询)
- 功能: 查询实时天气或预报。
- Schema:
city(Required): 城市名称或 adcode。type(Optional):base(实况) 或all(预报)。
- 实现: 调用高德
/weather/weatherInfo接口。
5. amap_route_driving (驾车规划)
- 功能: 规划驾车路线,支持途经点。
- Schema:
origin,destination(Required): 起终点坐标。strategy(Optional): 策略(速度优先、费用优先等)。
- 实现: 调用高德
/direction/driving接口。
6. amap_route_walking / bicycling / transiting
- 功能: 分别提供步行、骑行和公交(含跨城)路线规划。
- 特点: 公交规划支持跨城(如北京到上海),支持多种换乘策略。
7. amap_geocode / regeocode
- 功能: 地址与坐标互转。
- 场景: 用户说“我要去天安门”,先用 Geocode 转坐标,再规划路线。
8. amap_distance
- 功能: 批量测量多点之间的距离。
- 场景: 规划师计算景点间的距离,判断是否顺路。
9. amap_adcode (区县编码)
- 功能: 查询城市的行政区划编码(Adcode)。
- 场景: 当其他 API 需要 Adcode 作为参数时使用。
10. amap_iplocation (IP 定位)
- 功能: 根据 IP 地址查询所在城市和经纬度范围。
- 场景: 初步推断用户所在位置。
10.2 Archive MCP (档案管理)
1. readee_archive (读取档案)
- 功能: 读取当前工作空间中的用户出行档案。
- 逻辑:
- 检查工作空间是否存在。
- 返回当前的档案数据。
- 自动检测缺失字段: 如果发现必填项(如出发地、日期、人数)缺失,会在返回结果中明确提示【待收集】,指导 Agent 下一步的提问方向。
2. update_archive (更新档案)
- 功能: 更新用户出行档案。
- Schema:
departureCity,startDate,averageBudget等档案字段 +token,workspaceId。 - 逻辑:
- 增量更新: 只更新传入的字段,保留原有的其他字段。
- 状态流转: 只有当所有必填字段(出发地、目的地、日期、天数、人数、预算)都有值时,状态才流转为
finished,否则保持processing。 - 反馈机制: 同样返回【待收集】列表。
10.3 Planning MCP (行程管理)
1. readee_planning (读取规划)
- 功能: 读取当前的行程规划数据。
- 场景: Renderer Agent 在生成卡片前,必须先调用此工具获取 Planner 生成的数据。
2. update_planning (更新规划)
- 功能: 提交生成的行程规划。
- Schema:
markdown: 用于对话展示的文本行程。pois: 结构化数据。包含每日 (day) 的地点列表 (points),每个点包含坐标 (location)、类型 (category) 等。
- 逻辑: 这一步是连接 Planner 和 Renderer/Map 的桥梁。
pois数据直接用于前端地图打点。
10.4 Card MCP (卡片生成)
1. update_card (更新卡片)
- 功能: 提交生成的 UI 卡片代码。
- Schema:
code(HTML 字符串)。 - 逻辑: 将生成的 UI 代码存储到数据库,前端监听到变化后,会使用
dangerouslySetInnerHTML或特定的渲染器动态展示。
10.5 Handy MCP (通用工具)
1. handy_time (时间查询)
- 功能: 获取当前服务器的本地时间、ISO 时间、时间戳和时区信息。
- 场景: 当用户说“明天”、“下周三”等相对时间时,Agent 需要以此为基准计算具体日期。
11. 性能优化
11.1 核心请求处理优化
createMessageHandler 是整个系统的核心入口,集成了多项性能与可靠性优化:
- 快速失败 (Fail Fast):
- 使用
JoiSchema 对请求 Body 进行严格校验,参数错误立即返回 400,避免无效计算。 - 数据库操作失败立即返回 500。
- 使用
- 中断处理 (Abort Controller):
- 监听
req.on("close")事件。 - 设置
isAborted标志位,在主循环中检测。 - 一旦检测到客户端断开,立即停止 Agent 思考,并保存
[ABORT]状态,防止后台资源空转浪费。
- 监听
- 前置/后置风控 (Guardrails):
- Pre-check: 在进入 LLM 之前,通过正则匹配拦截恶意 Prompt。
- Post-check: 在流式输出过程中,实时检测敏感词,一旦触发立即截断并返回
[BLOCK]。
- 历史消息清洗 (Sanitization):
- 自动检测并修复“烂尾”的
tool_calls(即有调用请求但无结果的消息),防止 LangChain 因上下文不完整而崩溃。 - 智能移除无效的工具调用,保证 Prompt 的纯净度。
- 自动检测并修复“烂尾”的
- 批量持久化 (Batch Persistence):
- 引入
persistGraphMessages机制。 - 不在 SSE 循环中逐条保存消息,而是等待一轮对话结束(或中断)后,批量从 Graph State 中提取新消息并保存。
- 实现了消息合并逻辑(
lastAiMessage),自动将同一 Agent 的连续输出(如思考 + 文本 + 工具调用)合并为一条数据库记录,减少数据库 I/O。
- 引入
- 邀请语去重 (Deduplication):
- 扫描历史消息,使用正则
TourAI 邀请 (.*) 进群识别已邀请的 Agent。 - 维护
invitedAgentsSet,确保同一场对话中不会重复发送相同的邀请语,减少信息噪声。
- 扫描历史消息,使用正则
- 工作区懒激活 (Lazy Activation):
- 仅在用户发送第一条消息 (
userHistory.length === 1) 时才调用updateWorkspace激活工作区,避免创建空会话占用资源。
- 仅在用户发送第一条消息 (
11.2 流式响应优化
TourAI 实现了一套高度定制化的 SSE 流式处理机制 (MultiAgentProcessor),旨在降低首字延迟 (TTFT) 并提供丝滑的用户体验。
- 事件驱动架构: 基于 LangGraph 的
streamEvents(v2),将后端复杂的图执行过程转换为细粒度的前端事件。agent_start: 智能体切换事件,前端据此显示“Archiver 正在思考...”。thought: 实时透传 LLM 的思维链 (Chain of Thought),让用户看到 AI 的思考过程。tool_call: 工具调用事件,前端展示“正在查询高德地图...”。text_delta: 最终回复的逐字输出。
- 兼容性处理: 针对不同 LLM 提供商 (OpenAI, Alibaba Bailian) 的
reasoning_content字段差异进行了统一适配,确保“思考”内容始终可见。
11.3 显存与上下文优化
为了解决长对话导致 Token 消耗指数级增长的问题,实现了智能的显存管理策略 (getSummarizedHistory)。
- 滑动窗口: 仅保留最近 10 条活跃消息 (
activeHistory) 参与当前的 Prompt 构建。 - 增量摘要:
- 当活跃消息超过阈值时,触发后台异步摘要任务 (
summarizeMessages)。 - 将旧消息压缩为一段精炼的
summary文本。 - 新的 Prompt 结构为:
System Prompt + Summary + Active History。
- 当活跃消息超过阈值时,触发后台异步摘要任务 (
- Redis 缓存: 摘要结果存储在 Upstash Redis 中 (
summary:${workspaceId}),避免每次请求都重新生成摘要,显著降低延迟和 Token 成本。 - Prompt 注入优化:
formatProfileToPrompt: 仅注入非空的偏好字段,减少 Prompt 长度。systemPrompt: 采用结构化 Prompt (Role, Tone, Rules),提升 LLM 指令遵循能力。
11.4 状态持久化与并发控制
- Checkpointer: 使用
MemorySaver对 LangGraph 的状态进行内存级持久化(生产环境可替换为 PostgresSaver),确保多轮对话中状态(如TravelArchive)不丢失。 - 单例连接池 (Singleton Connection):
McpClient全局单例:整个应用生命周期仅维护一个 MCP Client 实例,复用底层的 SSE 连接。- Lazy Connection: 仅在首次调用工具时建立连接,且具备自动重连机制。
- Loopback 架构: MCP Server 与 Client 部署在同一实例(Localhost),通过回环接口通信,消除网络延迟,保障工具调用的毫秒级响应。
- 异步非阻塞: 摘要生成、日志记录等非关键路径操作全部采用异步执行 (
Promise.all或不await),不阻塞主线程的 SSE 响应。
11.5 传输层优化
- SSE over HTTP/2: 虽然代码层面使用的是标准的 HTTP/1.1 语义,但在部署层建议开启 HTTP/2,以利用多路复用特性。
- 数据压缩: 所有的 MCP 消息体都经过精简,移除了无用的元数据。
- Keep-Alive: SSE 连接保持长连接,减少 TCP 慢启动的影响。
11.6 数据库高可用
- 指数退避重试 (Exponential Backoff):
- 实现了
operateDatabaseWithRetry机制,针对数据库的瞬时故障(如网络抖动、连接超时)自动重试。 - 策略:最大重试 3 次,初始延迟 2000ms,每次重试延迟翻倍(2s -> 4s -> 8s),并引入随机抖动(Jitter)防止惊群效应。
- 实现了
- 上下文隔离 (Context Propagation):
- 使用 Node.js
AsyncLocalStorage实现authorizationStore,在异步调用链中隐式传递用户 Token。 - 优势:避免了 Token 在函数参数中的层层透传(Prop Drilling),降低了代码耦合度,确保了并发请求下的用户身份隔离与安全。
- 使用 Node.js
11.7 智能体稳定性与安全
- 死循环熔断 (Loop Prevention):
- ReAct 循环限制: 在
createAgentNode中设置了maxTimes = 20的硬性阈值,防止 Agent 进入 "思考 -> 调用工具 -> 思考" 的无限循环。 - 路由死循环检测: Supervisor 具备智能检测机制,如果检测到同一个 Agent 连续发言且没有调用工具(通常意味着在等待用户输入),会强制将控制权转交给
END,防止 Agent 自言自语。
- ReAct 循环限制: 在
- 运行时动态鉴权 (Dynamic Auth Injection):
createAgentNodeWithMcpTools实现了工具调用的安全包装。- 敏感凭证(Token, WorkspaceId)不包含在 Prompt 中,而是在工具执行的瞬间由系统自动注入。
- 优势:防止 LLM 泄露 Token,同时确保工具调用始终拥有最新的用户权限。
11.8 智能体模式优化
- 结构化输出路由 (Structured Output Routing):
- Supervisor 节点使用
model.withStructuredOutput(routeSchema)强制 LLM 输出严格的 JSON 格式。 - 优势: 相比于传统的文本解析,这彻底消除了路由指令解析失败的可能性,实现了零重试(Zero-Retry)的高效路由。
- Supervisor 节点使用
- 上下文压缩 (Context Compression):
- 在 Supervisor 的 Prompt 中,不注入完整的
TravelArchive或TravelPlanning对象(可能包含数千字)。 - 而是仅注入状态摘要(如 "当前出行档案: 已完成", "当前规划: 未开始")。
- 收益: 将 Supervisor 的 Token 消耗降低了 90% 以上,显著提升了路由决策速度。
- 在 Supervisor 的 Prompt 中,不注入完整的
- 参数调优 (Hyperparameter Tuning):
- 针对不同职责的 Agent 采用差异化的参数配置。
- Renderer Agent: 设置
temperature: 0.2,低创造性高精确度,确保生成的 UI 卡片代码(HTML/Tailwind)语法正确,减少渲染错误。 - Planner Agent: 使用默认温度,保证行程规划的多样性和趣味性。
- 静默回复 (Silent Reply):
- Supervisor 实现了智能静默逻辑:当子智能体(如 Archiver)已经向用户提问时,Supervisor 会自动抑制自己的回复。
- 体验: 避免了 "Archiver: 你想去哪?" 紧接着 "Supervisor: 好的,请回答他" 这种冗余对话,让交互更像真人。
12. 提示词调优
提示词工程是多智能体系统的灵魂。在 TourAI 的开发过程中,我们经历了从“简单指令”到“结构化思维链 (CoT)”的深度调优过程。以下是各个 Agent 的核心调优细节与演进记录。
12.1 Supervisor (总管) 调优
- 早期痛点:
- 经常出现“抢话”现象,即子智能体刚问完问题,Supervisor 又跳出来说“请回答他”。
- 路由不稳定,有时会把只要修改档案的请求错误地路由给 Planner。
- 调优策略:
- 状态快照注入: 在 Prompt 中动态注入
[状态快照]区域,实时显示档案收集进度(已收集/未收集)和规划状态,帮助 Supervisor 做出基于数据的决策。 - 静默协议: 明确写入规则 —— “如果上条消息是子智能体的提问,Supervisor 必须路由给 [END] 且 reply 为空”。
- 死循环防御: 增加规则 “如果检测到 [Archiver] 连续发言且未调用工具,强制路由给 [END]”,防止 Agent 自言自语。
- 状态快照注入: 在 Prompt 中动态注入
12.2 Archiver (档案大师) 调优
- 早期痛点:
- 喜欢一次性把所有问题都问完(“请提供出发地、目的地、时间、人数、预算”),像查户口,体验极差。
- 经常反复调用
readee_archive工具,浪费 Token。
- 调优策略:
- One-Shot 约束: 明确规定 “在同一次回复中,只能调用一次 [update_archive] 工具”,迫使 Agent 在内部思考清楚再行动。
- 循序渐进: 修改 Prompt 要求其 “仔细阅读【待收集】列表,优先针对这些缺失的关键信息进行提问”,引导其进行多轮自然对话。
- 状态显性化: 要求回复中必须包含 “当前出行档案状态” 列表,让用户清晰看到哪些已确认,哪些待确认。
12.3 Planner (规划大师) 调优
- 早期痛点:
- 容易产生幻觉,规划出不存在的景点或不合理的路线(如一天内往返相距 500km 的两个城市)。
- 输出格式混乱,难以被前端解析。
- 调优策略:
- 工具强制: 明确规定 “你必须使用高德地图工具 [amap_*] 查询地点、路线”,禁止凭空捏造。
- 结构化分离: 要求输出分离为两个字段:
markdown: 包含 Emoji 的图文并茂的行程单,用于人类阅读。pois: 结构化的 JSON 数据,包含经纬度,专门用于地图渲染。
- 合理性约束: 增加 “路线安排必须顺路,避免折返” 的显性规则。
12.4 Renderer (渲染大师) 调优
- 早期痛点:
- 生成的 HTML 经常包含
<html>或<body>标签,导致嵌入前端时样式冲突。 - 样式丑陋,缺乏设计感。
- 生成的 HTML 经常包含
- 调优策略:
- 角色设定: 赋予其 “拥有极高审美水准的设计师” 人设,并指定对标 “Airbnb 或 携程 的高端行程单风格”。
- 技术约束:
- 强制使用 TailwindCSS,禁止引入外部 CSS/JS。
- 明确只生成
<div>片段。 - 使用
source.unsplash.com作为图片占位符,解决了图片缺失问题。
- 温度控制: 在代码层面将其
temperature设为0.2,配合 Prompt 中的 “严格遵循语法” 指令,确保生成的 HTML 结构 100% 可用。
13. 技术栈
13.1 Backend
- Runtime: Node.js (>=22.0.0)
- Language: TypeScript
- Framework: LangChain, LangGraph
- Protocol: MCP (Model Context Protocol) SDK
- Database: Supabase (PostgreSQL)
- LLM Provider: Alibaba Bailian (Qwen models) / OpenAI Compatible
13.2 Frontend
- Framework: React 18+
- Build Tool: Vite
- Styling: SCSS / CSS Modules
- State Management: Zustand
- Components: Lucide React (Icons), Ant Design X (Markdown)
14. TodoList
P0: 核心体验优化
- Agent 响应速度优化: 减少 Supervisor 的决策延迟。
- 流式输出稳定性: 修复在网络波动下 SSE 连接中断的问题。
- 地图交互 (Map Interaction):
- 实时 POI 展示: 在对话过程中自动识别并收录提到的地点,并在地图上实时标记。
- 规划动画: 在生成规划时或规划完成后,在地图上展示动态的路线轨迹和 POI 动画效果。
- 交互增强: 支持点击跳转、缩放、多点标记等基础操作。
P1: 功能扩展
- 多模态支持: 允许用户上传图片(如小红书截图)进行行程解析。
- 协同规划: 支持多个用户在一个 Workspace 中共同规划。
- 行程导出: 支持导出为 PDF 或图片格式。
P2: 工程化
- 单元测试覆盖: 增加 Agent 逻辑的单元测试。
- E2E 测试: 增加关键路径的端到端测试。
- 监控告警: 接入更完善的日志监控系统。
- MCP Client 重构: 修复
connect()方法中可能存在的竞态条件(connected标志位设置过早)。 - 配置管理: 将 MCP Server 的硬编码地址 (
localhost:9999) 移至环境变量配置。
15. 缺失模块补充与建议
15.1 错误处理机制
当前文档未详细描述错误处理。建议补充:
- Agent 降级策略: 当某个 Agent 连续出错时,如何优雅降级?
- 工具调用重试: MCP Client 层应实现自动重试机制。
- 全局异常捕获: 在 Supervisor 层捕获所有未处理异常,返回友好的错误提示。
15.2 安全与隐私
- Prompt 注入防护: 增加对用户输入的清洗和检测。
- 数据脱敏: 在日志中自动脱敏用户的敏感信息(如具体的家庭住址)。
- 鉴权: 确保所有 MCP 工具调用都经过严格的
workspaceId和token校验。
15.3 部署与运维
- Docker化: 提供标准的 Dockerfile。
- CI/CD: 配置 GitHub Actions 自动构建和部署。
16. 交互流程示例
为了更好地理解多智能体的工作机制,以下模拟了一个真实的用户交互场景。
场景:成都三日游规划
1. User: "我想去成都玩。"
- Trigger: 用户发起新的对话。
- Supervisor: (思考:当前档案为空,缺少出发地、时间等关键信息) -> 路由至 Archiver。
2. Archiver: "好的,请问您计划玩几天?大概什么时候出发?还有预算大概是多少?"
- Action: Archiver 识别到意图,但发现信息缺失,开始追问。
3. User: "三天,端午节去,预算人均 2000 左右。"
- Supervisor: (思考:用户回复了,继续交给 Archiver 处理) -> 路由至 Archiver。
4. Archiver:
- Tool Call: 调用
handy_time获取当前时间(例如 2026-03-03),推算出端午节的具体日期。 - Tool Call: 调用
update_archive保存档案:destinationCity: "成都"travelDuration: 3startDate: "2026-06-19" (假设)averageBudget: 2000
- Reply: "档案已更新。出发地默认是您所在的城市吗?"
5. User: "是的,我在北京。"
- Supervisor: (思考:继续交给 Archiver) -> 路由至 Archiver。
6. Archiver:
- Tool Call: 调用
update_archive补充departureCity: "北京"。 - State Check: 检查发现所有必填项已收集完毕,档案状态流转为
finished。 - Reply: "好的,所有信息都已确认。正在为您规划行程..."
7. Supervisor:
- Decision: (检测到
travelArchive已完成,但travelPlanning为空) -> 路由至 Planner。
8. Planner:
- Tool Call: 调用
amap_weather查询成都端午节期间的天气。 - Tool Call: 调用
amap_search_keyword查询 "成都热门景点" (宽窄巷子, 大熊猫基地, 春熙路...)。 - Tool Call: 调用
amap_route_driving计算各景点间距离,规划顺路行程。 - Thinking: 构建每日行程 (Day 1: 抵达+春熙路; Day 2: 熊猫基地+武侯祠; Day 3: 宽窄巷子+返程)。
- Tool Call: 调用
update_planning提交 Markdown 行程单和 POI 数据。 - Reply: "出行规划已经完成。"
9. Supervisor:
- Decision: (检测到 Planner 已完成,且
cardCode为空,触发自动流转规则) -> 路由至 Renderer。
10. Renderer:
- Tool Call: 调用
readee_planning读取刚刚生成的规划数据。 - Thinking: 设计卡片布局,生成 TailwindCSS HTML 代码。
- Tool Call: 调用
update_card提交代码。 - Reply: "地图卡片已经生成。"
11. Supervisor:
- Decision: (所有任务完成,子 Agent 汇报完毕) -> 路由至 END。
12. Client:
- UI Update: 前端接收到 SSE 流中的
cardCode更新事件,动态渲染出精美的行程卡片。 - Map Update: 前端地图组件读取
pois数据,自动在地图上打点并绘制路线。