先聊个实际现象。今年接的不少项目,都在往 AI Agent 上靠,但多数团队卡在同一个地方:模型能聊天,能写文案,一旦让它去执行“查库存→算折扣→生成报表→发邮件”这条链路的任务,立刻就开始胡说八道或中途卡死。有人认为换个更强的模型就行,其实模型只是“大脑”,真正决定 Agent 能不能干活的,是它有没有一套组织良好、可复用的技能——也就是标题里写的 agent-skills。我在多轮落地里最大的体会就是:Agent 的上限由模型决定,但下限完全由 skill 的设计质量决定。这篇文章不聊概念,只聊我把“技能”拆开、重组、塞进 Agent 里并让它稳定干活的全过程,以及踩过的那一堆坑。
1. 为什么 Agent 需要一套“技能”而不是一段“提示词”
1.1 从“会聊天”到“能干活”,中间差的是边界
如果只是让模型回答开放性问题,一段精心设计的 system prompt 就够了。但一旦涉及真实业务操作,问题就变了:模型不知道该调哪个接口、不知道参数怎么传、不知道上一步的输出如何映射到下一步的输入,更不知道出错时该重试还是该换路径。
我给“skill”下的定义很简单:它是一组预定义的行为单元,包含触发条件、输入输出格式、执行步骤、异常处理和退出策略。它把模型从一个“什么都懂但什么都不精”的通才,变成一个“知道在什么场景下调用什么专用流程”的调度者。这跟人类员工很像——一个新员工再聪明,也要先看 SOP 手册才能上手。Skill 就是 Agent 的 SOP。
1.2 单一 Prompt 的致命短板:不可组合、不可维护
拿一个真实需求举例:要做一个“自动生成营销周报”的 Agent,它需要拉取后台数据、调用模板工具、生成图表、按渠道分发。如果用一段超长 Prompt 描述,模型在短任务上没问题,但任务一多,上下文会互相干扰,前面指令会被后面覆盖,而且每次调整业务逻辑都要改那一大段文字,改完还要担心影响其他任务。
Skill 模型的核心价值是把复杂性拆散后重新收拢。每个 skill 只负责一件事,输入和输出都有 schema 约束,Agent 本身只做拆解和编排。比如“营销周报”可以拆成 5 个 skill:数据查询、异常标注、图表渲染、内容组织、渠道分发。任何一个环节更换或升级,都不需要动其他 4 个环节的代码和提示词。
所以我对 agent-skills 的整体认知是:它不是某个具体工具,而是一种组织 Agent 行为的方式。它解决的是“如何让模型稳定执行多步骤任务”的工程问题,让 Agent 从“demo 里能跑通”变成“生产环境里敢用”。
2. Agent 与 Skill 的匹配关系
2.1 Agent 只做“翻译”,真正执行的是 Skill 链
在早期的 Agent 架构里,我走过一段弯路:把所有指令和工具描述都塞给模型,让它自己决定调用什么。当时跑起来效果很“惊艳”——模型能自己选工具,还能做复杂推理,但上了真实流量就崩溃:模型的调用模式不稳定,偶尔会跳过步骤,偶尔会传错参数。
后续改为“Agent 负责解析意图和编排 skill 链,skill 负责具体执行”的分层结构,稳定性才有了质的提升。现在我的做法是:
- Agent 层:只负责把用户请求拆解成有序的 skill 调用序列,它不直接操作数据和工具;
- Skill 层:每个技能内部高度自治,自己管理上下文、参数校验、执行逻辑和失败恢复。
一个简单比喻:Agent 是项目经理,只负责派活和汇总结果;Skill 是工程师,每个工程师都只做自己的专项任务。项目经理不该去帮工程师写代码,工程师也不该越权去替项目经理做决策。
2.2 三件事:设计归一、调度归一、协议归一
不管底层是用 Funcation Calling、手动流程编排还是 LangChain 式 Tool Calling,我总结的通用原则就三条。
一是设计归一。所有 skill 必须有统一的输入输出格式。以前给 Agent 加工具时,有人习惯给字典结构,有人给 Markdown 描述,还有人直接传函数对象,结果就是 Agent 经常在理解不同的参数格式时出错。后来我强制规定:每个 skill 的输入输出必须包含 type、description、required、properties 四部分,谁引入新工具都要填这套元信息;不填就不同意发布。
二是调度归一。把所有 skill 用统一的契约描述,然后让 Agent 基于这些描述来做选择。具体到实现里,这个契约可以是一个 JSON 数组,每个条目包含技能名称、适用场景、参数约束和返回结构。Agent 每收到一个任务,先从契约里挑选适用的技能组合。这个契约本身就是一个“技能注册表”。
三是协议归一。技能间通信统一走标准协议。不管底层工具是 REST API、Python 函数还是数据库查询,封装成 skill 后,一律通过统一的执行器来调用。执行器负责参数校验、超时控制、错误捕获和结果格式化。这样即使底层换了接口,skill 对外暴露的协议不变,Agent 层完全无感。
这三条归一听起来像废话,但真正做到位的团队很少。大多数 Agent 在真实场景翻车,翻的不是模型能力,而是协议混乱。
3. 从 0 到 1 搭建一套 Skill 系统的实操记录
3.1 第一步:任务拆解与技能边界划分
搭建 skill 系统的第一个坑,就是容易把技能拆得太粗或太细。拆得太粗,技能内部还是一大坨逻辑,Agent 调用后依然不可控;拆得太细,技能数量爆炸,Agent 的调度决策难度也剧增,而且每个技能都要维护,成本反而更高。
以“订单售后处理”为例。如果只用一个大 skill“处理售后”,Agent 接到请求时会傻掉——它不知道当前该走退款流程还是补发流程,也不知道处理到一半出现新问题该怎么兜底。我的做法是先做子任务拆解,再合并同类项:
| 子任务 | 对应的 Skill |
|---|---|
| 查询订单基本信息 | 订单查询 |
| 判断是否符合退款条件 | 规则引擎调用 |
| 执行退款操作 | 退款执行 |
| 通知用户处理结果 | 消息发送 |
| 记录处理日志 | 日志写入 |
这样拆完后,“订单查询”“规则引擎调用”这类技能还能复用到其他场景里。实务上我的建议是:技能粒度以“一次能独立完成且结果可被下一个技能直接使用”为准。
3.2 第二步:给每个 Skill 补全“使用说明书”
一个 skill 如果只给 Agent 一个函数名和参数列表,它经常会误用。比如“查询订单”,Agent 可能把用户 ID 当作订单号塞进去,因为它在语义上觉得这俩是一回事。解决这个问题,必须给每个 skill 写一份“使用说明”,让它清楚介绍自己适用什么场景、不适用什么场景、参数怎么填。
我经常用的示例定义长这样:
{ "skill": "订单查询", "description": "根据订单ID查询订单的详细信息,包括商品列表、金额、状态、物流信息。仅支持精确匹配,不支持模糊搜索。", "input_schema": { "type": "object", "properties": { "order_id": { "type": "string", "description": "订单号,例如 SO-20250601-001,请在用户消息中提取完整订单号", "required": true } } }, "output_schema": { "type": "object", "properties": { "order_status": { "type": "string", "description": "订单当前状态" }, "items": { "type": "array", "description": "商品列表" } } }, "error_handler": { "order_not_found": "返回明确提示,并向用户确认订单号", "timeout": "重试一次,超时则上报调度中心" }, "fallback": ["人工客服转接", "重新填写订单号"] }这段 JSON 本身不是给用户看的,是给 Agent 决策引擎看的。Agent 会先阅读每个 skill 的 description 和 input_schema,来判断自己到底该调用哪个技能、参数怎么填。所以 description 里要写得尽量不含糊,明确写明边界条件和常见错误用法。
3.3 第三步:编排层怎么处理上下文
技能执行过程中,最怕上下文在多个 skill 之间传递时丢失或污染。举个具体问题:用户说“帮我退款,订单号 12345”,Agent 先调用“订单查询”技能拿到订单详情,接着要调用“退款执行”技能。如果编排层没有把订单详情里提取出的金额、商品信息、用户 ID 映射到“退款执行”的参数里,这个技能必然报错。
我的办法是引入一个“会话级上下文池”,它存放三类数据:用户原始输入、上个技能的输出、系统注入的静态数据(比如用户身份、当前时间)。每个技能调用前,编排层会从池子里挑出必要字段,拼装成该技能所需的输入;技能执行完,再把输出合并回池子。这相当于给每个技能都加了一道“输入投影层”和“输出落地层”。
这个做法的收益最明显:技能之间的耦合度下降。哪怕“退款执行”的参数类型从字符串变成数字,也只需要修改编排层的映射规则,不必改 Agent 的调度逻辑和其他技能。
4. 常见问题与排查技巧实录
4.1 技能匹配不准:新增了“让利计算”技能,Agent 却总调用“订单金额核算”
这个问题我排查了很久,最后发现根源在于两个技能注册表里的 description 过于相似。Agent 在挑选技能时,本质是在做语义匹配,两个描述重叠度太高,它就可能选错。
解决办法有两个。一是拉开描述差异,明确使用场景关键词。比如“让利计算”的 description 里明确写“仅在用户发起优惠、折扣、减免请求时使用”,而“金额核算”写明“仅在统计订单总金额时使用”。二是增加技能优先级字段。调度引擎在匹配到多个候选时,按优先级排序,而不是随机选。这样就算语义重叠,也能通过规则兜底。
4.2 参数幻觉:模型没拿到订单号,却“编造”了一个格式相似的单号继续执行
这是最危险的一种错误——模型为了完成任务而自我补全。技能在入参校验时必须严格拦截。我在执行器里加了“必填参数空值检测”,一旦缺失就直接终止技能执行并上报,不允许模型用幻觉数据继续跑。宁可让流程断掉返工,也不能让它拿假数据写库。
4.3 长链路任务中途失败后的恢复策略
Agent 执行四五个技能时,可能做到第三步就失败了。刚开始我用“整条链路重跑”的方式,结果发现前面步骤做的写操作会重复执行,污染数据。后来优化成“断点续跑”模式:每个技能执行完都把中间结果持久化(比如存 Redis),失败后从断点处恢复,而不是从头再来。
这里有个细节:执行器需要在每个技能的开始和结束各打一个日志,起始日志记录输入参数,结束日志记录输出摘要和耗时。没有这两个日志,断点恢复就无从谈起。
4.4 技能内部出现长耗时调用时,Agent 以为系统卡死了
有一次技能里嵌了一个要跑 30 秒的同步接口,Agent 迟迟等不到结果,就误判“技能不存在”,于是尝试调用另一个技能,导致整个任务乱套。后来我统一在技能封装里加了“进度心跳”——每 5 秒上报状态给编排层,Agent 知道技能还在正常执行,就不会瞎重试了。
5. 写在最后的一点体会
在我现在做的项目里,skill 已经被当成一等公民来对待。每个 skill 独立成一个目录,包含实现文件、测试用例、注册描述文档和调用示例。只要新需求能拆成已有技能的拼接,我基本不用改代码,只需在编排层新增一条规则即可。这套体系跑顺之后,给 Agent 加一个新能力到稳定上线,可以从一周压缩到半天,这个收益比换更强的基础模型来得直接得多。
这也是我后来反复给团队讲的一句话:别急着追新模型,先把 skill 做扎实。模型半年换一代,但一套边界清晰、可组合、可复用的技能库,能用很多年。如果你也在做 Agent 落地,建议从整理一份“技能清单”开始——不用写代码,先把你期望 Agent 能做的所有事情列成一个个动词短语,再逐个标注输入输出和边界条件。这份清单,就是你 Agent 系统的第一版架构图。