1. 项目概述:为什么我想把“技能”做成独立层
最近在做一个基于大语言模型的多轮对话项目,前期效果一直不太稳定。明明底模选得不错、Prompt 也反复调过,可只要涉及多步骤任务,比如“帮我把需求整理成清单,再按优先级排好,最后生成一段周报”,模型输出就开始飘:要么漏步骤,要么格式不统一,要么干脆自己加戏。
后来跟几个同行交流,发现大家逐步在往一个方向收敛——不给模型堆一个大 Prompt,而是给模型配一套“技能”。这个思路对应的项目就叫“agent-skills”,简单说,是把可复用的能力(查询、格式化、规划、校验、生成模板等)拆成独立、可描述、可调用的技能模块,让智能体在运行时按需选择和执行。
我最初看到这个名字时,把它理解成一个“工具箱”项目,后来做完一轮重构才发现,它解决的远远不止“多几个工具函数”的问题。它真正解决的是:如何让大模型的应用从“写提示词”升级为“搭建能力体系”。尤其当你面对复杂任务、长链路、多状态流转时,一套成体系的技能定义,比在系统提示词里堆砌几十行“你必须如何如何”要可靠得多。
这篇文章是从一个实际落地者的视角来复盘 agent-skills 项目的设计与实践。适合正在做智能体应用开发、想摆脱纯提示词工程、又不想一上来就上重框架的朋友参考。我会从需求拆解、技能设计、调用机制、排错方法这几个维度依次展开,能让你少踩几个我踩过的坑。
2. 技能体系的设计思路:不是写函数,是搭能力层
2.1 从一次翻车现场讲起
项目早期,我用最传统的方式写了一个“全能助手”Prompt。需求描述得很详细,比如“你是一个负责整理信息的助手,请按照目标、现状、行动、风险四段式输出”。单测时效果还行,但放到真实对话里,用户往往不会把需求说得那么标准。有人连“目标”带“现状”混在一起说,有人直接问“你就说这事能不能干”,还有人要求中途换任务目标。
这一轮折腾下来,最典型的翻车案例是:用户先让我查某商品库存,然后紧接着说“那顺便把这个商品的所有规格参数整理给我”。全能Prompt模式很容易把“查询库存”和“整理规格参数”混成一个动作,结果输出在库存数字里夹杂着一堆规格表,而且谁先谁后完全随机。
后来我把流程拆开:先有一个“意图识别”技能判断该走查询还是走整理,再有一个“信息检索”技能,最后有一个统一的“格式化输出”技能。每个技能只管一件事,且都有清晰的输入和输出描述。模型不再被要求“一次性理解所有规则”,而是每进入一个环节只需要理解一个技能。效果立竿见影,至少不会再发生职责错乱的问题。
2.2 技能层、记忆层、模型层各自该干什么
agent-skills 的核心思想,是把智能体应用拆成三个层次:模型层负责推理和语言生成,记忆层负责会话状态与历史信息,而技能层负责封装“动作能力”。这三者的边界如果分不清楚,很容易又退回“全塞进 Prompt”的旧路。
模型层在 agent-skills 里不做任何业务逻辑判断,只负责两件事:理解当前用户意图,以及根据已有的技能清单决定调用哪一个。这里有一个很微妙的点:模型本身并不执行技能,它只是决策者。真正执行技能的是代码逻辑,或者是被精准设定好参数的外部API。因此,一个技能的定义文档必须写得极其明确,包括触发条件、输入参数、输出格式,以及失败时的降级行为。
记忆层解决的是“上下文连续性”。很多时候任务中断并不因为模型不会做,而是因为它在长流程中忘记了自己之前选择了什么。我最初没有区分记忆层,导致技能执行一半时,模型又开始重新理解用户最初的需求。后来我在记忆层里维护了一个“当前技能栈”的状态字段,每执行完一个技能,就把它记录在案。这个小小的改动,直接让多步骤任务的成功率提升了约三成。
技能层本身是最能体现工程价值的部分。它不是一个简单的函数列表,而是一个带描述、带约束、带组合关系的执行单元集合。在这个项目里,我把技能看成“可以被模型选择、被代码执行、被会话状态记录”的一等公民。
2.3 为什么纯提示词模式撑不住复杂场景
很多刚接触 agent-skills 的人会问:为什么我不能把所有指令写在系统提示词里,非要搞技能拆分?
我的体会是,纯提示词模式在以下三个场景中特别容易崩:
第一,任务空间大时,规则之间会互相覆盖。比如你定义了“输出要简洁”,又定义了“输出要包含详细依据”,模型会在两者之间摇摆。而技能拆分后,“简洁输出”和“详尽报告”是两种不同的技能,通过路由决定,而不是同时存在。
第二,模型上下文窗口有限。复杂业务的规则描述动辄上千字,一旦占据大量上下文,留给真实对话和知识检索的空间就被压缩。技能清单可以写得很短,类似于函数签名,模型只需要知道“什么场景下调用什么”,具体怎么执行,由代码端去处理。
第三,可测试性差。纯提示词模式下,你很难定位到底是哪一条规则让输出变形。但技能模式下,每个技能可以单独测试,甚至可以针对单个技能做批量回归。
所以 agent-skills 本质上是在为智能体应用建立“能力抽象层”。它让模型不再试图成为一个什么都懂的万能机器,而是成为一个懂得“如何调用正确工具”的调度器。
3. 实操落地:如何从零搭建一个可用的 agent-skills 模块
3.1 先定义技能清单,别急着写代码
我对 agent-skills 的第一次落地就犯了顺序错误。当时一头扎进代码里,写了好几个工具函数,自认为它们就是“技能”。结果模型根本不知道这些函数什么时候该用,因为它们没有一个声明性的描述层。
后来我换了一种方式,先用表格把技能清单列出来。清单包含:技能名称、一句话说明、触发条件、需要的上下文、输出形式、失败时的替代方案。以我们当时做的一个客服场景为例,技能表大致长这样:
| 技能名称 | 一句话说明 | 触发条件 | 输入上下文 | 输出形式 | 失败替代 |
|---|---|---|---|---|---|
| 查订单状态 | 查询用户订单的实时物流与状态 | 用户提到订单、物流、快递 | 订单号或用户ID | JSON { status, update_time } | 返回“请提供订单号” |
| 生成退款标签 | 创建退款工单并生成提交摘要 | 用户表达退款意愿 | 用户ID、订单ID、退款原因 | Markdown 摘要卡片 | 转人工客服 |
| 核对收货地址 | 校验地址完整性与可配送范围 | 用户修改地址 | 地址文本 | JSON { valid, reason } | 提示并重新收集 |
这个阶段看起来好像在做“产品设计”,但其实非常关键。技能清单要先于代码,因为它实际上是在定义“模型能看到的能力范围”。技能列得越清晰,后面路由逻辑越简单;技能描述写得越模糊,模型就会频繁选错或者不选。
3.2 技能描述里必须写清楚的部分
技能清单定好之后,最核心的工作是写每份技能描述。这部分就是模型在运行时看到的内容,所以质量直接决定 agent 的性能。我习惯把每份技能描述按模板来写:
- 名称:简短、动词开头,比如“查库存”
- 描述:说明这个技能解决什么问题,避免与相邻技能产生歧义
- 触发词:给模型一些参考,比如“查一下有没有货”“库存多少”
- 参数模式:是自由文本、固定枚举,还是需要结构化提取
- 输出规范:严格定义输出的结构,防止模型在输出时自行发挥
- 边界条件:哪些情况不应该调用这个技能
以“查库存”为例,最早期我写的描述是“查询商品库存”。后来模型经常在用户问“价格”时也去调用它,因为“价格”和“库存”在用户口头表达里经常连在一起。我改成“查询商品可售数量与补货状态,不包括价格、促销、评价等其他商品信息”,调用准确率立刻提升。
这里有一个容易忽略的细节:技能描述不是给用户看的,是给模型看的。它需要用模型更容易理解的语言来写,同时兼顾“辨析度”。比如两个技能都很像“获取信息”,就要在描述里明确彼此的差异点,否则模型会在它们之间随机选择。
3.3 技能注册与调用链:让模型能看见、能选择、能执行
技能描述准备完毕后,来到整个 agent-skills 模块最关键的工程环节:技能注册与调用链。我采用的方式是:把技能清单通过“工具列表”机制暴露给模型,模型在推理时判断当前应选中哪个技能,返回结构化的调用信号,随后代码端执行该技能,得到结果后再交回模型。
这个链路我拆成四步:
- 注册:启动时读取技能描述文件,加入统一的技能注册表。
- 暴露:将注册表形式化为模型可见的声明(通常精简后拼进系统上下文)。
- 推理:模型基于用户输入输出一个结构化动作,格式为
调用技能X,参数为Y。 - 执行与回填:代码端执行技能逻辑,将结果以消息形式回填给模型,让模型基于结果继续生成最终回答。
这个链路意味着 agent-skills 不是把技能写成函数后直接挂在代码里,而是让模型“按清单调用”。所以我非常强调“注册表”和“路由”完全分离。“路由”是模型做的推理动作,而“注册表”则是工程端的声明。如果注册表没有把技能描述写清楚,模型的调用必然混乱。
实际操作中,我遇到过一个频发的错误:技能返回值很大,比如一次查出二十条商品记录,模型在生成回答时超时。后来在技能返回前加了一个摘要函数,只把前三条核心信息返回给模型,其余数据走独立查询接口,问题才解决。
3.4 参数传递与状态保持的细节
还有一个很容易踩坑的地方,是参数传递。很多技能需要依赖上一轮技能的执行结果。比如“生成退款标签”需要“查订单状态”得到的订单号。我最初没有维护这个状态,结果每个技能都是独立执行的,模型每次都要重新向用户确认订单号。
后来我采用了一种简单但有效的上下文拼接方式:每个技能在被调用时,不仅接收用户当前输入,同时接收“当前会话的摘要摘要”与“上一技能执行结果”。我把它们拼接成一段上下文前缀,再交给模型推理。这样技能之间的信息断档问题就被掩盖了,模型在调用下一个技能时有足够的背景。
经验之谈:不要试图让技能之间直接通过一个复杂的全局数据结构通信。对多数场景来说,把上一轮结果以“历史消息”的形式塞入上下文,是最简单、最容易排查的方式,而且不会出现变量被意外覆盖的问题。
4. 垂类场景实战:客服智能体的技能化改造记录
4.1 原来一个 Prompt 承担了太多职责
前面提到的那次客服场景改造,算是我对 agent-skills 理解最深的一次实践。最初的版本是个大而全的 Prompt,职责范围包括:识别用户意图、查询订单、生成安抚话术、解答优惠规则、转接人工,甚至还要判断用户情绪。模型在短对话里表现不错,但在长对话中,规则就被慢慢“遗忘”了。
最典型的问题是:用户先问“可以退款吗”,模型正确判断要退,又回答“请提供购买订单号”。用户提供了订单号后,模型却开始解释退货政策,而不是生成退款标签。原因就在于,模型并没有一个“当前正在执行退款流程”的状态概念。
4.2 技能拆分后整个会话发生了什么变化
改造后的客服智能体,技能拆成了五个:意图识别、查订单、查售后退款政策、生成退款申请、生成安抚话术。每个技能职责单一,描述清晰。模型不再被要求一次理解几十条规则,而是每次只需要理解当前这一步的技能描述。
以“退款”为例,完整的执行链路变成了:
- 用户说“我要退款”,意图识别技能会判定为“售后-退款”
- 系统根据意图,把“查订单”技能的描述推入当前上下文
- 模型调用“查订单”技能,拿到订单详情与状态
- 系统读取订单状态,判断是否符合退款条件,这时把“查政策”技能推入上下文
- 模型查看政策后,决定直接进入“生成退款申请”或提示用户补充信息
- 最终把所有信息汇总,生成一段结构化答复
这个链路全程不需要用户重复提供信息,因为每一轮的结果都被记录在“当前会话状态”里。整个流程从原来的一轮 Prompt 直接出结果,变成了“分阶段决策 + 逐步执行”。最初看起来步骤多了,但实际效果是结果稳定得多。
4.3 这条链路带来的三个直接收益
第一个收益是可解释性。以前模型输出错了,只能靠猜去调 Prompt。现在每个技能的执行日志独立保留,一旦结果不对,输入日志就能看到是哪一步选错了技能,或者“查订单”技能返回的数据格式有问题。排查效率高了很多。
第二个收益是迭代成本下降。原来的全能 Prompt 每次修改都牵一发动全身。技能化之后,我可以单独优化“安抚话术”技能,而完全不动“查订单”逻辑。测试时也可以单独对“安抚话术”出一批测试用例,而不必在完整链路里反复验证。
第三个收益是复用性。客服场景里的“查订单”技能,换成电商助手、售后助手、甚至内部工单系统也能用。技能注册表机制让我可以把同一个技能在不同项目间迁移,只需要改参数来源,不需要改核心逻辑。这就是“技能即能力”这个理念的实际价值。
5. 避坑与排查:agent-skills 落地中的七个高频问题
5.1 技能就是工具函数?这是最常见的误解
很多初次接触 agent-skills 的人,容易把技能等同于工具函数。工具函数关注的是“能不能执行”,而技能关注的是“模型能不能正确选择”。一个技能除了实现的代码,更关键的是描述文档与参数协议。我在这个项目里最大的教训之一,就是不要把宝都压在实现上,要多花时间打磨技能描述本身。
5.2 技能描述到底写多少合适?
描述太短,模型分不清技能之间的边界;描述太长,又占上下文且干扰判断。我测试下来,一个技能的描述控制在 100 到 200 字之间比较理想。其中触发条件与边界条件是最重要的,宁可多写一句“这不是什么场景”,也别让模型去猜。
5.3 技能路由不准怎么办?
先检查是否是技能描述重叠。比如 A 技能说“整理信息”,B 技能也说“整理信息”,模型当然会随机选。我通常的做法是给每个技能加一个“典型触发场景”的示例,以少量示例的方式告诉模型什么时候用这个技能。这个做法比单纯抽象描述可靠得多。
5.4 技能执行成功后模型仍乱说怎么办?
技能返回结果与最终回答之间,要有一条严格的“回填格式”约束。我会要求技能返回值是一个严格 JSON,并且要求模型基于结构化数据生成最终回复,而不是自由发挥。如果模型还是乱说,就检查最终生成这一步的 Prompt 是否缺失,是否没有明确要求只能基于数据回答。
5.5 多技能串联时模型忘了前面的结果?
这就是前面提到的状态保持问题。我建议不要在全局变量里存“中间状态”,而是通过对话消息历史自然携带。每执行完一个技能,把结果摘要封装成一条不可变消息,追加到会话上下文。这样模型在后续步骤中天然看得到历史,不会因为代码层状态丢失而断链。
5.6 技能返回内容太长怎么办?
在技能返回值传给模型前,增加一个摘要节点。摘要节点不是简单的截断,而是根据当前用户问题,提取关键字段。例如用户问“哪些商品缺货”,摘要节点就只保留库存为0的商品名和预计补货时间。这样既避免无关信息占用上下文,又让模型不至于被数据淹没。
5.7 技能回归测试怎么做?
每个技能要有独立的测试样例。比如“查订单”技能的测试集应该包含:提供订单号、不提供订单号、订单不存在、订单已取消等多种情况。我习惯在每次调整技能描述或路由逻辑之后,先跑一遍全部测试集,再进完整链路测试。这样能把出问题的范围缩小到具体技能,而不是整个系统。
6. 一些体会与扩展思考
做 agent-skills 最大的收获不是写了几段优雅的代码,而是改变了看待智能体应用的视角。以前我总想通过更精细的提示词来“教”模型做正确的事,后来发现真正可靠的方式是给模型一个清晰的“可选动作空间”,让它自己决定怎么组合这些动作。前者是不断提高模型的服从性,后者是发挥模型的调度能力。虽然模型还是那个模型,但系统的鲁棒性完全不同。
我后来把同样的模式用在了内容生成场景、数据分析场景和自动化办公场景上,基本思路都是一致的:先圈出能力边界,再写技能描述,最后做路由和执行。每次换场景只需要新增几个技能,而不用推翻整套架构。这种“可插拔”的感觉,和传统软件开发中模块化的体验很接近。
如果你也想在自己项目里试 agent-skills,我的建议是别一上来做一堆技能。先挑一个你最常被问到、最头疼的业务动作,把它拆成一个单一技能,配上完整描述,再接入模型调用链路。跑通之后,再逐步扩展。技能不是越多越好,也不是越复杂越好,而是“刚刚好能让模型做对事”最好。
按照这几步落地,你会发现同样的模型、同样的数据,任务完成质量会有肉眼可见的提升。