1. 先搞清楚:Agent技能到底解决了什么问题
做Agent落地这一年多,我最强烈的体感是:大模型本身的“思考能力”已经不怎么卡脖子了,真正卡脖子的是Agent能不能稳定地把想法变成动作。你让LLM写一首诗、总结一份文档,它很强;但你让它去“把昨天的新客户线索同步到CRM,再给销售发一条提醒”,它就开始自由发挥了——今天调用这个接口,明天换个参数格式,后天干脆自己编一个不存在的函数名。这种不确定性,是所有做Agent应用的人早晚要撞上的墙。
Agent技能(Agent Skills)就是冲着这堵墙来的。用最简单的话说:技能是给Agent预装好的、带清晰边界和完整说明的行动模块。它把“调用什么工具、参数怎么填、返回怎么解析、什么时候不该用”这些事,从模型临场发挥变成开发者预先定义。Agent在运行时不是“想起什么用什么”,而是从技能库里选择最合适的那一个,像工人从工具箱里挑扳手,而不是现场拿铁丝拧螺丝。
这里要分清几个容易混的概念。Function Calling是让模型学会调用函数的机制,属于“地基”;MCP(模型上下文协议)解决的是工具和服务怎么标准化接入的问题,属于“管道”;而技能层解决的是行为封装与复用,属于“上层应用”。打个比方,Function Calling像是给了Agent一本电话簿,MCP是统一了电话接口,而技能是给每个常用操作写了“操作手册+使用场景说明”,确保Agent拿到电话知道该说什么、不该说什么、说错了怎么补救。
什么人最需要关注技能化?三种人。第一种是做企业内部Agent平台的工程团队,你们正在被“工具几十个、调用一团乱”折磨;第二种是垂直行业SaaS的服务商,想把自己的业务能力变成可复用的Agent能力;第三种是独立开发者和AI产品经理,想让Agent在真实场景里干活而不是只当聊天机器人。下面所有的内容,都是我实际踩坑和验证过的思路,不是纯理论推演。
2. 技能设计:不是“写个工具函数”那么简单
很多团队第一次做技能,习惯性地把它当成“封装好的API调用”,写个函数、加个描述就往Agent里塞。用完之后发现效果很差:Agent压根不调用、调用了参数乱填、或者该调的时候不调、不该调的时候瞎调。原因很简单——技能和普通函数之间,隔着一层“Agent能否正确理解和使用它”的距离。
2.1 一个技能到底由什么组成
我习惯把技能拆成四个部分:
- 技能描述(Description):给LLM看的“使用说明书”,说明这个技能是干什么的、适合什么场景、不适合什么场景。
- 输入输出契约(Schema):参数定义、返回结构、错误码规范。这部分决定了Agent能不能准确地填入参数、解析结果。
- 执行逻辑(Execution):真正干活的代码或服务调用。可以是一次HTTP请求、一段Python脚本,也可以是编排其他子技能的复合逻辑。
- 验收与边界(Validations):包括参数校验、权限校验、可选的技能评测用例。
这四部分缺一不可。很多团队只做了前两部分,执行逻辑随便糊一个函数,边界完全不定义——结果就是Agent“看起来有技能,用起来全翻车”。
2.2 技能描述:决定Agent“愿不愿意用”的关键
我给技能写描述时遵循一个原则:不要只写“这个技能能做什么”,还要写清楚“什么时候用、什么时候千万别用”。原因在于LLM做工具选择时,本质是在做意图匹配。描述写得模糊,它就会在边缘场景里瞎猜。
举个例子,你做了一个“获取天气”的技能,描述如果只写“获取天气信息”,Agent在用户问“明天适合爬山吗”时,可能不调用它——因为模型不确定“适合爬山”算不算天气查询。但如果描述写成:“获取指定城市当前及未来几天的天气数据,包括温度、降水概率、风力。适用于用户直接询问天气,或需要根据天气辅助决策(如出行、活动安排)的场景。不要用于询问空气质量、紫外线指数等非天气指标,这类请求应使用空气质量技能。”——模型就能精准地做出工具选择。
这个细节我测试过很多次。描述里多写两句话,工具调用的准确率能从70%左右拉到90%以上,而且幻觉参数、错误调用大幅减少。这是整个技能设计里性价比最高的投入。
2.3 原子技能与复合技能
技能有粒度之分。原子技能是不能再拆的最小动作单元,比如“发送邮件”“查询订单状态”;复合技能是由多个原子技能或子技能编排而成的完整业务流程,比如“处理客户投诉”:先查订单、再查工单、判断责任方、生成回复,最后把处理结果写入CRM。Agent会把复合技能当成一个整体来调用,内部怎么编排对模型是黑盒,或者由模型按需调度。
在实际设计时,我建议先原子、后复合。理由很简单:原子技能的通用性强,可以被多个复合场景复用;复合技能一旦绑定到具体流程,改起来牵连很大。最稳的做法是——底层沉淀通用原子技能,上层按业务流程设计复合技能,复合技能本身尽量只做编排,不含具体的业务硬编码。
2.4 参数设计的两个大坑
参数Schema有两个高频翻车点,我每次都要反复提醒团队:
第一个坑是参数过多。模型在上下文里的“注意力”是有限的,你定义20个参数,它经常遗漏其中的四五个,或者用错默认值。我的经验是:核心参数控制在5个以内,边缘参数能合并就合并,能自动推导就自动推导——比如当前用户ID、当前时区这类,让执行层自己补全,别让模型填。参数越少,模型填对的概率越高,这是测试验证过的结论。
第二个坑是类型与取值边界不明确。不要用“string”“integer”糊弄过去。枚举值就老老实实列出可选集合,取值有范围就写明范围,日期格式必须给示例。LLM不是传统程序,它不会因为你定义了“date”类型就自动按ISO格式填。你必须在描述里给它一个活生生的例子:“date: 2024-12-01”。这种细节,省掉一个就会多一次调用报错。
3. 一个真实技能的完整落地过程
理论讲多了容易飘,我直接用一个我最近在做的“团队消息发送”技能来走一遍完整流程。这个技能解决的需求很朴素:让Agent能帮运营同事往企业IM群里发消息,并附带@指定成员——注意“@指定成员”是个很容易让Agent犯错的点,正好能说明交互约定有多重要。
3.1 技能定义阶段
先写技能描述,我实际用的版本是:
技能名称: team_message_sender 功能: 向指定企业IM群组发送文本消息,支持@同事。 适用场景: - 用户明确要求“通知群里的人”“@某某” - 需要定期/定时提醒、告警、汇总同步的自动化场景 不适用场景: - 发送邮件、短信(使用mailer或sms技能) - 查询消息历史记录(使用message_query技能) - 用户只是提到“给某人留言”但未说明发送渠道时,先向用户确认这个描述很啰嗦,但字段很明确。模型在工具选择时会扫描几十个技能,你写清楚“不适用场景”,就是在帮模型做排除法,效果比只写正面能力好得多。
接下来定义参数Schema。这是我的设计:
{ "type": "object", "properties": { "channel": { "type": "string", "description": "群组标识。使用群组名称或ID,如'运营-日常'。" }, "content": { "type": "string", "description": "要发送的正文内容,支持纯文本和富文本标记,不超过2000字。" }, "at_members": { "type": "array", "items": {"type": "string"}, "description": "需要@的成员姓名,格式为['张三','李四']。不需要@任何人时,此字段填空数组[]。" } }, "required": ["channel", "content"] }这里两个关键决策:一是at_members设为必选,但允许填空数组,这样能避免模型因为可选参数缺省而犹豫要不要传——要么明确不传,要么明确传[],别留模糊地带;二是把成员用姓名而不是工号暴露给模型,因为LLM从对话里能识别出的是姓名,工号需要执行层做映射,这是“让模型做它擅长的事,让代码做代码擅长的事”的典型例子。
3.2 执行逻辑与异常返回
执行代码不复杂,但要注意返回结构。我写了一个Python示例:
def execute(channel, content, at_members): # 校验 if len(content) > 2000: return {"status": "error", "code": "CONTENT_TOO_LONG", "message": "消息内容超过2000字上限"} if not channel_whitelist.contains(channel): return {"status": "error", "code": "CHANNEL_NOT_ALLOWED", "message": f"群组 {channel} 不在允许列表中"} # 名字映射成工号 member_ids = translate_names_to_ids(at_members) try: resp = im_client.send(channel=channel, content=content, at=member_ids) return {"status": "success", "data": {"message_id": resp["id"], "sent_at": resp["time"]}} except IMException as e: return {"status": "error", "code": "IM_SEND_FAILED", "message": str(e)}注意几点。第一,返回里必须有状态字段,并且错误码要结构化。Agent拿到{"status":"error","code":"CONTENT_TOO_LONG"},就知道下一步是让用户精简内容,而不是再试一次相同的参数。第二,不要在返回里塞大段日志或调试信息,那会污染Agent的上下文,让它忘记本来的任务主路径。第三,校验尽量放在执行层,不要依赖模型认真填写——模型的参数校验从来都是“尽力而为”,真正的安全边界得靠代码守住。
3.3 注册进Agent运行时
技能不是写了就生效,还要完成注册。注册信息通常包括:技能名称、描述、Schema、执行入口、依赖权限、缓存策略、超时设置。我强烈建议每个技能标注预估执行耗时——比如“小于500ms”或“可能3-5秒”。因为Agent在规划时,如果知道某个操作很慢,它会更倾向于先做别的事,或者明确告知用户“这一步需要等待”,这个行为差异会明显影响多步骤任务的用户体验。
注册之后还有一个重要步骤:给技能配置“触发建议”。有些技能适合模型自动决策,有些技能必须由用户显式确认后才能触发。比如发消息、删数据、提交订单这类有副作用的操作,我强烈建议在运行时层面强制加一层“人类确认”钩子。不要让模型在Agent内部直接调用,而是让它“提出请求、等待确认、再执行”。这不仅是合规问题,更是产品体验问题——用户对“AI自作主张往外发消息”的信任成本,远比“AI问我是否确认”高得多。
3.4 技能评测:不评测就上线,等于赌博
技能上线前我至少跑三轮评测。第一轮是离线用例,准备50到100条典型的用户请求,逐条检查“该调用的有没有调用、不该调用的有没有误用、参数是否填对”。第二轮是对比组测试,同一个请求分别用“接入了技能”和“没接入技能但给了工具描述”两种方式跑,对比完成率和出错率。第三轮是真人小范围试用,收集真实场景下的调用日志和失败样本。
这里我建议关注三个核心指标:
- 调用准确率:应该调用时,Agent是否调用了正确技能。
- 参数完整率:成功调用中,必填参数正确填写的比例。
- 恢复成功率:技能返回错误后,Agent是否能基于错误码做出正确恢复动作。
这三个指标的及格线我一般定在90%以上。不达标的技能,要么改描述,要么改Schema,要么重新设计执行逻辑——绝对不允许带着50%的调用准确率上生产环境。
4. 技能治理:从“能用”到“好用”的关键一步
技能数量一多,问题就来了。技能A和技能B功能重叠,描述长得差不多,模型选择时开始随机摇摆;旧技能更新了参数格式,但Agent的缓存里还保留着旧描述,导致调用报错;新上线的技能有权限漏洞,被恶意提示词诱导去读取不该访问的数据。这些都是我实际遇到过的问题,技能治理不是锦上添花,是规模化使用的前提。
4.1 版本管理与兼容性控制
每个技能都要有明确的版本号,并且要支持多版本并存。原因很现实:你不是同一时刻给所有Agent升级的,有的业务线还在用旧配置,强行切换会直接破坏它们的流程。我的做法是:技能注册表里保留当前版本、上一个稳定版本、以及各Agent实例实际使用的版本号映射。新版本先灰度给测试Agent跑两三天,确认稳定后再逐步扩大范围。
兼容性控制上有个小技巧:描述更新比代码更新更频繁。模型调用行为主要受描述影响,描述文案的微调不需要发版,直接在注册表里热更新就好。但代码逻辑变更一定要走版本管理,不能偷偷改——我见过有人直接改了线上函数不吭声,结果Agent调用接口参数全错,排查了半天的教训。
4.2 安全边界和权限隔离
技能安全有一个核心模型:技能的权限边界,应该小于等于其“最小职责所需权限”。发消息的技能不应该有删除消息的权限,查询订单的技能不应该能访问客户手机号,这是所有安全设计的起点。
具体落地上我做三件事:
第一,技能权限表。每个技能声明自己需要读取/写入的资源列表,运行时根据这个表做访问控制。Agent的整体权限是“它拥有技能的权限并集”,不是“Agent有全平台权限然后靠技能自己收敛”。
第二,敏感操作埋点。所有带“写操作”属性的技能执行时,都要记录操作人员/Agent ID、参数摘要、时间戳和结果。这个日志不是为了事后的追责,而是为了出问题时能快速定位是“模型误调用”还是“执行层代码bug”还是“第三方服务故障”。
第三,沙箱与超时熔断。执行逻辑尽量跑在受限环境里,限制文件写入、网络访问范围、资源消耗。超时时间一定要设好——我见过一个技能因为第三方接口一直不返回,导致Agent卡在那个步骤上整整20分钟不往下走。
4.3 效果回收与迭代闭环
技能发布不是终点,是迭代循环的起点。我每周会跑一次技能调用分析,重点关注:
- 调用次数趋势:某些技能逐渐没人用了,是真没人需要,还是描述更新后模型开始漏选?
- 失败错误分布:哪些错误码出现频率最高?CONTENT_TOO_LONG是不是经常触发?说明前置的“字数说明”写得不够清楚。
- 用户纠正率:Agent用技能做出来的结果,用户是否经常手动修改?修改比例超过30%的技能,基本可以判定为“模型理解与用户意图存在偏差”。
这套闭环要常态化,千万不能“上线即不管”。技能本质上是在用编写者的语言去约束模型的行为,而模型的行为总会超出你的预期。你只能通过不断观察真实调用样本,持续打磨技能的设计。
5. 避坑实录:我在Agent技能化里踩过的雷
最后分享几个我实际踩过、现在每次都会拿出来提醒自己的坑。
坑一:技能描述里用缩写和内部术语。我一度在技能描述里写了“SD”代表“销售订单”,自己觉得很清楚,结果模型在用户说“查一下上周的订单”时,完全没把“SD”和“销售订单”联系起来,导致该调用的没调用。后来我把描述改成了“销售订单(以下简称SD)”,准确率立刻上来了。技能描述是写给模型看的,要用模型最可能理解的表达,而不是你最习惯的表达。
坑二:过度封装导致技能变成“黑盒”。有段时间我把整条业务逻辑全封装成一个技能,看起来调用很简洁,但一旦出了问题,根本定位不了是哪个环节错了。后来我拆成了“查订单”“查库存”“算价格”“生成订单”四个原子技能,再由复合技能编排——虽然调用链路长了,但每一步都能追踪、能测试、能单独替换,整体可靠性反而更高。
坑三:不在返回中给模型“下一步指令”。早期的技能返回都是干巴巴的JSON,模型拿到一个错误码,完全不知道该怎么处理。后来我在所有失败返回里都加了一个user_message字段,专门写“面向用户的友好解释和建议动作”,模型可以直接引用或改写,恢复成功率明显改善。你想想,模型本质上是靠“文字理解”来决策的,如果你的返回信息本身就是为它“如何决策”而设计的,效果自然不同。
坑四:没有评测就在生产环境跑。这一点前面说过了,但我损失惨重,当时节省下来的两天时间,最后花了两周去修线上问题。技能的评测是高杠杆的投入——花一小时写评测用例,能省下未来二十小时的问题排查时间,这笔账怎么算都划算。
坑五:忽视技能的市场化/团队复用意识。很多时候不是没有好技能,而是团队内部重复造轮子。我推进技能化之后做的第一件事就是建立技能共享库——所有原子技能统一登记、统一命名、统一质量标准,团队里谁要用直接接入,不许自己另起炉灶。有了这层沉淀,后边的Agent开发速度是真的翻倍提升。
写到最后,说点个人的感受
Agent技能化这条路,我走了大半年,最大的体会是:技术难点不在模型能力,而在系统设计与边界意识。你给模型一个技能,本质上是在与它签订一份行为契约——你负责把边界和说明写得滴水不漏,它负责在合适的时候做出正确的选择。这个过程没有捷径,只能靠反复测试、持续打磨、认真复盘。
如果让我给一个刚起步的团队提一条最实在的建议,那就是:先选一个最高频、边界最清晰的业务动作,把它做成一个高质量的原子技能,跑通“设计—注册—评测—上线—反馈”的完整闭环。不要一开始就贪多贪全。一个真正好用的技能,胜过十个凑数能用但把模型搞糊涂的技能。
最后分享一个小技巧:每当你在技能描述里写“这个技能非常重要”的时候,先停下来想一想——你觉得重要没有用,你要让模型觉得“什么时候用”才是关键。技能描述的目标不是表达你的设计意图,而是驱动模型在合适的时候按下正确的按钮。把这句话想通了,你的Agent技能化之路,就已经走了大半。