☰
Agent技能封装全指南:从工具调用到可复用能力设计
2026/9/26 19:02:09 网站建设 项目流程

在 LLM 应用落地过程中,我一直有个很深的感受:做一个能跑通的 Agent 不难,难的是让 Agent 的每一项能力都能被稳定复用、被团队协作维护、被业务场景自由组合。如果你也在折腾 agent-skills,大概率已经经历过这种场景——某个功能明明写好了,换一个 Agent 框架、换一个业务场景,就得重来一遍。这篇内容我会把围绕 agent-skills 的整套玩法拆开揉碎,从概念定义到落地方案,再到我实际踩过的坑,一次性讲清楚。

这套思路适合正在做 AI 应用、智能体开发的工程师,也适合想把自己领域里的经验沉淀成“可被 LLM 调用能力”的产品经理和技术负责人。它不是某个框架的官方文档,而是从实践里长出来的经验总结。

1. agent-skills 到底是什么:先搞清楚定义

我第一次听到“agent-skills”这个概念时,第一反应是“这不就是 functions calling 换个皮吗”。但真正用起来才发现,如果只是把它当函数封装,后面的维护成本会非常高。所以先把这个概念对齐了,后面所有操作才有意义。

1.1 技能与工具、插件的边界

现在市面上的术语很乱:工具(tools)、插件(plugins)、技能(skills),很多人混着用。我自己的理解是这样的:

工具是最底层的形式,通常就是一个函数,接收参数、返回结果,Agent 通过 function calling 机制来调用它。工具的粒度通常比较小,比如“获取天气”“计算两数之和”“查数据库”。

插件是工具的集合,围绕某一个平台或产品形态做了适配,比如 ChatGPT Plugin 这种,本质上是把一组工具包成一个可以在特定运行时里加载的单元。

技能(skill)则更偏“能力封装”。它不一定只是一个函数,而是一整套“什么时候用、怎么用、输入什么、输出什么、内部怎么处理”的完整定义。一个技能可以包含多个工具、可以有自己的提示词模板、可以有内部的决策逻辑,甚至可以对 LLM 进行约束和引导。

我用一个生活化类比帮助理解:工具是一颗螺丝钉,插件是一盒带说明书的标准螺丝套装,而技能是一个“会拧螺丝的人”——它不仅知道螺丝怎么拧,还知道什么场景该用多大的力、拧到什么程度、拧完怎么检查。

1.2 为什么技能是代理能力复用的最小单元

早期我直接把所有业务能力都注册成几十个 function 塞给 Agent,结果很快发现几个痛点:

一是上下文爆炸。function 的 description 和 schema 都要占 token,几十个 function 塞进去,光定义就有几万个 token,很多还是用不上的。

二是选择和调度困难。LLM 面对几十个平铺的函数,经常出现选错函数、参数乱传的问题。函数越多,准确率越低,这是实测的结论。

三是复用性极差。同一个业务能力,在这个 Agent 里这么写,换个 Agent 又要重新定义一遍,团队协作时更是灾难。

技能的概念就是为了解决这些问题。它强调的是“把一个领域的完整能力打包”,对外只暴露一个经过设计的、语义清晰的入口,内部怎么组织不关你的事。而且技能本身可以分层——底层技能可以被上层技能调用,形成一个能力的嵌套结构,这比平铺一堆工具要优雅得多。

2. 设计一个技能之前,先把这几个问题想明白

技能设计是最容易忽视、也最影响长期效果的一环。很多人上来就写代码,写到一半发现技能跟实际需求对不上,或者 Agent 老是不按预期调用,问题根源基本都在设计阶段。

2.1 技能的职责边界如何划分

划分职责边界是个典型的“看着简单做起来难”的活。我现在的判断标准就三条:

第一条,一个技能只解决一个问题。比如“处理订单”就不是一个好技能,因为订单处理涉及创建、查询、退款、修改等多个动作,这不是一个技能,是一组技能。好的划分是“创建订单”“查询订单状态”“处理退款”,每个技能职责单一。

