☰
Agent技能体系设计指南:从Prompt膨胀到结构化技能注册与调度的工程实践
2026/10/14 8:53:16 网站建设 项目流程

1. 为什么要给Agent建一套"技能体系"——先想清楚这层再动手

我最早做Agent的时候,有一个特别原始的冲动:既然模型那么聪明,把能用到的能力全写进system prompt不就行了?于是我在某个内部工具型Agent里堆了二十多项指令,从"获取用户订单信息"到"分析库存周转率"再到"生成日报文案",全都塞在一段提示词里。结果Prompt到了三千多字之后,模型开始"精分":我问它库存数据,它给我输出日报格式;我让它查订单,它先把所有能调的函数全部罗列一遍。最崩溃的是,只改其中一个业务规则,整段提示词的表现都会跟着波动,排查起来完全没有抓手。

后来我换了一个思路:不再把能力写成"一段话",而是把每种能力封装成独立的、可注册、可调用的技能。这个做法在很多资料里叫agent-skills,本质上是给Agent建一套结构化的能力资产库。每个技能就是一份包含触发条件、入参契约、执行逻辑、输出约束的完整单元,Agent按需加载、按说明调用,而不是把所有玩法都灌进它的脑子里。这是我做Agent以来收益最大的一次重构,也是这篇文章想聊透的东西。

这套思路适合谁?两类人最需要:一类是正在做多任务Agent的,比如客服机器人、内部运营助手、个人知识管家,它们要处理的目标非常多,Prompt已经膨胀到难以维护;另一类是做自动化工作流的,想在流程里插入能自主决策、能感知上下文的智能节点。如果你只是做一个只回答固定问题的聊天机器人,那确实不需要技能体系,一套扎实的Prompt就够了——所以先别急着跟风,要判断自己是否真的有"多能力、多场景、动态切换"的需求。

那agent-skills到底解决的是什么问题?往深了说,它解决的是能力的组织问题。模型本身只是一个推理引擎,它知道"怎么说话",但不知道"你的系统里有哪些工具、每种工具在什么场景下用、参数格式是什么"。技能体系就是在这两者之间架了一层规范化的中间层:对外,它把Agent的能力清单清清楚楚地暴露给模型;对内,它把每种能力的实现细节、依赖关系、版本归属管起来。最终的效果是,你的Agent不再是"一个巨大的prompt",而是一个"拥有若干可插拔技能的运行体"。

2. 技能的结构化定义:从"提示词拼接"进化到"可复用资产"

既然要把能力做成技能,第一步就得定义清楚:一份技能由什么构成?我见过不少团队一开始就写代码,实现了一堆函数,却没有给这些函数配"使用说明书",结果Agent完全不知道怎么用。技能不只是执行函数,它至少要包含四部分:能力标识、触发说明、参数契约、执行体。

2.1 能力标识与触发说明:Agent判断"该不该用我"的唯一依据

这是技能体系里最容易偷懒、也最致命的部分。很多人写description习惯写"这个函数用于发送HTTP请求",这种写法从工程文档的角度没错,但Agent在决定要不要调用时,读到的不是"这个函数是什么",而是"当前任务符不符合这个技能的使用条件"。所以我建议把description写成触发条件式的句子,明确给出使用场景、前置条件、不适合的情况。

我用一个实际例子说明。下面是我在某个模拟项目里给"获取订单状态"技能写的定义:

name: get_order_status description: > 当用户想要查询订单的物流进度、当前状态、签收情况时使用。 适合包含"订单号""物流""快递""到哪了"等意图的请求。 如果要查询历史订单列表,不要使用本技能,改用 list_orders。 version: 1.2.0 parameters: type: object properties: order_id: type: string description: 订单号,格式如 ORD-2024-xxxx mobile_tail: type: string description: 收件人手机号后四位,用于身份校验,可选 required: [order_id] execute: type: function entry: actions.get_order_status timeout: 5

看这段描述,"适合包含...意图的请求"和"如果要查询历史订单列表,不要使用本技能"这两句话,比单纯写"查询订单状态"有用得多。因为Agent的技能选择本质上是一个语义匹配任务,你给它越清晰的边界,它越不容易选错。

