
1. 从“agent-skills”这个命名说起它到底在解决什么问题第一次看到“agent-skills”这个命名我的直觉是这大概率不是一个具体的业务系统而是一套围绕“智能体能力”做抽象、封装、复用和编排的基础设施。事实也确实如此。在智能体Agent开发这条线上摸爬滚打过的人都知道真正让人头疼的从来不是“能不能调通一次大模型”而是当你要把同一个智能体放到十个不同场景里跑的时候能力怎么复用、权限怎么隔离、状态怎么管理、失败怎么兜底。agent-skills 这类项目本质上就是在回答这个问题。我先把结论摆在前面agent-skills 是一套把智能体的“技能”从“提示词”里剥离出来、变成可注册、可发现、可组合、可版本化的独立单元的工程化方案。它要解决的核心痛点有三个。第一提示词和业务逻辑高度耦合改一个技能要动整个智能体的配置牵一发动全身。第二技能无法跨智能体复用A 智能体写好的“查天气”能力B 智能体想用只能复制粘贴维护两份甚至多份。第三技能的执行缺乏统一的输入输出契约导致编排层无法可靠地做串联、并行和错误处理。这套东西适合谁来参考我认为有三类人最该认真看。一类是正在做多智能体协作系统的开发者你迟早会遇到技能复用和编排的问题一类是把智能体往生产环境推的工程团队你需要的是可观测、可回滚、可灰度而不是一个能跑通就行的 Demo还有一类是产品经理或技术负责人你需要理解智能体能力的边界在哪里才能设计出靠谱的产品形态。需要说明的是agent-skills 这个命名本身带有很强的“能力抽象”意味。skill 这个词在智能体语境里通常指的是一段有明确输入输出、有明确副作用边界、可以被独立调用的能力单元。它和 tool工具有重叠但不完全等同tool 更偏向底层函数调用skill 更偏向“完成一件有业务意义的事”。这个区分很重要后面讲设计取舍时会反复用到。2. 技能抽象层的设计取舍为什么不是简单的函数注册表2.1 技能与工具的本质区别决定了抽象层级很多人第一反应是技能不就是函数吗注册一个字典key 是技能名value 是函数调用的时候查表执行不就完了。我一开始也这么想直到在一个多轮任务编排的场景里被现实教育了。问题出在“上下文”上。一个底层工具函数比如“发送 HTTP 请求”它是无状态的输入 URL 和参数输出响应。但一个技能比如“帮用户预订会议室”它需要知道当前用户是谁、当前时间是什么、用户偏好哪个楼层、这个会议室是否已经被占用。这些信息不在函数签名里而在智能体的运行上下文里。所以 agent-skills 的第一个设计取舍就是技能必须携带上下文契约。也就是说一个技能的定义里除了输入参数和输出结果还要声明它需要哪些上下文、会产生哪些副作用、在什么条件下可以安全重试。这听起来有点重但正是这层声明让编排层能够做出可靠决策。我见过太多项目技能执行到一半失败了编排层不知道该不该重试因为没人知道这个技能是不是幂等的。结果要么重复下单要么该重试的没重试。2.2 技能描述文件应该包含哪些字段基于常见实践一个技能的描述文件通常会包含这些字段我把它整理成表格方便对照理解。字段名作用是否必填常见坑name技能唯一标识是命名冲突建议加命名空间前缀description给模型看的自然语言描述是写得太模糊模型选错技能parameters输入参数的 JSON Schema是类型定义不严谨模型传错格式required_context执行所需上下文键否漏声明导致运行时取不到值side_effects副作用声明否不声明导致重试逻辑出错idempotent是否幂等否默认 false 更安全timeout_ms超时时间否不设超时导致任务卡死version版本号否不版本化导致灰度困难这张表里的每一行背后都是踩过的坑。我重点说两个。一个是 description 的写法。很多人把它当成注释随便写但它是给模型做技能选择用的本质上是一段提示词。写“查询天气”和写“根据城市名称查询当前实时天气返回温度和天气状况”模型的选择准确率差别非常大。另一个是 idempotent 字段。默认值一定要设成 false也就是默认不幂等。因为把不幂等的技能误判为幂等后果是重复执行产生脏数据而把幂等的技能误判为不幂等最多是少重试一次代价小得多。这个默认值的选择逻辑和分布式系统里“宁可少做不可做错”的原则是一致的。2.3 技能注册与发现的机制设计技能注册表是 agent-skills 的核心组件。它要解决的是“有哪些技能可用”和“某个技能在哪里”这两个问题。常见做法是维护一个中心化的注册表技能启动时向注册表注册自己编排层通过注册表查询可用技能。这里有个关键设计点注册表存的是技能的元数据还是技能的执行入口我的经验是元数据和执行入口要分离。注册表只存元数据包括技能名、描述、参数 schema、版本、健康状态等。执行入口通过一个统一的调用网关来路由网关根据技能名和版本找到实际的服务地址。这样做的好处是注册表可以轻量化、高可用而执行网关可以独立做限流、熔断、鉴权。如果混在一起注册表一旦挂了整个系统就瘫了。还有一个容易被忽略的点技能的发现应该是带过滤条件的。不是所有智能体都能看到所有技能。比如一个面向普通用户的智能体不应该看到“删除数据库”这种高危技能。所以注册表查询接口通常要支持按标签、按权限域、按版本范围做过滤。这个过滤逻辑如果放在编排层做容易漏放在注册表层做更安全。3. 技能编排的执行链路从模型决策到实际调用中间发生了什么3.1 模型如何选择技能描述质量决定一切智能体编排的第一步是模型根据用户输入从可用技能列表里选出要调用的技能。这一步的准确率几乎完全取决于技能描述的质量。我做过一个对比实验同一批技能用两种描述方式模型选择准确率差了将近三十个百分点。第一种是简短描述比如“查订单”“改地址”“退款”。第二种是完整描述包含技能做什么、什么时候用、输入输出是什么、有什么限制。比如“根据订单号查询订单的当前状态和物流信息适用于用户询问订单进度或物流情况的场景需要提供订单号返回订单状态和最新物流节点”。这里有个实操技巧描述里要包含“负向信息”。也就是明确告诉模型这个技能不适用于什么情况。比如“退款技能仅适用于已支付且未发货的订单已发货订单请使用退货技能”。这种负向信息能显著降低模型选错技能的概率。很多团队只写正向描述结果模型在边界场景上频繁出错。另外技能数量多了之后不能把所有技能描述一股脑塞进提示词。一是 token 成本高二是模型在长列表里的选择准确率会下降。常见做法是分层检索先用一个轻量模型或关键词匹配做粗筛把候选技能缩小到十个以内再用主模型做精筛。这个分层思路和推荐系统的召回加排序是一个道理。3.2 参数填充与校验模型不是万能的模型选定技能后下一步是填充参数。这里必须有一个校验层不能直接把模型输出的参数传给技能执行。原因很简单模型会犯错。它可能把字符串类型的参数传成数字可能漏传必填参数可能传一个超出范围的数值。校验层要做三件事类型校验、必填校验、业务规则校验。类型校验和必填校验可以用 JSON Schema 自动完成。业务规则校验需要额外写逻辑比如“日期不能早于今天”“金额不能为负数”。我建议把业务规则校验也尽量声明化写成配置而不是硬编码这样新增技能时不用改校验层的代码。参数填充还有一个常见问题多轮对话中的参数继承。用户第一轮说“帮我订明天下午三点的会议室”模型提取了时间和类型。第二轮用户说“改成四点”这时候只需要更新时间参数其他参数要继承上一轮的值。如果每轮都重新提取很容易丢失上下文。所以编排层需要维护一个“参数槽位”的状态每轮只更新用户明确提到的槽位。3.3 执行、超时与重试的完整链路技能执行阶段最考验工程能力。我把它拆成几个关键环节来说。第一个环节是超时控制。每个技能都必须设超时而且超时时间要分层设置。技能自身的执行超时、编排层的等待超时、整个任务的全局超时这三层要配合好。我见过一个案例技能自身没设超时编排层设了三十秒结果技能卡在一个慢查询上编排层超时后重试又发起一次慢查询数据库直接被拖垮。正确的做法是技能自身超时设得比编排层短比如技能二十五秒编排层三十秒这样编排层超时的时候技能已经自己结束了不会产生僵尸调用。第二个环节是重试策略。重试不是无脑重试要满足几个条件技能声明为幂等、错误类型是可重试错误比如网络超时、限流、重试次数有上限、重试间隔有退避。我通常用指数退避加抖动第一次等一秒第二次等两秒第三次等四秒再加一个随机抖动避免惊群。不可重试的错误比如参数校验失败、权限不足直接失败不要浪费重试次数。第三个环节是结果处理。技能返回的结果要经过一层归一化转成编排层能理解的统一格式。因为不同技能返回的结构可能千差万别有的返回 JSON有的返回纯文本有的返回状态码。归一化之后编排层才能做后续的串联或汇总。这一步如果偷懒后面做多技能结果合并的时候会非常痛苦。4. 多技能协作时的状态管理与冲突处理4.1 共享状态该放在哪里当多个技能需要协作完成一个任务时状态管理就成了核心问题。比如一个“出差安排”任务需要依次调用“查航班”“订酒店”“约会议室”三个技能。这三个技能都需要知道出差人是谁、出差日期是哪天。这些共享状态放在哪里常见有三种方案。第一种是放在编排层的上下文对象里技能执行时从上下文读取。第二种是放在一个外部状态存储里比如 Redis技能通过一个 session id 去读写。第三种是每个技能自己维护状态通过消息传递来同步。我的经验是短生命周期的任务用第一种长生命周期、需要跨进程恢复的任务用第二种第三种尽量别用同步逻辑太容易出错。第一种方案最简单但有个限制编排层必须是单进程的或者至少同一个任务的所有技能调用都路由到同一个编排实例。如果编排层是多实例的上下文对象没法跨实例共享就得用第二种方案。所以选型的时候要先想清楚部署形态。4.2 技能执行顺序的依赖与并行不是所有技能都要串行执行。有些技能之间没有依赖可以并行。比如“查航班”和“查酒店”可以同时进行最后再汇总。并行执行能显著缩短任务总耗时但会带来新的问题部分失败怎么处理。假设并行执行三个技能两个成功一个失败。是整体失败回滚还是保留成功的部分这取决于业务语义。如果是“查询类”任务保留成功部分、对失败部分做降级提示是合理的。如果是“交易类”任务比如同时扣款和扣库存那就必须保证原子性要么全成功要么全失败。所以编排层需要支持两种并行模式尽力而为模式和全有全无模式。这个选择应该在任务定义的时候就声明清楚而不是运行时临时决定。4.3 冲突检测与解决的实际做法多技能协作时冲突是难免的。最常见的冲突是资源竞争比如两个技能同时想修改同一条记录。解决思路有两种悲观锁和乐观锁。悲观锁是在技能执行前先锁定资源执行完释放。乐观锁是执行时不锁提交时检查版本号版本不一致就重试或报错。在智能体场景里我更倾向乐观锁。因为智能体的任务执行时间往往比较长悲观锁会导致资源被长时间占用影响其他任务。乐观锁的代价是可能重试但只要冲突概率不高整体吞吐量更好。实现上可以在共享状态里给每个资源加一个版本号技能提交修改时带上版本号不匹配就拒绝。还有一种冲突是语义冲突比如一个技能把订单状态改成“已取消”另一个技能同时把它改成“已发货”。这种冲突没法靠版本号解决需要业务层定义优先级规则。我的做法是在编排层加一个“状态机校验”每个状态变更都要检查是否满足状态机的合法迁移。不满足就直接拒绝并记录冲突日志。这样虽然不能自动解决冲突但至少能保证数据一致性不会出现非法状态。5. 技能版本化与灰度发布的工程实践5.1 为什么技能必须版本化技能版本化这件事很多团队一开始不做等到出问题才后悔。我举个真实场景一个“计算运费”的技能原本的规则是满百包邮。某天运营改了规则变成满八十包邮。开发直接改了技能实现并发布。结果正在执行中的一批订单前半段用旧规则算的运费后半段用新规则算的同一批订单运费不一致财务对账直接炸了。如果技能有版本号编排层在任务开始时锁定技能版本整个任务执行期间都用同一个版本就不会出现这种问题。所以版本化不只是为了回滚更是为了保证单个任务执行的一致性。版本号的命名我建议用语义化版本主版本号表示不兼容的变更次版本号表示向后兼容的功能新增修订号表示向后兼容的问题修复。编排层引用技能时可以指定精确版本也可以指定版本范围。生产环境建议指定精确版本避免自动升级带来的意外。5.2 灰度发布的几种策略技能升级不能一把梭全量发布要灰度。常见的灰度策略有几种。按流量比例灰度比如新版本先接百分之五的流量观察一段时间没问题再逐步扩大。按用户维度灰度比如先对内部用户开放再对部分外部用户开放。按任务类型灰度比如先对查询类任务开放再对交易类任务开放。我比较推荐组合使用先按任务类型灰度因为不同任务类型的风险等级不同再按流量比例灰度控制影响面。灰度期间要重点监控几个指标技能调用成功率、平均耗时、错误类型分布、下游依赖的健康度。如果新版本的错误率明显高于旧版本自动回滚。这里有个细节灰度发布时新旧版本可能同时存在共享状态的数据结构要保证兼容。比如新版本给某个状态字段加了新属性旧版本读到这个状态时不能报错。所以状态结构的变更要遵循“向后兼容”原则新增字段要有默认值删除字段要先标记废弃而不是直接删。5.3 版本回滚的触发条件与操作回滚不是拍脑袋决定的要有明确的触发条件。我通常设三条红线错误率超过基线两倍、平均耗时超过基线一点五倍、出现新的错误类型。任意一条触发自动回滚。回滚操作要快最好是一键回滚而不是重新走一遍发布流程。所以技能包要保留最近几个版本回滚时直接切换版本号即可。回滚之后要做的事也很重要保留现场。把出问题版本的调用日志、错误堆栈、相关状态快照都保存下来方便事后分析。我见过团队回滚完就把日志清了结果同样的问题又出现一次因为根本不知道根因是什么。6. 可观测性建设技能执行的黑盒怎么打开6.1 日志、指标、追踪三件套的落地智能体系统的可观测性比普通服务更难做因为执行链路长、涉及组件多、还有模型的非确定性。我的经验是日志、指标、追踪三件套一个都不能少而且要统一关联。日志要结构化每条日志都要带任务 id、技能名、技能版本、执行阶段、耗时、结果状态。这样排查问题时可以按任务 id 把所有相关日志串起来。指标要覆盖几个维度调用量、成功率、耗时分布、错误分类。耗时分布建议用直方图不要只用平均值因为平均值会掩盖长尾问题。追踪要能还原完整的调用链路从用户输入到模型决策到技能执行到结果返回每一跳都要有 span。关联的关键是 trace id。任务开始时生成一个 trace id贯穿整个链路。模型调用、技能调用、状态读写所有操作都带上这个 trace id。这样不管是查日志还是查追踪都能一键拉出全链路。6.2 模型决策过程的可观测性模型决策是个黑盒但至少要记录几个关键信息输入给模型的技能列表是什么、模型选择了哪个技能、选择时的置信度或概率分布、有没有触发兜底逻辑。这些信息对于调优技能描述、排查选择错误非常有用。我通常会记录模型决策的原始输出包括它选择的技能名和填充的参数。如果模型输出格式不对解析失败也要记录原始文本方便分析是提示词问题还是模型能力问题。这些日志量可能比较大建议单独存一个索引设置较短的保留期比如七天。6.3 告警策略什么该报什么不该报告警最怕两件事该报的没报不该报的狂报。智能体系统的告警我建议分三级。一级是致命告警比如技能注册表不可用、编排层整体错误率飙升这种要立即通知。二级是异常告警比如某个技能错误率超过阈值、某个下游依赖响应变慢这种可以聚合后通知。三级是提示信息比如某个技能被降级、某个任务重试次数偏多这种记录但不通知定期review。告警阈值不要拍脑袋定要基于历史数据算。比如错误率阈值可以取过去七天同时段错误率的均值加三倍标准差。这样能自适应业务量的波动减少误报。另外告警要有抑制和聚合同一个根因引发的多个告警合并成一条避免告警风暴。7. 我在实际落地中踩过的几个坑7.1 技能描述里的“隐藏假设”有一次我们上线了一个“发送通知”的技能描述写的是“向用户发送通知”。结果模型在用户只是问“有没有新消息”的时候也调用了这个技能因为它把“查询消息”理解成了“发送通知”。问题出在描述里的隐藏假设我们默认“发送通知”是主动推送但模型不知道这个默认。后来改成“主动向用户推送一条通知消息适用于需要告知用户某件事的场景不适用于查询消息”准确率立刻上来了。这个坑的教训是技能描述要站在“完全不了解你系统的人”的角度写把所有隐含假设都显式表达出来。你觉得理所当然的事模型可能完全不知道。7.2 上下文膨胀导致的性能问题早期我们的编排层把整个对话历史都塞进上下文传给每个技能。结果任务执行到后面上下文越来越大模型调用越来越慢token 成本也飙升。后来改成只传技能声明需要的上下文键上下文大小立刻降下来了。这个优化的关键是 required_context 字段。技能声明它需要哪些上下文编排层只传这些。这样既减少了传输量也避免了技能看到不该看的信息。不过要注意有些技能可能需要“最近三轮对话”这种动态上下文声明的时候要支持这种表达。7.3 技能超时设置不当引发的雪崩前面提过超时分层这里再展开说一个真实案例。我们有个技能调用外部接口外部接口偶尔会慢。技能自身超时设了六十秒编排层设了九十秒。结果外部接口一慢大量技能实例卡在六十秒等待上线程池被占满编排层的新任务进不来整个系统雪崩。后来改成技能超时十秒编排层十五秒并且加了熔断连续失败超过阈值就直接快速失败不再发起调用。这样外部接口慢的时候系统能快速失败并降级不会拖垮自己。这个教训是超时时间要基于下游的实际响应时间分布来定不能拍脑袋。而且要有熔断兜底不能只靠超时。7.4 版本兼容性引发的状态读取失败有一次技能升级新版本给状态里的一个字段改了类型从字符串改成了对象。旧版本的任务还在执行中读到这个字段时解析失败直接报错。问题在于我们没有做状态结构的兼容性检查。后来我们定了个规矩状态结构的任何变更必须保证旧版本能安全读取。新增字段要有默认值修改字段类型要同时支持新旧两种格式删除字段要先标记废弃并保留至少两个版本。这个规矩虽然麻烦但避免了升级期间的线上故障。8. 技能生态的扩展思路从单智能体到多智能体协作8.1 技能共享与权限隔离当技能多起来之后共享和隔离就成了矛盾。一方面希望技能能跨团队复用减少重复建设另一方面又担心权限失控A 团队的技能被 B 团队滥用。我的做法是引入“技能域”的概念。每个技能属于一个域域之间默认隔离。跨域调用需要显式授权授权可以按技能粒度、按调用方粒度、按时间窗口来配置。授权信息存在注册表里编排层查询技能时带上调用方身份注册表返回该调用方有权访问的技能列表。这样权限控制就在技能发现阶段完成了编排层不需要额外做权限判断。这个设计的好处是权限逻辑集中不容易漏。8.2 技能组合成更高阶能力单个技能是原子能力多个技能可以组合成更高阶的能力。比如“查航班”“查酒店”“查天气”组合成“出差规划”。这种组合能力我称之为“复合技能”。复合技能本身也是一个技能对外暴露统一的输入输出内部编排多个原子技能。复合技能的价值在于复用编排逻辑。如果每个智能体都自己写一遍出差规划的编排重复且容易出错。做成复合技能后所有智能体都能直接调用。实现上复合技能可以用一个声明式的编排描述来定义比如用 YAML 描述步骤、依赖、条件分支。这样非开发人员也能参与编排降低门槛。8.3 多智能体之间的技能协商在多智能体系统里不同智能体可能拥有不同的技能集。当一个智能体需要某个它没有的技能时可以向其他智能体发起协商。协商的内容包括请求技能调用、传递参数、返回结果。这本质上是一个分布式的技能调用协议。实现这种协商需要解决几个问题智能体如何发现彼此、如何建立信任、如何处理调用失败。发现可以通过一个目录服务信任可以通过技能域授权失败处理需要定义超时和降级策略。这块目前还在演进中没有特别成熟的方案但方向是明确的技能会从单智能体的内部能力演变成多智能体之间的可交易资源。9. 给准备上手 agent-skills 的团队几点实在建议如果你正准备在项目里引入 agent-skills 这套思路我有几个建议都是花钱买来的教训。第一先从两三个技能开始不要一上来就搞大而全的技能平台。技能抽象的价值要在一定规模后才显现过早平台化会拖慢迭代速度。先用最朴素的方式把技能跑通等技能数量超过十个、复用需求出现之后再考虑注册表和编排层。第二技能描述当成产品文案来写不要当成代码注释。描述的质量直接决定模型选择的准确率值得花时间打磨。建议建立一个描述模板包含“做什么、什么时候用、什么时候不用、输入是什么、输出是什么”五个要素所有技能按模板写。第三可观测性从第一天就要做不要等出问题才补。技能执行的黑盒一旦形成后面再想打开成本极高。日志、指标、追踪三件套哪怕先用最简单的实现也要有。第四版本化和灰度发布要提前设计。技能升级引发的事故往往比技能本身的功能缺陷更严重。版本号、灰度策略、回滚机制这些基础设施要在技能数量还少的时候就建好等技能多了再改迁移成本很高。第五状态管理要克制。共享状态是复杂度的主要来源能不用就不用。如果一定要用尽量用不可变的状态每次变更产生新版本而不是原地修改。这样排查问题时可以回溯任意时间点的状态也方便做并发控制。最后说一个我自己的体会agent-skills 这类项目的核心价值不在于技术有多高深而在于它强迫你把智能体的能力边界想清楚。当你不得不为一个技能写清楚它的输入、输出、副作用、幂等性、超时时间的时候你其实是在做一次严肃的接口设计。这个过程本身就能帮你发现很多之前没想明白的问题。技能抽象做得好不好最终会体现在系统能不能稳定跑在生产环境上而不是能不能跑通一个 Demo。