第二条,技能的边界跟着业务概念走,而不是跟着代码实现走。我之前把一个“发送通知”的技能拆成了“发邮件”“发短信”“发站内信”三个技能,因为底层调用不同服务,结果 Agent 经常要连续调三个技能才能完成一次通知任务。后来合并成一个“发送通知”技能,内部根据参数判断走哪个渠道,效果立刻好很多。因为对 LLM 来说,“通知用户”是一个业务概念,拆分反而制造了理解障碍。

第三条,技能之间不要互相依赖对方内部细节。技能 A 需要技能 B 的输出时,应该通过标准化的返回结构来衔接,而不是 A 里去读 B 的状态。一个技能的修改不应该导致另一个技能不可用,这是我们团队的一条硬规矩。

2.2 命名与描述的隐藏价值

如果你以为技能名和描述只是方便人看的,那就大错特错了。Agent 选择技能的唯一依据就是“技能名 + 描述 + 参数 schema”这三件事,它们本质上决定了 LLM 能不能在正确的场景选中正确的技能。

技能名要短、准、动词开头。比如“获取天气信息”“提交请假申请”“计算运费价格”,这种名字 LLM 一看就懂。千万不要用“工具A”“func_001”这种名字,也不要在一个名字里塞两个以上动作。

描述是所有字段里最关键的。我总结了一个模板:

此技能用于【在什么场景下/当用户想要做什么事】时使用。 当【具体哪种情况】时不要使用本技能,请优先考虑【另一个技能】。 输入参数说明:【参数1】是xxx,可选值为【枚举】,默认值【xx】;【参数2】必须符合【格式要求】。 输出说明:返回【什么格式】,包含【哪些关键字段】。

这个模板看起来啰嗦,但实测能明显提高技能选中的准确率。LLM 天然对语义敏感,你给它越多关于“什么时候该用”的信息,它就越不容易选错。

有个细节值得单独拿出来强调:描述里一定要写负面场景,也就是“什么时候不要调用我”。我见过太多失败的案例,就是因为 Agent 在模棱两可的情况下选择了一个看似相关、实际错位的技能。你告诉 LLM 这个技能不适合哪些情况,比只告诉它适合哪些情况更重要。

2.3 参数设计的三种模式

参数是技能与 LLM 交互的接口,设计不好就会出现“LLM 传了参数但你接不住”的尴尬局面。我总结了三种常见模式:

模式一:扁平参数。适合简单场景,几个业务字段直接平铺,例如查询天气需要 city、date 两个参数。这种模式解析简单、错误率低,但字段一多就失控。

模式二:嵌套结构。适合复杂业务对象,用一个 JSON 对象承载多个子字段,如创建订单时传入 items 数组,每个元素包含商品 ID、数量、单价。这种模式对 LLM 的理解能力要求更高,但表达能力更强。

模式三:原材料模式。不直接结构化参数,而是让 LLM 传入原始文本或文件,技能内部自己解析。比如“解析简历”技能,入参就直接传简历文件路径或文本内容,内部调用解析逻辑。这种模式降低了对 LLM 参数规整能力的要求,把复杂度收口到技能内部。

三种模式没有绝对优劣,核心原则是:能用扁平参数解决的就不要用嵌套,能用结构化解决的就不要用原材料。参数结构越复杂,LLM 出错概率越高,这直接跟 token 消耗和使用体验挂钩。

3. 从零实现一个技能:完整实操

光讲概念没用,我直接带你跑一遍完整实现。这里我用一个“请假申请审批”的技能作为例子,它既有表单结构、又有内部状态流转,非常适合演示技能设计的完整思路。

3.1 技能骨架与项目结构

我现在倾向于用一个标准目录结构来组织技能,做到“一个技能一个目录”,方便版本管理和复用:

