
1. 项目概述从“出差靠运气”到“让智能体替我扛事”出差这件事看着简单真落地的时候全是琐碎决策订哪趟航班最稳妥、酒店选在哪个商圈能兼顾价格和通勤、中间加一场客户会议要怎么插进去、整体预算会不会超。以前我都靠“手动切N个App再拍脑袋”直到看到阿里智能体开源的那波动作萌生了一个想法——能不能照着阿里商旅的产品思路用多智能体架构做个差旅助手把订票、排程、预算检查这些环节全部交出去。我这个项目就叫“仿阿里商旅的多智能体差旅助手”核心是把一次出差请求拆解成“差旅诉求解析 → 行程规划 → 资源预订 → 待办执行 → 费用总结”五段式流水线由不同职能的Agent协作完成而不是靠一个巨大的Agent包办所有事情。如果你正准备做智能体应用或者想接手一个偏“决策工具调用”类场景比如客服工单、预约调度、流程审批这篇文章值得一看。我会给出能直接抄作业的架构设计、关键代码模式、以及我踩过的坑。最后你会理解一个道理真正好用的智能体应用不是模型够强而是工程够稳。2. 整体架构设计为什么差旅场景必须用多智能体2.1 差旅场景为什么不能用单Agent硬扛我最早其实试过单Agent方案把所有指令塞给一个大模型让它自己决定订票、订酒店、排日程。初版Demo跑起来就像让一个刚毕业的实习生同时当行政、财务和秘书问两句还行任务一多就乱拿到复杂行程时上下文容易丢失关键约束比如“必须下午3点前到客户公司”。一次任务里混了“查询”“计算”“预订”“生成提醒”四种动作模型经常把动作顺序搞错。工具返回值混在长对话里Token占用飙升响应越来越慢。要单独调整某个环节的逻辑只能改一大堆Prompt改一处崩三处。这就是我决定切换为多智能体架构的直接原因。多智能体不是炫技而是把“大而全的智能”拆成“小而专的智能”每个Agent只管自己擅长的事通过明确的协议协作。2.2 仿阿里商旅的产品链路拆解阿里商旅的产品逻辑其实就是一套“管控效率”的流程员工提出差旅申请 → 系统根据政策判断等级 → 生成可选方案 → 预订后同步行程与报销。我仿的不是它的UI而是这背后的责任链环节职责对应Agent差旅诉求解析从自然语言里抽取出差人、日期、城市、预算上限、偏好入口解析Agent行程规划根据约束生成交通住宿会议的候选方案行程规划Agent资源预订调用模拟接口完成航班、酒店、车次的锁定资源执行Agent待办执行生成日程提醒、会前准备清单日程管理Agent费用总结汇总所有费用判定是否超预算费用审计Agent这里的关键洞察是差旅不是一个NLP任务而是一个业务流程。NLP只是入口真正撑起用户体验的是“流程管控”。所以我给每个Agent都定义了清晰的输入输出协议并用一个状态机把整条流水线串起来。2.3 多智能体的三种协作模式我为什么选了管线式多智能体常见的协作模式有三种星型一个主管分派给多个专员、环型逐个传递加工、管线型按固定步骤依次执行。我最终选了管线式为主、星型为辅的混合模式。原因很实际差旅流程天然是一个有序流水线前序输出是后序输入适合管线编排。但“行程规划Agent”内部又用到星型模式它会同时询问交通偏好、住宿偏好和会议时间三个子查询我让规划Agent扮演“小主管”把查交通、查酒店、查会议室的工具当作“虚拟专员”。这样既保持了主流程的可控又保留了分支的灵活性调试起来不会变成一锅粥。注意别一上来就搞“自由协商多智能体”让Agent自己决定下一步做什么。真实工程里流程的稳定性远大于智能性。先把主流程用状态机锁定再在局部放权。2.4 状态机让每个Agent有明确的“生老病死”多智能体系统最怕的就是Agent行为不可预期。我参考了工作流引擎的做法给差旅助手定义了一个最小状态集received已收到诉求解析完成。planning规划Agent正在生成行程方案。confirming正在等待用户确认方案。booking资源执行Agent正在预订。scheduled日程Agent已创建提醒。settled费用审计Agent完成核对。每个Agent执行完都会向状态机上报一个事件。状态机负责判断当前状态是否允许迁移到下一个状态如果乱序到达就丢弃或重试。这一步直接避免了下面这种崩溃场景用户还没确认方案系统已经把酒店订了。3. 核心角色拆解五个Agent各司其职谁也别越权3.1 入口解析Agent用Structured Output锁住关键字段入口Agent主要负责把“我下周一要去深圳上午见客户下午希望早点回来预算3000以内”这句话解析成如下结构的JSON{ traveller: 张伟, start_date: 2025-06-16, city: 深圳, time_constraints: [ {type: meeting, start: 09:30, end: 11:30, note: 见客户} ], return_preference: afternoon, budget: 3000 }这块我踩过一个比较深的坑最初我让大模型直接输出JSON结果它偶尔漏字段、偶尔多字段下游Agent拿到解析结果就直接崩溃。后来我引入了一套“Structured Output”机制用JSON Schema约束模型输出还开了校验层解析失败会让模型基于错误信息再修一次最多重试2次。重心放在约束输出别考验模型的自由发挥能力。给模型越大的自由你后期的数据清洗工作越多。3.2 行程规划Agent同时查询三个资源绝不自己拍板行程规划Agent是我整个系统里最复杂的角色。它拿到入口Agent的JSON后不是直接生成答案而是并行调用三个“资源查询工具”查询航班根据日期/城市/时间约束返回可选航班列表查询酒店根据商圈/预算/住宿晚数返回可选酒店列表查询会议根据会议时间生成预约占位这三个工具调用完成后规划Agent汇总结果生成3套候选行程方案每套方案都包含“总体耗时、总费用、是否符合预算、推荐理由”。这里的设计要点是“智能体只能选择不能创造”。模型最擅长在给定选项里做权衡而不是凭记忆编造航班号。一旦让它直接生成行程它就会一本正经地告诉你“建议乘坐某航班9:00起飞”实际上那个航班根本不存在。所以我把数据获取全部封装成工具行程规划Agent只做综合和排序。3.3 资源执行Agent工具调用要像写“有契约的接口”资源执行Agent是唯一允许修改外部状态的角色负责锁价、创建订单、占会议室。我给它封装了如下工具签名def book_flight(flight_no: str, date: str, passenger: str, cabin_class: str economy) - dict: 预订指定航班 返回: {order_id: ..., status: confirmed, total: 860.00} 这种“一个函数一个用途”的做法好处是异常处理很集中如果航班已满函数返回{status: failed, reason: sold_out}执行Agent会据此改选备选航班。如果预订成功返回order_id之后再对账会用到。我强烈建议所有工具的返回都用统一JSON格式包裹别让Agent去猜。我在项目里定义了ToolResponse的标准结构包含status/data/error三个字段。Agent只需要按固定模板解析既降低了幻觉率又方便后续接入监控日志。3.4 日程管理Agent把“通知”变“任务”很多人忽略日程管理实际上这块才是差旅助手让用户“觉得好用”的关键。日程管理Agent不是简单生成一条日历提醒而是把行程里的重要节点拆成带状态的待办出发前1天准备出差用品清单依据目的地天气和会面类型生成出发前2小时航班/高铁值机提醒会议前30分钟推送客户纪要素材与会议室位置这项功能做的核心动作是“目标分解”。用户告知“要见客户开方案会”Agent会把它分解成“准备PPT、打印纸质合同、确认会议室设备、提前15分钟到场”四项待办。相比一条冷冰冰的文字提醒这种结构化的待办列表建议明显让用户更愿意使用。3.5 费用审计Agent最后一道“反幻觉”防线每个Agent都有可能犯错费用审计Agent就是安全网。它做两件事把预订阶段产生的所有order_id汇总拉取实际金额。与预算常量对比输出OVER_BUDGET或OK。这块还顺便处理了对比容易误判的隐藏费用场景有的航班价格不含税有的酒店价格含早餐但不含服务费如果不拉明细算术上看着没超实际早就超了。我在这里用了最传统也最可靠的方法——“分账本”。每生成一笔订单就往一个本地SQLite库里插入一条记录。费用审计Agent只查表做聚合不看大模型的输出汇总。这种设计是可以推广的规律只要涉及钱都别让模型“凭印象”做算术直接用代码查表。4. 实操过程从零搭建可复现的多智能体差旅助手4.1 环境准备与框架选型这个项目我用的是阿里开源的智能体相关框架作为底座结合自己的工具调用封装层。环境依赖如下# Python 3.10 pip install alibab-agent-sdk pip install pydantic pip install fastapi pip install sqlite3 # 内置模块选这块框架的核心考量有三个意图识别、函数调用、多轮上下文管理都内置了省去大量Prompt工程。支持Agent级隔离每个Agent的Prompt和模型参数可以独立配置。输出层自带JSON Schema校验不用自己重复实现Structured Output。如果你不打算引入这个框架也可以用任何你熟悉的大模型接口 pydantic校验自己搭建核心架构是通用的。4.2 领域数据层模拟现实世界资源由于没有真实航班和酒店数据我自己写了模拟数据源覆盖了三个城市的基础资源。数据量不大但每个字段都按真实逻辑设计# data/flights.py FLIGHTS [ { flight_no: MU5271, from_city: 上海, to_city: 深圳, departure: 08:15, arrival: 10:40, price: 620, cabin_rank: [economy, business] }, ]这里建议数据量不要太小否则Agent很难出现“换备选方案”的行为。每个城市至少准备早、中、晚三个时段的班次才能让规划Agent真正做排序和权衡。4.3 Agent定义用配置式声明替代堆Prompt直接堆Prompt容易失控。我参考了配置式Agent定义的方式每个Agent有两个核心配置role描述和tools列表。from agent_sdk import Agent planner_agent Agent( name行程规划Agent, role你是差旅行程规划助手职责是根据用户约束筛选最合适的资源禁止自行编造航班信息。, tools[search_flights, search_hotels, search_meeting_rooms], modelqwen-plus, temperature0.3 ) executor_agent Agent( name资源执行Agent, role你是差旅资源预订助手职责是按计划锁定资源。只允许调用预订工具不允许修改行程。, tools[book_flight, book_hotel, book_meeting_room], modelqwen-plus, temperature0 )两个Agent用的是同一个模型但因为角色与工具不同行为模式完全不同。这让我意识到“温度参数”很值得研究执行类Agent我把温度设为0保证它不做任何创造性发挥规划类Agent用0.3保留一点综合候选方案的空间。4.4 状态机实现用事件驱动让Agent跑起来状态机是这个系统的心脏。我实现了一个非常精简的事件循环class TripStateMachine: def __init__(self): self.state received self.runner get_agent_runner() def handle_event(self, event: str, payload: dict): if self.state received and event PARSE_DONE: self.state planning return self.runner.invoke(planner, payload) if self.state planning and event PLAN_READY: self.state confirming return {action: confirm, plans: payload[plans]} if self.state confirming and event USER_CONFIRMED: self.state booking return self.runner.invoke(executor, payload) # ... raise StateTransitionError(finvalid transition: {self.state} - {event})这个状态机做到两件事非法事件会被直接拒绝不会污染后续步骤每个事件都带全量payload保证Agent之间不需要共享可变内存。实操心得状态机的状态迁移事件建议全部小写“下划线”命名不要混用驼峰日志检索和回调处理会省心很多。我在初期因为事件命名不统一反复排查过“PLAN_READY”和“plan_ready”两个事件同时存在导致重复触发的问题。4.5 验证测试用一套模拟数据跑完整流程搭建完成后我跑了一遍完整的模拟数据输入“张伟6月16日去深圳出差上午10点见客户预算控制在2500以内下午希望早点回上海。”执行结果解析Agent 5秒内输出结构化JSON识别到会议约束和预算上限。规划Agent在航班列表里选出方案A早班机08:15去17:05回总费用1890元和方案B09:20去19:30回费用2130元。用户确认方案A。执行Agent依次锁定机票、酒店、会议室生成三个order_id。日程Agent创建了提醒清单和3项待办。审计Agent汇总费用1890元判定未超预算。整个过程约45秒大部分耗时来自大模型的串行调用。如果要上生产我会把“规划Agent里的三个查询”从串行改成并发预计能压到20秒以内。5. 常见问题与排查技巧实录5.1 大模型“自作主张”生成了不存在的航班号症状行程规划Agent返回的航班列表里混入了“XX8888”查无此航班。原因规划Agent在拿到工具返回结果后没有完全按工具数据输出而是“补充”了记忆里的航班信息。排查思路在规划Agent的工具调用结果中增加“来源标识”让后续Agent只信任带标识的数据。解决我给每个工具返回都包了一层metadata.source tool:flights并在系统提示里明确指出“只允许引用工具返回的数据禁止臆造行程信息”。加上这条之后幻觉航班基本消失了。5.2 状态机卡在“confirming”状态无法推进症状用户已经在聊天窗口回复“确认方案A”但状态机一直没有进入booking。原因状态机监听的是USER_CONFIRMED事件但前端聊天回调发的是user_confirmed大小写不一致事件没被匹配到。排查思路在事件入口打日志打印原始事件名与状态机的事件白名单一眼就能看出大小写差异。解决统一事件名为小写并在启动脚本里加事件名校验不合规的事件在接入层直接拒绝。5.3 规划Agent对“预算2500以内”理解成“单人单程2500”症状预算判断完全错误给出的方案费用远超用户预期。原因用户输入里“2500以内”没有明确说明是总预算还是单程预算模型默认理解为总费用但某个方案由于查询了多段航班内部计算时被当成了“每人每段”。解决在解析Agent的Prompt里增加一条规则——“预算未特别说明时默认指整个差旅的总预算”同时让费用汇总工具按“所有订单金额汇总”计算不给模型自由发挥的空间。5.4 多Agent串行调用太慢用户体验不行症状整条流水线要45秒用户觉得等太久。原因每个Agent依次调用大模型尤其规划阶段“差旅方案规划”和“资源查询”全部串行化。排查思路先看调用链耗时分布发现60%时间耗在规划Agent的三个并行查询被强行转成了串行执行。解决把三个查询包装成并发调用用gather并发搜航班、酒店、会议室同时引入“半程交互”方案生成后先给用户一个“正在预订可用资源”的缓冲提示体验上就流畅多了。5.5 工具调用偶发返回超时下游Agent直接报错症状执行Agent预订航班时接口偶发超时整个流程中断。原因工具函数没有做超时控制底层模拟数据库锁表时偶尔阻塞。解决所有工具函数统一用Timeout装饰器包一层超时后返回{status: timeout, error: ...}。执行Agent遇到timeout会自动选择备选航班重试而不是把整个流程卡死。下面把完整的排查要点整理成一张速查表问题现象核心原因首选排查点解决思路Agent输出不存在的航班/酒店模型臆造数据是否只信任工具返回加数据来源标记禁令提示状态机卡住不推进事件名不匹配事件进入日志统一事件名接入层校验预算判断错误口径不明确解析Prompt规则明确默认语义汇总用代码响应慢Agent串行调用链耗时分析并发化工具调用工具超时中断缺超时与重试工具异常处理统一Timeout备选方案6. 复盘与后续扩展方向做完整个项目我个人最有价值的心得是智能体应用的工程上限往往取决于“工具接口的契约设计”和“状态机的严谨程度”而不是模型的聪明程度。模型的能力会越来越好但差旅流程里的“预算不能超、航班不能乱选、订单状态不能错”这些硬约束永远要靠工程来兜底。从实际使用体验来讲多智能体架构给差旅场景带来的提升比传统“一个Prompt走天下”要明显得多。每个环节都能独立测试、独立调优、独立降级。比如费用审计Agent出问题时我只需要单独修那个Agent不会影响预订Agent的正常工作。后续我还计划在这个项目上做三件事接入真实出行数据源把模拟资源替换成可用接口。增加“多模态行程单”让助手根据用户上传的会议邀请截图自动解析会议时间。把状态机配置可视化做成本地编排工具让不熟悉代码的运营也能调整差旅审批规则。如果看完这篇文章你也想动手做一个多智能体项目我建议从“流程最固定、约束最清晰、错误影响可承受”的场景开始。差旅、会议预约、工单分配都属于很好的切入点。等你把状态机、工具契约、Agent编排这一套打法跑通之后再谈让智能体更“自由”也不迟。