ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI Agent技能封装实战:从工具混乱到技能沉淀的工程化之路

AI Agent技能封装实战:从工具混乱到技能沉淀的工程化之路 先交代一个背景我做AI Agent落地有一段时间了从最早给大模型套一层死板的ReAct循环到后来接Function Calling再到折腾多智能体协作踩过的坑基本能出一本书。最近把一个内部项目整理成了开源库名字就叫agent-skills。这名字听起来平平无奇但它解决的是一个特别具体、特别痛的工程问题Agent的技能Skills到底该怎么封装、怎么管理、怎么编排才能让模型真正用得上、用得稳。这几个月实测下来agent-skills帮我省掉的不是写代码的时间而是反复调试Prompt、排查工具调用失败、跟上下文超长搏斗的时间。这篇文章不聊花哨的概念就用我实际踩坑和重构的经验把这个库的设计思路、核心实现、实操步骤和排障记录完整拆给你看。如果你也在做Agent开发尤其是被“工具越来越多、模型越调越乱”折磨过那这篇文章应该能给你一些直接能用的东西。1. 整体设计与思路拆解1.1 从“堆工具”到“沉淀技能”这个转变解决了什么早期做Agent最常见的做法是把所有能力都塞进一个大列表。比如你有查天气的API、有发邮件的SDK、有读数据库的接口全部注册成Function一股脑丢给模型。表面上看模型“什么都会”实际用起来问题一大堆模型在十几个甚至几十个工具里做选择命中率断崖式下降经常挑错工具或者一个都不用。每个工具都只有一个名字加一段描述缺少“什么场景下用、怎么用、注意什么”的上下文模型只能靠猜。同一个业务逻辑散落在多个工具里比如“查订单”可能要分别调用订单接口、物流接口、退款接口模型根本没有能力把它们串起来。换一个项目复用能力时所有工具要重新写一遍描述、重新调一轮Prompt经验完全沉淀不下来。agent-skills的核心思路是把工具从“一个孤立函数”升级为“一个带完整上下文的技能包”。一个技能不只是“能做什么”它还包含触发条件、适用场景、输入输出约束、执行流程、错误处理甚至配套的Prompt片段。模型不需要在细粒度函数里做选择而是先判断“当前任务匹配哪个技能”再由技能内部去执行具体的函数调用。这就像你雇一个助理不是把螺丝刀、扳手、电钻全堆在桌上让他自己挑而是告诉他“这边有个架子需要组装你用工具箱B就行里面有全套工具和说明书”。这个转变最直接的收益有两个。第一模型的选择空间大幅缩减准确率自然上去了第二工程上实现了“技能即资产”一个技能写好并验证通过就可以在不同Agent、不同项目里反复复用不需要每次重新调Prompt。1.2 技能原子化为什么单一职责是铁律在设计技能边界时我一开始走过弯路。最早我把“客户全流程服务”做成了一个巨大的技能里面包含身份识别、订单查询、售后处理、话术生成一大堆逻辑结果模型根本不知道该先调用哪个子功能经常答非所问。后来我强制自己遵循“单一职责原则”一个技能只做一件完整的事。技能拆分的粒度参考就是“一个人类客服接到一个问题他会说‘好的我来帮您查订单’这个动作就是一个技能”。“查订单”和“查物流”必须分开“生成退款申请”和“提交退款审核”也必须分开。这样拆完技能数量虽然变多了但每个技能的职责描述非常清晰。模型面对“我的快递到哪了”这类问题时匹配到“查物流”这个技能的概率会远高于匹配到一个笼统的“客户服务”。而多个原子技能之间通过Agent的编排层串联起来就能完成更复杂的任务比如先“查订单”确认订单号再“查物流”返回轨迹最后“生成延误补偿方案”给用户一个交代。1.3 为什么我不用MCP而是自建了一套轻量定义现在市面上MCPModel Context Protocol热度很高很多人建议直接把工具包装成MCP Server。我在初期也试过但在实际项目中明显感觉到一个问题MCP更适合“跨应用、跨平台”的标准化接入比如让Claude Desktop去调用本地文件系统、GitHub、Slack它解决的是“工具怎么被模型发现和访问”的协议层问题。而agent-skills要解决的是“技能如何被设计、组织、调试和沉淀”的工程层问题。它更像是一个技能编排框架不排斥底层用MCP去执行具体工具调用但往上多了一层“技能定义、技能路由、技能内部状态管理”。如果需要我可以把一个Skill的实现代理给任意MCP Server解决了协议兼容性问题同时保留了自己对技能行为的完整控制。自建这一层还有个现实原因团队协作时需要一份人类和模型都能读懂的技能文档。MCP的tool schema偏机器可读业务人员很难参与评审。而agent-skills的技能定义用接近自然语言的描述写清“什么时候用、怎么用、边界在哪”产品经理、测试、运营都能看懂评审效率提升不止一个量级。2. 核心细节解析与实操要点2.1 技能描述怎么写模型才真正听得懂这是整个库里最不起眼但最重要的一件事。技能描述写不好后面全白搭。我踩过的最典型的坑是“描述写得像功能说明书”比如“查询订单状态并返回物流信息”模型看似能懂但遇到“用户说货还没到”这类口语化表达时匹配率很低。后来我总结出一套“三段式描述法”每个技能描述必须包含触发条件什么样的问题、什么样的上下文、用户提到哪些关键词时应该调用这个技能。要写具体比如“当用户询问包裹当前位置、快递到达时间、物流异常如滞留、派送失败时”。执行说明技能会做什么、需要哪些前置信息比如订单号、手机号、输出给用户的形式是什么。边界与禁忌什么情况下不要调用这个技能比如“仅用于查询已支付订单未支付订单请调用支付引导技能”。这里还有一个经验描述里尽量包含用户可能会用的口语化表达。比如“查快递”技能的描述里就显式写上“用户可能说‘我的货呢’‘东西到哪了’‘快递咋还不来’”实测覆盖率提升非常明显。模型是概率匹配你给它越多的样例表达它越容易在真实对话中想起这个技能。注意技能描述不要超过300个汉字。实测超过这个长度模型在长上下文中容易出现关注点漂移反而不利于准确匹配。2.2 参数Schema的约束多严格都不为过技能的参数定义直接决定模型生成参数的稳定性。我见过很多人在写工具参数时只给一个类型比如order_id: string然后祈祷模型能传对。现实是模型经常把“手机号”填到“订单号”字段里或者把“深圳市”传给一个要求ISO国家码的参数。在agent-skills里我给每个参数域都做了强约束包括详细的自然语言描述说明这个参数是什么、从哪里获取、取值范围是什么。枚举值列表能枚举的绝不放任自由输入。必填/选填标记以及“在没有拿到这个参数时应该怎么办”的默认策略。参数之间的依赖关系描述比如“如果传了refund_typefull则reason必须填”。这些约束写起来繁琐但能极大降低模型幻觉参数的概率。实际操作中我发现一个规律参数描述里写清“这个值来自上一步的哪个字段”时多步任务中参数传递的出错率会下降一半以上。比如“订单号来自‘查订单’技能的返回值order_info.order_id”模型就知道要从前文结果里取而不是自己编一个。2.3 技能内部的三段式执行逻辑一个好的技能内部逻辑不能只是“调个API然后返回结果”。我在设计agent-skills时把每个技能的执行体定义成三段前置检查、核心执行、结果规整。前置检查负责确认“当前状态是否满足执行条件”。比如“发送验证码”技能前置检查就要确认上一次请求时间距现在是否超过60秒“生成退款单”技能前置检查要看订单状态是否允许退款。这些规则如果不在代码里显式拦截靠模型自觉基本等于没有总会在某个深夜出生产事故。核心执行就是正常的业务逻辑调接口、查数据库、拼参数。这部分相对简单但要注意一个原则技能内部的实现要对Agent调度层保持黑盒Agent只关心“传什么入参、拿什么结果”不关心内部调了几个API。这样技能才具备可替换性今天用A厂商的API明天换B厂商的Agent层完全无感知。结果规整是很多人忽略的环节。模型拿到{code: 0, data: {waybill: [SF123..., SF456...]}}这种一次性JSON时通常不会“翻译”给用户。所以在技能内部我会把结果处理成最终可读的文本片段比如“您的包裹已从深圳发出当前正在前往杭州中转场预计明天18:00前送达。运单号SF123...”。这样做有两个好处一是模型拿到结果后几乎不需要额外加工直接输出给用户即可省一层幻觉风险二是如果结果需要格式化比如金额保留两位小数、时间转本地时区可以在这一步统一处理不用依赖模型的临场发挥。2.4 记忆与上下文管理技能怎么记住之前聊过什么Agent应用中上下文管理是最容易爆的雷之一。agent-skills的方案是内置了一个轻量的工作记忆机制。每个技能在被调用时会接收一个结构化的“会话上下文对象”里面包含用户身份、最近的几轮对话摘要、当前任务相关的中间结果。技能执行完成后会把自己的关键输出写入记忆区供后续其他技能读取。这个机制直接解决了一个高频问题多技能协作时的参数接力。比如用户先说“帮我查一下上周买的那个手机订单”Agent先调用“识别用户与订单”技能拿到订单号然后下一步“查物流”技能直接从记忆中读取这个订单号不需要用户在对话里重复也不需要模型靠概率去“回忆”前文里出现过什么数字。内存管理的核心是“按需存取”。我不建议把整个对话历史原封不动传给每个技能。更稳妥的做法是摘要化把长对话实时压缩成“用户目标、已确认信息、待办动作”三条结构化数据技能执行时只读这三条既省token又减少干扰。3. 实操过程与核心环节实现3.1 从零搭建一个技能库以“网页内容总结”为例我用一个最简单的例子走一遍完整流程创建一个技能作用是把输入的一段网页文本进行摘要总结并用结构化方式返回要点。第一步定义技能的元信息。在agent-skills目录下新建一个web_summarizer文件夹里面创建skill.json{ name: web_summarizer, description: 对网页正文进行智能摘要提取核心观点、关键数据与结论。当用户提供网页文本并要求整理要点、提炼结论时使用。, triggers: [总结, 摘要, 提炼要点, 太长不看, 提取核心观点], input_schema: { type: object, properties: { text: { type: string, description: 网页正文纯文本内容应去除HTML标签和导航信息 }, max_bullets: { type: integer, description: 最多提取的要点数量默认5, enum: [3, 5, 8] }, language: { type: string, description: 输出语言默认从原文语言自动判断, enum: [auto, zh, en] } }, required: [text] } }第二步实现执行体。这里我用一个简单的Python函数内部结构严格按照前置检查、核心执行、结果规整三段来写。def execute(inputs: dict, context: dict) - dict: # 前置检查文本长度合规 text inputs.get(text, ).strip() if len(text) 100: return {status: error, message: 文本太短无法提取有效摘要} if len(text) 30000: text text[:30000] # 核心执行调用LLM做摘要 bullets summarize_with_llm(text, max_bulletsinputs.get(max_bullets, 5)) # 结果规整拼装成可直接返回给用户的文本 formatted 为您提炼了{}个核心要点\n.format(len(bullets)) for i, b in enumerate(bullets, 1): formatted {}. {}\n.format(i, b) return {status: success, summary: bullets, formatted: formatted}第三步注册技能到技能库并写一段自测from agent_skills import SkillRegistry registry SkillRegistry() registry.register(web_summarizer, skill_jsonweb_summarizer/skill.json, executorexecute) # 自测 result registry.invoke(web_summarizer, { text: 这里是某科技媒体的一篇长文讲述多模态大模型的最新进展..., max_bullets: 3 }) print(result[formatted])到这里一个最小可用的技能就落地了。虽然例子简单但整套流程中“模板、代码、自测”三步缺一不可后续所有技能都应该按这个模板批量生产。3.2 Agent调度层如何决定“该用哪个技能”技能库建好了接下来最关键的问题是怎么让Agent在合适的时机选中并调用合适的技能。我采用的是“路由验证”的双层机制。路由层核心是一个分类Prompt把所有技能的名称、触发条件、一句话能力说明传给LLM要求它从用户当前输入中判断“是否有需要调用技能的意图”并返回技能名称和入参。这里有个设计细节我不会把技能的完整描述发给模型做路由只发一句话摘要。因为完整描述加起来太长会稀释分类器的注意力导致选错技能。完整的描述是在技能选中之后才进入上下文供模型复用。验证层是路由之后的一道保险。技能执行前会检查入参是否完整、是否满足前置约束不满足就直接拒绝并返回“缺什么参数”而不是让模型硬着头皮去猜。这层拦截能过滤掉大量无意义的错误调用生产环境中特别管用。比如用户说“帮我退个货”但还没提供订单号验证层会直接返回“需要您提供订单号或下单手机号”模型拿到这个反馈后自然知道下一步该问什么。实现上调度层的关键代码如下def plan(user_input: str, available_skills: list) - SkillCall: skill_index \n.join( [- {}: {}.format(s.name, s.trigger_hint) for s in available_skills] ) route_prompt 根据用户输入选择合适的技能。 可用技能如下 {skills} 用户输入{input} 请直接输出JSON{{skill: 技能名, arguments: {{...}}}} 如果不需要调用任何技能输出{{skill: none}} # 调用LLM获取路由结果随后进入验证层 ...建议路由层使用温度调到0的模型参数尽可能让选择结果确定化。实测温度大于0.3时技能选择的结果偶发性会让用户震惊。3.3 多技能编排如何不把流程写死在代码里技能化之后很容易陷入另一个极端把“查订单→查物流→生成补偿”这种编排流程硬编码在Agent代码里。这确实能用但一旦流程变化就要改代码发版敏捷不起来。我目前在agent-skills里实现了一套“声明式编排”方式。在技能定义中显式声明“该技能执行完成后可能接着使用哪个技能”形成技能间的候选链路。Agent在执行完一个技能后会参考这些候选链路决定下一步动作但不会被写死。比如“查物流”技能执行完后技能元信息里声明next_candidates: [生成延误补偿, 推送异常提醒, 结束对话]模型会根据当前上下文和用户情绪选择是否继续调用。如果用户只是单纯问进度模型可能就选择结束对话如果用户表达不满模型自然会走向“生成延误补偿”。这套机制的工程价值在于流程的维护从“改代码”变成了“改配置”产品同学都能上手调。而且因为每个技能的边界稳定调整链路时不需要回到模型Prompt里做“手术式”修改风险大幅度降低。4. 常见问题与排查技巧实录4.1 模型就是不调用任何技能怎么办这是上线初期最头疼的问题。用户在对话里明明说“帮我查一下快递”但Agent就硬聊不触发技能。我排查这个问题的顺序基本是固定的先看路由层Prompt里技能摘要有没有写清楚触发条件。很多时候是摘要写得过于模糊比如只写“查询物流信息”而用户表达是“我的货咋还不来”模型匹配不上。把口语化触发词补充进摘要后问题立刻解决。再看温度参数。有些场景会为了“回答多样性”把温度调高结果模型在“调工具”和“自由发挥”之间选择了自由发挥。技能路由场景强烈建议温度设为0。最后看是否给模型设置了过强的系统指令比如“你是友好的客服”这类人格化提示。人格化越强模型越倾向于自己回答而不是把任务托管给技能。适当弱化人格强调“遇到具体查询任务时必须调用技能”能明显改善这个问题。4.2 技能被调用了但传入的参数乱七八糟参数问题比不调用还要隐蔽。体现在日志上就是技能已经执行但返回的全是“参数缺失”或“数据找不到”。这个问题的高频原因是参数Schema描述不清模型不知道该填什么。最有效的修复方案是给每个参数补上“来源提示”。在参数描述里写清楚这个值应该从哪个会话上下文节点取或者从前序技能的哪个输出字段取。比如order_id: { type: string, description: 订单号优先取对话历史中用户提供的数字串其次取用户身份绑定账号的最近订单号 }这比只写“订单号”管用得多。另外如果某个参数经常传错可以考虑把它从必填改成选填并在技能内部增加“自动推断逻辑”。比如“查物流”技能不一定非要用户手动传订单号技能内部可以自动关联用户手机号去拉取最近订单这样既省了用户输入又减少模型传参出错的机会。4.3 上下文太长技能调用响应越来越慢技能调用次数多了之后对话历史越长模型的响应就越慢甚至直接超时。这个问题在高频客服场景特别明显用户一个问题能聊半个小时历史累计上万字。我的解决思路是做“历史摘要分页”。每次技能执行完后只保留两部分内容技能执行的动作和结果摘要以及用户最新的原始意图。其余历史全部压缩成一段全局摘要放在系统消息里。技能执行时只接收“全局摘要当前轮消息必要的中间结果”而不是累计对话原文。实测下来这种方式能把上下文token占用降低60%以上响应速度提升明显而且因为模型接收到的是提炼过的摘要注意力更集中技能选择准确率反而提升了。代价是摘要本身需要消耗一部分token但对于长会话场景收益远大于成本。4.4 技能执行成功了但模型给用户的回答文不对题这种情况最气人技能返回了正确的物流轨迹结果模型跟用户说“您的订单已退款成功”。这类“事实漂移”问题是LLM应用里最难根治的根因是模型在组织自然语言回复时非要自作主张补充一些技能返回里不存在的信息。我的应对是“回答模板约束”。技能执行完成后会在上下文中写入一段带修改指令的提示模板明确告诉模型“当技能返回内容可以直接回答用户时不得额外推断当需要补充说明时必须先引用技能返回的原文再给出解释。”再配合温度调低这类问题出现的频率能降到可接受的范围。如果对某些高风险领域涉及金额、售后承诺、医疗建议我会直接绕开模型组织语言由技能返回固定的回复文案。例如“退款成功通知”就是一段模板文案加几个参数变量模型只负责把参数填进模板没有自由发挥的空间。牺牲一点多样性换取安全性和确定性在ToB场景是完全值得的。4.5 技能间的依赖冲突一个技能改坏了另一个技能随着技能数量增多还会遇到一个工程上很头疼的问题——技能A的修复影响了技能B的表现。比如为了提升“查物流”的准确率我调整了它的触发条件结果“查售后”技能也跟着触发两个技能在Agent调度层里打了起来。解决办法是把技能测试做成自动化回归。我给每个技能都配了一份“技能验收用例集”里面包含50条以上标注了预期行为的相关语料。每次有技能变更就跑一遍全量用例集看每个技能的命中率有没有跌出阈值。这套回归测试机制看着笨重但在技能数量超过20个以后是唯一能让人安心睡觉的方式。没有回归测试的时候“改一个技能崩三个场景”的惨案几乎每周上演上了回归之后这类问题基本能被扼杀在提交前。写在最后的一点经验如果你准备把Agent从demo推到生产我的建议是先别急着上复杂的框架用一个周末把现有工具重新按“技能”的方式梳理一遍提炼出真正被反复使用的核心能力用agent-skills这类轻量方案管起来。我一开始也是从两三个技能开始跑跑顺了以后自然会发现哪些边界需要调整哪些参数约束是刚需然后在真实流量的锤炼下逐步补全。另外技能库这东西维护成本不在写代码而在持续观察真实用户到底怎么和Agent说话。那些让你拍大腿的口语化触发词永远来自线上日志不来自产品评审会。定期翻一翻Agent的实际调用记录把模型犯过的错翻译成更好的技能描述这比调任何超参数都更能提升体验。
返回列表