Agent技能体系设计实战:让大模型稳定选对工具与参数

发布时间:2026/10/13 5:27:30
Agent技能体系设计实战:让大模型稳定选对工具与参数
开篇先交代一个背景。前阵子团队接了个活儿给内部业务流程做一个多步骤的智能助手要求它能听懂一句话需求然后自动拆解成若干动作调用不同系统把事办了。项目刚开始大家都觉得挺简单——大模型谁不会接工具函数写一堆提示词里列清楚不就行了。结果一跑真实场景就翻车了模型要么在多个工具之间犹豫不决要么把参数编得离谱要么干脆用错技能把一个查询类动作误触发成写操作。那段时间天天在日志里捞调用记录一边看模型演戏一边反思。后来才慢慢意识到问题不在模型笨而在于我们根本没有一套像样的Agent技能体系。所谓agent-skills说的就是给智能体建立一套能力清单 调用协议 运行管线的系统化方案。它要解决的不只是模型会不会调函数而是模型能不能在几十个能力项里稳定选对、把参数传准、在失败后懂得补救。这套东西设计好了智能体才能从玩具变成能扛活的员工。这篇文章就把我踩过的坑和最终沉淀下来的设计思路完整拆一遍适合正在做大模型应用、智能体流程编排、或者想把内部工具接入 AI 系统的开发者参考。1. 技能系统到底在治什么病三个逃不过的现实问题很多人一开始把技能系统理解成给模型加几个函数其实这是本质性的偏差。函数调用只是协议层面的一小步技能系统要治的是更现实的三类病模型能力的虚胖、提示词空间的物理极限、以及业务逻辑与模型逻辑的纠缠。1.1 模型什么都会背后的真实困境大模型训练的语料太广你问它会写周报吗它说会会查数据库吗它也说会。但真让它去查库它连连接串在哪都不知道。这就是典型的知识幻觉——模型以为自己会但实际上没有任何可执行的抓手。技能系统的第一层价值就是给模型一个确定性抓手每一项技能背后对应一段真实可运行的代码、一个真实的 API 请求、一条真实的业务规则。模型不需要知道怎么做它只需要知道该选哪项技能具体怎么做由技能实现者负责。这里有个特别容易被忽略的点技能本质上是把模型的判断力和程序的执行力做了分工。模型负责语义理解和意图匹配程序负责确定性的执行逻辑。一旦你想让模型直接写代码去调外部接口风险就失控了。所以我在设计时坚持一条铁律技能描述里只能出现做什么事,不能出现怎么实现。模型一旦开始自己推导实现细节参数幻觉和错误调用就会成倍增加。1.2 提示词塞不进第二十个工具说明早期方案确实粗暴把所有工具函数的说明拼进 system prompt一个工具写两三百个字符二十个工具就是六千字符加上业务上下文直接逼近上下文窗口的舒适区。更要命的是说明越长模型的选择准确率越低。这是个被反复验证的现象——工具列表超过十几个之后模型开始选择困难把相似工具的 description 混淆、把参数 schema 搞串。技能系统的第二个价值是在模型和真实工具函数之间加了一层索引和路由。系统先维护一份所有技能的结构化清单模型只需要通过技能 ID、简要描述、参数协议来点名不需要看到完整实现。这就像一个大公司的前台你不必认识每个部门的人只需要告诉前台你要办什么事前台帮你路由到对的人。技能清单就是那个前台而且它的信息密度经过精心控制保证模型在有限上下文里看得清、选得准。1.3 技能与业务逻辑不能焊死在一起第三类问题来自工程侧。很多团队的代码里业务流程和 API 调用逻辑是同构的比如创建订单就直接调创建订单的接口中间没有任何抽象。这种写法在传统后端里没毛病但放到智能体场景就坏了模型的一次决策可能需要跨多个系统而每个系统的鉴权方式、数据格式、错误语义都不一样。如果你把这些差异直接暴露给模型它一定会蒙圈。正确的做法是把业务能力封装成标准技能的形态技能内部自己去处理鉴权、数据转换、错误码翻译。外部看起来就是统一的输入输出协议内部爱怎么折腾都行。这套封装思路借鉴了后端服务里的防腐层概念只不过这一次的下游调用方不是另一个服务而是大模型。封装做得好技能的复用性和可测试性都会显著提升这也是后面搭建技能市场的前提。2. 技能描述层让模型一眼选中正确技能技能描述层是整个体系的灵魂。模型看不见你的代码它只能靠名称、描述、参数协议来决定要不要调、怎么调。这一层写得好不好直接决定了调用的准确率。我见过太多团队把 80% 的时间花在执行逻辑上描述随便写两句最后模型选错技能就骂模型不行其实是描述层没下功夫。2.1 一份合格技能说明书的三件套一份可用的技能说明至少要包含三个部分技能名称、一句话功能描述、边界声明。名称要短且唯一最好符合业务直觉比如query_order_status就比order清晰得多。功能描述要讲清楚这个技能做什么、适用于什么场景而且要主动声明它不做什么。我举个真实对比同样的技能两种写法效果完全不同。写法一不合格名称get_info描述获取相关信息。写法二合格名称get_order_delivery_info描述根据订单编号查询物流配送状态适用于用户询问我的货到哪了什么时候送到等场景。边界仅支持查询已发货的订单不支持修改配送地址、不支持催发货。写法二的命中率几乎可以做到百分之百写法一纯属碰运气。为什么边界声明这么重要因为相似场景太多了模型判断这个技能能不能解决当前问题时需要大量排除法信息。你告诉它能做什么只是给了正例你告诉它不做什么等于给了充足的负例模型的选择边界一下就清晰了。2.2 参数协议的细腻度决定调用成功率参数协议是模型最容易翻车的地方也是最值得花时间打磨的地方。如果你只给一个字段叫date类型是 string模型根本不知道这个 date 应该是什么格式。是2024-05-01还是2024/05/01是本地时间还是 UTC需不需要带时区所有这些都是模型的猜测空间而猜测往往就是错误的开始。在设计参数协议时我总结了一套可操作的规则每个字段必须有明确的业务含义描述不要只写类型。给format或pattern约束比如日期强制YYYY-MM-DD。枚举字段列出所有可选值和对应含义。可以给default默认值特别是可选参数让模型在犹豫时有兜底。对容易混淆的参数在描述里写明不要和另一个参数同时使用。以下是一个实际用过的参数协议简版示例类型标注用的是 JSON Schema 风格{ type: object, properties: { order_id: { type: string, description: 订单编号一般是字母加数字的混合字符串, pattern: ^[A-Z0-9]{8,20}$ }, timezone: { type: string, description: 查询时区默认 Asia/Shanghai, enum: [Asia/Shanghai, UTC], default: Asia/Shanghai } }, required: [order_id], additionalProperties: false }注意最后一行additionalProperties: false。它的含义是模型只能传协议里声明的字段不能自己发明字段。这个约束看似死板实际上救了很多次命。模型的自由发挥能力远比我们想象的大没有这个开关它可能往请求里塞各种莫名其妙的参数。2.3 用离线样例集给技能描述打分描述写得好不好不能靠感觉要建立一套离线评测机制。方法是准备一批典型的用户问题比如我的订单物流到哪了这个包裹为什么三天没动静标注好它们应该命中的技能 ID然后批量让模型去选技能统计命中率。这套机制的效果非常直观。我第一次对自己的技能清单做评测时命中率只有不到 70%。逐条看失败案例发现大量问题出在描述歧义上。比如有个技能是查询账户余额另一个技能是查询优惠券余额模型经常把前者的参数套到后者上。后来我改了描述明确规定本技能只处理现金余额不包含任何优惠券、积分、赠金命中率立刻上到了 90% 以上。评测集最好保持持续更新。每发生一次线上选错技能的 case就把对应问题加进测试集下次改描述或加边界声明后重新跑一遍确保修复不是靠运气。这个习惯坚持两三个月技能体系的整体稳定性会有质的飞跃。3. 执行管线从技能清单到安全可控的动作描述层解决的是选对技能执行管线解决的是安全地把事办了。很多团队死磕 prompt 和描述却忽略了运行时设计结果模型选对了技能执行层却因为权限、状态、异常处理乱七八糟而频频失败。执行管线是技能体系的骨架它要保证每个动作可追踪、可中止、可恢复。3.1 技能注册中心的那些元数据所有技能在上线前先要到注册中心登记。注册中心维护的不仅是技能 ID 和描述还要有完整的状态机元数据。我常用的注册信息包括技能名称、版本号。启停状态启用、灰度、下线。权限等级只读、写操作、高危操作。超时时间、重试策略、限流策略。负责人和变更历史。权限登记这步至关重要尤其当技能要接入多个系统时。传统后端面试里有个经典问题如何控制越权访问在智能体场景这个问题被放大了——模型可能因为一次prompt语义理解偏差就触发一个高危操作。所以我把技能分成三类类别示例调用策略只读查询类查订单、查库存模型可直接调用业务操作类创建工单、提交审批必须有用户确认步骤高危操作类删除数据、转账、批量变更二次确认加权限校验必要时临门一脚由人工完成这个分类写在技能描述之外模型感知不到但执行管线强制校验。它能兜住模型最坏情况下的自作主张。3.2 状态机与执行生命周期每个技能调用实例我都会用一个标准的生命周期状态机来管理状态包括pending等待执行、running执行中、succeeded成功、failed失败、canceled取消。别小看这个设计它有三个直接好处。一是可观测性。所有历史调用都能落库方便回溯模型哪一步做了什么决定。没有状态机日志就是一团乱麻出了线上问题只能抓瞎。二是可中断。用户在模型还没执行完的时候说算了别搞了系统可以直接把 pending 或 running 状态置为 canceled避免后续动作继续执行。三是可恢复。失败的任务可以快速定位到具体环节决定是重试、跳过还是换一条链路走。这里有个工程细节状态变更必须走消息或事件机制不要直接在业务代码里改状态字段。否则多个并发动作同时修改一个技能实例状态就乱了。我实际用过的方案是每个技能调用生成一个唯一的execution_id所有状态流转都通过 Redis 锁再加数据库更新保证并发场景下的一致性。3.3 外部系统适配层与结果归一化执行层对接外部系统时最大的痛点是错误码和返回格式五花八门。有的接口失败返回{code: 500, msg: error}有的抛异常有的返回 200 但 body 里藏着错误信息。如果这些原始差异直接传给模型模型根本没法正确判断一次调用到底是成功还是失败。所以我在所有技能实现里加了一层适配器目标是让每个技能的返回结果都统一成固定结构正常情况返回数据本体异常情况返回错误码和可读的错误消息。结构如下{ status: success, data: { order_status: shipped } }{ status: error, error: { code: ORDER_NOT_FOUND, message: 订单不存在请检查订单编号是否正确 } }统一的返回格式对模型非常友好。模型在下一步决策时只需要看 status 字段就能判断要不要换一种方案而不需要去解析外部接口千奇百怪的错误。另外错误消息必须写成模型能理解、用户能看懂的人话。比如数据库连接池满这种技术细节模型读了也不知道该怎么办但如果写成系统繁忙请稍后重试模型至少知道这次动作没成功可以换个方式继续尝试。4. 多技能编排一次真实出差行程的推演单技能调用简单真正体现 agent 价值的是多技能协作。这里我用一个安排跨城市出差行程的例子完整拆一遍技能编排的过程。这个案例是我们当初做跨系统流程助手时的原型场景几乎覆盖了编排里会遇到的所有典型问题。4.1 观察-思考-行动循环在技能系统里的落地用户输入一句话帮我看下下周三去某城市出差的行程早上九点半开会开完会下午还要拜访供应商顺便订下住宿。这句需求里藏着多个技能调用点。系统要先解析出关键实体时间下周三、目的地某城市、动作查询车票、查询酒店。然后进入一个类似 ReAct 的循环观察当前状态思考下一步要调用什么技能执行调用拿到结果后继续观察再思考下一步。实际落地时我使用了一个面向功能的内部 DSL 来约束循环行为核心元素包括thought模型输出下一步理由、action调用技能名、action_input技能参数、observation技能返回结果。模型每次只会输出一个 action执行层跑完技能后把 observation 拼回上下文再让模型继续思考。这个模式说起来简单真正做稳很难难点在上下文的组织——每次循环产生的大量中间结果不能全部塞回提示词否则上下文爆炸。我采用的做法是为每一步循环生成一份摘要型的观察记录只保留关键信息比如火车票查询到 3 个车次可选最早的是 7:30 出发、9:15 到达而不是把所有 JSON 返回都原样贴回去。4.2 三种最常见的编排策略编排策略在技能系统里一般分三种顺序执行、并行请求、条件分支。出差这个场景里三者都会用到。顺序执行最典型先查车票确定抵达时间再根据抵达时间查酒店因为酒店入住时间依赖于到达时间。并行请求也不少见同时查车票和查供应商地址它们之间没有依赖关系时可以一次并行调用多个只读技能减少模型循环次数。条件分支体现在如果早上的会议准时结束就约下午两点的供应商拜访如果拖堂就顺延到三点。在实现上我会用一个轻量的调度状态来标记技能之间的依赖图。依赖图不是让模型直接生成的而是通过模型产出的意图序列在运行时动态判断。比如模型第一次调用了查询车次返回结果里包含到达时间 15:30那么下一步模型大概率会调用查询酒店并传入入住日期与期望区域。这时调度器会检查参数完整性如果缺少关键参数就向用户提问而不是直接让模型瞎猜。4.3 防跑偏步数上限、循环检测与交代边界编排最大的坑是模型跑偏在循环里反复调用同一个技能、编出无人能兑现的计划、或者绕来绕去不落定。我见过一个极端例子模型为了订酒店先调了一堆天气查询、周边景点查询、历史订单查询之类的无关技能才勉强回到酒店预订。不是模型坏而是技能清单里相似描述太多它把每个动作都想试一遍。防跑偏的设计有几个关键点。第一是步数上限单次任务限制最多 12 步调用超出即停止并转入人工兜底。这招看着粗暴但特别管用。第二是循环检测系统记录最近 N 次 action 的名称和参数签名如果出现重复调用同一技能超过两次且中间没有有效进展就直接打断并返回当前状态。第三是技能分组隔离把查询类和操作类分开模型一次任务里如果没有明确的业务对象不允许先调用操作类技能。另外还有一个容易忽略的点任务的终止判断。模型什么时候觉得事儿办完了很多系统里它办完一个动作就欢快地说完成了根本不管用户还有后续需求。我给 agent 加了一条规则只有当用户确认不需要后续操作或者所有意图项都完成了才能返回最终结果。否则继续循环或主动询问用户。5. 实测中反复踩的坑权限、幻觉与失败恢复再好的设计跑真实业务都会踩坑。这一节专门讲我们在实际项目中反复遇到的三个坑权限边界渗透、参数幻觉、失败后的恢复。每一条都是用真金白银的线上故障换来的经验。5.1 技能选错与参数幻觉的根因所谓参数幻觉是指模型调用技能时编造了不存在的参数值。比如用户说查一下最近一笔订单模型把order_id编成了ORD-2024-00001但这个编号根本不存在。根因有两层一是模型确实不掌握真实数据只能根据上下文推测二是技能协议没有给模型足够的不确定时该怎么办的指引。我后来在参数协议里专门加了可选的query_by字段允许模型用recent、latest、order_id三种方式查询。当用户没有明确给出订单编号时模型就可以选择query_byrecent再由技能实现内部去查最近订单而不是硬编一个假的 ID。这相当于给模型的编造空间一个合理的出口幻觉概率大幅下降。技能选错这个问题更多出在描述层的相似度上。我踩过的具体 case同时接入了查询门店营业时间和查询门店联系电话模型对一句话需求我想找这个门店问问情况反复横跳一会儿调营业时间技能一会儿调联系电话技能。后来我把两个技能的描述彻底重写一个强调当用户需要了解开店打烊时段时调用另一个强调当用户需要找人沟通或拨打电话时调用还在边界区写明彼此的排除关系问题立刻缓解。5.2 一次预订失败案例的完整排查链路讲一个真实排障过程过程比结果重要。某个预订类技能在测试环境一切正常一上生产就频繁失败模型的下一步决策经常是尝试再次预订同一时间形成死循环。排查链路是这样的一开始我怀疑是外部接口不稳定但单独用脚本调接口成功率接近 100%。接着怀疑是参数校验问题于是把一次失败的execution_id拉出来翻完整调用链。关键线索出现了技能执行层收到的时间参数是2024-07-03T09:00:0008:00而外部接口要求的格式是2024-07-03 09:00:00适配器没做转换直接按字符串传过去了。接口返回的参数格式错误被归一化成预订失败模型拿到失败结果后又重试同一个时间再次失败循环往复直到耗尽步数。根因找到后修复很快适配层加时间格式转换。但这个问题真正给我的教训是流程编排系统必须记录参数在每次循环前后的一致性。如果系统能检测到模型下一次调用传的入参与上一次完全相同就可以提前拦截而不是让模型白白重试。这也是调试智能体时最有用的一组日志时间戳、技能名、参数快照、返回状态。5.3 失败之后的兜底设计要提前做失败恢复经常被忽略直到线上出事才想起来。常规做法无非是重试但智能体场景的重试逻辑需要有更细的粒度。我把失败处理分成三个层次第一层重试。适用于超时、临时性网络抖动重试前对参数做幂等校验确保重试不会造成重复扣款或重复下单。第二层降级。比如某技能依赖的主数据源挂了如果备用数据源能提供大致可靠的信息就降级调用备用数据源并给模型返回一个标记提醒它当前结果可能不是最新需要向用户说明。第三层人工接管。高危操作或连续两次失败直接停止模型自主行动把当前状态和决策历史打包转给人工处理。这套分层设计不仅提升了成功率更给了产品一个明确的承诺边界模型搞不定的至少会在原地站住而不是越陷越深。这一点对用户信任的建立非常重要。6. 从单体技能到技能市场规模化的演进之路一套技能体系能用是一回事能让团队持续往里加新技能、不相互干扰、不越改越乱是另一回事。随着业务扩展我们逐步摸出了一些规模化演进的经验这里分享最关键的三点。6.1 技能版本化与制品化技能不是写一次就完了。模型升级、外部接口变更、业务规则调整都会导致技能行为变化。如果不对技能做版本管理线上就是一个黑盒你到底跑的是哪一版逻辑说不清。所以每个技能都是一个独立的制品版本号语义化。发布路径是开发分支编码 - 离线评测集跑分 - 灰度环境放量 - 全量上线。模型侧的技能描述和参数协议也跟随版本走不能只改实现不改描述。我踩过这个坑某查询技能的底层逻辑改了返回值格式但技能描述没改模型还按旧协议取数据结果一直取到空值用户那边看到的全是查无数据。版本化带来的另一个好处是回滚很容易。一旦上线的新版本在线上表现异常直接把版本切回上一个稳定版不用改代码不用换模型几分钟搞定。6.2 评测集是技能质量的考试大纲前面提到离线评测集用来检验描述质量到了规模化阶段评测集的意义就不止于此了。它应该像项目的考试大纲一样覆盖每个技能的核心路径、边界条件、异常输入。我见过一个反例一个新同事加了个查询会员积分的技能描述写得很漂亮但没测过会员不存在的场景。上线后用户一问到不存在号模型就反复查日志里全是错误码。把每个技能的评测用例沉淀下来纳入 CI 流程任何技能变更都必须跑一遍全量评测集才能合并是防止改一个技能弄坏另一个技能的最有效手段。尤其当技能数量超过几十个后回归测试的价值无可替代。评测集怎么建我的建议是每处理一次线上异常就补一个用例库里的用例只增不减。用不着一次搞得很全关键是持续沉淀。半年下来你的评测集就是团队最值钱的知识资产之一。6.3 组织级技能中心的价值与成本当技能数量多到一定程度共享就变得非常有价值。一个部门开发好的发票识别技能另一个部门也有类似需求如果没有技能中心两边各做各的重复开发不说能力还参差不齐。我的做法是把技能清单、评测集、使用文档放到内部统一的平台上支持检索和申请调用。使用方不需要知道技能的底层实现只需要在配置台上启用然后把自己的鉴权信息接进来。技能的运行和维护仍由原团队负责平台提供监控和调用统计。这条路走通后新项目接一个新能力的时间从按周计缩短到按天计。当然也有成本技能文档要维护权限要定期审计废弃技能要清理。但总体来看组织级技能中心属于越早做越划算的投入。它把我们前面讲的所有散点——描述规范、执行管线、评测集、版本管理——全部串成了一个体系。这也是我把这个项目命名为 agent-skills 的最终意义技能不是零散的函数而是组织能力的结构化沉淀。最后再分享一个我个人特别受益的小习惯所有技能描述上线前先丢给模型做一次阅读理解让它用自己的话复述这个技能能做什么、不能做什么、需要什么参数。如果复述得乱七八糟说明这个技能描述连模型自己都看不懂上线后也会让别人看不懂。就靠这个简单的测试我至少拦下了十几个质量不合格的技能。