基于开源多智能体框架的差旅助手原型搭建实操
差旅这件事说简单也简单订张机票、选个酒店、排个行程三步搞定。但真到执行层面尤其是多人、多城市、多约束条件叠加的时候复杂度会指数级上升。我最近花了两周时间参考市面上主流商旅产品的交互逻辑用开源多智能体框架搭了一套差旅助手原型从需求解析到行程编排再到异常处理全部由多个智能体分工协作完成。这篇文章不讲空泛的概念只聊我在搭建过程中踩过的坑、做过的取舍以及那些文档里不会写的实操细节。1. 为什么单智能体搞不定差旅场景1.1 差旅需求的天然复杂性差旅场景和普通的问答任务有本质区别。你问一个智能体帮我订明天去某地的机票它可能直接给你一个航班列表就完事了。但真实的差旅需求从来不是单一维度的。一个典型的商务出行请求可能包含这些信息出发城市、目的城市、出发时间窗口、返回时间窗口、舱位偏好、航司偏好、预算上限、是否需要接送机、酒店位置偏好、入住天数、会议室需求、用餐安排等等。这些信息之间还存在大量约束关系。比如预算上限会同时影响机票舱位和酒店星级酒店位置又和会议地点强相关会议时间反过来约束航班到达时间。单智能体在处理这种多约束、多目标的问题时往往顾此失彼。我实测下来让一个通用大模型直接处理完整的差旅请求它要么遗漏关键约束要么在多个约束冲突时给出自相矛盾的建议。更麻烦的是差旅需求经常是模糊的。用户说帮我安排下周去某地出差这里面下周是哪几天某地具体到哪个区出差目的是什么这些信息不补齐后续所有决策都是空中楼阁。单智能体很难同时做好信息补全、约束求解和方案生成这三件事。1.2 多智能体分工的天然优势多智能体架构的核心思路是分而治之。我把差旅助手拆成了五个角色需求解析智能体、航班查询智能体、酒店查询智能体、行程编排智能体和异常处理智能体。每个智能体只负责自己那一亩三分地通过消息传递机制协作。这样做的好处很明显。需求解析智能体可以专注做一件事把用户的自然语言输入转化成结构化的差旅需求对象。它不需要关心航班怎么查、酒店怎么选只需要确保解析出来的字段完整、准确。航班查询智能体拿到结构化的出发到达信息后专注调用航班数据接口做筛选和排序。酒店查询智能体同理。行程编排智能体是真正的大脑它接收前三个智能体的输出做全局优化。比如航班到达时间是下午两点酒店入住时间就不能早于这个点会议地点在城东酒店就不能选在城西。这些跨领域的约束只有站在全局视角才能处理好。异常处理智能体则负责兜底。航班取消了怎么办酒店满房了怎么办用户临时改了会议时间怎么办这些异常情况在差旅中太常见了必须有一个专门的智能体来监控和响应。1.3 我为什么选择开源框架而不是自己造轮子一开始我也想过自己写一套调度逻辑但很快放弃了。多智能体系统的核心难点不在业务逻辑而在通信机制、状态管理和错误恢复。自己造轮子意味着要处理消息队列、智能体生命周期、上下文传递、超时重试等一系列基础设施问题这些和差旅业务本身没关系但会消耗大量精力。开源框架的好处是这些基础设施已经帮你搭好了。你只需要定义好每个智能体的职责、输入输出格式和协作流程剩下的通信和调度框架会处理。我选用的框架支持基于消息的异步通信每个智能体可以独立运行通过消息总线交换信息。这种松耦合的设计让调试变得容易很多——我可以单独测试每个智能体确认它工作正常后再接入整体流程。选框架的时候重点看三个东西通信机制是否灵活、是否支持智能体动态注册和发现、错误处理是否完善。差旅场景对实时性要求不算极端但对可靠性要求很高一个环节出错不能导致整个流程崩溃。2. 五个智能体的职责边界与协作机制2.1 需求解析智能体把人话翻译成结构化数据需求解析智能体是整个系统的入口它的任务是把用户那句帮我安排下周去某地出差三天翻译成机器能理解的结构化数据。我给它定义的输出格式是一个JSON对象包含出发地、目的地、出发时间范围、返回时间范围、出行人数、预算范围、特殊偏好等字段。这里有个关键设计决策解析智能体不负责补全缺失信息只负责提取已有信息并标记缺失字段。比如用户没说预算解析结果里预算字段就是null同时会在missing_fields列表里标记出来。补全信息的动作交给后续的交互流程来处理。为什么这样设计因为信息补全涉及到和用户的多次交互如果解析智能体同时做提取和补全它的职责就太重了而且补全策略可能需要根据上下文动态调整。分开之后解析智能体可以做得非常专注准确率也更高。实测下来解析智能体最容易出错的地方是时间表达。下周这种相对时间需要结合当前日期计算三天需要明确是自然日还是工作日下午到需要转化成具体的时间范围。我的做法是在解析智能体里内置一个时间归一化模块把所有相对时间表达统一转化成绝对时间戳。这个模块我调了大概三十个测试用例才稳定下来。2.2 航班与酒店查询智能体并行执行与结果缓存航班查询和酒店查询这两个智能体在架构上是对等的它们都接收结构化的查询条件返回候选列表。我把它们设计成可以并行执行因为航班和酒店的查询互不依赖并行能显著缩短响应时间。航班查询智能体的核心逻辑是根据出发地、目的地、时间范围调用航班数据接口拿到原始结果后做过滤和排序。过滤条件包括舱位偏好、航司偏好、直飞/中转偏好等。排序策略我默认用的是时间优先但会根据用户的历史行为动态调整。如果用户之前多次选择了最低价航班排序权重就会向价格倾斜。酒店查询智能体稍微复杂一点因为酒店的选择和会议地点强相关。我的做法是让酒店查询智能体接收一个锚点位置参数这个参数由行程编排智能体根据会议安排计算得出。酒店查询智能体以锚点位置为中心按距离排序同时考虑价格、星级、评分等因素。这两个智能体都实现了结果缓存。同样的查询条件在短时间内重复出现时直接返回缓存结果避免重复调用外部接口。缓存的有效期我设的是十五分钟因为航班和酒店的价格变动相对频繁缓存太久会导致信息过时。2.3 行程编排智能体全局约束求解的核心行程编排智能体是整个系统里逻辑最复杂的部分。它要解决的问题是给定一组航班候选、一组酒店候选和一组会议安排找出一组最优组合使得所有约束都满足且总体满意度最高。我把这个问题建模成一个约束满足问题。硬约束包括航班到达时间必须早于会议开始时间、酒店入住时间必须晚于航班到达时间、酒店退房时间必须晚于最后一场会议结束时间。软约束包括总费用尽量低、中转次数尽量少、酒店距离会议地点尽量近、航班时间尽量符合用户偏好。求解策略我采用的是分层搜索。第一层先确定航班因为航班的时间约束最刚性。第二层根据航班时间确定酒店的可选范围。第三层做微调比如如果两个酒店方案得分接近就选那个离地铁站更近的。这种分层策略比全局搜索快很多虽然不保证找到全局最优解但在差旅场景下一个足够好的解比一个最优但慢的解更有价值。这里有个实操心得约束的优先级不是固定的要根据用户类型动态调整。比如高管出行时间约束的优先级最高价格约束可以放宽普通员工出行价格约束的优先级更高。我在编排智能体里加了一个用户画像模块根据历史数据自动调整约束权重。2.4 异常处理智能体兜底与自愈异常处理智能体平时处于待命状态一旦其他智能体报告异常它就会介入。常见的异常包括航班取消或延误、酒店满房、会议时间变更、预算超支等。处理策略分三类。第一类是自动重试比如航班查询接口超时直接重试即可。第二类是方案替换比如原定航班取消了自动搜索同时间段的其他航班重新走一遍编排流程。第三类是人工介入比如所有方案都超出预算这时候需要通知用户让用户决定是提高预算还是调整行程。我特别想强调的是异常处理智能体的自愈能力。它不只是被动响应还会主动监控。比如它会定期检查已预订的航班状态一旦发现异常在用户还没察觉的时候就启动备选方案搜索。这种主动式的异常处理能大幅提升用户体验。2.5 智能体之间的通信协议设计五个智能体之间通过消息总线通信消息格式我定义了一套简单的协议。每条消息包含发送者、接收者、消息类型、负载和上下文ID。上下文ID用于关联同一个差旅请求的所有消息方便追踪和调试。消息类型我定义了六种请求、响应、通知、查询、命令和事件。请求-响应用于同步调用通知用于异步告知查询用于获取状态命令用于触发动作事件用于广播状态变更。这套协议不复杂但足够覆盖所有协作场景。有个细节值得注意消息的负载格式我强制要求用JSON Schema校验。每个智能体在发送消息前先校验负载是否符合预定义的Schema。这样做的好处是接收方可以信任消息格式不需要做防御性解析。我在开发初期没做这个校验结果因为一个字段类型不匹配排查了大半天。3. 从零搭建的完整实操路径3.1 环境准备与框架选型对比搭建之前先明确技术栈。我用的开发语言是Python因为多智能体框架的生态在Python里最成熟。框架方面我对比了三个主流选择AutoGen、CrewAI和LangGraph。框架通信机制智能体定义方式适用场景我的评价AutoGen基于消息的异步通信对话式智能体之间多轮对话需要复杂协商的场景灵活但调试稍复杂CrewAI基于角色的任务分配角色目标工具声明式流程明确的协作任务上手快适合快速原型LangGraph基于图的状态机节点边显式定义流转需要精确控制流程的场景控制力强学习曲线陡我最终选了AutoGen原因是差旅场景里智能体之间需要大量协商。比如行程编排智能体可能需要和航班查询智能体来回沟通好几次才能确定最终方案。AutoGen的对话式通信机制天然适合这种场景。环境准备清单Python 3.10以上、框架最新稳定版、一个支持函数调用的LLM接口、航班和酒店数据的模拟接口我用的是本地Mock服务避免依赖外部真实接口。Mock服务我用FastAPI写的返回预置的JSON数据这样开发和测试都不受外部因素影响。3.2 定义智能体的输入输出契约在写任何业务逻辑之前先把每个智能体的输入输出契约定义清楚。这是整个项目里最重要的一步契约定好了后续开发就是填空。需求解析智能体的输入是用户自然语言字符串输出是结构化的需求对象。我定义的Schema包含这些字段departure_city、arrival_city、departure_date_range、return_date_range、travelers、budget_range、preferences、missing_fields。航班查询智能体的输入是出发地、目的地、时间范围、偏好条件输出是航班候选列表。每个航班对象包含航班号、起飞时间、到达时间、价格、舱位、中转次数、航司。酒店查询智能体的输入是锚点位置、入住日期、退房日期、偏好条件输出是酒店候选列表。每个酒店对象包含名称、位置、距离锚点的距离、价格、星级、评分、房型。行程编排智能体的输入是航班候选、酒店候选、会议安排输出是完整的行程方案。方案里包含选定的航班、选定的酒店、每日行程安排、总费用估算。异常处理智能体的输入是异常事件输出是处理动作和更新后的方案。契约定义好之后我用Pydantic做了强类型校验。每个智能体在接收消息时先用Pydantic模型解析负载解析失败直接返回错误不进入业务逻辑。这个做法帮我省了很多调试时间。3.3 逐个实现智能体并单独测试我的开发顺序是先实现需求解析智能体因为它是入口其他智能体都依赖它的输出。实现完之后我准备了五十条测试用例覆盖各种表达方式包括模糊时间、多目的地、特殊偏好等。准确率从最初的百分之六十多经过三轮迭代提升到了百分之九十以上。然后是航班和酒店查询智能体。这两个相对简单核心是接口调用和数据过滤。我重点测试了边界情况没有符合条件的航班怎么办酒店全部满房怎么办接口超时怎么办这些异常路径必须覆盖。行程编排智能体是最难调的。我一开始用暴力搜索结果候选航班和酒店稍微多一点组合数就爆炸了。后来改成分层搜索性能问题才解决。这里有个经验约束求解不要追求一步到位先做可行性筛选再做优化排序最后做微调。每一步都缩小搜索空间整体效率会高很多。异常处理智能体我放在最后实现因为它依赖其他智能体的输出格式。实现完之后我做了一轮集成测试模拟各种异常场景确认系统能正确响应。3.4 集成联调与性能优化所有智能体单独测试通过后开始集成联调。联调阶段暴露出来的问题大多和通信有关。比如消息丢失、消息重复、消息顺序错乱。这些问题在单智能体测试时不会出现只有多个智能体同时运行时才会暴露。我采取的解决措施是给每条消息加唯一ID和序列号接收方根据序列号做去重和排序。同时给消息总线加了持久化确保消息不会因为智能体重启而丢失。这些措施增加了系统的复杂度但对于差旅这种对可靠性要求高的场景是必要的投入。性能优化方面我做了三件事。第一把航班和酒店查询改成并行执行响应时间从原来的三秒多降到了一秒多。第二给查询结果加了缓存重复查询直接命中缓存。第三把行程编排的搜索深度做了限制超过一定深度就返回当前最优解避免无限搜索。4. 实测中暴露的问题与修复过程4.1 需求解析的歧义处理测试阶段遇到一个典型案例用户说帮我订下周三去某地的机票当天往返。这里当天往返就有歧义。如果会议在下午当天往返意味着晚上回来如果会议在上午当天往返可能意味着中午就要走。解析智能体最初的处理是直接标记为当天往返不做进一步推断。结果行程编排智能体拿到这个信息后只能给出一个很宽泛的时间范围导致航班选择过多排序困难。后来我改进了策略解析智能体在遇到模糊表达时不直接下结论而是生成一个澄清问题通过交互模块询问用户。比如您希望当天几点前返回这样把模糊性消除在入口阶段后续所有智能体都能受益。这个改动看起来简单但涉及到一个设计原则模糊性应该在最早可能的阶段消除而不是留给下游去猜。下游智能体没有足够的上下文来做准确判断猜错了会导致连锁错误。4.2 多约束冲突时的优先级仲裁另一个典型问题是约束冲突。用户要求预算不超过五千、必须直飞、酒店要四星级以上但实际查询下来满足这三个条件的组合根本不存在。这时候怎么办我最初的实现是直接返回无解但这显然不是好的用户体验。后来我引入了约束软化机制当硬约束无法同时满足时按优先级逐个放宽。优先级的确定有两种方式一是用户显式指定比如预算可以超一点但必须直飞二是系统根据用户画像推断比如历史数据显示该用户对时间更敏感就优先保证直飞。软化之后系统会返回一个妥协方案并明确标注哪些约束被放宽了、放宽了多少。用户可以选择接受也可以调整约束重新查询。这个机制上线后无解的情况从百分之十几降到了百分之二以下。4.3 智能体死锁与超时处理集成测试时遇到过一次死锁行程编排智能体等待航班查询智能体的响应航班查询智能体又在等待行程编排智能体提供更多参数两边互相等待整个流程卡死。排查过程比较曲折。我先看了消息日志发现两个智能体之间的消息来回了好几轮每轮都在请求对方先提供信息。根本原因是职责边界没划清楚有些参数到底该由谁提供定义模糊。修复方案是明确每个参数的唯一来源。航班查询所需的所有参数必须由需求解析智能体或行程编排智能体在首次请求时一次性提供不允许航班查询智能体反向请求参数。同时给所有消息加了超时机制超时后自动触发异常处理流程避免无限等待。这个坑给我的教训是多智能体系统里职责边界必须清晰到每个参数级别。模糊的边界在单智能体里可能只是代码混乱在多智能体里会直接导致死锁。4.4 外部接口不稳定时的降级策略Mock服务很稳定但真实的外部接口不可能永远可靠。我模拟了接口超时、返回错误数据、限流等场景测试系统的降级能力。降级策略分三级。一级降级接口超时但重试可能成功自动重试两次间隔一秒。二级降级接口持续不可用返回缓存数据如果有或预置的兜底数据。三级降级所有数据源都不可用通知用户当前无法完成查询建议稍后重试。这里有个细节降级时要记录详细的日志包括降级原因、降级级别、影响范围。这些日志对后续排查问题非常重要。我在降级逻辑里加了一个监控埋点每次降级都会上报方便统计接口的稳定性。5. 多智能体差旅助手的扩展思路5.1 接入真实数据源的注意事项目前用的是Mock数据接入真实数据源时要注意几个问题。第一是数据格式的差异不同数据源的字段命名和结构都不一样需要做适配层。第二是调用频率限制真实接口通常有QPS限制需要做限流和排队。第三是数据质量真实数据可能有缺失、错误、重复需要做清洗和校验。我的建议是先在适配层做统一的数据模型所有外部数据进来先转成内部模型再交给智能体处理。这样智能体不需要关心数据来源只关心内部模型。适配层的变化不会影响智能体逻辑。5.2 增加更多专业智能体的可能性当前五个智能体覆盖了差旅的核心流程但还有很多可以扩展的方向。比如可以增加一个费用报销智能体自动生成报销单、匹配发票、计算补贴。可以增加一个偏好学习智能体从用户的历史选择中学习偏好动态调整排序权重。还可以增加一个同行协调智能体处理多人出行的协调问题比如找到大家都方便的航班和酒店。每增加一个智能体系统的能力就增强一分但复杂度也相应增加。我的经验是不要一次性加太多先把核心流程跑通再根据实际需求逐步扩展。每加一个智能体都要重新审视通信协议和职责边界确保不会引入新的死锁或冲突。5.3 从原型到产品的差距原型和产品之间有巨大的鸿沟。原型只需要在理想条件下跑通产品需要在各种恶劣条件下稳定运行。从原型到产品至少还要做这些事完善的安全认证和权限控制、全面的日志和监控、自动化的部署和扩缩容、详细的用户文档和操作指引、以及大量的真实场景测试。我目前这个原型距离产品化还有很长的路。但作为技术验证它已经证明了多智能体架构在差旅场景下的可行性。核心的协作机制、约束求解、异常处理这些难点都已经跑通了。剩下的更多是工程化的工作。如果你也想尝试类似的项目我的建议是从最小的闭环开始。先实现需求解析和行程编排两个智能体跑通输入需求-输出方案这个最小闭环再逐步加入查询和异常处理。不要一上来就追求大而全那样很容易在细节里迷失。最后分享一个我在调试多智能体系统时的小技巧给每个智能体加一个调试模式开启后它会打印所有收发的消息和内部状态变化。这个功能在排查协作问题时特别有用能让你清楚地看到信息在智能体之间是怎么流动的、在哪里卡住的。我大概有百分之七十的问题都是靠这个调试模式定位的。