这个项目标题看似简单,"agent-skills"四个词,做过大模型应用落地的人应该能立刻get到分量。它背后是一个完整的、决定智能体上限的子系统。我过去在几个真实项目里反复踩过这块的坑,从最初把技能简单做成"函数注册表",到后来理解到技能其实是"模型与世界交互的协议",中间积累了非常多值得写下来的经验。这篇文章就围绕"如何构建一套可靠、可扩展、能让模型真正用好的技能系统"来展开,大多数内容也来自我实际调试时的记录和对常见踩坑的复盘。
1. 为什么"技能"是这个时代智能体的核心短板
1.1 从"模型聊天"到"工具调用"的转变
先回到一个最基本的观察。过去两年,基于大语言模型的对话系统遍地都是,但真正让人觉得"这玩意儿能干活"的,往往不是因为它话术漂亮,而是因为它能调用外部能力:查数据库、发请求、读写文件、执行计算。这个"外部能力"落到工程上,就是一个又一个可被模型感知和调用的函数,也就是我们今天说的"技能"。
单纯靠模型内在的知识,它是无法操作实时系统的。举个例子,让模型回答"当前系统里有多少个在线节点",它只能胡编,除非我们给它一个查询技能。而一旦有了技能,它就变成了一个"能动手的助手"。所以,"agent-skills"这个标题的核心价值,不是教模型更会说话,而是让模型更会做事。它是一切自动化、一切真实业务操作发生的基础单元。
在技术选型层面,常见的做法有三种,我列出它们的优缺点供参考:
| 实现方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 直接把函数源码塞进system prompt | 实现简单,模型能看到内部逻辑 | 上下文爆炸、泄露实现细节、函数一多模型就糊涂 | 玩具Demo、内部一次性脚本 |
| 使用标准函数声明格式,把函数schema传给模型 | 结构化、省token、主流生态支持 | schema写不好时模型调用频繁出错 | 中小规模生产系统,绝大多数情况 |
| 独立技能服务,通过注册中心和协议动态暴露 | 可动态增删、权限可控、支持多技能协作 | 架构复杂度陡增,需要配套治理体系 | 大型平台,多团队协作,技能数量超过几十个 |
我的看法是,如果技能数量少于20个,老老实实用第二种就够了,把schema质量打磨好比什么都强。技能数量一旦超过50,第三种架构才会体现出优势。很多项目一上来就追求微服务架构,结果光管理技能就累死,反而忽略了模型真正需要的是"清晰的指令、准确的参数、可预期的副作用"。
1.2 技能究竟是什么:不仅仅是工具注册
先纠正一个常见误解:技能不等于函数。函数是客观存在的实现,技能是"模型可理解的、关于这个函数如何使用的一组契约描述"。换句话说,技能由两部分组成:
- 执行端:真实的代码逻辑,干活的
- 描述端:给模型看的说明书,告诉它这个技能有什么用、参数怎么填、什么时候该用、什么时候不该用
很多人把精力全放在执行端,代码写得很漂亮,但描述端草草几句话就交差。结果是模型要么不调用这个技能,要么调用时传一堆错误参数。技能描述写得好不好,直接决定了智能体的"智商"。这里我敢说一个比较绝对的经验:调试一个大模型应用时,遇到"模型不按预期调用工具"的问题,十有八九是技能描述写得不够好,而不是模型不行或者上下文不够长。
所以,这篇文章后面谈的所有内容,本质上都是在讲一件事:如何写好、管好、度量好这套"模型与世界的契约"。
2. 技能系统的核心设计思路
2.1 我是怎么拆解一个技能的
一个完整的技能,我会从五个角度去定义,少一个都不踏实,这在真实项目中是有深刻教训的。
第一,技能名称与定位。名称要短、要语义清晰,比如"查询用户订单"而不是"fetch_order_details_for_user_account"。后者虽然更精确,但占用模型注意力,而且太长的名字容易在模型推理时被截断。定位则是"这个技能是干嘛的"一句话总结,通常写入技能的description开头。
第二,触发条件。一定要说清楚"什么情况下该用我"。这块是大多数人会忽略的。我见过很多技能描述写成"获取天气",但没写"仅当用户明确询问天气时使用"。看起来是废话,但模型确实会因为没有负面约束,而在用户问"今天适合穿什么"的时候胡乱调用天气技能。
第三,参数契约。每个参数的名称、类型、取值范围、必填可选、默认值,都必须清清楚楚。这里尤其要注意枚举值的写法,不要把取值范围藏在文档式描述里,而要在schema层面直接约束,能少让模型"猜"一次就少一次。
第四,副作用声明。这个技能是否会修改数据?是否会进行不可逆操作?是否需要二次确认?这些信息如果不在描述里写清楚,模型很可能在用户还没确认时就执行了破坏性操作。对于带副作用的技能,我习惯在描述里加一句"仅在用户明确同意后调用"。
第五,输出格式。模型需要把技能执行的结果包装成回答。如果技能返回的是原始结构,模型还要自己转述,这时候最好在技能描述里说明"该技能返回的是JSON字符串,请提取其中的message字段向用户播报,不要暴露原始技术细节"。
以上五要素,是我目前验证下来最能稳定提升技能调度可靠性的拆法。后面文中的"技能设计模板"我会再给一份可以抄作业的版本。
2.2 技能描述的质量直接决定智能体智商
我做一个不太严谨但很好用的类比:技能描述之于智能体,就像API文档之于开发者。文档晦涩,再好的API也没人会用;文档带坑,再聪明的开发者也会误用。大模型本质上是"最遵守字面意思的实习生",它不会质疑你描述里的歧义,它会按照字面理解去执行。
基于这个认知,我在写技能描述时给自己定了几条铁律:
- 每条技能描述必须有"该技能能做什么"和"该技能不能做什么"两部分。
- 参数描述里禁止出现形容词和模糊词汇,如"较大的数字""用户最近的数据",全部替换为具体数值或时间范围。
- 描述里不要用否定句写参数约束,比如"如果没有传ID就不要查询"。模型容易漏掉否定逻辑。正确写法是"ID为必填参数,缺失时向用户索要"。
- 用具体例子代替抽象说明。比如某个日期参数,与其写"格式为YYYY-MM-DD",不如写"例如2025-01-01,格式为YYYY-MM-DD"。
- 同一技能的描述全文不超过三条核心指令,模型会被过长描述中的关键信息稀释。
我见过一个把技能描述写了上千字的项目,作者希望把各种边界情况都塞进去,结果模型在实际调用中频繁漏参数、报错,原因就是关键信息被淹没在长文本里。后来精简到120字以内,准确率肉眼可见地涨了一截。我一般要求自己在写完技能描述后做一次"英文翻译法"自测:把描述翻译成英文再翻译回来,如果意思变了,说明描述有歧义;如果翻译后显得很蠢,说明描述逻辑不够直接。
2.3 为什么技能需要组合而不是堆砌
等到技能数量多起来,你会遇到一个更深的挑战:模型该选哪个技能。给模型100个技能,它并不会像一个熟练工人一样随便挑,反而会因为选择过多而犯迷糊。这时候,组合式技能就变得尤为重要。
组合式技能是两个或多个原子技能通过编排形成的新技能。比如,"生成周报"复合技能 = "查询本周日志" + "分析commit记录" + "生成Markdown文档"。我习惯把原子技能做小做标准,把组合技能做成一种"定向路由",让模型先选择组合技能,再由组合技能按固定顺序调用原子技能。这样做有两个好处:一是模型只需要做少数几个"关键决策",而不是在100个技能里大海捞针;二是组合技能的编排逻辑是确定的,不会因为模型每次推理的随机性而出现"这周先查日志、下周先查commit"这种不稳定行为。
你可以把技能体系想成一个工具箱:原子技能是各式各样的螺丝刀、钳子、锤子,而组合技能则是"电路维修方案""家具组装流程图"。后者的价值不在于单个工具,而在于工序和顺序。模型是很好的"方案执行者",但不是一个天生的"工艺规划师"。我们设计技能系统时,应该尽量把规划的重任留给自己,把执行的空间留给模型。
3. 实操:从零构建一套可用的agent技能
3.1 基础技能:从计算到查询
说再多理论,不如跑通一个最小闭环。我用一个模拟项目X的具体案例,与你一起走一遍技能实现的全过程,这样你能清楚地看到每一步怎么落。
场景需求是"内部运维助手",这个助手需要能回答三类问题:
- 当前服务器CPU和内存占用情况
- 某个服务的运行状态
- 常见部署文档查询
这三类能力对应两个原子技能和一个知识技能,分别是"查询服务器指标"、"查询服务状态"、"搜索运维文档"。我首先定义一个统一的函数调用格式,这里我选JSON Schema风格,因为主流模型对它的理解更准确,生态支持也好。为了简洁,我用伪代码展示核心结构:
{ "type": "function", "function": { "name": "query_server_metrics", "description": "查询指定服务器的实时CPU使用率、内存占用率、磁盘IO负载。仅在用户询问服务器性能、负载、资源占用时使用。服务器ID为必填参数,缺失时请向用户索要。返回结果为JSON对象。", "parameters": { "type": "object", "properties": { "server_id": { "type": "string", "description": "服务器唯一标识,形如SVR-001,可以从用户描述中提取,若无法提取则要求用户补充." } }, "required": ["server_id"] } } }这个schema看起来很简单,但里面有几个反直觉的细节。比如我没有提供"查询所有服务器"的路径,而是强制要求server_id。很多初学者会做一个"不传ID就返回所有服务器"的兜底逻辑,但对于模型而言,兜底逻辑会鼓励它偷懒不提取参数。相反,强制必填参数会让模型主动从上下文里找服务器编号,找不到就向用户提问,这样交互会更可控、更准确。
服务状态查询技能类似,但它的描述里我增加了一条约束:"本技能不负责重启服务或修改配置,若用户提出此类需求,明确告知需要手动介入。"设定这条约束的目的,是为了防止模型在权限边界之外擅自执行操作。运维场景中,查询和变更的权限必须分离,这是生产环境的底线。
3.2 技能管理与路由
最小闭环跑通后,就要面对第二个问题:技能多了之后,如何组织、路由、更新。
我在某跨平台系统中实践过一套相对轻量但很多场景下都足够用的管理方案,核心思想是"目录化 + 注册中心 + 环境隔离"。先在代码库里建立这样的目录结构:
skills/ system/ query_server_metrics.py query_service_status.py search_docs.py order/ create_order.py cancel_order.py analytics/ report_daily.py每个子目录代表一个技能域,目录名对应描述中的领域字段。这里领域字段的价值体现在路由阶段的大粒度过滤:当用户问运维问题时,模型能更大概率优先选中system域的技能。
注册中心我实现为一个简单的Python字典加载器,启动时扫描所有技能目录,校验schema合法性,然后注册到内存中。每个技能除了schema,还带元数据配置,例如:
SKILLS_META = { "query_server_metrics": { "version": "1.2.0", "domain": "system", "owner": "infra", "timeout_sec": 10, "allowed_models": ["default"], }, "query_service_status": { "version": "1.0.1", "domain": "system", "owner": "infra", "timeout_sec": 5, }, }这套元数据有什么实际价值?第一,版本号支持"技能灰度更新":新版本schema先只对部分测试会话生效,没问题再全量放量。第二,超时时间保证技能执行不会拖垮整个agent流程,一个查询技能如果5秒内不返回,就不如不调用。第三,域信息和拥有者信息用于审计和权限控制,这是上生产前的硬性要求。
路由策略上,我查了不少资料,也验证了一种简单有效的方法:先按领域粗筛,再按相关性精排。具体做法是,把所有技能的name和description拼成一段文本,用向量检索按相似度召回TopK,然后把召回结果连同用户问题一起交给模型做最终决策。这能省下大量token,也能减少模型在处理上百个技能时的"迷失"情况。尤其模型支持OpenAI风格函数调用的时候,先粗筛再精排,效果远好于把全部技能都塞进请求里。
3.3 好用的技能设计模板
为了让你能直接上手,我把经反复验证的一套"技能设计模板"分享出来。这个模板的适用范围很广,从简单的查询到复杂的审批流都能套用。我建议把关键的description模板固定下来,甚至可以做一套lint规则来检查新技能是否符合模板规范。
命名规则:动词 + 对象,动词统一用query/create/update/delete/search/execute六种。这样当模型面对"查询订单"和"创建订单"时,能快速区分操作类型。很多失败的调度案例,问题就出在把动词写成do_operation这种含糊形式,模型理解不了到底是查还是改,实际中使用准确的动词后,混用率明显下降了。
描述公式:[功能一句话] + [触发条件] + [禁止事项] + [参数说明] + [副作用说明],各部分用句号分隔,不要用分号,不要用换行。模型在处理分号时偶尔会忽略后半句,句号则更干脆。参数描述公式:参数作用 + 取值格式 + 来源提示。这三个信息缺一不可,缺少来源提示时,模型经常会凭空捏造参数值。
架构上,我总结了一份通用模板,可以参考:
{ "function": { "name": "动词_对象", "description": "该技能可以[功能],当用户[场景]时调用。禁止[反向场景]。除[必填参数一]和[必填参数二]是必须提供外,其他参数均为可选。本技能[是否改动数据],如需二次确认请等待用户授权。", "parameters": { "type": "object", "properties": {...}, "required": [...] } } }这个模板最核心的价值,是把"技能之间边界"通过统一格式强制划清。你可能会觉得"禁止事项"写在description里好像有点多余,但这是我经过调参对比测试后确认最有效的防误用手段,建议保留。
4. 技能系统落地中的常见问题与排查技巧实录
4.1 问题一:技能不触发或乱触发,怎么定位根因
这是所有做agent落地的人都会遇到的头号问题,我再单独详细说说。症状分两种:该调的不调,不该调的瞎调。很多新手一上来怀疑模型能力不行,直接换更强的模型,但换完后问题照旧。根本原因通常在技能设计上。
我最常用、也最有效的排查方法是"隔离测试"。把用户的那句话单独拿出来,在一个干净的会话里只挂一个技能A,看模型是否调用。再单独只挂技能B,看是否调用。如果单独挂都正常,两个一起挂就乱套,问题基本出在"技能描述互相干扰"上。
什么情况会互相干扰?典型的是两个技能的description里有相似词汇。比如"查询服务器指标"和"查询服务状态"都包含"服务器"和"查询",模型就容易混淆。解决办法是区分"术语表":每个技能负责的实体用不同术语表达,比如前者专门用"负载数据",后者用"运行状态",把各自的独特词汇写进description开头,能有效降低混淆。
第二种常见原因是参数缺失。模型想调用技能,但发现用户问题里缺少必填参数,于是放弃调用,选择直接回答。这种情况的现象是:模型宁可胡编答案也不调用技能。解决办法是在描述里明确写上"若参数缺失,请向用户提问",这是给模型一个"台阶",它就知道在参数不全时可以主动询问,而不是跳过技能自己硬答。
第三种原因是技能描述与用户问题语义跨度太大。用户说"看看那台机器是不是要炸了",技能描述里却写着"查询服务器CPU使用率"。模型不会自动把"机器要炸了"转译为"查询CPU",除非我们用别名机制。给技能加一个aliases字段,写入口语化别名,例如"机器快炸了""负载很高""卡死了",实测对提升触发率效果非常明显。
4.2 问题二:技能执行期间的异常与超时,如何兜底
技能就算被正确触发,执行过程也有一堆幺蛾子:接口超时、返回数据格式不对、依赖服务临时故障。我把这些称为"技能链路异常",它们对agent体验的杀伤力极大,因为模型一旦拿到异常结果,要么原样复述一堆天书般的报错,要么自己编一个"成功"的结果回来。
我整理了一份快速排查对照表,都是调试中遇到的真实差错的总结:
| 症状 | 可能原因 | 排查与解决方案 |
|---|---|---|
| 模型返回"技能执行失败"但没细节 | 执行端抛异常被吞掉 | 在技能外层加try/except,把异常信息转成结构化对象,透传给模型 |
| 模型复述原始报错原文 | 错误信息里包含日志堆栈或内部路径 | 统一错误输出为"错误码+人话描述"两层结构 |
| 技能超时导致整个agent卡死 | 调用技能设了无限等待 | 给所有技能加超时上限,超时后返回"技能超时,请稍后重试"并允许模型转用其他方案 |
| 技能返回大量数据 | 模型需要全文阅读才能提取答案 | 把大返回拆成"摘要+详情引用",先让模型基于摘要作答 |
| 二次确认丢失 | 多轮对话中模型忘记之前确认过 | 把确认状态存进会话上下文变量,技能执行前检查状态 |
在处理异常时,有一条原则我一直在践行:永远不要让技能的错误出口变成"非结构化文本"。哪怕是个错误,也要用固定的JSON结构返回,例如{"code": "TIMEOUT", "message": "查询超时,请稍后重试"}。这样模型能稳定理解失败原因,而不是从一段换行混乱的报错文本里刨信息。
我还实践过一套"降级策略":为关键技能配置降级路径。比如"查询服务器指标"失败时,降级为"查询最近一次缓存指标",再失败就"返回最近一次已知指标并标注时间"。这一套配置下来,用户体感会好很多,毕竟agent的任务是"解决问题",而不是"报告系统不可用"。
4.3 问题三:技能版本更新后,老对话会话里的缓存怎么办
一个很容易被忽视的问题是:skill描述是随时可能更新的,但多轮对话的上下文是连续的。模型在某个会话中已经记住了旧版技能描述,开发者这时更新了schema,要求新增一个参数,模型会在后续轮次中依然按旧的参数结构去调用。这种新旧不一致,比skill一直没更新还麻烦,因为它会引发间歇性故障,非常难排查。
我在实践中采用的方案是"会话技能快照":当一段多轮会话开始时,系统把当时生效的技能schema快照存入会话状态。之后该会话内的所有技能调用都使用快照版本,而不是全局最新版本。这意味着新版本的schema只会影响新会话,旧会话按旧版本执行直到结束。用户不会因为schema更新而在对话中途突然遭遇"参数缺失"的报错。
这是很多文档不会写的细节,但对生产环境非常重要。你不想在周五下午更新了一套技能描述,结果周一早上收到一堆"你们的agent疯了"的投诉。快照机制虽然简单,但能把"发布流程"和"对话连续性"彻底解耦。
另外,技能更新后我习惯跑一遍回归集。我维护了一份大约包含几十条典型用户话术的测试集,覆盖每个技能的"触发-执行-返回"主路径,以及两条常见异常路径。每次技能变更后,脚本调用模型跑一遍回归集,对比各项指标是否有明显下滑。这里不要求用繁琐的自动化框架,简单写个脚本逐条调用、打印结果就行。能守住回归测试,技能系统的迭代才有安全感。
5. 技能系统的下一步:评估、进化与协作
5.1 如何用数据衡量技能质量
技能系统建好之后,最大的坑是没有度量体系,就无法持续迭代。每个技能的调用情况都应该被记录下来,至少包含以下字段:session_id、调用时间、触发的用户问题原文、模型输入的技能列表、最终调用的技能名、执行时长、执行结果、用户是否追问或纠正。这些数据沉淀一个周期后,就能看出谁在裸泳。
我在实际项目里扶持过一套很轻的"技能健康度看板":将数据聚合为三个指标,即触发率、成功率、修正率。触发率衡量该触发时是否触发成功;成功率衡量执行后是否返回正常结果;修正率衡量用户是否在技能执行后仍然追问不同问题。一个健康的技能,触发率应该稳定,成功率应尽量接近100%,修正率则应很低。如果触发率正常但修正率偏高,说明技能返回的内容虽然没报错,但没有真正解决用户的问题。这是最值得深挖的信号。
有个发生过很多次的案例:查询订单技能每个查询都成功返回了订单列表,但用户总是追问"这个订单是什么状态"。后来把用户追问的那一轮也记录下来放进分析,发现答案里压根没回传状态字段,模型也不知道该展示这个字段。修正率能帮你定位这类"执行成功但体验失败"的隐性坑,这比只看成功率靠谱得多。
5.2 尝试让agent自己演化技能
技能系统做到一定成熟度后,你会开始探索"让agent自动创建新技能"。这个方向很有想象空间,但我也建议谨慎落地。我亲测过一条可行的路线:当一个任务无法用已有技能完成,但模型成功完成了一次推理时,系统把这次推理的步骤记录为"候选技能"。后续验证评估它能否复现,再决定是否转正。
举例来说,用户要求"把本周所有未读消息汇总成摘要"。系统没有专门技能,但模型利用"查询消息列表"和"生成摘要"两个技能组合完成了任务。此时系统可以自动保存一个新的组合技能"weekly_unread_summary",它关联两个子技能和编排顺序,等待管理员审核启用。这能在不牺牲可控性的前提下,让智能体具备初级的"能力生长"能力。
但这里要明确:自动生成技能,要非常谨慎。我建议所有由agent生成的技能都标记为"实验性",只能在小流量会话中生效,并且要有完整的更新溯源。当技能自动生长"失控"时,运维能一键冻结它相关目录的所有版本,而不是在数百个技能里手工排查。技能进化是很好玩的探索,但它不是第一优先级。先把基础技能体系的可靠性和度量体系建好,再谈进化也不迟。
5.3 从技能到Agent协作:最终形态的思考
单个agent的技能再丰富,也只是孤军奋战。随着业务复杂度上升,必然走向"多agent协作"的形态:每个agent拥有各自的技能集,并通过协商机制互相委托。比如,"客服agent"接到一个要求退款的问题,它自身没有退款技能,但它知道"订单agent"有,于是通过agent间协议转发请求,由订单agent完成退款后回传结果。
这种模式下,技能的意义就不只是"模型与世界的契约",更升级为"模型与模型之间的服务契约"。技能描述需要额外说明"本技能可接收来自其他agent的委托请求""本技能的结果是否可被代理转发"等元信息。我还没有看到有完美的现成框架解决这个问题,现阶段大多还是基于约定schema的协议栈,但这方向我判断会是未来一段时间agent工程化的焦点。
在实现层面,多agent协作对技能系统的要求会更高:每个技能不仅要描述"做什么",还要描述"对调用者的权限要求""可接受的调用方身份""调用链路的审计信息"。这是从"技能"到"服务治理"的一次跃迁。如果当前你的技能结构还是纯函数式,建议在设计上预留一些字段给"调用方身份"和"令牌校验",以免将来重构伤筋动骨。
6. 最后的经验之谈
如果只让我挑三条最值得分享的心得,我会选这三条:
第一,给模型写技能描述,心态要摆正。它不是作文,不是论文,而是一份"给不那么聪明但极其较真的实习生看的操作手册"。说得越直接、越具体、越无歧义,效果越好。宁可看起来像说明书,也不要像散文。
第二,技能的数量不是越多越好。每增加一个技能,模型的选择成本就高一分。我见过有人往系统里塞了上百个技能,结果核心技能触发率反而下滑。与其堆数量,不如把十几个核心技能打磨到极致,再用组合技能去覆盖复杂场景。这个道理和真实团队管理很像:五六个靠谱的核心骨干,远胜于几十个边界模糊的兼职人员。
第三,一切技能系统的长期竞争力,都来自度量。没有调用日志,没有成功率,没有修正率,你根本不知道这个系统是在变好还是在悄悄腐烂。早一点建设埋点和看板,你就能早一点从"玄学调prompt"进阶到"数据驱动迭代"。
这篇内容基于我实际做过、调过、翻过车又修好的经验整理而来。技能系统这个方向,理论并不深奥,但细节极其密集。希望这篇文章能帮你少走几段弯路,也希望看到更多人在这个领域分享硬核的实操经验。对于任何刚起步的agent项目,我都建议从今天开始就认真对待"agent-skills"这几个字,它不是项目里的加分项,而是决定整个智能体是否真正可用的地基。