ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

从脚本堆到智能任务信使:自研Agent框架设计实践

从脚本堆到智能任务信使:自研Agent框架设计实践 大概在大半年前我被一个反复出现的问题折磨到崩溃手上一堆重复性工作——整理周报数据、巡检服务状态、批量处理工单——每件事单独写脚本都不算难但脚本越堆越多互相之间没有联动改一个需求就得翻好几个文件而且每次跑完还要人工核对一遍结果。那时候各种AI Agent框架开始冒头我也跟着用过几个但始终觉得像穿了一件不合身的衣服编排逻辑不透明、工具接入方式绑定太死、出了问题根本没法定位到底是模型的问题还是框架的问题。于是在某个周末我决定不再纠结干脆自己动手写一个。这就是hermes-agent的由来。名字取自希腊神话里的信使神赫尔墨斯——在我眼里Agent的本质就是一个信使把用户的意图翻译成可执行的任务序列再把任务结果带回来。这个项目不是玩具demo它后来成了我日常工作流里的基础设施。它做的事情其实很朴素接收一个任务描述自动拆解步骤按需调用注册好的工具最后汇总结果返回。如果你也在做类似的事——想搭一个属于自己的智能任务执行框架或者正在用现成的Agent框架但总觉得哪里别扭——这篇文章应该能给你一点参考。我会把架构设计、关键代码思路、实测数据和我踩过的坑都摊开讲。1. 从脚本堆到任务信使hermes-agent到底解决什么问题1.1 我在什么场景下决定动手在动手之前我的工作流长这样定时任务用crontab挂着数据抓取用Python脚本报表生成用另一个脚本通知推送再写一个。每个脚本单独看都很清晰但放到一起就是一团乱麻。最麻烦的是跨脚本协作——比如发现磁盘使用率超过80%时抓取近一周的日志统计并生成一份报告发到群里这个需求涉及监控、日志解析、统计汇总、消息推送四个环节我用Shell脚本拼过用Python串过最后都因为维护成本太高而放弃。直到我意识到一个更本质的问题我真正需要的不是写更多的脚本而是有一个能理解任务并调度已有能力的中间层。它不需要多聪明但必须能听懂自然语言描述的目标然后自己决定用什么工具、按什么顺序去完成。hermes-agent就是奔着这个目标去的。1.2 它和现成Agent框架的差别市面上现成的Agent框架我试过几个功能都很强但我始终觉得有问题。最大的问题是抽象层级太高——你很难看清楚一次任务执行过程中每一步到底是模型在生成计划还是代码在实际执行出错了也不知道该去调模型参数还是改框架配置。其次是工具接入方式往往绑定特定生态我已有的脚本和内部API要迁进去得先做一层适配这种改造成本在项目初期是致命的。所以我给hermes-agent定了几条硬性设计原则透明每一轮思考→行动→观察的循环都有完整日志任何一步都可以回放。轻耦合核心只做任务编排工具通过统一协议注册不关心工具内部实现。可控任务可以随时中断、插入人工确认节点、限定最大执行轮数。这三条原则贯穿了后续所有设计决策。说白了我不需要一个黑盒帮我搞定一切我需要的是一个我能随时打开机箱看内部构造的、听话的执行框架。2. 整体架构拆解一个可运行的Agent由哪几块组成2.1 五个核心模块的分工hermes-agent整体上是调度核心外围模块的架构我在实际落地时把它拆成了五个部分各管一摊互不越界。模块职责关键点任务规划器把用户指令拆解为步骤清单决定做什么产出结构化步骤工具注册中心管理所有可用工具的元信息决定能做什么用JSON Schema描述执行引擎按步骤调用工具并收集结果决定怎么做含重试与超时控制记忆管理器维护会话上下文与长期记忆决定记住什么分短期与长期两层安全护栏参数校验、权限校验、敏感操作拦截决定能不能做最后一层防线这个划分并不是一拍脑袋定的。最初我只有一个规划执行的循环但实际跑下来发现没有工具注册中心规划器根本不知道该推荐什么工具给模型没有安全护栏一次错误的参数构造就可能触发生产环境的危险操作。这五个模块是逐步演化出来的不是一开始就设计好的。2.2 一次任务请求的完整流转过程一次标准的任务请求实际走的是下面这条链路接收请求用户提交自然语言任务比如检查所有测试服务的健康状态异常的把日志摘要发到运维群。意图解析与步骤规划规划器把任务拆成枚举服务列表→逐个调健康检查接口→筛选异常项→拉取异常服务的日志摘要→推送消息五个步骤。工具匹配执行引擎根据每个步骤的需求从注册中心选出匹配的工具填充参数。执行与观察调用工具拿到返回结果把结果回填到会话上下文。循环判断Agent判断任务是否完成。未完成则回到步骤2继续规划下一批动作完成则进入汇总阶段。结果汇总把所有步骤的中间结果整理成最终回复返回给用户。这个流程看起来不复杂但真正的复杂度全在步骤2和步骤5里——规划器怎么保证拆解的步骤没有遗漏循环判断怎么避免在一个死胡同里打转这些我在后面专门用一节来讲。整个核心循环我用了事件驱动的方式实现每个模块之间的通信走统一的消息结构。这样做的好处是每一步的输入输出都是可序列化的方便记录审计日志也方便在出问题的时候做回放调试。3. 三个决定体验的关键设计工具协议、任务编排与记忆管理3.1 工具注册协议把AI能用什么这件事显式化Agent能不能干好活一半取决于模型能力另一半取决于工具描述是否清晰。我在hermes-agent里设计了一套基于JSON Schema的工具描述协议每个工具注册时都要提供四样东西工具名字、一句话功能描述、参数Schema、执行函数引用。参数Schema是整个协议里最关键的。我在实践中发现模型在生成工具调用参数时如果字段描述不清晰非常容易出现幻觉——比如把日期格式从YYYY-MM-DD写成了MM-DD-YYYY或者把枚举值写成不存在的选项。所以我在每个字段的description里都会写清楚格式要求、取值范围、以及一两句示例。下面是当时注册一个查询服务日志工具的示意{ name: query_service_logs, description: 查询指定服务在指定时间范围内的日志内容返回最近的N条记录, parameters: { type: object, properties: { service_name: { type: string, description: 服务名称必须是已注册服务列表中的值, examples: [auth-service, order-service] }, start_time: { type: string, description: 起始时间格式为 YYYY-MM-DD HH:mm:ss, examples: [2025-01-01 10:00:00] }, end_time: { type: string, description: 结束时间格式为 YYYY-MM-DD HH:mm:ss必须晚于start_time }, limit: { type: integer, description: 返回日志条数上限默认50最大200, minimum: 1, maximum: 200 } }, required: [service_name, start_time, end_time] } }注册中心里存的是这份Schema的元数据而不是工具实现本身。执行的时候把Schema传给模型模型输出一个符合Schema的JSON调用再由执行引擎做一次Pydantic校验校验通过才真正调用函数。这个先校验、后执行的顺序非常关键——模型输出直接透传给业务函数是事故的根源中间必须卡一道强校验。3.2 任务编排不追求一次到位而是逐步收敛任务编排是Agent最核心的智力活动。最早我参考过一些方案试图让模型一次生成完整的执行计划然后按图索骥。但实测下来复杂任务的计划一次性生成经常出错要么漏步骤要么步骤依赖关系搞错。后来我改成渐进式编排——每轮只让模型从当前状态出发生成下一步要做什么最多生成一个包含多个并行动作的动作集。这个改动的效果非常明显。原因在于模型的上下文里只能看见当前这一步的结果而计划中后续步骤的内容其实是盲猜。渐进式编排相当于把决策窗口缩短每一步都建立在最新观察的基础上。为了控制轮数我给执行引擎设置了一个max_rounds参数默认10轮超过就强行终止并返回任务未完成已执行的部分结果。这能有效防止Agent在一个复杂任务上无限发散。另外我加入了动作依赖校验。举个例子如果模型决定先查询磁盘使用率再根据结果决定是否清理日志这两个动作之间就存在依赖关系。执行引擎会检查当前动作的输入是否依赖前序动作的输出如果依赖但前序还没执行就强制调整顺序或要求模型重新规划。这一步没有用复杂的图算法只做了一层简单的依赖检查但在实际场景中拦住了不少低级错误。3.3 记忆管理短期靠会话长期靠外置存储Agent在任务执行中会积累大量中间信息——工具的返回结果、模型自己的推理过程、用户临时补充的约束。如果全塞进上下文很快就把模型窗口撑爆。我采用了两层记忆架构。短期的会话记忆保存的是最近几轮思考→行动→观察记录直接放在上下文里供模型参考。为控制长度我会做分层摘要每轮结束后生成一个不超过50字的本轮摘要当上下文里的完整记录超过阈值时就把最早几轮替换成它们的摘要。长期的跨会话记忆我用的是向量数据库。每次任务结束后把任务描述最终结果关键工具调用链写入向量库并打上标签。下次遇到相似任务时检索历史记录作为参考示例拼进Prompt。这个机制在报表生成这类高频重复任务上的效果尤其明显——Agent会记住上次的数据口径和格式偏好不需要每次重新解释。关于技术选型向量存储我选了pgvector原因是项目本来就用PostgreSQL加一个扩展就能用少维护一套组件。如果你的项目还没引入PG用单独的向量数据库也可以关键是要和现有业务数据在一起还是分离这个没有标准答案取决于你后续打算怎么用这些记忆。4. 真实场景实测自动化报表、服务巡检与文档问答4.1 场景一自动生成周度数据报表我把每周五下午5点自动生成当周订单统计报表并发到工作群整条链路交给了hermes-agent。这个任务涉及四个工具查订单库、做聚合统计、渲染图表、发送消息。第一次完整跑通花了大概三周调试主要是在统计口径上反复调整。比如某个字段在库里是城市名称但报表需要的是大区维度模型不理解这个映射关系经常直接透传。后来我加了一个数据字典工具让模型在聚合前先查一次字段口径说明问题就解决了。跑稳之后的实际数据是平均每轮任务耗时约42秒其中大部分时间花在查询和渲染上模型规划耗时只占不到5秒。成功率方面连续跑了两个月自动执行成功率在88%左右剩余12%主要是数据源临时抖动和表格格式异常这些情况Agent会主动标记为需要人工介入而不是硬着头皮报错。4.2 场景二多服务健康巡检与异常通知这个场景更偏实时性。原来我用脚本定时轮询十几个服务的健康接口有问题就往群里丢一条告警但告警内容很干瘪经常还要人工去翻日志才能判断原因。接入hermes-agent后我给它配了健康检查、日志查询、指标查询、消息推送几个工具并在Prompt里明确了处理策略先看健康状态异常则查最近30分钟的错误日志再结合错误率指标给出原因判断。这里有个人工确认节点如果Agent判断是疑似配置变更导致的异常会先推送一条待确认消息等负责人回复确认后才执行后续操作。这避免了很多误报警带来的噪音。实际运行三个多月发现异常的召回率能做到90%以上而且因为Agent每次都会附上日志摘要和初步判断值班人员处理告警的时间明显缩短了。4.3 场景三内部文档问答第三个场景比较常规——把公司的技术文档、运维手册、历史故障记录喂给Agent做问答。这里用的就是前面说的长期记忆每篇文档先切片写入向量库问答时检索相关片段作为上下文。这个场景本身不复杂但有一个坑文档里的命令示例和配置文件片段经常被模型原样照搬而这些内容未必适用于当前环境。我后来在安全护栏里加了一条规则涉及执行命令的回答必须附上建议先在测试环境验证的提示并且在工具注册层面禁止Agent在问答场景下直接执行任何命令。这个限制让问答场景的安全风险降到了可接受的范围。从实测数据看文档问答的准确率比直接问大模型高不少原因很朴素——有检索兜底模型不需要靠记忆猜答案编造的概率就低了。但还是要提醒一句检索质量决定了回答质量切片粒度和检索Top K要做几次实验才能找到平衡不能拿默认参数直接上生产。5. 踩坑实录Agent落地中我遇到最多的五个问题5.1 工具参数幻觉模型编造了不存在的参数值这个问题是我的头号困扰。有一次Agent要查询一个订单号在参数里填了一个看起来格式正确但实际不存在的编号导致查询返回空结果而Agent居然据此得出结论该订单不存在。这类幻觉很难靠改Prompt完全根除我最终的方案是三重校验参数Schema强校验、工具返回结果语义校验、以及执行关键查询前要求模型先说明数据来源。第三点最有效——强迫模型在填写参数时给出依据幻觉率能下降不少。5.2 死循环Agent在同一个错误上反复打转典型场景是Agent调用工具失败错误信息是权限不足但它不调整策略而是用同样的参数再调用一次反复失败直到触发轮数上限。我排查后发现问题根源在于上下文里没有对已经尝试过且失败的动作做记忆标记。修复方式很直接在会话记忆里维护一张failed_actions表记录某个动作在哪些条件下尝试失败过。每轮规划前把这张表注入上下文并提示模型以下动作已确认失败请勿重复建议更换参数或换一个工具。加上这个机制后同类循环基本绝迹。类似的还有重复输出相同推理的自转问题也是靠标记中断解决的。5.3 上下文爆炸会话一轮比一轮慢跑长任务时每一轮的完整对话历史都丢给模型导致token数线性增长响应越来越慢费用也越来越高。我上面提到的分层摘要是解决方案的一部分但还有另一个容易被忽略的点——工具返回的大块数据不宜全文保留在上下文里。比如日志查询工具返回了200行日志实际对后续决策有用的可能只是其中的错误关键字和出现次数。我在执行引擎里加了一层结果压缩每个工具返回时可以附带一个摘要器把大块返回内容压缩为要点式说明再进入上下文。原始数据写到本地缓存文件里Agent需要查看细节时再通过一个read_file工具去读。这是一个很笨但很实用的办法极大降低了长任务的上下文压力。5.4 并行调用的竞态冲突为了让Agent执行得更高效执行引擎支持一轮内并行调用多个工具。最开始没有做隔离结果两个工具同时写一个配置文件后写的覆盖了先写的导致数据丢失。定位这个问题花了很长时间因为错误不是必现的要靠审计日志里的时间戳才还原出竞态现场。后续处理方案是给工具注册协议增加了concurrency_safe字段标记为False的工具不会和其他工具并行执行必须串行。同时涉及文件写入的动作默认进入串行队列。这个改动牺牲了一点点效率换来的是确定性——对任务型Agent来说确定性远比那几秒的并发收益重要。5.5 调试黑盒不知道Agent内部在想什么早期hermes-agent的调试体验很痛苦任务跑挂了只能看到最终报错完全不知道Agent中间经历了什么。后来我下了狠心给执行引擎加了完整的追踪日志每一轮循环记录以下内容模型生成的计划、工具调用的输入输出、校验结果、上下文摘要的变化。日志格式是结构化的JSON一行一轮配合一个简单的回放脚本可以按时间顺序逐条回放一次任务的完整过程。这套追踪系统成了我后续所有调优的基础。没有它我上面说的几个问题根本无从定位。所以我的建议是任何Agent框架追踪日志功能必须从第一天开始就设计进去而不是等出问题了再加。这个体会是拿几个失眠的夜晚换来的。6. 经验沉淀给打算做Agent框架的人几条实在建议6.1 先把单轮调通再谈多轮编排很多人上手就做复杂的多Agent协作结果项目死在调试地狱里。我的建议是先做一个单轮单工具的最小闭环用户说一句话Agent选一个工具调用并返回结果。把这个闭环彻底跑通、日志追踪完善、异常处理稳定了再逐步加多工具、多轮规划、记忆管理。hermes-agent到现在快一年了核心循环的代码改动其实很少大量工作都花在外围能力和边界打磨上这个先后顺序是经过验证的。6.2 用评估集锁住回归Agent系统的行为有随机性今天调好了一个场景明天换个模型版本又坏了。我后来建了一个约50条用例的评估集覆盖每个场景的正常路径和典型异常路径每次改动代码或换模型后全量跑一遍对比成功率和关键输出格式是否达标。这个评估集不复杂就是一组输入-期望结果的JSON但它是防止系统悄然劣化的唯一可靠手段。6.3 人工确认节点不是累赘而是安全网我在设计hermes-agent初期倾向于让Agent全自动完成所有步骤觉得人工确认是一种倒退。直到有一次Agent误删了一组测试数据——虽然影响不大但吓出了冷汗。后来我把涉及删除、覆盖写、外发消息、付费操作的动作都默认加了确认节点。现在回头看这反而是整个系统最值得称道的设计之一Agent的定位是放大人的能力而不是替代人的判断。另外还有一个小技巧想分享给每个任务分配一个全局唯一的task_id从接收到最终返回所有日志、中间产物、工具调用记录都挂在这个ID下。排查问题的时候一个ID就能拉出整条链路比任何花哨的调试工具都管用。hermes-agent到现在还在持续演进最近的计划是加入更细粒度的权限控制让每个工具调用都能匹配到具体的角色权限而不是沿用统一的执行身份。这个方向是踩过权限越级的坑之后确定的。如果你也在做类似的Agent项目或者对某个设计细节有不同的看法欢迎交流——这种东西一个人琢磨和一群人互相补位效率差距还是很大的。
返回列表