2.2 参数契约:不让模型自由发挥,也不让业务侧兜底

参数设计是技能体系里另一道关键防线。模型调用函数时天然会"脑补",你没约束的字段它就会编。所以参数必须用JSON Schema这类结构化方式做强制契约,哪些必填、哪些可选、格式是什么,一旦模型给出的参数不符合契约,技能框架应该在调用前就拦截,而不是让业务代码去处理各种脏数据。

我建议每个技能在注册阶段就完成参数Schema的校验能力测试。做法很简单:准备几组典型输入(正常输入、缺必填字段、类型错误、完全无关的字段),跑一遍校验逻辑,确保框架能挡下错误调用。这块我吃过亏,早期图省事,让业务函数自己判空,结果模型偶尔漏传了order_id,业务代码抛了异常,整个对话流程就断了。后来把校验放到技能框架层,错误在入口就被接住,还能返回友好的提示让模型自行修正,稳定多了。

2.3 执行体:函数式、提示词式、混合式

执行体是技能里真正干活的组件,我接触到的项目里大致有三种形态:

  • 函数式:技能对应一段可执行的代码,输入参数,返回结构化结果。适合确定性强的操作,比如查数据库、调API、读写文件。
  • 提示词式:技能本身不作为代码执行,而是作为一段"专家指令模板",由模型按模板完成推理。适合需要LLM发挥语义理解能力的场景,比如总结文档、生成邮件、加工文案。
  • 混合式:先执行函数取到结构化数据,再让模型参考数据生成最终回复。适合"工具负责取数、模型负责表达"的组合场景。

这三种没有谁绝对好,关键是匹配你任务的本质。只要任务结果是确定性的,就尽量用函数式,把可变性收窄;只有需要模型做语义加工的时候,才用提示词式。我见过一些项目把"计算订单金额"也做成提示词式技能,结果模型偶尔算错,这种就属于用错了形态。

2.4 技能粒度怎么切?这决定的不是代码质量,是Agent的智商

粒度是技能设计里最考验经验的一环。切得太粗,一个技能里塞了太多逻辑分支,Agent调用时容易"拿大炮打蚊子",而且到底该不该调这个技能,边界也模糊;切得太细,技能数量爆炸,Agent在几十上百个技能里做选择,匹配准确率反而下降,调度开销也变大。我对粒度取舍的判断依据是:技能的使用意图是否单一可描述。

我做过一个对比,把两种极端情况列出来供你参考:

粒度优点缺点适合场景
粗粒度(意图多、逻辑复杂)技能数量少,Agent的候选集小描述难写,触发边界模糊,不便复用高度垂直的专用Agent
细粒度(意图单、逻辑简单)描述容易写准,触发准确率高,便于组合复用技能数量多,需要更强的调度机制多任务通用型Agent
折中(按域聚合)在候选数量和准确性之间平衡需要在设计阶段做领域分析大多数实际项目

我给一个具体例子:把你的Agent能力拆成"订单域""库存域""客户域",每个域下面再挂若干只做单件事的技能。这样既控制了顶层候选规模,又能保证具体技能的边界清晰。这种折中是我目前最推荐的做法,尤其适合业务流程复杂的内部系统。

3. 技能注册与调度:让Agent在正确的时机调用正确的能力

有了技能定义,下一步就是解决"什么时候调哪个"。这是技能体系真正有技术含量的部分。它包含三块:注册表、检索机制、调度策略。

3.1 注册表:Agent的能力清单在哪里

注册表是一个中心化的技能目录。所有技能启动时先注册进去,供检索模块使用。技术选型上,简单项目用内存字典就够了,复杂项目可以用独立的配置中心,让技能支持远程加载。我自己的经验是,注册表不止要存技能元信息,还要缓存一些统计信息,比如最近调用次数、平均耗时、最近一次是否成功,这些对后续做调度优化有价值。

下面是一个很精简的注册表实现思路:

