
Agent Skill是近两年Agent开发圈里很火的一个词但也是被误解最多的一个词。我见过不少团队把Skill当成“API封装”也有人把Skill和Tool混为一谈最后写出来的所谓Skill既没有触发条件也没有参数说明模型根本不知道什么时候该调用它。这篇文章想从我实际做Agent项目踩坑的经验出发把Agent Skill的概念、架构与开发指南完整梳理一遍——不堆术语就讲清楚它到底是什么、为什么要这样设计、怎么写才不容易翻车。先给个最简单的心智模型Agent本体是个“大脑”Skill是大脑可以按说明书操作的一双手。大脑负责理解任务、拆解步骤、决定什么时候用哪双手Skill负责把某个具体能力做得又快又稳。概念上好像很简单但一旦涉及架构设计和工程落地里面的坑相当多。1. 概念拆解Skill到底是干什么用的1.1 一个真实场景理解Skill的定位假设你要做一个企业知识库问答Agent最初版本只靠大模型回答效果很快就见顶了。模型答不了实时库存也查不了订单状态于是你给Agent接了一堆工具函数查库存、下订单、查物流。结果又出现新问题——工具越来越多模型经常在几十个函数里选错提示词稍微改一版工具调用逻辑就乱套整个Agent越来越难维护。如果你把“查库存”这个操作升级成一份完整的Skill情况就完全不同了。这份Skill不光包含查库存的API调用代码还包含一份行为说明书什么时候该查、参数怎么校验、库存不足时怎么处理、并发请求怎么合并、超时怎么办。Agent看到这份说明书后会在合适的场景下主动调用而不是像以前那样在茫茫函数列表里瞎猜。这就是Agent Skill的定位它不是简单的代码封装而是把“能力、规则、说明、边界”打包成一个Agent可以直接理解和执行的单元。Skill的价值不在代码有多复杂而在于让模型在正确时机、用正确参数、做正确动作。1.2 Skill、Tool、Agent、Workflow的区别很多项目把Tool、Plugin、Skill混着叫团队沟通时鸡同鸭讲。按我做项目的习惯把它们放在同一条能力链上对比更清楚概念核心问题抽象层级典型形态Tool单个原子操作函数级查库存API、发送邮件Plugin一组工具和资源的打包模块级浏览器插件、IDE插件Skill一个完整问题域的能力能力级库存查询Skill、合同审核SkillWorkflow多个能力编排的固定流程流程级采购审批流程、订单处理流水线Agent自主决策调度多个能力系统级客服Agent、数据分析Agent先用Tool解决“能不能做”再用Skill解决“做得好不好”。Tool是零件Skill是把零件按图纸组装成可独立运转的机器。Cost的比喻很好用Tool告诉你“每个元件长什么样”Skill告诉你“这台设备什么时候开机、怎么操作、会有哪些异常”。Skill和Agent的区别更关键。Agent是决策主体负责任务理解、规划、记忆、反思、多轮交互Skill是执行单元负责把Agent交给它的问题域处理干净。Agent决定做什么Skill决定怎么做。如果你把Agent做的大部分决策逻辑下沉到Skill里那你的Skill就退化成了Workflow反过来如果Skill什么都要问Agent再做决定它也失去了封装的价值。1.3 Skill的典型组成结构业界常见的Skill包结构说穿了就是“说明书加执行体”skills/ inventory_query/ # Skill目录名一般用短横线命名 SKILL.md # 行为说明书模型主要读这个 run.py # 执行脚本模型通过它完成实际动作 requirements.txt # 依赖清单 assets/ # 可选模板、样本数据、配置文件这套结构的核心设计意图是模型不直接读Python源码而是读SKILL.md这份说明书。说明书里写清楚了触发条件、参数表、返回格式、边界条件和异常处理策略模型判断“该不该用这个Skill”时对照的是说明书而不是去解析代码逻辑。很多新手第一次上手会问为什么不能把说明写进代码注释里原因是Agent的调度机制。Agent在用户发起请求后会把候选Skill的说明摘要通常是SKILL.md的开头部分或元信息加载进上下文而不是加载整个代码库。代码注释写在run.py里模型在决策阶段根本看不到写了等于白写。说明书必须独立存在并且首屏信息要足够关键。1.4 Skill的三种触发形态根据Agent框架不同Skill有三种触发形态理解它们对设计很有帮助工具注册式框架把Skill描述编译成工具Schema模型通过函数调用机制触发。适合参数明确、原子性强的Skill例如天气查询、数学计算。指令匹配式Skill的描述作为系统提示词的一部分模型在自由生成时自己决定是否执行。适合判断型、多步骤的Skill例如“用户抱怨时安抚并转人工”。显式指令式框架通过关键字或语义路由直接把用户消息转发给某个Skill。适合特定领域垂直动作例如“设计Logo”“生成周报”。实际项目中三者往往混用。架构上开放的设计是把Skill描述统一成一套Schema再由路由器决定走哪种触发方式而不是每种触发方式都重新写一套Skill。2. 架构设计Skill系统该怎么搭2.1 Skill注册中心与元信息建模架构上第一件事是建Skill注册中心。它有点像服务注册发现机制所有Skill启动时都向注册中心汇报自己的能力清单运行时框架才能知道当前有哪些Skill可用。注册内容的元信息建议包含以下字段{ name: inventory_query, version: 1.2.3, description: 查询线上商品库存余量与补货建议, trigger_keywords: [库存, 缺货, 还有货吗], parameters: { sku_id: {type: string, required: true}, warehouse: {type: string, enum: [华东, 华南, 华北]} }, output_schema: ..., timeout_ms: 3000, permissions: [inventory:read] }这套元信息解决三个问题模型决策时知道Skill会做什么、需要什么参数、产出什么结构框架执行时知道超时上限和权限边界人在审核时能一眼看出这个Skill动用了哪些系统权限。我在项目里吃过亏早期没有统一建模每个Skill自己定义入参格式有的用JSON字符串有的用位置参数结果路由层解析逻辑满屏if else后来花了整整两天重构才把参数校验统一成Schema驱动。制定规范越早后面的开发越轻松。2.2 描述词与触发条件的写法Skill能不能被正确触发百分之六十取决于描述词写得好不好。描述词写得模糊模型就会乱触发写得过度具体模型又会在边缘场景不敢触发。描述词设计可以直接套用一个三段结构第一段是什么一句话说清能力本体。第二段什么时候用列出触发场景和禁止场景。第三段边界注明吞吐上限、时效限制、依赖条件。我见过最好的一个例子是这样的查询商品库存信息适合用户在询问现货、缺货、到货时间时使用。 当用户询问价格、评价、产地时不要使用本Skill。 注意本Skill只返回当前库存快照不包含预购队列信息。这种描述对模型非常友好尤其负例给了很明确的范围。相比之下只写“库存查询”这种干巴巴的说明十次触发能错五次。2.3 上下文管理与状态传递Skill不是独立进程它运行在Agent对话上下文的约束下。架构上一个容易踩坑的地方是Skill内部产生的中间状态到底存在哪、保留多久、传给谁。推荐的分层原则是长期记忆用户偏好、历史事实放向量库或KV库Skill只读不写。会话状态当前订单、当前选中商品放会话上下文Agent统一管理。Skill内部临时状态临时计算结果、中间文件放Skill专用存储结束时清理。如果允许Skill直接读改写长期记忆会出现很讨厌的幻觉串线问题。我踩过的一个典型坑是让一个数据分析Skill顺手把“用户名称”写进了用户画像表结果把两个同名用户的信息合并了后续推荐逻辑全部漂移。从那以后我定了死规矩Skill只能通过框架提供的上下文接口做受控读写禁止直接访问底层存储。2.4 路由分发与并行调度当系统里有几十个Skill时架构上必须做一个显式路由器它本质上是一个轻量级意图识别服务。Router接收用户诉求在注册中心里筛出候选Skill列表按匹配分排序再把控制权交给选中的Skill。Router有两条路线可选基于规则的语义匹配或基于小模型的分类器。我的经验是先用规则把排序范围从几十缩小到三五个再用分类器精排最后结合模型工具调用来保证兜底。纯靠一个模型做几十类意图分类边界很模糊效果反而不如规则确定性好。并行调度是另一个常见的性能优化点。一个复杂请求经常能拆成几个互不相干的Skill并行执行例如“比较A和B两款手机的差价与发货时间”可以同时调度价格查询Skill和物流查询Skill。架构上要抽象出DAG调度层给每个Skill节点标记依赖关系能并行就并行串行路径才串行。2.5 权限、限流与安全隔离这一点我以前在架构设计里几乎忽略直到出了事故才补课。某个Skill接入了第三方供应商系统供应商的接口不支持并发而Agent自动调用了两次直接把对方压挂。从那以后权限与限流被提到了架构第一位。每个Skill的元信息必须声明三件事数据权限能读哪些库、执行权限能调哪些API、资源配额并发数、QPS上限。框架在调用前做一次权限校验在运行中做一次资源配额检查。安全隔离方面Skill的代码应该跑在独立沙箱或容器里至少也要做到进程级隔离尤其是那些调用外部命令或解析用户上传文件的Skill绝不能直接跑在主Agent进程内。3. 核心实操从零写好一个可复用的Agent Skill3.1 需求拆解与边界划分动手写Skill之前先回答四个问题这个Skill解决的“问题域”是什么比如“订单查询”比“查询接口”更像一个问题域。输入是什么输出是什么数据一致性要求多高什么情况下不该用这个Skill负例场景是什么失败时怎么处理是需要重试、降级还是直接向用户道歉这四个问题的答案直接决定SKILL.md怎么写、参数怎么设计、异常分支怎么铺。以我经验凡是后面改锅的Skill几乎都是因为最初没想清楚负例场景和失败策略。3.2 编写SKILL.md模型的第一阅读物SKILL.md的内容就是给模型看的行为规范。它的质量直接决定Agent调用Accurancy。我会按这个模板填# 订单取消审批 Skill ## 一句话说明 用于处理用户提交的订单取消申请审批。 ## 触发条件 - 用户明确表达取消订单意向例如“帮我取消订单”“订单不要了”。 - 用户在售后流程中选择“取消订单”入口。 ## 禁止触发 - 用户只是咨询退款政策并未提出取消请求。 - 用户要求修改收货地址或换货。 ## 输入参数 - order_id: 必填订单编号字符串。 - cancel_reason: 可选取消原因。 ## 执行步骤 1. 调用订单服务校验订单状态。 2. 若状态为已发货进入拦截分支提示用户请收货后退货。 3. 若状态为待发货执行取消并生成退款单。 4. 返回结构化结果。 ## 异常处理 - 订单不存在返回错误码4001。 - 订单支付异常返回错误码5002并告知用户需人工处理。 - 所有超时请求重试一次单次时间不超过1500ms。 ## 输出格式 返回 JSON 对象包含 status、message、refund_id。写完以后自己扮演模型把这份说明书当作唯一依据看看能不能判断出正确行为。如果自己都判断不出来说明边界还没理清。3.3 参数Schema与返回结构设计参数Schema是Skill和Agent之间的契约。边界越清晰幻觉越少。约束条件我建议给足parameters_schema { order_id: { type: string, pattern: ^[A-Z0-9]{16}$, description: 用户订单编号, examples: [ORD202501180001] }, cancel_reason: { type: string, maxLength: 200, required: False, description: 用户填写的取消原因用于客服归档 } }第一次给参数加正则表达式时团队同事觉得多此一举结果上线后的确阻止了一堆格式非法的订单号进入下游系统。模型能读懂正则的不多但它会用描述里的例子去匹配有了examples字段模型调用参数的准确率肉眼可见地上升。返回结构统一用JSON已经不用争论了但要注意两点一是返回结构里应包含明确的置信区间或状态标志让Agent知道这次调用的结果是否可信二是错误码要有统一枚举表不要让Agent自己从自然语言里猜失败原因。3.4 执行体代码与依赖隔离执行体代码的编写原则是短、透明、无状态。短是指控制逻辑尽量精简复杂判断尽量下沉到SKILL.md的规则里透明是指每一步都打日志尤其是入参、出参、耗时无状态是指Skill不缓存任何业务级数据要缓存也必须走框架缓存API。一个订单查询Skill的执行体骨架大致是这样的import json import time def handle(params, context): log context.getLogger(fskill.{params.get(name)}) start time.time() order_id params.get(order_id) if not _is_valid_order_id(order_id): return {status: error, code: 4001, message: 订单号格式非法} try: order context.call_service(order.query, order_idorder_id, timeout1500) except TimeoutError: log.warning(order.query timeout for %s, order_id) return {status: error, code: 5002, message: 查询超时触发重试} return {status: ok, data: order, elapsed_ms: time.time() - start}依赖隔离是个容易翻车的点。开发环境里装包很随意但生产环境每个Skill的依赖最好做到独立锁定。踩过一次大坑A Skill升级了requests库B Skill依赖的旧接口立刻挂了排查了两小时才发现是共享环境依赖冲突。从那以后我们每个Skill都用独立requirements.txt部署时用多阶段镜像尽量把依赖边界锁死。3.5 测试策略从单测到Agent级验证Skill测试不能只测函数逻辑还得测“Agent会不会正确触发它”。这个认知是很多开发团队的盲区。只调通了执行代码不测试触发链路上线后照样可能被模型绕过。我现在的测试分了三层单元层直接执行Skill用构造好的边界参数验证输入输出。覆盖正常场景、非法参数、超时、依赖服务异常。触发层准备一批用户话术样本交给Agent跑统计Skill召回率与误触率。误触率高的Skill说明描述词写得有歧义。端到端层模拟完整多轮对话验证Skill在过程中是否被正确调度、中间状态是否正确流转、结果是否被Agent消化。触发层测试最实用的方法就是维护一个话术样本库按意图分类贴标签每次改SKILL.md描述后全量跑一遍回归。我自己维护的样本库已经积累了七百多条话术每次优化描述词后召回率变化都能一眼看到。3.6 版本管理与灰度发布Skill也是代码必须有版本管理。但Skill的版本和普通代码有一点区别模型系统提示词里缓存了Skill描述改了版本之后缓存不一定跟着更新经常会出现逻辑已经改了描述还是旧版的情况。比较稳的做法是版本号写进注册中心每次变更记录description变更摘要框架在构建提示词时带上版本水印。上线走灰度先在一个路由分组里放5%流量跑几小时看触发准确率有无异常再逐步放量。放量要看的是触发层指标不是单纯的成功率因为很多错误触发表面看也是“执行成功了”。4. 常见问题与排查技巧实录4.1 Skill不触发模型根本不调用它这是群里被问得最多的问题八成原因都在描述词上。排查步骤看日志里Agent决策时的工具列表确认Skill有没有被注册进去。检查SKILL.md首句如果首句是“本模块用于处理订单取消申请审批包含以下功能”模型很可能把整体当背景知识读了应该改成命令式“当用户要求取消订单时调用本Skill”。检查负例边界有没有可能用户说的是“取消订单里的赠品”而你的Skill描述只写了订单取消模型没意识到这是同类动作。看是不是参数Schema让模型无从下手。某个Skill要传一个包含了七个嵌套字段的对象模型经常因为凑不齐参数而放弃这种情况需要拆成多个小Skill。4.2 Skill误触发不该调用时偏要调用误触发比不触发更危险它会污染上下文、浪费token、甚至执行副作用操作。最常见的误触发原因是描述词里的关键字太泛例如“订单”这个词把所有跟订单有关的用户意图全吸了过去。我的修复思路是给描述词增加“禁止触发”段落这类负例描述对模型决策的作用立竿见影。另一个有效做法是在Skill执行前加一个轻量校验步骤如果某个关键参数缺失先返回“缺参”状态而不是直接动手。这相当于给模型加了一道闸门避免它在不确定的情况下一通操作。4.3 Skill输出被Agent篡改或忽略Agent拿到Skill的输出后还要把结果组织成用户话术。这个环节很容易出问题模型觉得Skill返回的JSON太啰嗦自己脑补简化了导致关键信息丢失。解决办法是在SKILL.md的输出格式说明里明确告警输出中所有字段都要原样保留禁止删减数值和数据。更稳的做法是让Skill直接返回格式化好的话术片段模型只负责拼接前后文而不是重新转述。部分框架支持把Skill输出标记为“不可变性字段”有这个能力就用上。4.4 依赖环境不一致开发正常生产报错这个问题我在3.4提过这里再展开讲一个典型的排查路径。现象是本地跑Skill全通过部署到容器里就报ModuleNotFoundError。第一反应是查Dockerfile里有没有漏装依赖但更多时候其实是本地Python环境恰好装了另一个包代码里通过隐式依赖用上了而requirements.txt没锁住。排这种问题的最快方式是在干净环境里重跑一遍测试用docker run拉全新镜像只安装requirements.txt里的包然后跑测试用例。如果复现了错误就去代码里看import链有没有隐藏依赖。生产环境最好启用依赖白名单机制禁止隐式import非声明包。4.5 多Skill之间互相干扰系统里有多个同类Skill时Router经常选择困难表现为这次选中A、下次选中B导致行为不一致。要根治就得把技能边界划得足够清晰让每个Skill的触发条件互斥。以我的踩坑经验为例当时同时存在“订单状态查询”和“物流轨迹查询”两个Skill用户说“我的快递到哪了”两个Skill都匹配Router随机选了一个。修复方式是两个Skill共用一个Route分组在分组内做二次意图判断用户问“到达时间”走物流查询问“订单包括什么商品”走订单查询。与其让Router硬分不如共享更细粒度的意图判断。4.6 问题排查速查表问题现象可能原因首选排查动作Skill不触发描述词缺少命令式触发句检查SKILL.md首句与触发条件参数频繁缺失Schema定义太复杂拆分参数对象降低嵌套深度输出被篡改输出字段未被标记为不可变在描述中强制原样返回或使用框架锁定执行超时同步阻塞调用第三方接口增加超时与重试策略考虑异步化依赖报错隐式依赖未声明干净环境重跑白名单依赖多Skill行为漂移触发边界重叠调整分组路由与意图二次判断线上逻辑和本地不一致版本缓存未刷新注册中心检查版本水印5. 从Skill到Skill生态开发心法与进阶方向5.1 一个Skill能拆多细很多人纠结“这个能力到底该拆成一个Skill还是两个”。我给一个很土但很实用的判断标准如果你的Skill描述里出现了“并且”“然后”“如果……但另一边……”这种拐弯表达说明它内部缝了不止一件事需要拆。举例来说“订单处理Skill”这种名字一看就要拆。表面上是处理一个流程实际上包含订单查询、订单取消、退款进度、发票申请四个动作。与其做一个大而全的Skill不如拆成四个小而专的Skill再用一个Workflow把它们编排起来。小Skill的另外一个好处是描述词容易写精确模型触发的准确性高得多大Skill为了概括所有子能力描述词只能写得很泛后果就是各种误触发。5.2 复用Share与跨项目迁移Skill真正发挥威力的地方是复用。我们在一个客服项目里沉淀了八个Skill第二个电商项目直接搬过去六个只改了数据源配置参数开发周期直接砍了一半。所以设计Skill时要把数据源依赖参数化不要硬编码在代码里否则搬到其他项目就要开箱改代码。跨项目复用时最有价值的资产不是代码而是SKILL.md里的规则沉淀。电商客服的“退款规则判断”Skill里那套拆解用户意图、识别不可退款条件、判断运费承担方的决策逻辑换个行业照样能用这是真正的领域经验结晶。5.3 设计Skill的三个心法和团队反复折腾了一年后我总结出三条开发心法分享给正在踩坑的朋友。第一Skill的说明书永远比代码重要。模型决策看的是描述不是源码。描述词优化投入一小时换来的是线上触发准确率的持续提升。代码写满半天往往只换来一次调用少10毫秒。第二边界意识是Skill开发的灵魂。谁的职责是什么、什么情况不归我管这些边界在SKILL.md里写清楚比写代码逻辑更难也更重要。边界清晰Agent才能正确决策。第三测试样本库是核心资产。把用户话术样本积累当成持续投入来对待每次线上出现异常触发就把案例加入回归集。这个库到后期会比任何测试文档都值钱因为它是真实用户行为的浓缩。5.4 下一步Skill编排和主动学习现在团队的方向是两层演进。一层是编排层把日常高频流程沉淀成WorkflowWorkflow内部再组合若干个Skill让Agent不再是每次从零开始规划而是像在流水线上调用标准工序。另一层是主动学习层当Skill在某类用户话术上反复失手时系统自动收集这批案例推送给开发者做描述词迭代形成“线上失败–离线修正–回归验证–上线验证”的闭环。用这套方法论我们把客服Agent的技能调用准确率从81%提到了93%误触率从12%降到了4%以内关键在于每一次迭代都有真实样本支撑不是在办公室里拍脑袋改提示词。Skill开发没有玄学把概念想清楚架构定清楚描述写清楚样本攒清楚效果是稳定可预期的。