
如果你让一个通用大模型“帮我把这周的项目周报整理好发给负责人”它大概率会回你一段“好的已经为你整理如下”然后什么都没发生。这不是模型不够聪明而是它缺了 AI 应用落地里最关键的一层东西可执行、可校验、可复用的技能agent-skills。今年以来越来越多团队开始把“提示词调优”的重心转向“技能工程”原因是纯对话式的 Agent 撑不过三个真实任务就会翻车。这篇内容想把我自己在 agent-skills 技能库设计、封装、编排和迭代过程中的完整思路写下来适合正在做 AI 应用、客服机器人、办公自动化或者自研 Agent 框架的开发者参考。1. agent-skills 要解决的问题为什么 Agent 老是“嘴上会手上废”1.1 只有对话能力的 Agent 等于“纸上谈兵”大模型本质上是一个文本生成引擎它的强项是“接话”不是“办事”。你让它描述一封邮件应该怎么分类它可以讲得头头是道你让它真的去读取收件箱、判断邮件类型、写入工单系统它就卡住了。这不是模型能力不够而是我们根本没给它“手”。我见过很多初期项目团队花了大量时间写系统提示词试图让模型自己完成“理解需求—规划步骤—逐项执行”的完整链路。结果测试集上跑得不错一上真实数据就原形毕露要么模型忘记调用工具要么调用了但传参是编的要么执行到一半开始一本正经地胡编结果。问题的根源不在模型而在我们让模型承担了太多“确定性工作”——这些工作恰恰是概率模型最不擅长的。agent-skills 想解决的就是这个错位。它把“与人对话的能力”和“与系统交互的能力”彻底分开对话归对话技能归技能。模型只负责决策——判断当前该用哪个技能、传入什么参数真正干活的是技能执行器它是确定的代码不是概率输出。1.2 agent-skills 是什么把“能做事”沉淀成系统资产技能这个概念听起来抽象放到工程里其实很具体。它是一组“描述 参数协议 执行器 校验规则”的组合单元。模型看到技能描述知道这个技能是干什么的、什么时候该用、参数长什么样然后生成一个结构化调用请求技能执行器收到请求后执行真实操作返回结构化结果校验规则确保模型没有乱传参、执行器没有乱返回。这个设计最关键的一点是技能是系统资产不是提示词附属品。提示词改一版就没了技能却可以沉淀下来被不同场景反复调用。今天客服机器人用邮件分类技能明天知识库助手也可以复用同一个技能。每封装一个技能系统就多了一份确定性的能力而不是多了一段迟早会失效的“咒语”。我在团队里经常打一个比方技能之于 Agent就像岗位职责说明书之于新员工。面试的时候对话聊得再好不给他明确的职责边界和操作手册他也只能坐在工位上发挥创造力——而创造力在填工单这件事上通常不是优点。1.3 技能化带来的三个改变可复用、可测试、可观测把能力拆成技能之后最直观的变化有三个。第一是可复用。同类操作只需要封装一次后续所有 Agent 都可以调用。原来每个业务线各写各的提示词现在统一走技能库行为一致性大幅提升。第二是可测试。技能是相对独立的函数单元输入输出都有契约可以像测普通代码一样对技能本身做单元测试而不需要每次都端到端跑整个 Agent。回归测试成本直线下降这在你迭代技能描述时尤其重要。第三是可观测。每一次技能调用都可以记录模型选了哪个技能、传了什么参数、执行器返回了什么、耗时多久、成功还是失败。这些记录是后续优化的数据基础也是排查线上问题的核心线索。纯对话系统里你是看不到这些的模型脑子里想了什么你根本无从追溯。1.4 先想清楚边界什么值得做成技能我踩过的一个坑是“什么都想技能化”结果技能库膨胀到几百个模型在决策时反而更晕。后来团队定了一个筛选标准高频、确定、低容忍。高频是指这个操作经常被用到值得封装确定是指操作本身有明确输入输出不需要主观判断低容忍是指这个操作不能接受随机性——分类错了、计算错了、单据漏了后果比较严重。符合这三条的就做成技能不符合的保持对话或走人工。反例也很明显一次性的数据清洗、需要行业经验判断的复杂决策、以及“帮我想一个方案”这类开放性问题都不适合硬塞进技能体系。技能要解决的问题是“把话说清楚之后能立刻执行”不是“替人类思考”。2. 技能协议设计让模型能看懂、能调用、能后悔2.1 技能描述第一句做什么第二句何时用第三句别做什么技能描述是模型做决策的主要依据它的重要性被我严重低估过。早期我随手写描述比如“对邮件进行分类”结果模型在遇到“帮我把这封邮件转给张三”时也调用它。后来总结出一个三段式写法效果稳定很多。第一句写这个技能做什么动作要具体。第二句写什么时候用最好带上触发条件。第三句写不做什么也就是负向边界。举个例子邮件分类技能的描述可以这样写将一封邮件文本归类到指定类别列表中返回每种类别的置信度。当用户要求对邮件打标签、分拣、归类且提供了邮件正文时使用。不要用它来发送邮件、回复邮件或判断邮件是否为垃圾邮件。这个描述看起来简单但每一句都有作用。“提供邮件正文”这个触发条件直接堵住了“帮我分一下收件箱”这种没有正文输入的调用“不要用它发送邮件”则防止了模型把转发、回复等动作也归到这个技能上。模型是概率决策你给它的信号越清晰它越不容易自由发挥。2.2 参数 Schema给模型的“填表说明”参数是模型传给执行器的数据。模型并不“理解”参数含义它只是在根据你的说明猜。所以参数协议的每个字段都要像给一个很聪明但没经验的实习生写填表说明一样。我一般用 JSON Schema 来定义。最重要的是字段说明、必填约束和枚举约束三件事。字段说明告诉模型每个参数是什么意思required 告诉模型哪些不能省enum 则是在参数取值范围明确时把选择权从模型手里收回来。{ type: object, properties: { email_text: { type: string, description: 需要分类的邮件正文纯文本不要截断 }, categories: { type: array, items: { type: string }, minItems: 2, description: 候选分类名称列表必须来自预设分类表 } }, required: [email_text, categories] }这里包括了一个容易忽略的点minItems。它防止模型只给一个分类那样分类就没有比较意义了。参数设计的原则是“能收窄就收窄”——每个约束都在降低模型的出错空间而不是增加它的自由度。2.3 执行器的返回值协议结构化才能被二次利用技能执行器返回什么直接决定了下游能不能继续干活。我最开始的执行器返回的是“操作成功”“操作失败”这种人类可读文本结果下游技能解析起来痛不欲生。后来统一改成结构化 JSON字段固定为 status、data、error 三块。{ status: success, data: { category: inquiry, confidence: 0.92 }, error: null }status 取 success 或 errorerror 里带机器可读的错误码。这样既方便模型理解执行结果也方便代码层面做分支处理。还有一个隐藏好处结构化返回值本身就是一份日志回放的时候一眼就能看出问题出在哪一步。人类可读文本适合展示给用户不适合作为系统内部协议——系统内部需要的是精确的契约。2.4 技能注册与发现模型怎么知道有哪些技能技能多了之后怎么让模型在合适的时机找到正确的技能会变成一个独立的工程问题。最朴素的做法是把全部技能的描述塞进系统提示词但技能数超过二三十个之后一是 token 开销变大二是模型决策精度明显下降——它开始忽略部分技能只盯着前几个看。更实用的做法是引入检索层。先把技能描述做向量化模型接到来话后先在技能库里做语义检索召回 TopK 个候选技能再把这些候选技能的完整描述注入上下文。这个方案让技能库的上限大幅放宽几百个技能也可以管理。代价是引出了一个新问题检索质量差会直接导致 Agent 看不见正确技能所以技能描述里的关键词质量又变得极为关键。我的体感是五十个技能以内全量注入加精心排序还能撑住超过五十个必须上检索。这个数字不是精确门槛但可以作为规划技术方案的参考。3. 从 0 到 1 打磨一个技能以邮件分类为例3.1 选场景客服邮件自动分类为了把上面的协议讲透我拆一个完整的例子。某公司客服团队每天要处理几百封邮件希望先由 Agent 自动分类再按类别分配给不同专员。这个任务完美符合技能化的三个标准高频、确定、低容忍。团队最初试过让模型直接输出“这封邮件属于哪个类别”准确率在百分之七八十左右上不了生产。问题出在两点一是模型的分类标准不稳定同样的邮件换个说法就分到别的类二是没有校验环节分了就分了错了只能等用户投诉。随后我们把这个任务改造成 agent-skills 体系下的一个正式技能情况才稳定下来。3.2 第一步写技能定义文件技能定义文件承载的是描述、参数 Schema 和元信息。它既是给人看的文档也是模型运行时读取的配置。name: email_classifier version: 1.0.0 description: 将邮件正文归类到给定类别列表并返回置信度。 当用户提供邮件正文并要求分类、打标、分拣时使用。 不要用于发送、回复邮件也不要用于判断垃圾邮件。 parameters: type: object properties: email_text: type: string description: 完整邮件正文 categories: type: array items: type: string minItems: 2 description: 类别列表必须来自预设分类表 required: [email_text, categories]这个文件里最容易被忽略的是 version 字段。技能上线后会持续迭代没有版本号你连“这次准确率下降是不是因为改了描述”都没法定位。版本号是技能工程里最低成本的投入。3.3 第二步写执行器执行器是纯代码部分负责接收参数、执行真实分类逻辑、返回结构化结果。分类逻辑可以用规则加模型打分结合的方式也可以用一个小分类模型看业务场景。def email_classifier(email_text: str, categories: list[str]) - dict: if not email_text or not categories: return { status: error, data: None, error: email_text and categories are required } scores classify_with_model(email_text, categories) best_index max(range(len(scores)), keylambda i: scores[i]) return { status: success, data: { category: categories[best_index], confidence: float(scores[best_index]) }, error: None }执行器必须做输入校验不能假定模型传参一定合法。模型传空字符串、传一个类别、传根本不存在的类别都是常态执行器的第一行代码应该永远是参数防御。别把防御逻辑放到调用方技能自己就要能兜底。3.4 第三步注册进技能库并做第一轮实测技能定义好之后把它注册到技能库中。注册时要把描述文本也过一遍检索测试——直接用几个典型的用户话来检索看能不能召回这个技能。召回都不行的话后面模型根本不会调用它描述写得再好也没用。第一轮实测时我们组让 A 同学准备了一批真实历史邮件作为回归基准。跑完之后看了几类错误有一部分是分类模型本身错了这个只能换模型或加规则还有一部分是模型调用时传参不完整比如没传全邮件正文只传了主题。前者调整分类算法后者靠优化参数描述解决。两件事要分开处理混在一起调只会越来越乱。3.5 第四步观察模型调用行为迭代描述第一轮实测后我们发现一个高频问题面对“这封邮件要不要优先处理”这类问题模型也会调邮件分类技能传的类别却是“priority”。其实优先级判断不在这个技能的职责范围内。这是典型描述边界不清导致的误用。我们对描述做了一次小改动在第三句负向边界里加了一条“不要用于判断紧急程度、优先级或是否需要立即回复”。改完之后同类误用从每百次调用约十多次降到了两三次。这轮迭代让我意识到技能描述不是写一次就完的静态文本它和代码一样需要根据线上反馈持续修改而且修改之后必须回归验证。4. 实测中的四个翻车场景以及对应的技能优化思路4.1 翻车一描述里写了“可以用于”模型就到处乱用我们有个技能是用来生成内部工单编号的。描述里我随手写了句“可以用于查询工单、创建工单、修改工单备注”。结果模型在用户说“帮我看看上周那个工单怎么样了”时直接调用生成编号技能而不是查询技能。追溯调用日志后我们发现问题就出在“可以用于”这三个字上——它在描述里放大了技能的适用范围模型把生成编号当成了通用工单入口。修法是收紧描述改成“仅为工单创建流程生成唯一编号。不返回查询结果不修改工单内容不涉及工单状态判断。”同时把触发条件写得更窄“仅当用户明确要求新开工单时才调用”。修改后压测了两周误用率归零。经验就是技能描述写得太“积极”模型就敢越权描述里要刻意增加“不要”的分量。4.2 翻车二模型自作主张填参数有些技能参数来自系统上下文不该让模型编。比如一个发送通知的技能收件人列表应该是从通讯录系统查出来的但模型可能直接按用户话里的名字拼一个字符串传进来。这种错误很难靠提示词完全压住因为模型对“参数是如何产生的”没有概念。我们的做法是执行器侧严格校验接收人字段在调用前强制走一次通讯录匹配匹配不到就返回 status: error错误码为 invalid_recipient。不做模糊容忍。一旦执行器对非法参数采取“零容忍”策略模型经过几轮反馈后会逐渐学会从正确来源取参数准确率会有明显提升。这里的关键是不要让模型对参数正确性负责要让系统对参数正确性负责。4.3 翻车三技能返回了一段人话下游直接炸了早期有个技能返回的是“该客户近30天共下单5次平均金额约300元状态正常”。这段人话拿给用户看没问题但另一个技能要拿“平均金额”做后续判断时就傻眼了只能再让模型从文本里抽一次。多了一次概率判断就多了一个出错点整条链路变得不稳定。后来所有技能返回值统一改成结构化 JSONdata 里是数值就放数值是列表就放列表需要展示给用户的文案由前端另行生成。系统内部传递的是数据不是语言。模型擅长生成语言但系统的稳定性恰恰建立在“不需要模型解释语言”之上。4.4 翻车四两个技能“打架”模型选错业务里同时有“查询订单状态”和“查询物流轨迹”两个技能。订单状态只在商家系统更新物流轨迹是快递公司数据。模型经常在用户问“我的东西到哪了”时调用订单状态查询返回“已发货”之后就结束了用户其实想知道快递到哪个驿站了。这类技能相似度太高靠描述去区分是有极限的。我们加了两层机制一是在描述里明确各自的数据来源和更新频率二是做了技能优先级配置当两个技能置信度都较高时优先选更细粒度、信息更新更及时的那个。同时把“到哪了”“运输中”“快递”这些词加到物流技能的触发关键词里。改完后选错率掉了六成左右。翻车场景根因优化手段描述太宽泛导致误用正向描述过多负向边界缺失三段式描述明确“不做什么”模型编造参数参数来源不清晰执行器无校验执行器防御式校验非法参数零容忍返回人话导致下游解析炸裂返回值非结构化统一 status/data/error 协议相似技能互相抢占技能界限模糊无决策优先级补充数据来源描述配置优先级规则这四类问题有一个共性它们都不是模型多笨而是我们在协议层和执行层留下了太多模糊地带。技能工程做得好不好从这些边缘情况的数量上就能看出来。5. 技能编排当单个技能不够用时怎么连成流程5.1 串行链上一个技能的 data 是下一个技能的口令单个技能能解决单一操作真实业务往往是多步骤流程。客服邮件分类是第一个环节分类完成后要自动触发后续动作投诉类邮件进客诉系统并给用户回执咨询类邮件查知识库后自动拟草稿其他类别直接转人工。这类顺序流程我推荐用代码层面的编排引擎来定义而不是让模型自由发挥。分类技能的 data 输出直接交给编排引擎再由引擎决定下一跳。result agent.invoke(email_classifier, { email_text: email_text, categories: CATEGORIES }) if result[status] success: category result[data][category] else: category manual_fallback if category complaint: ticket_id agent.invoke(create_ticket, {...})这么做的好处非常明显流程可控、执行透明、测试起来也简单。你不需要让模型去规划“先分类再建单再回执”的完整路径路径由代码锁定模型只负责填参数。模型越少参与流程决策流程越稳定。5.2 条件分支与路由让模型选路径还是让代码定路径关于路由我见过两种极端一种是谁都让模型决定结果路径飘忽不定另一种是完全代码写死模型只做参数抽取灵活度为零。两种都不太健康。我的折中方案是流程骨架用代码写死流程里的“非确定性选择点”才交给模型。比如用户说“帮我查一下订单如果发货超过三天没更新物流就催一下商家”。这个流程里“查订单”和“催商家”是确定动作但“是否要催商家”是个判断而且这个判断需要理解用户语境适合让模型做。于是我们在编排层设了一个条件节点——模型只有在这个节点上输出 yes 或 no后续动作由代码接管。判断是个小决定执行是套流程这样既保住了灵活性也压住了不确定性。5.3 人工审批点把人的判断插到流程中间不是所有技能都适合自动执行。涉及对外发送、资金操作、数据删除等动作哪怕模型判断得再准也必须有一个人工审批闸门。编排引擎里我会设一种特殊节点执行到该节点时流程挂起生成审批任务等人在后台确认后才继续走后续节点。这个设计还有一个额外收益审批数据成了天然的人工标注样本。每次审批其实都在告诉系统“模型的判断对不对”攒下来就是一份高质量微调或评估数据集。团队后来做技能迭代评估时这批审批记录发挥了大作用。5.4 可观测性没有 trace 的技能编排就是盲盒技能编排上线后可观测性就从一个“加分项”变成了“必选项”。每一轮技能调用都要记录触发了哪个技能、传入了什么参数、执行器返回什么、编排走到了哪个分支、耗时多少、有没有回退。否则线上出了问题你只能看到“用户不满意”却不知道是哪一步错了。我们内部维护了一份技能 trace 规范字段类似这样trace_id、agent_id、skill_name、input_summary、output_summary、start_ts、end_ts、status、error_code。这份日志无需太多额外成本却是技能迭代最重要的数据底座。我见过太多团队凭感觉调技能描述调了十次不知道哪次有用本质就是因为没有 trace 数据支撑。6. 技能库的评估与迭代没有数据的技能迭代都是自嗨6.1 先建回归数据集真实任务记录比想象中更值钱技能库一旦超过十个技能靠人工拍脑袋判断“这次改得有没有用”是完全不行的。我们做的第一件事是把线上真实调用记录攒成回归数据集。每一份数据包含原始用户任务、期望调用的技能、期望的参数值、期望的执行结果。来源就是 trace 数据和人工审批记录。数据集不需要很大但真实度很重要。我见过团队拿 AI 生成的模拟用户话来当评测集结果技能在测试集上表现优秀上线就崩。模拟数据覆盖不到真实用户的表达方式——省略、口语、错字、多意图这些恰恰是决定技能成功率的地方。真实数据即使每天只攒一二十条一个月后的回归集就已经足够让你发现大量问题了。6.2 用三个核心指标判断技能好坏迭代技能时我只看三个指标任务完成率、无效调用率、平均调用轮次。三个指标配合看能快速定位问题类型。指标含义判断参考任务完成率用户任务经过技能编排后成功完成的占比低于 85% 说明技能或编排有系统性缺陷无效调用率调用后返回 error 或明显与任务无关的占比高于 10% 说明描述或路由有问题平均调用轮次完成一个任务平均需要几轮模型决策持续上升说明技能边界在恶化一个常见误区是单看任务完成率。某次优化后完成率没变但无效调用率从 8% 降到了 3%这其实是有效进步——零散错误少了模型决策更精准了只是还没反映到完成率上。三个指标一起看你才能分辨“改好了”和“没变化”之间的微妙差异。6.3 什么时候拆技能什么时候合并技能技能库不是越大越好也不是越精简越好。我常用的判断依据来自 trace 分析如果某个技能经常因为“分支逻辑太复杂”而传错参数说明它承担了太多职责需要拆。比如“处理订单”技能拆成“查订单”“改订单”“取消订单”之后参数空间变小准确率立刻上去。反过来如果两个技能经常在同一任务里被连续调用比如“查订单”之后必然“查物流”而且参数高度重叠就该考虑合并成“查订单全量信息”。合并可以减少一次模型决策也就减少一次出错机会。技能的拆分合并不是拍脑袋看数据说话——哪类调用失败率高哪个技能组合出现频率高驱动你调整结构。6.4 技能升级时的兼容性策略技能一定会升级而升级最容易伤到的是存量流程。我们踩过一次把“create_ticket”的参数从“customer_id content”改为“customer_id content priority”结果线上一个老流程没适配新参数连续报错一晚上。现在我们对技能升级有一个硬性要求只允许加可选参数不允许删参数、不允许把必填改成选填、不允许改返回值结构。如果实在要破坏性变更就启用新版本技能老版本保留一段时间通过路由配置灰度切换。版本号不是摆设它是线上稳定的第一道防线。升级完成之后回归数据集必须重新跑一遍和变更前的指标做逐项对比确认没有回退再放量。我自己的体会是agent-skills 这套体系的价值不在于它有多高级而在于它逼着你把“不确定的模型”放回“确定的系统边界”里。模型负责聪明系统负责可靠。每次技能调用、每个参数校验、每份 trace 日志都是在把运气变成工程。如果你正在做 Agent 相关项目我建议从一个小而真实的场景开始先封装一个技能、跑通校验和 trace再慢慢把更多技能加进去——这条路比先搭框架再填内容要稳得多也快得多。