# skills_registry.py class SkillRegistry: def __init__(self): self._skills = {} def register(self, skill_meta: dict): self._skills[skill_meta["name"]] = { **skill_meta, "call_count": 0, "error_count": 0 } def unregister(self, name: str): self._skills.pop(name, None) def list_skills(self): return list(self._skills.values()) def get(self, name: str): return self._skills.get(name)

注册表的Unregister也很重要,上次我在某个环境里热更新一个技能,忘了先下掉旧版本,新旧两个同名技能同时在列表里,模型看到两个"get_order_status"直接懵了,随机抽了一个调用,结果行为不稳定。所以注册表一定要提供显式的下线接口,更新技能时先unregister再register,保证同一时刻只有一个活跃版本。

3.2 两种检索方式:关键词匹配与语义匹配的配合

当Agent接到用户请求,它需要从注册表里挑出可能相关的技能。这一步通常有两条路线:

  • 关键词/规则匹配:快、可控、开销低,但泛化能力差。适合技能描述里有明确业务术语的场景,比如"订单""库存"。
  • 语义匹配:用Embedding算用户意图和技能描述的相似度,泛化能力强,但需要维护向量索引,也有一定延迟。

我在实践中发现,只靠语义匹配并不稳,因为技能描述里的术语和用户口语里的表达经常对不上,比如技能叫"inventory_query",用户说的是"仓库还剩多少货",纯语义匹配容易出现似是而非的结果。比较稳的做法是先做关键词粗筛,再做语义精排。粗筛先根据规则把明显不相关的技能滤掉,缩小候选集,然后用语义打分从候选里挑Top K,K建议控制在3到5之间,再交给模型做最终决策。

为什么要把最终调用决策交给模型,而不是直接按相似度取最高?因为模型能看到对话上下文、能理解用户当前的整体目标和语气,这些是向量相似度算不出来的。语料匹配适合做候选召回,不适合做最终决定。这也是我在实际测试里反复验证过的:强制Top1直接执行,错误率明显高于"TopK给模型选"。

3.3 冲突消解:两个技能都像,怎么办

技能多了以后,难免出现边界重叠。比如"get_order_status"和"get_order_detail",前者侧重物流状态,后者侧重订单条目明细,但如果用户说"查一下我那个订单",两个技能的Description都能匹配上。这时候调度层需要有冲突消解机制。

我的做法是给技能加一个priority字段,语义匹配分相同或相近时,优先选优先级更高的技能。同时,技能描述里要写清楚区别项,比如"查询订单条目明细,不要用它查物流状态"。这两招配合下来,大部分边界重叠问题都能解决。如果还不够,就说明这两个技能该合并,或者需要在一个技能里加更细的分支参数,而不是硬撑两个独立技能。

3.4 动态更新:技能要能热插拔,而不是重启改代码

Agent上线后,业务的调整会很频繁:规则变了、接口字段变了、新增了一个能力。如果每次都要改代码重启服务,技能的"可插拔"优势就没了。所以技能框架一定要支持热加载。我在某个模拟项目里的做法是:技能模块独立成包,目录结构固定为schema.yaml+actions.py,文件变化时通过watch机制自动重新注册。

热更新的注意点有两个:一是版本意识,技能文件里要有version字段,更新时如果版本号没变但代码变了,要报警而不是静默覆盖;二是参数兼容,新版本技能如果删了某个老参数,必须确认没有存量流程依赖它。这两个点坑过我好几次,有一次新版本把mobile_tail字段从必填改成了可选,但某个旧流程还在传这个值,结果校验层直接报错,排查了半天才发现是版本不兼容。

4. 实测中暴露出的真问题:技能冲突、上下文污染与性能开销

纸上谈兵说得再好,上线跑几轮就会露出真实问题。下面这几类问题是我在实测中反复遇到的,也算是最常见的"技能体系翻车点"。

4.1 技能描述"说得太抽象",模型根本选不对

