我最早注意到“agent-skills”——或者按我习惯的说法,Agent技能体系——是在一个客服工单自动化项目里。当时我手头有一个表现还不错的Agent,基于大模型做意图识别和话术生成,demo时惊艳全场,一上真实流量就露馅:用户换一种说法它就懵,遇到多步骤任务直接放弃,最要命的是同类工单每次处理方式都不一样,业务方根本不敢放手让它执行。折腾了大半个月,我意识到问题不在模型,而在Agent的能力组织方式上——我们把所有能力都堆在提示词里,却没有把任务处理逻辑封装成独立、可复用、可评测的“技能”。后来我把整套方案重构成以skills为核心的架构,情况才彻底改观。
这篇文章就把我对agent-skills的完整理解、落地经验和踩坑记录写出来。具体会包括:为什么Agent必须要技能化、技能系统的最小设计单元长什么样、多技能之间怎么编排协同、以及我在真实项目里遇到的那些坑和解决办法。如果你是做大模型应用开发的工程师,或者正在把Agent往生产环境推,这篇文章应该能让你少走不少弯路。
1. 为什么Agent不能只靠Prompt,必须要有技能体系
先聊清楚一个基础问题:Prompt和技能到底差在哪。
大模型确实很强,给它一段任务描述,配上几个工具列表,它往往就能干活。但这种模式撑死后端某个写死的场景,一旦进入生产环境,立刻暴露出一堆工程上的硬伤。我复盘那段时间踩的坑,其实集中在三件事上:
| 问题 | 单纯堆Prompt的表现 | 技能化后的表现 |
|---|---|---|
| 稳定性 | 每次输出有微小漂移,处理链路一长就失控 | 流程固定,模型只负责关键决策,结果可复现 |
| 可维护性 | 提示词和业务逻辑混在一起,改一处崩一片 | 技能独立成模块,改A不影响B |
| 可评测性 | 只能人工看对话记录,没法自动化回归 | 每个技能有独立评测集,改动后可批量验证 |
这里的核心认知是:技能不只是“更长的Prompt”,而是把大模型的能力、外部工具、内部状态、决策规则和异常处理封装成一个完整任务单元。它像一个有明确输入输出和内部协议的微服务,只是执行引擎里有一层大模型。
我记得很清楚,重构之后第一个受益的场景是退款处理。之前Prompt里的表述是“如果用户要求退款,调用退款接口”,看起来没什么问题对吧?但实际业务里有十几种退款类型,有的要审核、有的要原路退回、有的要扣手续费、有的账单已经封账还得走线下。业务方把这些规则一条条写进提示词,模型根本记不住,每次都得靠运气。
技能化之后,退款变成了一个独立技能:输入是用户订单和退款诉求,技能内部有标准评估流程,模型只做“判断走哪条分支”这一件事,其余步骤由确定性代码和子技能接管。结果就是,处理成功率从靠Prompt时的不到60%,提升到稳定在95%以上。
这也是我一直主张的观点:Agent的上限在模型,也取决于模型的推理能力;但Agent的下限,也就是它能在生产环境跑多稳,完全由技能体系的工程化程度决定。
那项目名叫“agent-skills”,到底想表达什么?
从我的角度理解,它指向的是一套完整的技能工程方法论,而不只是给模型塞几个工具描述。包含四层东西:
- 技能定义层:标准化的技能描述、输入输出约束、触发条件;
- 技能实现层:代码、Prompt模板、工具绑定、子技能编排;
- 技能调度层:Agent如何从当前任务路由到正确技能;
- 技能治理层:版本、评测、权限、审计。
后面章节我会把这四层一层层掰开来讲。
2. 技能的最小设计单元:一份配置搞定定义、触发与执行
很多团队做Agent功能时,习惯直接写“一个函数就是一个技能”,或者“一个Prompt就是一个技能”。这两种做法我都试过,最后都不太理想。真正的技能单元,应该是一份结构化的技能包,它能在不依赖系统prompt的前提下,向调度层传递所有必要信息。
2.1 技能包的标准结构
我在项目里最终沉淀出的技能包目录结构是这样的:
skills/ └── order_refund/ ├── SKILL.md # 技能声明:名称、描述、触发条件、依赖 ├── runbook.md # 执行流程:分步骤的决策指引和交接规则 ├── schemas.py # 输入输出Schema定义 ├── main.py # 技能主逻辑:确定性部分+模型决策点 └── tests/ # 该技能的独立评测用例关键在SKILL.md。它不是给模型看的长篇大论,而是给两个读者看的:一个是调度器(可能是代码,也可能是模型本身),另一个是后续维护这个技能的工程师。
一个典型的SKILL.md长这样:
--- name: order_refund description: 处理用户退款申请,支持原路退回、重新支付、线下转账三种方式 version: 1.3.0 author: platform-team tags: [billing, order, refund] trigger: - 用户明确表达退款诉求 - 用户在售后工单中选择"退款/退货"分类 - 风控系统标记的争议订单【仅管理员可触发】 input: order_id: string # 必填,订单ID reason_code: string # 必填,退款原因编码 amount: float # 选填,退款金额,默认原订单金额 output: refund_id: string status: enum[submitted, approved, rejected, manual_review] dependencies: - order_query - user_identity_verify - payment_gateway_refund timeout_ms: 15000 fallback: manual_escalation你先别急着看语法,里面有几个设计点我想单独强调:
第一,trigger字段是给技能路由用的。Agent拿到用户消息后,先做一次轻量分类,判断命中了哪些技能的触发条件。我踩过的一个坑是,有些技能描述写得太泛,比如“处理订单相关问题”,结果所有订单相关请求都路由进来,技能内部再做二次判断,导致大量无效调用和上下文浪费。触发条件写得越具体,路由越精准。
第二,dependencies不是装饰用的,它告诉调度层这个技能运行时依赖哪些其他技能或工具。这对后面做编排、做权限控制都有用。我在一次安全事故复盘时发现,某个技能因为依赖列表没写全,绕过了审计直接调用了高权限支付接口。自那以后,依赖声明和权限绑定成了代码审查的强制项。
第三,timeout和fallback是生产环境必备字段。模型调用耗时不可控,技能必须有超时上限,并且明确超时后怎么办。我的默认策略是:超时先重试一次,仍失败就走fallback指定的技能或人工程序。
2.2 技能和普通工具调用的本质区别
很多人会问:你的技能包不就是封装了一堆工具调用吗?和直接给模型几个函数有什么区别?
区别非常大,我用一个具体例子说明。
假设你要做一个“差旅报销单审核”技能。普通工具调用的方式是:
工具A:获取报销单 工具B:获取差旅标准 工具C:判断超支模型自己决定工具调用顺序,凭推理能力把A、B、C串起来。这事looks简单,但实际跑起来你会发现,模型经常会在拿到报销单后直接下结论,忘了查差旅标准;或者查完标准算错了超支金额。为什么?因为推理链路越长,上下文干扰项越多,模型的失误率指数级上升。
技能化之后,这条链路变成了:
# main.py 中简化后的确定性流程 def run(ctx: SkillContext): # 1. 固定顺序:先取单,再取标准,两者无依赖关系,但顺序固定 receipt = ctx.call("order_query", id=ctx.input.order_id) standards = ctx.call("travel_standard_query", employee_level=ctx.input.employee_level, destination=receipt.destination, date_range=[receipt.start_date, receipt.end_date]) # 2. 把"可计算"的部分从模型推理中剥离 over_items = [] for item in receipt.line_items: budget = find_budget_rule(standards, item.category) if item.amount > budget.ceiling: over_items.append({ "category": item.category, "amount": item.amount, "budget": budget.ceiling, "excess": item.amount - budget.ceiling }) # 3. 只有"需要解释的异常判断"才交给模型 if over_items: verdict = ctx.llm_judge( prompt_template="OVER_BUDGET_REASONING", data={"items": over_items, "policy_notes": standards.policy_notes} ) return {"verdict": "needs_review", "excess_items": over_items, "reason": verdict} return {"verdict": "approved", "excess_items": []}你有没有注意到,这个技能里,模型只被用在两个地方:路由决策(判断该走哪条分支)和开放解释(对超支情况的自然语言说明)。其他所有环节都是确定性代码,靠计算而不是靠感觉。
这就是技能和工具的本质区别:工具是单次动作,技能是完整任务处理单元。工具回答“你能做什么”,技能回答“这件事你怎么负责到底”。
3. 多技能协同:编排层的职责与边界
单个技能解决单一任务,Agent真正复杂的地方在于它需要面对的是一个完整用户请求,里面可能包含了多步骤、多依赖、多条件分支。这部分承担编排责任的,不应该是技能本身,而应该是独立的编排层。
3.1 编排器的职责:路由而不是啥都管
我先说我见过的一种普遍错误:把编排器和技能混在一起,在系统提示词里写“如果用户报告订单问题,先执行A技能,如果A技能返回x,再执行B技能...”。这种硬编码的逻辑写进Prompt,短期看能跑,长期看就是灾难——每次改流程都动Prompt,而Prompt一改,所有关联技能全部受影响,难以回归。
更好的做法是:编排器只负责路由和状态管理,不负责具体执行。
编排器需要回答以下几个问题:
- 用户当前请求应该交给哪个技能?
- 技能返回的状态是终态还是需要继续?
- 多个技能按什么顺序执行?
- 技能之间需要传递哪些上下文字段?
我用了一个很轻量但稳定的方案:基于意图分类的路由器+基于状态机的流程控制器。
# 路由器:把用户请求映射到技能名 ROUTER_PROMPT = """ 你是技能路由引擎。根据用户请求和可用技能列表,选出最合适的技能。 用户请求: {user_message} 可用技能(只允许选一个): {skill_list} 输出JSON格式: {"skill": "技能名", "confidence": 0到1之间的小数, "reason": "一句话理由"} """ # 路由器解析结果后,如果confidence低于0.7,不直接执行技能 # 而是转向澄清话术,让用户补充信息这个设计里有一个值得说的点:confidence阈值。很多人做意图路由时,模型说“我确定要调用退款技能”就真的调了。但实际场景里,用户说“我不想要这个订单了”可能是退款,也可能是取消订单或退货,两者流程完全不同。我设了0.7这条线,低于阈值就让用户先确认,宁可多一次交互,也不让路由错误导致后续流程全崩。
3.2 子技能组合:一个真实的多步流程是怎么串起来的
我拿一个“企业客户合同续期”流程举例,这个流程涉及5个技能,看起来是这样的:
contract_renewal_flow: steps: - skill: contract_expiry_query input: customer_id output: current_contract - skill: credit_check input: customer_id output: credit_score - skill: pricing_eligibility input: - contract_id - credit_score output: eligible_plans - skill: renewal_negotiation input: - current_contract - eligible_plans output: negotiation_summary - skill: contract_signature_generation input: negotiation_summary output: contract_draft看起来只是顺序执行,对不对?但实际复杂不在于顺序,而在于分支。
我的流程里,credit_check结果如果判定为低风险,直接跳到renewal_negotiation;如果判定为中风险,需要额外走一个manual_approval技能;如果高风险,直接终止流程并通知销售人工介入。这些分支逻辑应该放在流程控制里,由编排器判断前一步的技能输出值来决定下一步。
总来说,编排层的设计原则是:流程用代码定,决策用模型做,异常走fallback。不要让模型记住整条流程,也不要用超长Prompt把流程细节全部写进去,更不要让每个技能自己去考虑“下一个技能是谁”。这是我在多次重构后得出的最稳的平衡点。
4. 那些让人头疼的踩坑记录:技能落地最容易出问题的五个环节
技能化改造最大的挑战,不是设计,而是落地。下面这几个坑是我和团队在真实项目中踩过并最终解决的,每一条都对应着实际代码层面的修复,不是泛泛的经验之谈。
4.1 坑一:技能描述与触发条件的“虚假泛化”
我最初写技能描述时,非常有“复用意识”,一个“订单处理”技能恨不得包揽查询、修改、取消、退款、催单。描述是:“处理用户关于订单的所有问题”。
结果路由器一上来就被带偏了。用户说“我的订单怎么还没有送到”,路由器判定命中“订单处理”技能,技能内部再判断意图,再调用物流查询工具。绕了一大圈,多消耗了大量tokens,用户还觉得回复慢。
解决方式是先聚焦再扩展:每个技能只负责一类窄任务,触发条件精确到动作和对象。比如技能名称从“订单处理”拆成“订单状态查询”“订单配送时间修改”“订单退款申请”“订单取消”四个独立技能。表面上看技能数量变多了,但每个技能的命中率、执行成功率和评测得分都明显上升。
我后来给团队定的规矩是:如果技能描述里出现了“所有”“任何”“等”这类词,基本可以判定命名粒度过大,必须拆。触发条件宁可写得窄,之后靠路由和技能内部再做子任务细分,也不要一开始就写一个大而全的触发面。
4.2 坑二:上下文窗口和技能说明文档的冲突
这个问题非常隐蔽。技能做得越完善,说明文档就越长。我把一个“多平台商品比价”技能的runbook写到两万多字符,包含平台对接说明、反爬策略、价格计算规则、异常处理表。结果每次技能被调用,这堆文档全塞进上下文,模型还没开始处理用户问题,上下文就消耗了大半。
代价是显著的:响应变慢、成本上升、模型注意力被长文档里的细节带偏,反而遗漏关键任务字段。
我的修复方案是“分层说明书”:
- SKILL.md只保留路由层需要的信息:名称、触发条件、输入输出schema、前置依赖;
- runbook.md细分为按需加载的片段:技能执行时先加载第一个决策点所需的片段,后续决策点遇到时再动态拼接;
- 参考文档单独存放:不在技能执行时直接注入,而是通过RAG方式在需要时检索相关片段。
改造后,同一技能的单次调用上下文消耗从原来的约1.2万token降到了约3500token。这个数字差异在生产环境里影响非常大,既快又便宜。
4.3 坑三:模型的“自由发挥”与审批回退
大模型天生爱自由发挥,这在做创意写作时是优点,可在处理财务、合同、医疗这类强约束场景时就非常危险。我遇到过的情况是,模型在“报销单审核”技能里,自行提高了餐饮报销标准,理由是“用户出差地点属于一线城市,认为合理”。
我把“规则解读”和“规则执行”拆开了。规则执行全部落到确定性代码,用Python字典和计算公式直接比对发票金额与标准上限;模型唯一能做的是在异常情况下生成解释性文案,但生成内容不允许包含规则外的金额建议。
遇到模型自作主张,最保险的方法是让它“没机会做决定”而不是“好言相劝别这么做”。事实证明,不管你在提示词里写多少遍“不要超过标准”,都挡不住模型的幻觉,只有把决策从模型手里拿走,才是真正的稳。
4.4 坑四:失败与重试机制的设计缺失
技能执行中经常遇到外部API超时、返回格式异常、中间状态丢失等问题。早期我把所有失败都归结为“重试一次再不行就报错”,结果经常出现重复扣款、重复创建工单这种更严重的二次事故。
后来给每个技能加了两层防护:
- 幂等键:每次技能执行带上单调递增的execution_id,所有外部写入操作都校验这个ID,重复调用时不重复执行;
- 状态机明确的失败分类:把失败分成可重试、不可重试、需人工处理三类。
比如支付网关返回“网络超时”,属于可重试,最多重试两次;返回“余额不足”,属于不可重试,技能立刻终止并把用户转给话术处理;返回“风控拦截”,属于需人工处理,技能自动创建工单并通知审核人员。
这个机制上线后,技能相关事故率下降了接近80%。技能化不是只让流程自动化,更重要的是把不稳定因素纳入可控范围。
4.5 坑五:评测集到底覆盖什么
很多团队做技能评测,测的是“模型输出这句话对不对”。但技能在真实运行中是多步链路的组合,输出只是一环,光评输出没有意义。
我的做法是给技能建立“场景级评测矩阵”,每个技能至少覆盖四类场景:
| 场景类型 | 评测内容 | 典型用例数 |
|---|---|---|
| 主路径 | 按标准流程全流程执行成功 | 20 |
| 边缘条件 | 输入为空、金额为负、客户等级为空等 | 15 |
| 异常路径 | 外部API超时、返回格式错误、规则冲突 | 15 |
| 安全边界 | 越权操作、非法参数注入、绕过审批 | 10 |
每个用例不只看终态输出,还检查中间状态是否正确传递、调用了哪些外部工具、关键决策点是否落在预期分支。这套矩阵建好后,每次技能改动都能跑一遍回归,再也不怕“改A坏B”。
5. 从技能到技能库:沉淀可复用的Agent能力资产
当你的项目里技能数量超过10个,你会意识到“技能”这个层面已经不足以解决组织问题了。你会需要一套技能库的管理规范,来支撑跨业务线的复用。
5.1 分层设计:通用技能、领域技能、场景技能
我把技能库里的所有技能分成三层:
- 通用技能层:与业务无关的基础能力,比如”信息抽取““格式转换”“数据脱敏”“相似内容检索”。这类技能最容易被复用,单独拉出来治理。
- 领域技能层:基于通用层组合起来的领域能力,比如“报销合规检查”就是“数据脱敏”+“规则引擎”+“文本分类”的组合。
- 场景技能层:面向具体业务场景的端到端技能,比如“销售差旅报销审核”,直接面向最终用户。
分层之后逻辑清晰很多:上层技能的依赖只指向领域层,领域层只依赖通用层,通用层独立演进。避免最顶层技能直接调底层原子工具,否则依赖关系会乱成一锅粥。
5.2 版本演进与跨项目复用
技能包本质上是代码,所以它必须纳入版本管理。我用的规范是:每个技能一个Git仓库目录,单独的版本号和更新日志;技能之间的依赖声明具体到版本号,不允许“引用最新版”这种模糊依赖。
跨项目复用最大的优势是可以让不同团队避免重复造轮子。例如一个“客户身份核验”技能在支付团队已经打磨过,有完整的异常处理和评测集,其他团队接入时只需要改几个业务参数,几分钟就能完成适配。复用的前提是:
- 技能文档足够清晰,尤其“边界行为”部分,要说明哪些情况下它不会处理;
- 评测集对第三方开放,接入方可以先跑测试判断是否满足自己的场景;
- 接口变更必须走兼容性评估,每个发布周期先看有没有破坏下游调用方。
5.3 技能治理:权限、审计与淘汰
技能越来越多了,包括依赖关系、权限范围、生命周期管理。我把技能治理做成了每月一次的例行动作:
- 权限盘点:核查每个技能实际调用的API权限和声明是否一致,把闲置的高权限技能降权;
- 调用审计:按技能维度统计调用量、成功率、平均耗时、失败分布,全部落到面板上;
- 淘汰机制:连续60天无调用的技能标记为”过时“,连续90天仍无使用的移出主库。
这个机制可能听起来很重,但对一个中型团队来说,它能让技能库保持干净,减少维护负担。你也不想看到几十个无用技能躺在库里,每次路由时都来干扰模型判断吧。
6. 你接下来可以怎么开始落地
听到这里,你可能已经摩拳擦掌,想知道从哪里入手。我的建议是不要一上来就建宏大的技能库,而是从一个你手头最痛、最重复、最容易被老板催的那个Agent任务开始。
具体步骤是基于我的经验总结:
- 选一个任务:找一个当前Prompt处理得很费劲、错误率高的任务,先不要选太复杂的流程,中等复杂度最好。
- 写技能包骨架:按上面的结构初始化SKILL.md、main.py、schemas.py,先不追求完美,能跑通主路径就行。
- 把可计算逻辑移到代码里:检查技能执行链路,凡是能用规则、公式、查表解决的判断,一律从Prompt中移走。
- 给技能加三大件:幂等键、超时处理、回退策略。这一步不做好,你后面会天天被on-call电话吵醒。
- 搭评测集再上线:至少把你的主路径用例和异常用例写出来,再推进到生产。
- 观察运行数据并迭代:上线后持续关注调用成功率、失败类型分布,按数据反馈修技能包。
如果你手头有想尝试的Agent技能场景,照着这个流程走一遍,应该几天内就能看到明显变化。
我在把这个架构推到三四个不同业务线之后,最深的感受是:技能化不是一个技术决策,而是一个工程纪律。它逼着你把模型的自由度关进笼子里,把决定权收回到确定性代码手里,把Agent从“看起来很聪明”变成一个“行为可预期、结果可校验、质量可复盘”的生产工具。它确实是更麻烦一些,但这些麻烦,全部都是为了上线后的安稳觉。