leave_request_skill/ ├── skill.yaml # 技能元数据:名称、描述、参数schema ├── prompts/ │ ├── system.md # 技能内部的系统提示词 │ └── fewshots.md # 示例,帮助LLM理解输入输出 ├── actions/ │ ├── submit.py # 提交请假申请 │ ├── approve.py # 审批通过/拒绝 │ └── query.py # 查询请假状态 ├── utils/ │ ├── validation.py # 参数校验逻辑 │ └── storage.py # 数据存取封装 └── main.py # 技能入口,统一调度

这个结构的核心思路是把“对外接口”(skill.yaml)和“内部实现”(actions、utils)完全分离。外部 Agent 只看到 skill.yaml 里的描述和参数 schema,内部怎么实现都可以替换,不影响调用方。

skill.yaml 是一个技能的身份文件,长这样:

name: submit_leave_request description: | 当用户想要提交请假申请、填写请假单、申请休假时使用此技能。 当用户只是想查询请假规则或审批进度时,不要使用本技能,请使用query_leave_request。 参数start_date和end_date必须为YYYY-MM-DD格式,请假天数不得超过30天。 version: 1.0.0 parameters: type: object properties: applicant_name: type: string description: 申请人姓名 start_date: type: string description: 请假开始日期 end_date: type: string description: 请假结束日期 leave_type: type: string enum: [annual, sick, personal, marriage] description: 请假类型 reason: type: string description: 请假原因,必须填写 required: [applicant_name, start_date, end_date, leave_type, reason]

看起来简单,但这里面的细节都是踩过坑才补上的。比如“请假天数不得超过30天”这条,就是因为我遇到过 LLM 一次给用户批了半年的假期。参数设计上,所有用户可见字段都做了必要的约束说明,LLM 在提取参数时就会更谨慎。

3.2 核心逻辑实现与细节

技能入口是整个技能的调度中心,所有请求先进 main.py,再做参数校验、业务分发、结果返回。我把 main.py 做成了一个“薄入口 + 强校验”的模式:

import json from typing import Any, Dict from utils.validation import validate_params from actions import submit, approve, query def handle(action: str, params: Dict[str, Any], context: Dict[str, Any] = None): """技能统一入口:根据action分发到具体处理逻辑""" # 1. 统一校验参数,拿不到合法参数直接拒绝 errors = validate_params(action, params) if errors: return { "status": "error", "errors": errors, "hint": "请提供完整的申请信息,包括姓名、请假起止日期、请假类型和原因。" } # 2. 根据动作分发 if action == "submit": result = submit.run(params, context) elif action == "approve": result = approve.run(params, context) elif action == "query": result = query.run(params) else: return {"status": "error", "errors": f"Unknown action: {action}"} # 3. 统一包装返回结果,保证结构一致 return { "status": "success" if result.get("success") else "error", "data": result.get("data"), "message": result.get("message", "") }

这套实现里有几个细节值得说道说道。

第一,入口函数接收一个action参数,用来区分同技能下的不同操作。这是技能内部做子功能拆分的关键手法——对外是一个“提交请假申请”技能,对内可以细分为 submit、approve、query 三个动作,供内部调度或上层技能调用。

第二,params和context分开了。params是用户侧的意图参数,context是系统侧的运行上下文(如用户身份、租户信息、权限等级)。这个区分特别重要,否则技能很容易被“越权调用”——比如一个普通员工传一个 manager=true 就把自己审批通过了。

第三,所有返回信息都要带上hint字段。这个字段是给 LLM 看的,当参数校验失败时,Agent 可以根据 hint 提示用户重新提供正确信息,而不是对着一个“参数错误”的报错无所适从。

我再展开说一个容易被忽视的细节:返回结构的稳定性。技能返回给 Agent 的数据结构必须是一套固定模板,不能今天返{"status":"ok", "data": {...}},明天又改成{"code":0, "result":{...}}。Agent 的逻辑很多时候是在“猜”你的返回格式,格式越稳定,它越能正确理解你的输出。我甚至遇到过 Agent 因为上次返回结构带了一个多余字段,下次就误以为那个字段是必填的情况。格式统一这个事,值不值得都不过分。

