资讯详情

资讯详情

不依赖框架,手工实现旅行智能体:从意图解析到行程规划

1. 整体思路为什么我不直接套框架而是选择“手工拼装”这两年“智能体”几乎要被各类平台和框架包装成一种魔法你输入一句“帮我规划一次旅行”它就能像私人助理一样把所有事安排妥当。听上去很美但真正上手做过的人心里都清楚现成框架带来的抽象层级太高底层发生了什么基本是个黑盒。尤其在旅行规划这种“信息多、约束杂、场景碎”的领域里用通用Agent平台往往会出现三种让人抓狂的状况意图理解飘忽、信息检索不准、生成行程经不起推敲。这也是我这次决定做一个“手工实现版本”的根本原因——不求大而全只求把核心链路一条条亲手打通搞清楚智能体的每一步到底在做什么。我先给结论所谓“旅行智能体”本质上就是一个能理解用户旅行意图、能检索和筛选有效信息、能生成可执行行程方案的小型系统。你可以把它拆成四个模块意图解析、需求澄清、信息检索与筛选、行程规划与输出。这四个模块用一套编排逻辑串起来就是智能体的骨架。而我做的是不依赖任何Agent框架用最基本的编程语言工具把它们逐一手工实现。听起来朴素但恰恰因为朴素每一个环节的输入、输出、异常分支都在掌控之中排查问题的时候简直不要太爽。这个版本适合谁我建议三类人重点参考一是一直在折腾各类Agent框架、被抽象层搞得不知所措的开发者二是想给自己的旅行工具增加“智能规划”能力的独立产品作者三是对自然语言处理和流程编排感兴趣、想通过一个具体项目深入理解原理的学习者。如果你是这三类人之一这篇文章值得你花二十分钟完整看一遍因为它会从设计思路一直讲到底层实现全程没有含糊的地方。2. 核心细节拆解先搞懂旅行智能体需要哪些“器官”2.1 模块划分从“听懂话”到“办成事”旅行这个场景看起来直观但它其实是自然语言处理里最典型的“约束满足问题”。用户说“想带爸妈和孩子去一个不那么累、有海、吃的还行、预算五千以内的地方待三天”——这句话里藏着目的地偏好、出行人群特征、体力要求、饮食要求、时间跨度、预算上限六类信息。工具要做的就是在这些约束里找到一条合理路线。那手工实现该从哪些器官搭起我最终的版本划分如下意图解析器判断用户是在问攻略在要方案还是在改需求。这决定了整个系统的走向。槽位提取器从自然语言中抽取目的地、行程天数、人数、预算、偏好等结构化字段。需求澄清器当关键字段缺失或互相矛盾时主动向用户提问而不是直接给一个“通稿式”答案。信息检索器从内置知识库或开放数据源中根据槽位条件召回候选景点、餐厅、住宿、交通方案。方案构造器把候选集合编排成时间线生成逐日行程。校验与兜底器检查时间冲突、距离合理性、营业时间是否匹配发现问题时降级调整。这六个模块化结构最大的好处是我可以对每一个环节单独做测试单独改逻辑而不必牵一发动全身。比如检索器召回结果质量不够我只优化召回逻辑不用去动方案构造器。2.2 为什么手工实现比套框架更“稳”一个容易被忽视的小实验为了说服自己“手工实现”这条路走得通我做了一个小实验。我用同一个用户需求文本分别让一个通用Agent平台和一个手工搭建版本处理需求是“周四下午出发周日晚上回从某市去隔壁省的古城带老人不要爬山预算人均两千以内”。结果很有意思。通用平台给出的方案相对完整但在“周四下午出发”这个时间约束上失手了第一天的行程安排成了上午出发而手工版本因为槽位提取时把“下午”和“周四”显式绑定成了出发时间槽位方案构造器里也写死了“第一天行程开始时间不得早于用户指定出发时间”这个硬规则所以不会犯这个错误。不是说框架不好而是框架为了通用性牺牲了特定领域里的硬约束能力。旅行规划的约束体系非常具体时间、空间、体力、口味、预算五者相互缠绕通用模型能理解语义却未必能保证逻辑约束。手工实现则可以把这些约束变成显式代码规则即代码代码即底线。这个实验直接坚定了我的技术选型在这个领域里可解释的确定性逻辑远比模糊的智能重要。2.3 关键技术选型类似搭积木但每块积木要自己打磨我构建这个手工版旅行智能体时在技术选型上定了几条原则能用规则解决的绝不上模型能用小模型解决的绝不上大模型能本地解决的不依赖外部API。这不是吹嘘“纯手工”有多崇高而是实际工程里成本最低、迭代最快的方式。语言层面用Python。它处理文本和数据结构足够方便第三方库生态也全。意图解析这步直接用规则加关键词权重。旅行场景的意图类型本来就不多问攻略、求方案、改需求、闲聊四类意图用关键词匹配加打分就能达到百分之九十以上的准确率完全没必要去微调模型。槽位提取正则表达式加了时间、人数、预算、城市名四个词典。城市名词典是一份手工维护的国内热门旅游城市列表覆盖了百分之八十以上用户会提到的目的地。检索层自建了一个轻量知识库每个景点、餐厅、酒店记录里包含标签、建议游玩时间、交通耗时、适合人群、人均消费等字段。检索时用标签匹配加评分排序。规划引擎这部分是代码最重的地方——用动态规划思路来做时间线拼接。每天细分成上午、下午、晚上三个时段每个时段安排一个主要地点再加上交通时长的校验。你可能已经感觉到了这套选型整体思路很“固执”——就是不愿意引入任何黑盒环节。这样做的直接优势是整个链路上的每一环我都能一眼看穿出了问题几乎可以瞬间定位到某个模块。代价就是适配新场景时需要手工补充词典和规则但考虑到旅行领域的热门目的地数量是有限的这个代价完全可接受。3. 实操过程全记录从零到一实现一个可用版本3.1 数据结构先行一切从“统一的信息载体”开始动手写代码之前我做了整个项目里最值得的一次投入设计统一的信息结构。旅行智能体链条上的所有模块都需要互相传数据如果结构混乱后患无穷。我定义了三个核心数据结构。第一个是用户需求结构包含目的地可能为空、出行日期与时段、天数、人数区分成人老人儿童、预算、兴趣标签列表、特殊要求列表。第二个是地点信息结构包含景点或餐厅或酒店的ID、名称、类型、标签、建议游玩或停留时长、人均消费、交通耗时数组、开放时间、适合人群。第三个是行程方案结构按天分组每天包含若干时间槽每个槽位对应一个地点ID和预计耗时。其中我特别想强调的是交通耗时数组的存储方式——这是一个很容易被忽略又极其关键的设计。每个地点不能只存一个“距离市中心多远”而要存一个以“从任一地点到任一地点”为索引的二维耗时表。最初我偷懒只存了每个地点到中心点的距离结果行程编排时经常出现“上午在城东、下午跑去城西”这种荒谬方案。后来改成地点间的全连通耗时矩阵情况立刻好转。虽然数据录入时多花了不少功夫但这直接决定了行程的可执行性。这一步做完之后后面所有模块都变得顺滑许多。3.2 意图解析与槽位提取把自然语言“拧”成结构化数据这个环节是整个智能体的“耳朵”。用户留下的话五花八门有的特别零碎比如“五一去哪玩好呢不要太贵三天带娃”——顺序完全打乱有些还是口语碎片。我的目标就是从这些碎片里稳定抽出五个关键槽位目的地、日期、天数、人群、预算。具体做法是用规则管线加词典完成。比如时间槽位通过正则匹配“周[一二三四五六日天]”“下周”“月”“号”“天”“晚”这些关键字来识别预算槽位匹配“千”“以内”“人均”“不超过”这些词来提取人群槽位通过“老人”“小孩”“带娃”“爸妈”“情侣”这些词判断。目的地槽位相对麻烦因为它涉及专有名词识别我的方案是维护一个城市词典用最大正向匹配算法在用户文本里扫描。但这套方案刚跑起来时效果很差。用户说“去有海的地方”我的城市词典就完全不认识“有海的地方”这种语义表达。后来我意识到槽位提取不应该只做“抽词”还要做“推理”。我的解决方案是给每个槽位加一个“推断器”目的地槽位如果抽不到具体城市就根据“海”“山”“古城”“美食”等关键词做意图聚类再从城市标签库里反向匹配候选城市。比如“有海的地方”就会匹配标签里含“海滨”的城市生成一个候选列表再由方案构造阶段按其它约束逐步筛选。这样改了之后系统理解语义的能力一下子提升了一个档次。这里有一个踩坑经验正则和词典的正向匹配一定要做好优先级排序。比如“三天”可以匹配“三天”但不能让它匹配“周三”。最初我就是因为把“三”和“天”单独拆出来匹配导致“周三出发”被误提取成“三天”坑了自己一次后我把所有星期词条在正则结构上做了整体优先匹配问题才彻底解决。3.3 检索器即知识库手写一个“轻量级旅行百科”检索器本质上是一个带了过滤条件的地标查询引擎。知识库里每一条记录都有完整的标签体系比如“适合亲子”“适合老人”“夜景”“免票”“网红打卡”“辣味美食”“小众”等。检索时有三层过滤第一层是目的地过滤只返回目标城市内的地点。第二层是人群适配过滤比如有老人同行就排除需要长途爬山的景点有小孩就优先含“亲子”“游乐”标签的地点。第三层是预算和餐饮约束过滤把人均消费超出预算上限的地点剔除掉。但这只是初筛真正体现检索器水平的是排序策略。我使用的是加权打分的机制每个地点的基础分是它在所有用户评价中的平均分再按适配度做加法。具体来说匹配一个兴趣标签加十分匹配人群特征加十五分交通耗时低于平均值加五分有用户强推加十分。这样打分之后检索结果基本符合“合适优先、口碑随后”的直觉也比较透明分数一栏就能解释为什么这个地点排前面。为了兼顾系统的可解释性我在生成方案时会附带一句话说明“根据您提到带老人和孩子已优先安排平缓、短途的A景点步行时间控制在二十分钟以内。”这句话在手工系统里只是一条规则触发后的文本模板但用户看到之后对方案的信任度明显提升。这个细节让我意识到智能体的体验不仅取决于内容准不准还取决于它有没有把“理由”说出来。3.4 方案构造器把“零散地点”编织成“可用行程”这是整个系统里核心的模块。方案构造器做的事情是把检索出的零散地点按照时间和空间约束排列进一个行程线里最终输出早中晚三段时间的安排。我采用的方式是基于约束的贪心加回溯策略。算法大致流程把每天拆成上午九点到十二点、下午两点到五点、晚上七点到九点三个槽。按照检索打分从高到低的顺序尝试把地点放进其中一个槽里放进去之前必须检查四个条件当前排队顺序是否允许、地点是否在目标城市内、交通耗时是否合适、营业时间是否覆盖计划停留时段。如果四个条件都满足就放入否则尝试下一个时段或下一个地点。如果某一天出现“所有可选地点都已尝试但排不满”的情况就触发回溯回到上一天重新替换选择。这个流程写起来并不算特别难难在对“时间槽”的抽象定义上。最初我把一天只分成了上午、下午和晚上三个槽结果出现了上午去了一个需要三小时的景点导致下午来不及吃饭就要跑下一个点的情况。后来我改成每个槽位里加一个“可承载时长”字段例如上午槽默认三小时但如果用户出发时间晚这个槽的承载时长就缩短。所有地点都带一个建议游玩时长建议时长超过槽位承载时长的就直接不进这个槽。这一个优化直接消灭了一半的行程违和感。举个最终产物的例子。用户要求某城市三天两晚带老人出行不爬山预算人均一千五以内。我的系统最终生成的方案大致是第一天下午到达安排了一个免费的城市公园游玩两小时晚上去老字号美食街第二天上午安排博物馆室内、步行量小下午安排老街区闲逛第三天上午安排湖边漫步下午返回。整个行程没有一个大景点但处处匹配“适度游玩、悠闲不赶”的需求。用户如果要求更紧凑的安排也可以通过参数控制槽位密度来解决。3.5 输出层与可读性让人看得懂、愿意用的最后一道门方案生成以后必须要让它像“人”写出来的。这一步看着简单但我花了相当多心思。因为结构再合理的行程如果表达破碎、没有前后逻辑用户也不爱看。我设计了三段式输出结构先是一句整体概括说明总预算、总天数、节奏风格接着每一天单独成节每节按时间顺序以小列表形式展开最后附上“为什么要这样安排”的一句话说明。这个输出结构里没有一句废话每句话都对应着结构化数据里的真实字段不是模型生成的“漂亮话”。语言层面我会做一层“温度处理”。比如数据里写上“步行二十分钟”输出时就变成“距离不远慢慢走大概二十分钟”这样的表达是因为我知道很多用户的痛点其实是“看着近走起来远”。通过替换词表来调节语气和温度不涉及复杂生成逻辑但让整个输出一下子有了人情味。不得不承认市面上很多AI生成的旅行方案读起来像一篇华丽的散文但华而不实。而我这套手工版本生成的方案读起来像一份周到的朋友建议准确、克制、有依据。如果你问我哪一个更接近“智能体”的本质我会毫不犹豫选后者。4. 常见问题与排查技巧实录这些坑实测踩过4.1 常见的四类“翻车现场”与对症下药这个手工实现版本从写第一行代码到最终稳定运行中间经历了大量不如意。我把问题归纳成了四类每一类都附上了我实际采用的排查和解决方法。第一类是槽位提取不完整。最典型的是用户说了“预算合理就行”这种模糊表达我的预算槽位就提取不到任何数值。反复试了几次之后我给预算槽位增加了“默认值推断”机制如果没提取到就默认按“人均一千元”处理并在意图解析阶段追加一个澄清问题向用户确认。这类问题的排查思路是不要指望一次解析就达到完美要在解析结果里标记“置信度低的字段”然后用一轮澄清对话来兜底。第二类是排序结果跟用户直觉不符。比如用户说“想吃特色菜”系统却把人均消费非常高的网红餐厅排到了第一位因为它的基础分和兴趣标签加分实在太高。后来我调整了打分策略贴上“性价比”标签的地点在预算约束下可以获得额外加权而不是让基础分一骑绝尘。原则很简单——在约束条件内做优化而不是让全局优秀掩盖局部不合适。第三类是跨天行程的衔接断裂。第一天排得太满第二天又太空。这是因为我没有给连续两天的行程设置“疲劳累计”的控制变量。加入疲劳度模型之后系统会统计前两天步行量如果累计超过了用户群里老人或小孩的承受阈值第三天的行程就自动降低强度。虽然这个疲劳模型相当粗糙只是按年龄段设定每日步行上限但效果立竿见影。第四类是地点营业时间跟行程冲突。比如用户要求看夜景结果检索器晚上七点返回了一个六点闭园的景点。问题出在检索器只关注了地点本身的标签没有把“时间约束”作为过滤条件传入。修复也很简单就是在检索参数里增加一个“计划到访时段”字段营业时间不在该时段内的地点直接过滤掉。4.2 排查工具箱调试智能体跟调试普通程序不太一样我把这次项目里最频繁用到的三个调试方法整理出来希望能帮你省一些时间。第一固定输入法。准备一批覆盖正常情况、边界情况和异常情况的测试用例每次修改代码后都完整跑一遍观察哪些用例的输入解析和最终输出发生了非预期变化。“有老人”“带小孩”“下雨怎么办”这类用例必须常备它们才真正检验系统的鲁棒性。第二中间结果可视化。把每个模块的输出结果单独打印出来在命令行里做一次“管线巡检”。我一般会重点检查槽位提取后的JSON结构观察是否有字段丢失或类型错误再检查检索结果的前十条确认排序是否符合直觉最后看行程构造器的输出确认每天的时间槽是否排满。第三降级模拟测试。这一点很实用我可以临时把知识库里的一个热门城市数据全清空观察系统在“数据不足”的情况下会不会给出行程方案。理想的结果是触发兜底逻辑提示“该城市信息储备不足建议切换以下备选城市”而不是硬着头皮生成一个质量不达标的方案。4.3 经验教训速查表这些细节决定成败环节常见陷阱解决经验槽位提取正则优先级错误导致时间误判先匹配整体词周三、周五再匹配数字三天知识库建设只存单一地标距离忽略地点间关系构建地点间全连通耗时矩阵检索排序基础口碑分过高压制约束适配分适配度加分权重要大必要时再做二次筛选行程编排满打满算没有冗余时间每个时间槽预留三十分钟机动时间输出语言结构化数据直接堆砌缺乏温度设置语气转换词表统一润色输出我还想特别强调一点知识库的数据质量比算法本身更影响最终效果。算法再精巧如果知识库里一个景点标注错了建议游玩时长输出照样很不可靠。我后来建立了一个标注校验流程每一条新增数据都要经过“标签订阅、时长自检、人群适配”三道检查用一套规则去自动发现明显错误比如“建议游玩五小时”却只有“拍照打卡”一个标签这明显不匹配就会被自动拦截。数据质量才是这个手工系统真正的上限。5. 后续扩展方向我下一步会怎么“进化”这个智能体如果你手头也想做一个类似的版本我建议你起步时别贪多先把最基本的“意图解析、槽位提取、检索、规划、输出”跑通再一步步增加能力。我梳理了三个我自己认为最有价值的扩展方向按投入产出比排了序。方向一是增加多轮对话管理。目前这个版本只是一轮交互用户说需求、我出方案。但真实的旅行规划一定是多轮调整的比如用户看了方案后说“第二天不想走太多路想换成一个轻松点的”。要支持这种能力就得把上一次生成的方案结构保存下来下一轮输入时作为语境参考。旅行这个场景天然适合“基于上一版方案做修改”的交互方式这个功能一旦加上体验感会是质的飞跃。方向二是增加实时数据接入能力。比如天气、交通实况、景点人流量。手工版最大的优势是结构可控接入这些数据源时可以把它们作为信息检索阶段的附加过滤条件。比如检索到目的地未来三天有雨就自动排除纯户外景点安排一个室内备选。这一步从工程上完全能办到只需要在检索层前增加一个“天气预处理”的小模块。方向三是做定制化模板。把不同出行人群的场景沉淀成模板“亲子游模板”“老人慢游模板”“情侣美食模板”“独自穷游模板”。这些模板不是简单的标签集合而是带有结构化约束的规则包比如老人慢游模板里步行量上限、景点切换频率都有预设值。这样用户输入时信息稠密度不够系统也能基于模板补全需求信息生成的方案稳定性会好很多。如果让我给一句最核心的忠告我会说做这种手工智能体最大的收获不是最终跑通了哪一条流程而是你建立了对“自然语言到约束满足”这条映射链路的完整感知。它让你明白了智能体的每个能力边界到底在哪里哪些问题该交给模型哪些问题必须自己写规则兜底。这些认知在任何一个框架化、平台化的系统里都是学不到的。我个人建议每一位对Agent真正有兴趣的开发者都值得亲手从零搭一个这样的小系统。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →