
这两年做AI Agent应用我越来越觉得真正拉开团队差距的往往不是选哪个模型而是技能skills这层东西做得怎么样。agent-skills这个词乍一听像新名词但它实际代表了Agent从会聊天到会干活之间的那层关键封装。简单说一个技能就是教会Agent完成某类具体任务的最小可复用单元查订单、发周报、归档邮件、操作内部系统每一类都能封装成一个技能。技能写得好不好直接决定了Agent是靠谱的执行者还是只会嘴炮的聊天机器人。这篇文章不聊空概念只聊我实际搭技能库过程中的设计思路、实现路线和踩过的坑适合正在做Agent应用、尤其是准备把Agent接进真实业务流的工程师参考。我会从最底层的技能到底在解决什么问题讲起一直讲到技能库的治理和编排保证每一步都能直接落到代码和配置上。1. 从会聊天到会干活Agent技能到底解决什么问题1.1 技能不是函数也不是提示词而是一种新的软件形态很多人第一次接触技能这个概念时容易把它跟函数、跟提示词混为一谈。我一开始也是这样后来在项目里反复折腾才意识到三者本质上是不同层级的抽象。普通函数是给人用的参数靠人传流程靠人编排函数调用function calling比函数进了一步模型能在对话里选择合适的函数并生成参数但它本质上还是一次一问的交互模式没有状态也没有流程的概念。而技能是在函数之上又加了一层语义壳它自带三样东西触发条件、执行步骤、结果契约。模型面对的不是一个孤零零的函数清单而是若干完整的能力说明书它可以自主判断当前这个任务应该调用哪个技能并在一个多步对话里连续使用多个技能来完成复杂目标。我拿客服工单处理举个例子。用传统函数调用你得把分类、查知识库、生成回复、创建任务这些子步骤全部平铺成独立函数模型每次都从十几个函数里挑选错率很高。用技能以后只需要定义一个工单处理技能内部封装上述所有步骤模型只需要决定一件事现在该不该触发这个技能。这层抽象把选择负担从模型身上移走了一大部分。1.2 为什么提示词工具调用撑不起真正的Agent我在一个早期项目里有一个很直观的教训。当时系统里注册了15个工具功能彼此有交叠比如查询订单查询物流查询售后进度。模型经常选错用户问物流它去调订单查询工具返回结果里根本没有物流信息然后它就开始编。后来做了个粗糙的收拢把这几个查询逻辑封装成一个订单管理技能内部自己分流误选率立刻降了一个级别。这个现象背后有一个规律工具数量超过某个阈值后模型的工具选择准确率会明显下降。工具是扁平的没有语义边界模型只能靠函数名和一句description去猜什么场景用什么工具而技能在工具之上加了一层丰富的描述包括能力定位、适合场景、不适合场景、参数要求、副作用模型做决策时的推理负担小得多。所以我的结论是提示词解决的是模型的表达工具解决的是模型的触手技能解决的是模型什么时候、用什么触手、并且知道触手伸出去会有什么后果。不做第三层前两层铺得再多Agent也跑不稳。2. 技能设计的底层逻辑输入、输出、副作用三者缺一不可2.1 输入设计定义触发条件比定义参数更重要技能的第一个核心设计点是让模型明确什么时候该用这个技能。很多人只写一个description就完事了这是最常见的偷懒方式。我的经验是每个技能至少要包含触发条件when to use和禁止触发条件when not to use两块后者尤其容易被忽略。看一个实际技能定义示例name: generate_daily_report description: 生成每日销售数据简报并发送到指定邮箱 when_to_use: 用户要求查看当天销售汇总、生成日报、或者邮件推送销售数据时 when_not_to_use: 用户只是询问某个具体订单的金额时请使用 order_query 技能 parameters: date: type: string format: date required: true description: 报告覆盖的日期默认当天 audience: type: string enum: [management, team] default: team description: 接收简报的角色范围这个YAML里最关键的不是parameters而是when_not_to_use。它相当于给模型的决策边界划了一道线避免技能之间互相抢活。参数部分我建议严格用JSON Schema约束必填、枚举、格式都写死不要给模型留自由发挥的空间。很多Agent跑飞不是模型笨是你在参数层没有给它足够清晰的提示。2.2 输出契约结果返回的结构稳定性比什么都重要技能的输出是模型的下一步决策依据它必须是机器可读、结构稳定的。我看过不少团队写的技能返回结果是自由文本模型得自己去文本里挖关键信息一旦文本措辞变了下游决策就可能出错。稳定输出应该包含四个部分执行状态成功或失败失败还有错误码、结构化结果数据、人类可读的摘要、以及下一步建议。特别是最后这个下一步建议suggested_next_actions很多人会忽略但它对Agent的连续性非常重要。技能执行完不是终点模型需要知道接下来可以做什么这个提示放在输出里比让模型自己猜要可靠得多。{ status: success, code: OK, data: { order_id: ORD-20240615-001, status: shipped, logistics: [{time: 2024-06-16 10:00, event: 已签收}] }, summary: 订单已签收物流显示16日上午完成配送, suggested_next_actions: [ {skill: send_satisfaction_survey, reason: 订单已完成交付} ] }2.3 副作用声明让Agent知道技能干了什么额外的事这一条是我在真实业务里踩过最痛的坑之后才补上的。发邮件、创建工单、改数据库、扣款这类操作都是不可逆或者有外部影响的副作用。技能里必须明确声明这些副作用框架层才能决定要不要加人工确认。副作用声明至少要包括技能会执行哪些外部操作、操作是否可回滚、是否需要用户确认后再执行。比如一个发送周报技能它在执行时真的会发出邮件那它的定义里就应该有side_effects: send_email并配上requires_confirmation: true。没有这个声明模型可能在一个不合适的时间点擅自把邮件发出去这种事故比技能执行失败严重得多因为它影响的是真实业务和真实用户。3. 技能落地的三种主流实现路线与选型对比3.1 纯提示词驱动的技能快速验证期的最优选第一种路线是把技能全部描述成YAML或JSON文件通过系统提示词注入给模型。技能的执行逻辑基本靠模型自己推理框架只负责把技能清单塞进上下文、把模型的输出解析出来。这条路适合原型验证和技能数量少的场景比如10个以内。它的优点非常明显改技能描述不用发版改一行配置重新对话就能生效缺点是技能一多全部描述塞进上下文会把宝贵的窗口空间占掉大半而且执行逻辑完全依赖模型的临场发挥同样的输入可能跑出不一样的结果。我在项目初期用了这种方式快速验证了三四个技能的价值效率非常高。但到了第六七个技能的时候我发现系统提示词里光技能描述就占了快4000个token模型开始出现选择犹豫甚至把技能的description背出来当回答。这时候就该考虑上代码封装了。3.2 代码封装的技能确定性逻辑的归宿第二种路线是把技能写成真正的函数注册进技能框架。适合有状态、有循环、需要校验、需要调外部API的技能。代码封装的最大价值是确定性只要输入合法执行结果就是确定的模型不需要在这部分发挥想象力。agent_skill( nameorder_query, description根据订单号查询订单状态、物流进度和售后信息, when_to_use用户询问订单状态、物流进度时使用, when_not_to_use用户需要修改订单信息时使用 order_change 技能, parameters{ order_id: {type: string, required: True}, include_logistics: {type: boolean, default: False} } ) def order_query(order_id: str, include_logistics: bool False): order get_order(order_id) if not order: return { status: error, code: ORDER_NOT_FOUND, summary: 查无此订单请向用户核对订单号, suggested_next_actions: [] } # 业务逻辑查状态、查物流、查售后 result build_result(order, include_logistics) return result用装饰器或注册表的方式把函数暴露成技能是目前工程上最主流的做法。它跟工具调用的区别在于你把这个函数包装成了带完整使用说明书的技能对象模型看到的不是一个光秃秃的order_query(order_id: str)而是一份包含适用场景、边界、参数细节的能力说明。同样的函数包装成技能前后的调用准确率差别很大这一点我实测过很多次。3.3 混合形态提示词代码工具的三层技能第三种路线是前面两种的结合也是我现在的主力做法。一个技能拆成三层顶层是YAML/JSON描述相当于给模型看的说明书负责触发判断和参数提取中间层是编排代码负责确定性的流程控制比如先查A接口再根据结果调B接口底层是具体工具调用什么HTTP请求、数据库查询、内部服务调用都在这层。这个三层结构的核心思路是能用代码保证的就不用模型推理能用描述引导的就不让模型瞎猜。最理想的状态是模型只做两件事判断要不要触发某个技能、把用户的话填进技能参数里。剩下的流程、判断、容错全部由代码接管。这也是技能和Agent之间的关系Agent负责意图和决策技能负责执行和兜底。路线开发效率执行稳定性维护成本适用阶段纯提示词驱动很高低低但技能多了上下文成本高原型验证、技能少于10个代码封装中很高中核心逻辑、生产环境三层混合中高高中高大规模技能库、长期迭代4. 技能库的组织与治理从一个人的玩具到团队的基础设施4.1 命名规范与语义索引技能数量一旦超过二三十个你就会发现真正难的已经不是写技能了而是让别人甚至让Agent自己找到对的技能。我见过最乱的情况是技能名称随便起比如do_stuff、helper2这种模型面对这种名字根本无从判断。一个有用的命名规范是动词名词动词描述动作名词描述对象比如order_query、order_change、report_generate。描述里要把口语化的场景词带上因为用户不会说order query他们会说我的订单到哪了、能不能帮我看看那个快递。技能描述要尽量覆盖用户的自然说法这部分需要反复打磨不能一次写死。技能目录结构我建议按业务域组织skills/ order/ query.yaml change.yaml cancel.yaml report/ daily.yaml weekly.yaml customer/ satisfaction_survey.yaml complaint_handle.yaml index.yamlindex.yaml是一个总索引里面记录每个技能的名称、所属域、一句话简介Agent框架启动时先加载它再按需加载完整定义。技能多了还可以加一层技能路由器用向量相似度或者规则先粗筛出一批候选技能再交给模型细选。这招在技能数超过50的时候特别管用我建议有规模焦虑的团队提前做。4.2 技能的版本管理与兼容性技能本质上也是接口它的参数契约、输出结构一旦变了依赖它的Agent行为和下游流程都会受影响。所以技能不能改了就直接生效必须走版本管理。我现在的做法是每个技能目录下维护一个CHANGELOG.md技能文件的头部写版本号比如version: 1.2.0。代码封装的技能走GitOps合并主干后自动构建并发布到技能注册表registryAgent从注册表拉取指定版本。这个注册表跟npm、Maven的角色类似只是它管理的不是依赖包而是技能定义和执行代码。灰度上线也很重要。技能改动先发布到一个beta版本让一小部分流量使用观察调用成功率和用户反馈稳定后再全量切换。我见过一个团队直接改了一个核心技能的返回值格式结果Agent在之后的几天里频繁生成错误的下游操作就是因为没做兼容测试。技能的变更应该像修改公共API一样被对待。4.3 单元测试是技能库的命根子如果说版本管理是技能库的骨架那测试就是它的血液。没有测试的技能库改一次就赌一次命这句话真不夸张。每个技能至少要有四类测试正常路径测试给定合法参数返回预期结果、边界参数测试空值、超长字符串、枚举值、错误返回测试外部接口挂了返回什么错误码、触发边界测试不该触发的时候模型不触发该触发的时候不误判。前三种是普通单元测试第四种是模型行为测试需要用评测集跑。我的做法是把历史真实对话场景录下来做成固定数据集每次技能库有改动就自动用这个数据集跑一遍完整Agent流程看模型触发正确率有没有下降。这个回归测试比任何代码审查都有用因为它测的是模型技能这个组合的真实行为。反正我做了一圈下来最深的体会是技能库的维护成本里测试占了至少四成但这份投入省不得。5. 实测踩坑记录技能编排里最容易翻车的四个场景5.1 技能冲突两个技能同时被触发技能数量多了以后第一个高频事故是技能冲突。典型场景我同时定义了generate_daily_report和send_daily_report前者的描述里有生成每日简报后者是发送简报邮件。用户说把今天的日报发我一下模型直接把两个技能都调了先生成一份带附件的又发了一份不带附件的用户收到两封邮件直接懵了。这个事暴露了三个问题一是描述边界没划清二是生成发送本来应该是一个原子技能拆成两个就是在给模型挖坑三是框架层缺少冲突裁决机制。后来我把两个合并成一个report_deliver技能内置生成→确认→发送三步同时把负向触发条件写清楚如果只是预览报告内容发送步骤需要用户二次确认。这之后类似问题基本绝迹。5.2 递归调用技能A调用技能B再调用A第二个翻车点是递归调用。有一次我看到日志里Agent在查询订单技能内部调用了客户满意度调查技能后者又在问卷流程里创建了一个工单而创建工单的流程里居然又触发了查询订单整个调用链绕了一圈上下文被塞爆模型彻底迷失。根源在于技能框架允许技能在执行过程中继续调用其他技能形成隐式的递归链路。我现在做了三个限制第一设置技能调用深度上限默认5层第二执行上下文中维护一个调用栈记录检测到重复技能立刻中断第三技能内部不允许直接调用其他技能只能返回suggested_next_actions由Agent决策层判断是否继续。把技能间的调用改为技能间通过Agent间接协作复杂度可控很多。5.3 上下文污染技能执行后的后遗症第三个坑非常隐蔽上下文污染。技能执行过程中我们当时把中间日志、调试信息、临时变量全部塞进了返回结果模型拿到一堆垃圾信息后续决策明显被带偏。有个典型案例是技能返回了五条内部日志里面提到了重试3次成功模型之后回答问题居然自己补了一句系统自动重试了3次把内部实现细节当成事实告诉了用户。解决思路是结果净化技能返回给模型的必须是干净的输出——状态、关键数据、摘要、建议中间过程不写入上下文。日志走独立的日志存储链路的可观测性靠中间件采集而不是靠往上下文里塞。记住一个原则上下文对模型来说是决策依据不该出现任何跟决策无关的噪音。5.4 幻觉吞参数大模型捏造技能入参第四个坑可能所有做过生产级Agent的人都遇到过模型在参数缺失时开始编造。用户说帮我查一下订单没给订单号模型居然编了一个ORD-20240615-001塞进参数里然后煞有介事地返回查无此单。用户觉得这Agent蠢透了但实际上问题出在框架没有在参数层做拦截。我现在要求技能框架对每个参数都标注来源来自用户直接表达、来自上一技能输出、来自数据库回填、还是缺失待询问。必填参数缺失时框架不执行技能而是返回一个参数澄清请求让模型向用户追问。只要把住这一关幻觉吞参数的现象就能压掉八成。注意我发现光靠提示词说参数缺失时要问用户根本不可靠必须用框架强制拦截。人靠不住的地方要靠系统兜底模型也一样。6. 从单技能到技能编排下一步怎么走6.1 技能编排的两种范式技能库稳定以后真正的挑战就变成了编排。一个Agent做一个复杂的任务往往需要串起多个技能这里有两种截然不同的范式。第一种是流程编排也叫确定性编排。用有向无环图DAG或者状态机把技能的调用顺序固定死比如订单处理流程一定是order_query→refund_check→refund_apply这个顺序不可跳过。这种方式适合B端有固定规则的业务流程稳定、可审计、出问题好定位。第二种是意图驱动的自动编排。不给固定顺序只给Agent一个目标和一堆技能让它自己决定先调哪个、后调哪个、要不要重复调。这种方式适合开放式的探索性任务灵活度高但结果稳定性差一些需要更强的模型和更完善的兜底机制。我的建议是业务核心流程用范式一边缘长尾场景用范式二。全量用自动编排在现阶段就是给自己找麻烦。我们之前把一个结算流程交给了Agent自由编排它把生成对账单和发送对账单两个顺序搞反了客户收到了空白邮件。后来这个流程改成DAG固化再没出过事。6.2 技能的评估与迭代闭环最后聊聊技能的持续迭代。技能不是写完就完事的它的质量会随着业务变化、模型升级而漂移。我维护的一套评估指标包括触发准确率该触发时触发、不该触发时不触发的比例、执行成功率技能跑通并返回合理结果的比例、用户干预率用户纠正Agent的频率。每个月跑一次评测集把指标变化趋势拉出来重点盯那些触发准确率下滑的技能——往往意味着描述和真实用户表达已经脱节了。迭代的方式也很朴素每次用户干预都是送上门的高质量训练数据。把用户怎么纠正Agent的记录下来回填到技能的描述、触发条件、参数说明里去。比如用户经常纠正我说的日报是周报那report_deliver的when_not_to_use里就加上一句用户提到周报、月报时不要使用本技能请使用其他技能。这种细碎的优化积累三个月技能库的准确率会有肉眼可见的提升。我自己的体会是做技能设计的这几个月最花功夫的不是写执行代码而是写清楚那些边界什么情况能用、什么情况不能用、参数从哪来、执行后有什么影响。一开始我以为我在写代码后来我发现我是在写给模型看的说明书。写这本说明书的能力可能才是做Agent应用这个方向里最值钱、也最被低估的能力。