3.3 挂载到代理的两种方式

技能实现好之后,怎么让代理真正用上它?我实战中主要用过两种方式,各有适用场景。

方式一:直接作为函数调用注册。

如果你用的是支持 OpenAI function calling 风格的框架(LangChain、AutoGen、各种开源 Agent 框架基本都支持),可以把这个技能的入口函数直接注册成一个 tool。这种方式的好处是快速、直观,适合技能数量不多的场景。缺点是技能一旦增多,description 和 schema 全量塞进上下文,token 开销和选择准确率都会受影响。

注册时有一个小技巧:把 yaml 里的 description 和 parameters 直接作为 tool 的 description 和 parameters,不要自己再重新写一遍。这样保证 Agent 看到的信息和技能作者设计的信息完全一致,避免信息损耗。

方式二:通过技能路由器(Skill Router)挂载。

当技能数量超过十几二十个之后,我强烈建议引入一个路由器层。思路是:先注册一个“技能路由”函数,它的描述是“当用户有某类需求时,从技能列表中选择一个最合适的技能,返回技能名称”,然后路由器内部维护一个技能清单,用更轻量的方式(比如先做一次关键词/向量检索)缩小候选集,再把缩小后的候选技能定义给 LLM,让它在其中精确选择。

我用一个生活化类比说明这个做法的好处:如果一间办公室有 100 个人,你要找一个能修打印机的人,你不会把 100 个人都叫进来问一遍,而是先问“谁懂设备维护”,筛选出 5 个人,再从中精细匹配。路由器就是做这个初筛的人。

关于路由器用一个简单的关键词匹配还是用向量检索,我的经验是:先用关键词和规则能覆盖的场景不超过七成才上向量检索,否则就是过度设计。毕竟每增加一个被调用的模型或向量服务,就多一个延迟和失败的可能。

4. 测试:技能上线前必须过的三道关

好多团队做 Agent,最头疼的其实不是开发而是测试。技能这东西不像普通函数,同样的输入可能因为 LLM 的随机性得到不同的行为路径,所以测试策略必须分层设计。

4.1 单元级验证

单元级验证的核心是“不经过 LLM,直接调用技能”。也就是把技能的入参写死成不同的 case,验证返回结果是否符合预期。这一步能筛掉大部分参数校验、逻辑分支、异常处理方面的问题。

我一般会为每个技能准备三类用例:

正常路径用例:各种合法输入组合,验证业务逻辑正确性。对于请假申请,要覆盖年假、病假、事假、婚假等不同类型,验证计算逻辑是否正确。

边界值用例:比如请假天数恰好 30 天(边界允许)、31 天(边界拒绝)、跨年日期、闰年日期。这些边界最容易出 bug,也最容易在 Agent 真实使用时暴露。

异常输入用例:缺失必填参数、日期格式错误、类型不在枚举值中、超长文本等。这些 case 不是为了等 Agent 去兜底,而是直接通过校验逻辑拦截,返回带 hint 的报错。

单元级验证还有一个隐藏收益:它能反推你技能内部的分支逻辑是不是“可解释”的。如果你的技能逻辑复杂到连测试用例都写不清楚,那说明设计阶段就该优化。

4.2 代理级联调

单元测试通过后,必须把技能挂到真实的代理环境里做联调。这个阶段的目标是:让 Agent 通过自然语言指令触发技能,看它最终走的路是否正确。

联调时我有两个常用手段:

一是指令集覆盖。提前准备一批参考指令,覆盖各个调用场景。比如“请帮我请明天到后天的事假”“我下周一到下周三休年假,帮我提交申请”“帮我看看刘XX的请假审批到哪一步了”。每个指令都要记录三件事:Agent 是否选中了正确的技能、参数提取的准确率、返回结果是否符合预期。

