☰
Agent技能化实战:从单体提示词到可编排的技能架构
2026/10/14 10:15:13 网站建设 项目流程

过去几个月,我几乎把所有业余时间都砸在了一件事上,把业务里那个越来越臃肿的Agent机器人,重构成一套以技能为中心的架构。说白了就是围绕 agent-skills 这套思路做工程化落地:把Agent会做的事,从“写在提示词里的隐性能力”变成“注册在系统里的显式组件”。这个过程比我想象中曲折得多,也踩了不少文档里不会写的坑。这篇文章就把我拆解技能、设计注册规范、编排调度、建立评测机制、处理安全边界的全过程整理出来,包含最终沉淀下来的可复用做法。如果你正在构建自己的Agent应用,或者接手了一个功能越加越乱的多智能体系统,这篇文章应该能帮你省下不少试错时间。

1. 从“会聊天”到“会干活”:技能化要解决的核心矛盾

1.1 单体提示词是怎么一步步失控的

我最早做的Agent其实很简单,一个系统提示词加几个工具函数,直接在对话流里调用。演示的时候效果很不错,用户问天气、查日程、写周报都能完成。但随着业务场景增加,问题开始集中爆发。

系统提示词从上到下堆了几千字,功能描述、业务规则、Few-shot样例、输出格式约束全混在一起。模型的注意力根本抓不住重点,经常把A场景的规则套到B场景里。举个例子,我在提示词里写了“查天气时如果用户说冷,要顺便提醒穿衣”,结果模型在处理订餐需求时也蹦出一句“建议穿厚外套”。上下文越长,这种串味就越严重。

最难受的是没法评估。每往提示词里加一段能力,都要靠人工把所有场景回归一遍。改一次提示词,前面的功能可能莫名其妙就坏了。功能多了之后,你根本不知道是哪次改动影响了哪个能力,最后只能找块白板画依赖关系,越画越乱。

1.2 技能化的本质:把隐性能力变成显式组件

所谓技能,就是把Agent的一种行为能力封装成一份结构化定义。它至少包含这些信息:这个技能是干什么的、在什么条件下触发、需要哪些参数、执行后如何把结果接回对话、有哪些使用限制。

这样做的好处是,能力不再是一段飘在提示词里的文字,而是可以被检索、被验证、被单独改版的数据资产。

用一个生活化的类比:没有技能化的Agent像一本大杂烩笔记本,所有信息都写在同一页上,越往后越难找;技能化之后,Agent像一个带标签的工具箱,每个格子放一个盒,盒子上写明用途。模型只需要看标签决定取哪个盒,而不是把所有盒子的内容全部背下来。

这里我要强调一点:技能化不是否定提示词工程,而是把“能力描述的维护”这个环节独立出来。模型仍然需要系统提示词,但系统提示词的职责变了,不再是描述所有能力细节,而是负责统筹:知道有哪些技能可用、什么时候该用、怎么串联。我对比过两种方案的效果差异,在技能数量超过两三个之后,结构化方案在稳定性和可维护性上的优势非常明显。

对比维度单体提示词方案技能化方案
能力定位提示词里的段落结构化技能文件
修改影响面全局都要回归只影响单技能
可评估性人工逐一验证自动化评测集
复用性复制粘贴注册引用
扩展方式提示词越堆越长新增技能条目

1.3 一套技能系统必须解决的五个问题

从实际项目需求出发,技能化至少要覆盖五件事。

第一是能力发现。Agent要知道自己会什么,能在合适的时机找到正确的技能。这要求技能有清晰的描述和标签体系。第二是能力调用。给定技能描述和参数模式,模型能正确填参、正确发起执行。第三是能力组合。复杂需求往往要多个技能串起来,比如“查天气”和“生成穿衣建议”要配合。第四是能力治理。技能会持续增加,命名、版本、描述质量都需要管理手段。第五是能力评测。每次技能迭代,都要用固定标准判断到底是变好还是变坏,而不是凭感觉。

后面几个章节,就按这条线一一展开。

2. 技能定义与注册规范:先让Agent知道自己会什么

2.1 技能描述文件的核心字段与落地模板

