做 Agent 这几年,我最大的感受是:真正拖垮一个智能体项目的,往往不是模型能力不够,而是代码仓库越来越像一座垃圾山。今天这个 agent-skills 相关的话题,我还要从一次差点推翻重来的重构说起,它几乎改变了我对 Agent 工程化的全部理解。
半年前,我们团队做了一个内部客服助手,当时把所有的工具函数直接堆在一个 tools.py 里,判断订单、查物流、退换货、催发货,十几个函数。刚开始效果还行,但随着业务规则增加,模型频繁选错工具,明明该查物流,它偏去调了订单详情,用户问一句“我手机什么时候到”,它回一句“您的订单已签收”。后来我把所有函数拆成独立模块,给每个模块写了详细描述,效果才稳定下来。那段时间我反复琢磨的问题就是:Agent 真正需要的,不只是一堆函数接口,而是一套可管理、可编排、可评估的“技能体系”。agent-skills 这个名字,恰恰承载的就是这一整套方法论。
如果你也在做 Agent 应用,或者正被“工具越来越多、效果越来越差”困扰,这篇文章应该能帮到你。我会从为什么必须做技能管理、技能怎么建模、怎么把多个技能编排成一个完整流程、到落地实测中踩过的坑,完整拆开讲一遍。不会只给概念,每一步都有我可以直接复现的代码片段和配置说明。
1. Agent 技能碎片化:为什么每个智能体团队最后都会绕回“技能管理”这条路
1.1 函数堆积时代的崩溃现场
先复盘一下我是怎么从“函数”走到“技能”的。项目早期,每个业务操作就是一个 Python 函数,用 @tool 装饰器挂给大模型。问题爆发在函数数量超过 15 个之后。
最典型的表现是意图混淆。比如我们有一个 query_order 和一个 query_logistics,参数都是 order_id。模型经常在这个二选一里犯糊涂,原因是两个函数的描述都写了“根据订单号查询信息”,模型根本分不清哪个更匹配当前问题。还有更隐蔽的,比如 get_refund_status 和 get_after_sale_detail,业务上这两个其实是同一件事的不同视图,但模型不知道,于是出现了一个问题问两遍、拿到两套答案的尴尬局面。
另一个大问题是上下文污染。每个工具执行完都会把原始结果塞回对话历史,几个任务串下来,上下文里塞满了 JSON。模型在长上下文中提取关键信息的准确率明显下降,用户问“刚才那单退款到哪一步了”,模型开始东拉西扯。
这不是代码质量问题,而是抽象层次出了问题。函数是给程序员复用的,技能才是给 Agent 复用的。函数的输入输出是类型签名,技能的输入输出是语义契约。这个认知转变,是整个 agent-skills 设计的起点。
1.2 技能与函数的本质区别
我理解的“技能”,是一个可以被 Agent 理解、调度、组合和评估的独立能力单元。判断标准是:这个单元是否携带足够的自我描述信息,让模型在不需要查看源码的情况下,就知道它适合解决什么问题、需要什么输入、会产生什么影响。
一个合格的技能,至少要包含四层信息:
- 技能名称:全局唯一,用动宾短语,一眼能看出它做什么
- 技能描述:不是写给人类看的注释,而是写给模型看的“使用说明书”
- 输入输出 schema:明确每个参数的含义、格式、约束
- 执行副作用声明:这个技能是否会修改数据、是否需要权限、是否会调用外部服务
这些信息由开发者维护,但在运行时由模型消费。所以技能描述的质量,直接决定了 Agent 的选择准确率。函数时代我们写 docstring 是给 IDE 提示看的,技能时代我们写描述是给大模型做决策用的,这两者的措辞逻辑完全不一样。
还有一个被很多人忽略的点:技能必须具备版本。函数接口变了,改个签名然后全局搜调用处就能改完;技能变了,影响的是所有依赖它的流程编排和记忆缓存,没有版本管理,你根本不知道当前跑的是哪一套逻辑。这是 agent-skills 把技能当作“一等公民”来管理的一个核心原因。
2. 把技能当作“一等公民”:agent-skills 的核心建模思路
2.1 技能描述文件:写给模型看的说明书
在 agent-skills 的理念里,每个技能都有自己独立的描述文件。我习惯用 YAML 维护,因为它比 JSON 更易读,也容易写注释。一个典型的技能定义长这样:
name: check_refund_progress description: >- 查询退款申请的处理进度。当用户询问“退款到哪一步了”“退款什么时候到账” “钱退回来没有”等问题时使用。执行前必须先通过 verify_user_identity 技能完成身份校验。查询结果为快照数据,如需最新状态请配合 force_refresh 参数。 version: "2.1.0" emoji_policy: none inputs: order_id: type: string description: 电商平台订单号,格式为 10 位数字 required: true force_refresh: type: boolean description: 是否强制从支付渠道拉取最新退款流水,默认 false required: false default: false outputs: schema: type: object properties: status: type: string enum: [processing, success, failed, expired] estimated_arrival: type: string description: 预计到账时间,格式为 ISO 8601,仅在 processing 时有值 last_update: type: string description: 最近一次状态变更时间 side_effects: - reads_user_payment_flow - requires_identity_verified描述里我刻意用了“当用户询问……时使用”这种话术,这比写“查询退款进度”有效得多。模型看到的是用户表达层面的触发条件,而不是函数层面的功能摘要。这是一个我从惨痛教训里得出的经验,后面会专门展开。
2.2 技能的注册与发现机制
技能定义写好了还不够,还要有一个运行时机制让 Agent 知道“当前有哪些技能可用”。agent-skills 的注册中心解决的就是这个问题。
注册中心维护一张技能索引表,核心字段包括:技能名、语义指纹、输入摘要、当前版本、健康状态、平均延迟、最近失败率。Agent 在每次会话开始前拉取一次技能索引,然后根据用户问题从中筛选候选技能。这个过程可以理解为“文件的目录页”——模型不需要翻开每一页,只需要看目录就知道去哪一章找答案。
我早期试过把所有技能描述全塞进 system prompt,结果 token 消耗爆炸,而且模型在大量文本中反而抓不住重点。后来改成“两阶段召回”:先在注册中心做一次粗筛,挑出 3 到 5 个候选技能,再把候选技能的完整描述注入 prompt。这让选型准确率提升了大概 20 个百分点,也让单次请求的 prompt 体积缩小了 60%。
注册中心还负责技能的生命周期管理。下线一个技能时不会立刻摘除,而是标记为 deprecated,给存量会话一个过渡期。升级技能时采用蓝绿策略,灰度比例按流量百分比控制。这些机制在单体工具函数时代都是不存在的,但它们才是 Agent 应用能长期稳定运行的基石。
3. 技能编排:从“单个技能可用”到“多技能协同干活”的临界点
3.1 为什么单技能正确不代表流程正确
单个技能可用,只解决了“模型能不能调用对工具”的问题。但在真实业务里,用户诉求往往要串联多个技能才能完成。拿我们上线过的售后流程举例,用户说“我上周买的手机到了但屏幕有问题,想退货”,完整链路至少是:
- 先调用 verify_user_identity 确认用户身份
- 再调用 query_order_info 找到对应订单
- 调用 query_after_sale_policy 判断是否符合退货条件
- 调用 create_refund_request 创建退款申请
- 最后调用 notify_user 把结果通知用户
这五个技能如果靠模型在单轮对话里自由发挥,任何一个环节选错都会导致流程断裂。比如模型可能在身份没验证时就创建了退款申请,或者用错了订单号。所以技能编排的核心,是设计一套机制来约束调度顺序、传递中间数据、处理分支异常。
3.2 用 DAG 描述流程拓扑
agent-skills 的编排引擎采用 DAG(有向无环图)来描述技能之间的依赖关系。每个节点是一个技能,每条边是数据流或控制流。一个售后流程的 DAG 定义可以抽象成:
verify_user_identity ↓ query_order_info ↓ query_after_sale_policy ↓ ↓ 符合条件 不符合条件 ↓ ↓ create_refund_request → notify_user ↓ notify_user注意一个关键设计:条件分支不是把分支逻辑写在技能内部,而是交给编排引擎判断。因为技能本身应当保持单一职责,分支判断属于流程层。query_after_sale_policy 返回状态码和原因说明后,编排器的决策节点根据结果选择后续路径。这保证技能可以被复用到不同流程里,不会出现“这个技能只有在这个流程里能用”的耦合。
数据在技能间传递时,编排引擎会维护一份共享上下文,每个技能声明自己需要读哪些字段、写哪些字段。数据流字段在技能的声明里写清楚,引擎在运行前做静态校验,发现字段缺失就直接报错,而不是让技能运行到一半才发现问题。
3.3 模型自由规划与固定流程的平衡
讲到这里,肯定有人会问:那 LLM 的自主规划能力不是白费了吗?我做了一些实验得出结论:完全自由的规划适合探索型任务,比如“帮我想一个团建方案”,而确定性流程适合业务型任务,比如“处理一个退款请求”。两者的判断标准很简单:**步骤顺序错了,结果是否会产生严重错误。**如果能,就适合固化流程;如果不能,就让模型自由发挥。
agent-skills 在这两者之间采取混合架构。业务主链路用 DAG 固定,但在每个节点内部保留模型的决策空间。比如 create_refund_request 执行后,如果系统返回“余额不足”或“订单状态不允许”,引擎会把异常信息回到决策模块,由模型判断是重试、换方式还是转人工。你会发现,这既保住了业务的合规性,也没有牺牲模型的灵活性。
这一层设计对流程稳定性的提升是肉眼可见的。上线编排引擎后,我们的售后流程完成率从 61% 提升到了 89%,而且出错的场景都集中在单一技能内部,而不是流程跳转环节。
4. 落地案例:用 agent-skills 搭一个能处理日常事务的团队助手
4.1 从需求抽象到技能拆解
光讲概念很难有体感,我拿一个实际做过的“团队助手”项目来完整走一遍。目标很朴素:让一个对话机器人帮团队处理三件事——查知识库、生成周报、发起审批。
第一步不是写代码,而是做需求拆解。我把“生成周报”拆成三个技能:collect_work_logs(汇总团队成员本周的工作记录)、summarize_weekly_report(调用模型生成周报草稿)、send_report_to_channel(发送到指定群组)。为什么不直接做一个 generate_weekly_report 的大技能?因为“汇总数据”和“生成文本”是两种性质完全不同的操作,前者是数据读取,后者是模型生成,未来“生成文本”可能被替换成更强的模型,而“汇总数据”的逻辑不会变。按技术边界而不是按业务场景拆技能,这是拆解的核心原则。
4.2 环境搭建与核心配置
搭建 agent-skills 运行环境其实相当轻量。核心组件是三个:技能注册中心、编排引擎、技能执行器。我用一个简单的 Python 项目来组织:
agent-skills-demo/ ├── skills/ │ ├── knowledge_base/ │ │ ├── skill.yaml │ │ └── handler.py │ ├── weekly_report/ │ │ ├── skill.yaml │ │ └── handler.py │ └── approval/ │ ├── skill.yaml │ └── handler.py ├── registry/ │ └── index.py ├── orchestrator/ │ └── engine.py └── main.py技能执行器用装饰器模式注册,handler.py 里的核心逻辑大致如下:
# skills/knowledge_base/handler.py from agent_skills import skill @skill("knowledge_base_search") def search_docs(query: str, top_k: int = 5) -> list[dict]: """Search internal knowledge base and return relevant snippets.""" vectors = embed(query) results = vector_db.search(vectors, top_k=top_k) return [{"title": r.title, "snippet": r.snippet, "score": r.score} for r in results]这里有个很实用的配置细节:skill.yaml 里的 description 字段,我是从真实用户提问语料里提炼触发词才定稿的。比如 knowledge_base_search 的描述初稿是“搜索内部知识库”,后来改成“当用户询问公司制度、报销标准、考勤规则、设备申请流程等问题时,调用此技能检索相关文档”。这一改动让该技能的召回命中率提升明显。描述不是写一次就完了,要跟着真实对话数据持续迭代。
4.3 一个完整会话的执行链路追踪
在这个演示项目里,用户说“帮我查一下年假有多少天,然后生成一份本周工作周报发到群里”。这是一个典型的复合请求,涉及两个领域。编排引擎的处理过程可以拆成这几步:
- 意图分诊:引擎先判断这是一个多技能协同任务,而不是单一技能调用
- 候选召回:注册中心从索引中召回 knowledge_base_search、query_leave_balance、collect_work_logs、summarize_weekly_report、send_report_to_channel
- 路径规划:根据技能依赖关系,引擎生成两条并行子链——A 链查年假,B 链生成并发送周报
- 并发执行:A、B 两条子链没有数据依赖,可以并行,减少整体延迟
- 汇总输出:两条子链的结果引导模型组织最终回复
实际运行中,我特意观察了 B 链内部的一个细节:collect_work_logs 需要获取当前团队成员列表,依赖另一个用户服务。如果用户服务超时,整个 B 链都会挂住。后来我在这个节点加了缓存和降级策略,缓存 30 分钟内的成员列表,超时则从缓存读,缓存也没有就直接返回错误让模型告诉用户“稍后再试”。这个改动让周报生成的成功率提高了许多。
5. 实测阶段最容易踩的坑:技能描述写得不对,全盘皆输
5.1 一次“描述冲突”引发的选型事故
说一个我在真实项目中排查了一整天才解决的问题。现象是:用户问“我要离职,怎么走流程”,模型竟然调用了发起加班审批的技能。看日志时我一开始完全没头绪,两个技能从字面上看差异很大,模型怎么会搞混?
后来我把注册中心里所有技能描述导出来逐个看,发现问题出在离职流程这个技能的描述里写了“用于员工离职交接、资产归还、工资结算”这里。而加班审批的描述里写了“当用户提到‘流程’时,优先考虑使用此技能”。模型在处理“怎么走流程”这个模糊表达时,被“优先使用”这种措辞干扰,选择了加班审批。
这个坑的根因是:技能描述之间出现了关键词交叉覆盖,且部分描述带了过强的误导性指令。排查过程是这样的:
- 先复现问题,确认不是偶发
- 把模型调用日志中的 prompt 完整导出,查看两个技能的描述原文
- 用一个测试集反复触发,统计两个技能的召回重叠度
- 定位到高权重关键词“流程”在两个描述中都出现
- 修改加班审批技能的描述,明确限定“当用户明确提到加班、调休、补休等关键词时”,同时删除离职流程技能里的笼统表述
修复之后我在小流量试点跑了一周,误选率从 5.8% 降到了 1.2%。这也验证了一件事:排查模型选型问题时,不要急着调 prompt 或换模型,第一时间检查技能描述。技能描述才是 Agent 决策的第一依据。
5.2 技能间的隐式依赖:运行时才发现已经晚了
另一个高频事故是技能间的隐式依赖。比如 create_refund_request 内部假设订单状态是从 query_order_info 返回的,如果未来流程里有人直接调 create_refund_request 而不是走完整链路,它的逻辑就会因缺上游数据而报错。
这类问题单测是测不出来的,因为单测里我们已经把前置条件 mock 好了。agent-skills 的做法是在注册中心强制声明依赖:
requires: - skill: query_order_info provides: [order_status, payment_method]编排引擎在构建 DAG 时会先做静态检查,如果 create_refund_request 声明了 requires query_order_info,而当前流程里没有该前置节点,就直接抛错。这个设计逼着开发者把隐式依赖显式化。上手第一周会觉得很繁琐,觉得“多此一举”,但当你同时维护二十几个技能时,这份显式声明的价值就体现出来了——它相当于一张技能的依赖关系地图,让重构时不再提心吊胆。
5.3 上下文污染:技能的“记忆”不该无限增长
最后一个必须讲的坑是上下文污染。技能执行完,执行结果会返回主干模型,但如果结果本身太大,比如查知识库返回了 20 条文档片段,这些内容会全部塞进对话历史,后续模型生成时既容易被无关片段干扰,也会因为 token 太多导致响应变慢甚至截断。
我在 agent-skills 的上下文管理里做了三件事:
- 结果裁剪:知识检索只保留 top 3 条高分段落,且每条截断到 200 字以内
- 状态摘要:执行完一个技能后,引擎把“技能名 + 核心结论”压缩成一句话放回短期记忆,原始结果进入临时存储,不再进入模型上下文
- 引用回收:当用户明确表示“不需要了解细节”或者进入下一个任务时,上一任务的中间结果会被清理
这三件事做完后,长会话场景下的模型回答准确率明显回升。很多 Agent 项目用着用着效果变差,不是模型退化了,而是上下文里的垃圾越来越多了。控制技能产生的上下文噪声,和优化算法一样重要。
6. 把技能当产品运营:可观测性、评估与灰度发布
6.1 技能调用日志里藏着系统健康的全部秘密
技能系统上线不是终点,持续运营才是。我在 agent-skills 里为每个技能注入了完整的调用追踪,日志里必须包含这几个维度的信息:
- 请求维度:用户原始输入、触发的技能名、重试次数、完整延迟
- 决策维度:模型在选择该技能时的置信度、候选技能列表、被淘汰技能及淘汰原因
- 结果维度:技能执行是否成功、返回数据大小、异常堆栈
有了这些日志,我可以在一个看板上同时监控每个技能的调用量、失败率、平均延迟。但我们发现一个反直觉的现象:有时候技能执行成功率很高,但用户满意度却在下降。追查日志后发现,模型虽然选对了技能,却把技能返回的关键信息漏掉了,比如查到了退款状态是“失败”,但模型在回复时只说了“退款已处理”,没有提“失败”这个关键点。这是模型对技能结果的二次加工出了问题,光看技能执行指标根本发现不了。
后来我增加了一个“关键字段透传率”指标,用正则从技能输出中提取必填字段,再检查模型最终回复是否覆盖这些字段。这个指标出来后,很多潜在体验问题都浮出水面了。
6.2 用评测集给技能选型做“体检”
技能评测这件事,我建议从第一天就启动,不要等系统上线后再补。做法是维护一个评测集,里面每条样本包含四部分:用户问题、正确技能、禁忌技能、期望输出字段。
比如一条样本是“我的订单被快递弄丢了怎么办”,正确技能是 query_logistics_exception,禁忌技能是 create_after_sale_order。因为用户在确认丢件原因之前不应该直接创建售后单。评测逻辑是在固定模型配置下跑完整链路,计算三个分数:召回率(正确技能是否被选中)、误用率(是否选了禁忌技能)、字段完整率(关键信息是否传达到位)。
每周跑一次评测,把分数变化和本周技能描述改动关联起来。这个方法帮我抓到过不少“回归”——某次优化了技能 A 的描述,结果技能 B 的误用率涨了,因为 A 和 B 有共享关键词。评测集就是技能系统的安全网,没有它,你根本不知道自己改坏了什么。
6.3 灰度发布与快速回滚
技能升级是日常操作,但直接全量替换是危险行为。agent-skills 支持按用户维度灰度:先放 5% 流量跑新版本技能,观察错误率和调用失败率,连续稳定运行两天后才逐步扩大到 30%、60%、100%。如果某一步指标异常,立即回滚到上一个版本。
有一个设计让回滚格外轻松:技能执行器的内部接口保持兼容,新版技能只替换 handler 实现,不改变技能定义文件中的名称、参数和输出结构。这样回滚只是把流量切回旧版本,不需要改调用方代码。如果新版本技能真的需要改输入输出结构,我会把它注册为一个全新技能名,比如 v2,然后在编排层做流量切换。这样做虽然多了一点维护成本,但换来的是随时可以安全回退的确定性。
这套灰度机制落地后,团队对技能迭代心态变了。以前升级一个技能像是“拆弹”,现在就像日常发版——有评估、有监控、有兜底,改起来从容得多。
最后再分享一点个人心得。我见过很多团队把 Agent 效果不佳归咎于模型选型不当、prompt 不够精细,却忽略了一个更基础的问题:技能的抽象、描述、编排和运营,有没有像对待正式产品一样对待它?agent-skills 这条路真正教会我的,不是某一个框架或者工具链,而是把 Agent 的能力拆成可治理的单元,让每一个能力点都有描述、有测试、有版本、有监控。如果你正在搭自己的 Agent 系统,别急着堆功能,先从技能建模开始,把地基打好,后面才走得稳。