二是链路日志追踪。一定要在 Agent 运行链路里加上完整的日志,包括 LLM 每次的选路、工具调用的入参和出参、每步耗时。联调时出问题,九成都要靠日志来排查,没有日志等于盲人摸象。

联调时最常见的失败模式是“Agent 选了技能但没有正确传参”。比如用户说“帮我请个假”,Agent 选了请假技能,但是 start_date 和 end_date 都没填就去调用了,然后技能返回报错。这种情况要在技能的 hint 里引导 LLM 追问用户,让它通过“多轮对话”补齐信息,而不是强行调用。

4.3 长尾场景回归

这一个阶段是最容易偷懒、也最能体现经验的地方。如果只是用固定指令测一遍就上线,后面大概率会被各种长尾输入打脸。

我做长尾回归的方式是:把历史真实对话中出现过的、以及业务同事提出的“奇怪问题”全部收集起来,组成回归测试集。每次技能或系统更新后跑一遍,确保之前能通过的 case 没有回归,顺便观察新增了哪些覆盖。

比如请假技能上线一段时间后,我回收了这些长尾 case:

  • “我今天心情不好不想上班”(没有明确请假类型和日期)
  • “请帮我请一个明天的病假”(类型具体,但缺原因)
  • “你好,在吗,能帮我看看吗”(完全模糊,根本不涉及请假)
  • “帮我把上周提交的请假申请撤销”(不在当前技能的职责范围)

这些 case 的预期不是“调用成功”,而是**“正确处理”——有的预期是技能被选择后追问信息,有的预期是技能不被选择并转给其他技能或兜底回复。所以回归不是看技能能不能成功执行,而是看 Agent 的整体决策是否合理**。

我强烈建议每个团队把回归测试集沉淀成一个独立的数据文件,定期执行。这比任何单元测试都更接近线上真实情况,也是提升 Agent 稳定性的最大杠杆。回归测试这个事没有“做完”的时候,它是一个持续维护的资产。

5. 常见问题与排查技巧实录

开发 agent-skills 的过程里,我累积了一批高频问题。这里列出来,每个问题都附带我的排查思路和最终解法,希望能帮你少走点弯路。

5.1 描述写得太泛导致代理总是选错

现象:监控日志里发现,用户问天气,Agent 却调用了“获取日期和时间”的技能;用户想查物流,Agent 却调了“查询订单”。技能本身没毛病,就是 Description 写得太宽泛。

排查思路:打开 skill.yaml,看描述的精确度。之前一个技能描述只写了“查询订单相关信息使用”,实在是太泛了。我后来改成了“当用户询问订单状态、物流进度、预计送达时间时使用此技能。当用户想对订单进行修改或退款时,请勿使用此技能——那应使用 modify_order 或 refund_order 技能。”

结果:改完之后,技能选准确率明显上升。我复盘了几次出错案例,发现 LLM 在多个 Description 都比较泛时,会倾向于选择字面更接近用户问题的那个,这套逻辑不准确,但却是 LLM 的真实“思考路径”。所以描述越精确,它越不容易想歪。

5.2 参数歧义导致调用失败

现象:用户说“帮我请三天假”,Agent 把 start_date 提取了,但 end_date 怎么都提不出来,技能一直报参数缺失。

排查思路:这是典型的参数设计对 LLM 不友好。用户说“三天假”,LLM 需要自己计算 end_date,这步对很多小模型来说很容易出错。我后来把参数 design 做了调整:把 end_date 的必填逻辑放开,改为“end_date 与 start_date 至少提供一个,另一个可由技能内部计算”;并且在技能描述里增加一句“当用户只提供请假天数时,请先确认 start_date,再根据天数计算 end_date”。

结果:参数提取成功率大幅提高。这个案例给我一个启发:不要指望 LLM 去替你完成逻辑推导,把推导放到技能内部。LLM 最适合做的是意图理解和参数提取,纯计算和逻辑推导尽量交给代码。

5.3 技能内部出现不确定性