第一次给技能写description时,我很自然地写成了"该技能用于执行数据查询"。后来看日志发现,模型压根不调用这个技能,问什么都是一副"我不会"的样子。原因很直接:模型在选技能时,只看到技能的name和description,没有业务示例,也没有触发场景的具体描述,它无从判断"数据查询"到底对应什么操作。

我后来的做法是给description补充两层信息:一层是触发场景(什么情况用),一层是反例(什么情况不用)。就像前面订单状态那个例子里写的:"如果要查询历史订单列表,不要使用本技能"。不要小看反例的价值,它给模型划出了决策边界,模型的选型准确率会有明显提升。另外,有条件的项目还可以在注册表里维护一个"典型问题示例"库,每个技能配三到五个用户真实问法,检索时做相似度匹配用。这个是网上很少写、但实测非常有效的技巧。

4.2 上下文污染:把技能全文塞进Prompt,Token直接爆炸

技能体系如果实现得粗糙,最常见的做法是:启动时把所有技能的description拼成一大段放进上下文,让模型自己记住。技能少还好,技能一多,问题立刻出现。我在一个技能规模达到30个左右的Agent上做过统计:每个技能描述平均300字,30个就是9000字,再加上系统提示词和对话历史,单次请求轻松破两万Token。效果并没有变得更好,反而因为上下文太杂,模型把注意力分散到不相关的技能描述上,主任务理解出现偏差。

解决思路是分级加载:系统上下文里只放精简版技能清单,每条技能只保留name、一句话描述、参数摘要。等到模型决定要用某个技能时,再把该技能的完整定义(详细description、JSON Schema、执行逻辑)注入。这个思路跟操作系统的按需分页很像,核心思想是"用多少、加载多少"。我改造后,单次请求的Token消耗降了将近一半,模型对主任务的理解也明显更聚焦。

4.3 技能之间的隐式耦合:改一个技能,悄悄弄坏了另一个

技能体系做出来的初衷是解耦,但如果实现时没注意隔离,反而会制造出更隐蔽的耦合。我在某个项目里遇到的情况是:技能A在运行时把某个临时结果写进了全局变量,技能B在后面复用这个值。刚开始一切正常,后来某次更新把技能A的内部逻辑改了,不再写这个全局变量,技能B立刻拿不到数据,整个流程失败。排查了很久才定位到是隐式共享状态的问题。

为了避免这类问题,我给技能执行加了三条规范:输入只来自参数、输出只走返回值、不得读写模块级可变状态。技能之间需要数据传递时,通过上层编排层显式传递,比如技能A的输出结构化成字段,由编排层决定要不要喂给技能B。这个规范看起来简单,但能堵住绝大多数"改一个技能搞崩另一个"的悲剧。

4.4 性能开销:语义检索全量跑,延迟直接翻倍

技能数量上来以后,调度层的性能会成为瓶颈。我在一个技能数量40个左右的项目里实测过一组数据(基于我自己的模拟项目X):

调度方式平均耗时备注
全量关键词匹配2-3 ms快,但召回不准
全量语义匹配45-60 ms准确率高,延迟偏高
关键词粗筛 + 语义精排(Top5)8-12 ms延迟可控,召回准确率基本持平

我一开始用的是全量语义匹配,觉得准确率最重要。结果一次会话里如果涉及三四次技能调用,光调度就占掉近200毫秒,加上模型推理时间,用户体感明显变卡。改成关键词粗筛加语义精排之后,延迟降下来了,准确率只是轻微波动,完全在接受范围内。另外我还加了缓存:同一会话内已检索过的用户意图直接复用结果,进一步把调度开销降了下来。

5. 经验沉淀:我最终推荐的技能组织范式

研究了这么久的技能体系,归根到底要落到"我的项目里到底怎么组织这些技能"。下面这套范式是我沉淀下来后最顺手的一种,适合以Python为技术栈、有一定代码规范诉求的团队。

5.1 目录结构:把技能当独立微服务来养

我推荐的物理组织方式如下:

