Tour AI - 智能出行规划 Agent

首个地图 AI Agent,基于多智能体协作架构,用户只需一句话即可生成包含详细攻略与路线规划的精美地图卡片。

2025-12-01 ~ 至今
React
Node.js
LangGraph
Multi-Agent
Agent
MCP
ReAct

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 包装器,在工具执行上下文中注入并校验用户身份。
  • 内容风控 (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}),加速读取。
  • 错误恢复:
    • 数据库重试: 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 AgentUI 渲染师。负责将规划好的数据转换为前端可渲染的 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 (in packages/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。
    • 静默回复 (Silent Reply):
      • 如果决定路由到 END,且上一个发言的是子 Agent(如 Archiver 刚问完问题),Supervisor 会清空自己的 reply,避免输出多余的废话。

7.3 Worker 节点实现 (ReAct Agent)

Worker Agent (Archiver, Planner, Renderer) 采用标准的 ReAct (Reasoning + Acting) 模式。

  • 实现类: createAgentNode (in packages/server/src/agents/creater.ts)
  • 核心逻辑:
    • 工具绑定: 使用 LangChain 的 bindTools 将 MCP 工具绑定到 LLM。
    • 循环执行:
      • 思考 (Think): LLM 根据历史消息生成回复或工具调用请求。
      • 行动 (Act): ToolNode拦截带有 tool_calls 的消息,执行工具并返回 ToolMessage
      • 观察 (Observe): LLM 接收 ToolMessage,继续思考下一步,直到不再调用工具。
    • 最大循环限制: 设置 maxTimes = 20,防止 Agent 陷入无限思考或工具调用死循环。

7.4 状态图构建

使用 StateGraph 将 Supervisor 和 Worker 连接起来。

  • 构建过程: createAgentGraph (in packages/server/src/agents/workflow.ts)
  • 拓扑结构:
    • Entry: START -> TourAI (Supervisor)
    • Branching: TourAI 根据 next 字段路由到 Archiver, Planner, RendererEND
    • Loopback: Archiver, Planner, Renderer 执行完毕后,无条件流转回 TourAI,由 Supervisor 决定下一步。
  • 持久化: 使用 MemorySaver 作为 Checkpointer,支持跨请求的状态记忆。

8. MCP 架构设计

TourAI 深度实践了 Model Context Protocol (MCP),将工具(Tools)与 Agent 解耦,实现了工具的微服务化。

8.1 传输层实现

MCP 协议的核心是传输层的抽象。TourAI 采用了 SSE (Server-Sent Events) 作为主要的传输方式,实现了全双工通信。

  • Server 端 (McpServer):
    • 实现: 使用 @modelcontextprotocol/sdkStreamableHTTPServerTransport
    • 暴露方式: 通过 Express 的 createSseHandler 适配器,将 Express 的 req/res 对象传递给 Transport 处理。
    • 路由: /mcp/sse 是聚合所有工具的端点,同时也支持 /mcp/amap 等独立端点。
  • Client 端 (McpClient):
    • 实现: 使用 @modelcontextprotocol/sdkStreamableHTTPClientTransport
    • 连接方式: 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> 抽象类 (in packages/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 }

9.2 Archiver (出行档案大师)

  • Prompt 策略:
    • 角色设定: 细心专业的客服,语气热情、专业、礼貌。
    • 状态机思维: 每次对话先检查 readee_archive,然后针对缺失字段进行追问。
    • 数据校验: 明确要求出发地/目的地必须是中国城市,日期格式必须是 YYYY-MM-DD
  • 核心能力:
    • 信息提取: 从自然语言中提取结构化数据(如 "2k" -> 2000)。
    • 逻辑推断:
      • 时间推断: 将“明天”、“下周五”转换为具体日期(利用 handy_time 工具)。
      • 人数推断: “一家三口” -> 3人,“带上老婆” -> 2人。
    • 增量更新: 识别到新信息后立即调用 update_archive,支持多次补充。
  • 行为约束:
    • 严禁向用户索要 tokenworkspaceId
    • 严禁伪造数据,只保存用户明确提供或合理推断的信息。

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):
    • 使用 Joi Schema 对请求 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。
    • 维护 invitedAgents Set,确保同一场对话中不会重复发送相同的邀请语,减少信息噪声。
  • 工作区懒激活 (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),降低了代码耦合度,确保了并发请求下的用户身份隔离与安全。

11.7 智能体稳定性与安全

  • 死循环熔断 (Loop Prevention):
    • ReAct 循环限制: 在 createAgentNode 中设置了 maxTimes = 20 的硬性阈值,防止 Agent 进入 "思考 -> 调用工具 -> 思考" 的无限循环。
    • 路由死循环检测: Supervisor 具备智能检测机制,如果检测到同一个 Agent 连续发言且没有调用工具(通常意味着在等待用户输入),会强制将控制权转交给 END,防止 Agent 自言自语。
  • 运行时动态鉴权 (Dynamic Auth Injection):
    • createAgentNodeWithMcpTools 实现了工具调用的安全包装。
    • 敏感凭证(Token, WorkspaceId)不包含在 Prompt 中,而是在工具执行的瞬间由系统自动注入。
    • 优势:防止 LLM 泄露 Token,同时确保工具调用始终拥有最新的用户权限。

11.8 智能体模式优化

  • 结构化输出路由 (Structured Output Routing):
    • Supervisor 节点使用 model.withStructuredOutput(routeSchema) 强制 LLM 输出严格的 JSON 格式。
    • 优势: 相比于传统的文本解析,这彻底消除了路由指令解析失败的可能性,实现了零重试(Zero-Retry)的高效路由。
  • 上下文压缩 (Context Compression):
    • 在 Supervisor 的 Prompt 中,不注入完整的 TravelArchiveTravelPlanning 对象(可能包含数千字)。
    • 而是仅注入状态摘要(如 "当前出行档案: 已完成", "当前规划: 未开始")。
    • 收益: 将 Supervisor 的 Token 消耗降低了 90% 以上,显著提升了路由决策速度。
  • 参数调优 (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 自言自语。

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> 标签,导致嵌入前端时样式冲突。
    • 样式丑陋,缺乏设计感。
  • 调优策略:
    • 角色设定: 赋予其 “拥有极高审美水准的设计师” 人设,并指定对标 “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 工具调用都经过严格的 workspaceIdtoken 校验。

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: 3
    • startDate: "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 数据,自动在地图上打点并绘制路线。