现象:技能内部依赖了 LLM 来解析非结构化输入,结果有时解析对,有时解析错,导致最终结果不稳定。比如“解析请假原因”时,让 LLM 判断用户说的是病假还是事假,同一个输入多次运行结果不一致。

排查思路:我遇到这个问题的第一反应是优化 prompt,但连续调了几版都不稳定。后来我意识到:技能内部的确定性必须靠规则和代码保证,而不是靠 LLM 的自觉。于是我把内部解析逻辑改成了“关键词规则优先 + LLM 兜底”,规则能命中就直接返回,只有规则无法覆盖时才调用 LLM。

结果:稳定性立刻上一个台阶。这件事让我养成了一个习惯:凡是技能内部的决策,能写规则就不上 LLM;必须上 LLM 的地方,要把输出约束成极简枚举值,并且做好失败回退。

为了方便参照,我把这些问题整理成了速查表:

问题典型现象排查方向推荐解法
描述泛化选错技能看 Description 是否覆盖正负面场景用“何时用/何时不用”模板重写描述
参数歧义必填参数缺失分析 LLM 提取参数难易度放开必填约束,内部补齐缺失字段
内部不稳定同样输入不同输出检查内部是否依赖 LLM 决策规则优先,LLM 兜底
上下文膨胀响应变慢、选路变差技能注册量过大引入技能路由器做两级筛选
返回格式漂移Agent 理解错误检查返回结构是否稳定统一返回模板,禁止随意增删字段

5.4 额外分享一个容易被忽略的坑:并发与超时

Agent 调用技能时通常都有超时限制,如果技能内部处理时间过长,Agent 那边就会“断掉”。我遇到过一个问题:某个技能内部需要调一个外部 API,那个 API 偶尔响应特别慢,结果 Agent 一直拿到超时错误,用户以为系统卡死了。

解决方式有二:一是把技能内部的耗时操作改成异步化,先返回一个“处理中”的状态,再通过单独的状态查询接口拿最终结果;二是调大 Agent 侧的超时阈值,并给技能内部的服务增加显式超时和重试机制。很多 Agent 框架里 tool 的超时是可以配置的,别用默认值硬扛。

6. 从经验角度说几点我的真实体会

写到这里,我不打算给你一个“万能方法论”式的总结,因为 agent-skills 这个领域演进太快,方法论过几天就可能过时。我分享几个从实操中沉淀下来的判断,供你参考。

一个是我现在宁可技能少而精,也不多而滥。技能数量每增加五到十个,选择器的准确率就要掉一个台阶,这几乎是个铁律。与其把什么小操作都封装成技能,不如把高频、高价值的场景做深,低频小操作直接让 Agent 用通用能力处理。

第二个感受是,技能的描述比实现更重要。不管内部逻辑写得多好,如果描述写得不够精确,这个技能在 Agent 眼里就等于没有。我会花至少一半的时间在设计描述和参数 schema 上,代码反而是其次。团队里新人加入时,我也会要求他们先写技能描述,评审过关了再动手写实现,效果出奇地好。

第三个体会是,评估指标要跟着业务目标走,不要只看调用成功率。调用成功不等于任务完成,更不等于用户满意。我做技能效果评估时会统计三个维度:任务完成率(用户在合理轮数内得到有效结果)、参数提取正确率(Agent 是否从用户话语中准确提取必要信息)、无效调用率(Agent 选择了技能但其实不该调用或调用无意义)。这三个指标同时达标,才敢说技能是真正可用的。

最后一个建议:多去观察真实对话。别只看测试集。真实用户产生的输入,是最有价值的数据,也是改进技能设计和描述的直接素材。每过一段时间把线上对话日志拿出来遛一遛,你会惊讶地发现有那么多设计时没想到的边角场景。

agent-skills 是一条值得长期投入的方向,因为它直接决定了 Agent 能力的天花板和扩展效率。希望这篇内容能帮你少踩几个坑,更早地把你的 Agent 从“能聊”推向“能干活”的阶段。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询