我在项目里最终采用了一份四层结构的技能定义,用JSON存储。核心字段包括技能ID、版本号、自然语言名称、描述、标签、参数模式、返回值结构、执行端点和示例。拿一个天气查询技能举例,一份能直接落地的模板长这样:

{ "skill_id": "weather_query", "version": "1.3.0", "name": "查询指定城市天气", "description": "根据城市名获取实时天气与未来三天预报,适用于用户询问温度、降水、风力等气象数据问题。仅在用户明确询问某地天气时触发,不用于运动建议、出行规划等衍生建议。", "tags": ["天气", "查询", "生活服务"], "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "标准城市名称,例如A市、B市;若用户使用别名或模糊说法,需转换为标准城市名后再填写。" }, "days": { "type": "integer", "description": "预报天数,默认3,可选1到7。" } }, "required": ["city"] }, "returns": { "type": "object", "description": "返回结构包含 current.temp_c、current.humidity、forecast.daily 等字段。" }, "execute": { "type": "function", "endpoint": "skill://weather/latest" }, "examples": [ { "request": "明天A市会下雨吗", "params": {"city": "A市", "days": 3} }, { "request": "未来一周气温怎么样", "params": {"city": "B市", "days": 7} } ], "constraints": { "rate_limit": 30, "scope": ["public"] } }

每个字段都不是摆设。模型的决策链路是:先读 description 判断这个技能是否匹配当前用户意图,再读 parameters 学习如何填参,最后通过 examples 校准边界。三个字段里,description 管选择,parameters 管调用,examples 管纠偏。任何一个写得含糊,都会直接影响下游行为。

2.2 参数模式设计的三条血泪原则

参数模式的坑,我踩得最多。总结下来有三条原则值得守死。

第一条,参数尽量扁平。两层嵌套以内的对象模型,模型最容易填对。我之前有一个技能需要传用户画像参数,设计了三级嵌套结构,结果模型经常漏掉中间层,直接把最内层的字段对象塞进顶层。后来改成平铺加前缀命名,比如 user_profile_age、user_profile_city,正确率立刻上来了。参数结构和模型填参的准确率是直接挂钩的,越扁平越稳。

第二条,复杂枚举值必须写清候选。如果一个参数的取值有明确集合,要么列成枚举,要么在描述里写清楚。比如“地区级别:city/district/street”,如果你不写,模型非常容易自创值,比如填一个“county”。这种错误在评测里频繁出现,根源就是参数描述偷懒了。

第三条,required 只放真正必填的字段。很多开发者习惯把所有字段都设成必填,结果模型一旦缺参就不会动,宁可不调用技能也不愿冒险填。实测下来,给非关键字段设置合理默认值,把必填项压缩到最少,能显著提高调用率。

2.3 注册中心:技能清单与运行时裁剪

技能定义写好了,必须有一个地方做登记。我在项目里做了一个轻量的技能注册服务,每个技能在代码部署时自动注册,注册中心统一维护所有技能的元数据。

运行时的做法很关键:系统提示词里注入的不是完整技能文件,而是一份“技能清单摘要”,包括每个技能的名称、一句话描述、参数摘要。完整定义按需加载,当模型决定调用某个技能时,再动态拉取完整JSON。

这一步叫技能清单裁剪。它的目的是控制上下文长度。技能数量达到几十上百个时,如果全量注入描述,光技能介绍就能吃掉大几千token,既浪费成本又干扰模型。裁剪之后,提示词保持在可读范围,模型既能感知到“有哪些技能可用”,又不会被细节淹没。我对比过裁剪前后的效果,同样的任务量下,响应延迟降低了约35%,误选率也有下降。

3. 技能编排与调度:从“单点调用”走到“多技能协同”

3.1 验证下来够用的三种编排形态

技能丰富以后,复杂任务必须把多个技能串起来。我试过不少编排方案,包括重型的可视化流程图引擎,最后发现对于大多数对话型场景,三种形态就够用。

顺序编排是最常见的。比如“查天气之后再给穿衣建议”,先调天气技能拿到实时数据,再把数据传给建议生成技能。这种形式适合有明显先后依赖的流程。

条件分支处理分叉场景。比如用户输入地址后,先调用地址解析技能,再判断是否在服务范围内,在则调用下单技能,不在则调用附近门店推荐技能。这种形式要求调度器具备分支判断能力,通常会把条件判断交给模型,由它根据前置结果选择后续技能。

并行聚合处理对比需求。比如用户问“这两款设备哪个更适合办公”,同时调用两个参数对比技能,分别拿数据,合并后再生成回答。这种形态能明显缩短多步任务的耗时。

3.2 跨技能状态传递:中间上下文池

多技能协同最别扭的地方是状态传递。一个很常见的场景:技能A的执行结果,要作为技能B的输入参数。最开始我用的是传统工作流引擎的思路,把每个输出端口用线连起来维护数据流。但在LLM应用里,这往往会演变成一场灾难,因为模型生成的结果并不总是完全符合结构约定。

我最终改用了一个“中间上下文池”方案。每个技能执行完,把结构化结果写入一个共享上下文池,后续技能在描述里声明自己需要读取哪些前置输出,由调度器负责建立映射。这个方案的好处是解耦:技能之间不需要知道彼此的存在,只需要约定数据格式约定。

配套的动作是所有在执行技能返回结果时,必须统一包装成{status, data, error}结构。有了这一层标准化,下游技能读取数据就省心很多。记忆里最惨痛的一次,是技能A返回了裸的JSON数组,技能B期待的是一个对象,两边对不上,整个链路静默失败。后来所有技能都把结果包装进统一信封,这类问题基本绝迹。

3.3 技能冲突与动态选路

技能多了,难免出现功能边界重叠。比如“日程创建”和“日程修改”,描述里如果没写清楚,模型很容易在用户说“帮我约明天下午开会”时误调“日程修改”。

我的处理办法是维护一张技能冲突检测表。每个技能在注册时声明自己的明确触发词和排他领域。运行时,如果检索结果发现两个技能的相似度超过阈值,调度器不直接做最终决策,而是把候选技能连同它们之间的差异点,一起交给模型做二次确认。

更根本的解法是设计层就划清边界。我强烈建议一个技能只负责一个意图,不要在小技能里硬塞多功能。宁可定义多个细粒度技能,也不要做全能型选手。全能技能表面上看很省事,实际上在自动调用场景里,它就是误选率的最大来源。技能描述越聚焦,模型选错的空间就越小。

4. 技能评测与迭代机制:没有量化,改什么都没依据

4.1 把人工回归变成自动化评测集

传统测试Agent功能,靠人工一个个问。技能化落地以后,评测环节可以自动化,这是整个改造里收益最大的一块。

核心是构建一个评测集,每条评测用例包含:用户输入、预期触发的技能、预期生成的参数、预期输出要点。这个结构决定了一旦跑起来,你就能快速定位问题出在“选错技能”还是“填错参数”还是“生成不对”。

{ "case_id": "wc_001", "input": "帮我查下明天A市冷不冷", "expected_skill": "weather_query", "expected_params": {"city": "A市", "days": 3}, "expected_output_contains": ["温度", "穿衣建议"] }

评测执行逻辑很简单:把输入喂给Agent,记录它实际触发的技能和实际生成的参数,再和预期做比对。跑完一个技能相关的几十条用例,它的健康状况就一目了然了。

4.2 三个关键评估维度与健康度报告

我最终固定用三个维度给技能打分。

技能选择准确率,看期望技能是否真的被选中。误选率是最让人头疼的指标,因为错误路径一旦启动,后面的步骤全都会跟着歪。参数填充完整率,看期望参数是否都被正确填上,重点盯类型和枚举值是否符合约束。结果有效性,看技能执行后的返回能否支撑最终回答,这个维度负责捕获那些“选对了、填对了、但结果还是砸了”的场景。

每个技能单独跑分,形成一张技能健康度报告。报告跑完,哪个技能的评分在持续走低,就说明它的描述定义和现实场景不匹配,需要迭代。我习惯每周全量跑一次,十几个技能也就几分钟的事,比人工回归一整天靠谱太多。

4.3 从评测结果反推优化点,而不是瞎调提示词

评测的价值在于,它把“问题在哪”变成了一个可定位的事实,而不是一团模糊的体感。

技能选择不准,优先改描述。把触发场景写得更具体,同时补充负向描述,说明哪些情况不要用。参数填充不全,优先改参数模式,降低嵌套层级、写清枚举候选、压缩必填项。技能执行成功但最终回答不对,问题通常出在下游生成环节,这时候去调生成策略,而不是回头折腾技能定义。

版本管理也是这个思路的延伸。每次技能改动,必须带着评测集跑一遍回归,得分没有下降才允许合入。我还习惯给评测集按业务场景分目录,每新增一个功能点就补几条用例。这评测集的价值会随时间越滚越大,因为它是你对系统行为预期的唯一基准。

5. 实战阶段踩过的坑与应对方案

5.1 技能描述越“口语”,越容易被误调用

我一开始写技能描述,追求的是自然、好读。比如天气技能我写的是“帮助用户了解天气情况,包括温度、降水、风力等,也可以提供穿衣建议”。结果模型经常在用户问“今天适合跑步吗”这种衍生问题时误选天气技能,因为“适合跑步”和“天气、穿衣”在语义上确实相关。

后来我把描述改成“仅在用户明确询问某地气象数据时触发,不用于运动建议、出行规划等衍生建议”,误选率才明显降下来。这个教训说明,技能描述里要同时写“什么情况用”和“什么情况不用”,负向描述和正向描述同等重要。

5.2 技能超过五十个后,检索路由比“调用”更关键

技能数量超过五十个后,就算做清单裁剪,把所有技能描述一股脑喂给模型,效果和成本也都不理想。因为模型的注意力是有限的,候选太多等于没有候选。

我最终采用了两级路由。第一级是轻量分类器,用向量召回的方式从技能库里捞出Top K候选,K通常取5到8。第二级才是LLM决策,只把候选技能的完整定义交给模型做最终选择。这一步改造完成之后,技能库扩充到一百多个,响应延迟和误选率都保持住了。

这里有个经验:千万不要低估检索层的重要性。技能多了以后,路由准不准决定整个系统稳不稳。而这个检索器本身也需要评测,直接把技能选择准确率作为路由层的优化指标就能形成闭环。

5.3 权限与安全边界必须前置,不能上线后再补

技能背后是真实的数据访问和执行能力。一个只做查询的技能,和一个能修改数据的技能,风险等级完全不同。在agent-skills架构里,权限声明不应该是一个可选的附加项,而应该是注册的强制字段。

我在注册中心里给每个技能挂了scope字段,运行前做白名单校验。涉及写操作、外发消息、删除类动作的技能,必须走人工审批通道。没有权限声明的技能,注册服务直接拒绝,这条规则写在代码里,比写在文档里有效得多。

为什么我会这么强调?因为真实项目里我遇到过技能误调用导致线上数据被修改的险情。排查下来的根因就是两件事:技能描述写得太含糊,加上没有权限校验。一个描述模糊的删除类技能被模型选中,直接调用了接口。那次事故之后,权限声明成了我所有技能系统的硬性门槛,谁都不能绕过去。

再分享一个配套技巧:每个技能在执行前加一层“执行意图确认”,对高危操作强制二次确认,并且把“确认权”交给人工侧而不是模型侧。这会让部分场景慢个一两秒,但换来的安全兜底绝对值得。

整套agent-skills架构落地之后,最明显的变化是Agent的行为终于可以被度量、被拆解、被单独改进。以前调一个行为要靠玄学,现在每一项改动都有评测数据兜底,团队协作时也终于能明确说出“是这个技能的描述出了问题”而不是“Agent表现不好”这种空话。

最后再分享一个小技巧:如果你在建自己的技能库,给每个技能写一段“设计动机”备注,记录它当初是为了解决什么场景被创建的。这个备注不用进系统提示词,纯给人看。等技能库变大后,你会发现这段文字比任何文档都更能帮你做清理决策。哪些技能该合并、哪些该废弃、哪些描述已经偏离最初目标,看一眼动机就知道了。这个习惯,陪我渡过了好几次技能大扫除。

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

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

立即咨询