skills/ ├── order_query/ │ ├── schema.yaml # 技能元信息 + JSON Schema │ ├── actions.py # 技能执行逻辑 │ └── tests/ │ ├── test_schema.py # 参数契约测试 │ └── test_execute.py # 执行逻辑测试 ├── inventory_query/ │ ├── schema.yaml │ ├── actions.py │ └── tests/ ├── __init__.py # 技能自动发现与注册入口 └── registry.py # 注册表实现

每个技能都被当成一个可以独立演进的小模块。这里有一个很关键的点:actions.py里不要写任何"这份技能该怎么被调用"的说明,那是schema.yaml干的事;也不要写"为什么这么实现"的业务故事,那应该落在文档和PR描述里。代码只负责一件事——把输入参数变成可靠的输出结果。职责分清楚,后面的维护才轻松。

技能和微服务的相似之处在于:独立的生命周期、独立的接口契约、独立的重启和升级节奏。但技能比微服务做得更轻,它不要求独立部署、独立数据库,这就意味着你可以用非常低的成本快速新增一个技能:建目录、写schema、写actions、跑测试,注册进注册表,完事。

5.2 双重要求:每个技能都要过"单测+回归"

技能体系一旦搭起来,测试策略必须跟上。我的最低要求是两套测试:技能级单测和全链路回归测试。

技能级单测是针对每个技能独立执行的测试,重点覆盖三块:参数校验的正确性(合法入参能通过、非法入参被拦截)、执行逻辑的正确性(给定Mock入参返回预期结果)、异常处理(依赖服务挂了返回什么错误结构)。这块测试保证了每次改动技能的内部实现时,不会把自己那层弄坏。

全链路回归测试则是模拟真实用户对话,验证"意图识别→技能选择→参数抽取→技能执行→回复生成"整条链路。我常用的做法是准备一份基线问题集,比如把公司业务里最高频的100个问题录进去,每次技能库更新后跑一遍,对比模型给出的回复质量和技能调用路径是否符合预期。一旦某个技能描述改坏了,会在这份回归测试里立刻暴露,而不是等用户吐槽才发现。

5.3 版本管理:技能更新要有"变更记录"意识

技能的版本管理,尤其是AI相关技能,跟普通代码版本管理不太一样。普通代码关注的是函数行为是否变化,而技能还有一个更重要的维度——Agent的行为会跟着变。同一个"get_order_status"技能,v1.1和v1.2在代码上可能完全兼容,但因为Description里多了一句话,模型调用它的频率和质量可能就变了。

所以我建议每个技能都在schema.yaml里维护一个changelog字段,每次更新时写清楚改了哪个字段、对Agent行为可能有什么影响。这个习惯在排查问题时会救你一命。我印象最深的一次是:某次技能库批量更新后,模型的整体表现莫名下降,我逐个回滚定位到是某个技能的description里加了一句"适用于用户咨询订单问题时",结果把本来该走其他技能的请求也吸引过来了。如果没有changelog,根本不知道这句改动是什么时候进去的。

5.4 最后的操作建议:从最痛的Prompt段落开始拆

如果你正在考虑给自己的Agent引入技能体系,我的建议是不要一上来就推倒重来,把一个庞大Prompt彻底拆掉。那会让整个系统在很长一段时间里处于不稳定状态,尤其当你的业务规则特别多的时候。

更稳的打法是从最痛的点开始拆:找到Prompt里报错率最高的段落、模型最容易理解偏差的功能、或者改动最频繁的业务逻辑,先把这一块抽出来做成第一个技能,跑通注册、调度、测试全流程。有了第一个技能的成功样板,再逐步迁移下一个功能。这样每个阶段系统都是可用的,风险被控制在一个可控范围内,同时你也会逐渐积累技能设计的经验手感,而不是一次大重构后发现自己根本驾驭不了这套体系。

我实际体会里,技能体系的建立是一个持续演进的过程,它不是一个一次性交付的工程,更像是在给一个复杂的系统逐步装上"肌肉"。第一个技能可能花了你不少时间,第二个就快很多,到后面你会发现,每新增一个能力,就是新建一个目录、写一份schema、做一组测试。Agent的智力边界扩展起来,会变得异常清爽。

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

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

立即咨询