ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

agent-skills技能体系设计:让智能体稳定完成复杂任务

agent-skills技能体系设计:让智能体稳定完成复杂任务 让智能体真正会干活agent-skills 技能体系设计与落地实践这两年做大模型应用我最大的感受是模型能力本身已经不太是瓶颈了真正的分水岭在于怎么让模型稳定地完成具体任务。你给一个 LLM 接一堆工具它能调但调得乱、调得没章法你给它一套清晰的技能体系它才能真正像一个员工一样干活。这里说的技能就是 agent-skills 这套思路要解决的问题。简单来说agent-skills 是一个面向智能体的技能编排与管理框架核心是把调用工具这件事从零散的 function call 上升为结构化的技能单元。每个技能单元不只是接口和参数还包含触发条件、执行步骤、失败策略、依赖关系和使用边界。我在实际项目中用这套思路改造过一个内部客服机器人效果非常明显——任务完成率从刚接手的 63% 提到 88%上下文 token 消耗反而降了将近四成。这篇文章我会从设计思路、技能定义规范、运行时调度到踩坑实录把整套东西完整拆开讲。适合正在做 agent 应用、或者被模型乱调工具折磨得头疼的开发者参考。1. 为什么智能体需要一套技能体系而不是一堆工具函数1.1 从函数调用到技能单元的思维转变很多人第一次接触 agent 开发时都会走一条几乎相同的路先给模型塞几十个 function让它在对话里自己选。刚开始感觉很好模型的 tool calling 表现很惊艳但一旦任务变复杂、工具变多问题就全暴露了。我自己的经历是这样的项目里的工具数量超过 15 个以后模型的选择准确率会出现肉眼可见的下降。更麻烦的是模型经常把参数填错、漏掉前置校验、甚至在一个简单查询任务里绕了三个无关工具。这不是模型笨而是工具描述太薄弱模型根本不知道什么时候该用用之前要检查什么用完以后该怎么处理结果。agent-skills 的核心思路就是把工具升级为技能。一个技能单元讲的不是有这个函数可以用而是在某个状态下做某个动作达成某个子目标。它把上下文信息、前置条件、执行步骤、结果校验、异常处理全部打包进一个可复用的单元里。模型面对的不再是几十个孤立的端点而是一套有内在逻辑的行为模板。打个比方工具函数是给一个人一堆零件技能是给一个人一份带图纸的操作手册。前者靠人临场发挥后者让人按章执行。对 LLM 来说后者的稳定性显然高得多。1.2 技能复用与组织边界告别提示词泥潭我见过不少团队的 prompt 写在 3000 行以上所有工具说明、约束、示例全塞在一个 system prompt 里。这个方案的维护成本极高改一处逻辑可能影响全局而且模型的注意力会被大量冗余信息稀释。agent-skills 的解决问题方式是从组织边界下手把大而全的提示词拆成系统级指令 技能级定义 实例级数据三层。系统级指令只管整体策略和价值观约束每个技能单元独立描述自己的触发条件、步骤、校验规则实例级数据则是运行时传入的具体参数。这样拆完以后维护变成了一件局部化的事。想改订单查询逻辑只需要打开对应技能的定义文件其他技能完全不受影响。新增业务能力也简单就是新增一个技能单元不需要在全局 prompt 里挖坑打补丁。1.3 技能体系适合哪些场景不是所有项目都需要上这套体系。如果你的应用只有三五个工具、任务链路很短老老实实写 function calling 就够了强行上技能框架反而会增加开发成本。从我实际经验看遇到下面这些信号就应该考虑引入技能体系了工具数量超过 10 个且相互之间存在调用先后依赖同一个业务动作需要在多个场景中复用比如查用户信息在售前、售后、运营场景里都会用到任务链路长涉及多轮工具调用和中间状态维护模型经常出现工具选择漂移就是同样的意图有时调 A 工具有时调 B 工具团队成员多需要并行开发不同业务能力但又不能互相踩坏全局逻辑尤其是最后一条在协作开发场景下没有清晰的技能边界团队几乎一定会陷入谁改 prompt 谁背锅的泥潭。2. 技能定义规范把能力说清楚2.1 一个技能单元该包含哪些字段在 agent-skills 里一个技能单元建议用结构化 Schema 来描述而不是一段自然语言。结构化字段的好处是既能让模型稳定解析又能被工程代码做静态校验。下面是我目前用得比较顺手的字段集合skill: name: query_user_order description: 查询用户的订单信息支持按订单号或时间范围筛选 version: 1.2.0 trigger: conditions: - intent in [order_query, order_status] - required_slots: [user_id] preconditions: - api: auth.validate_user params: user_id: ${user_id} steps: - action: order_service.query input: user_id: ${user_id} order_id: ${order_id} date_range: ${date_range} output_alias: order_data - action: formatter.order_card input: order: ${order_data} fallback: - on_error: [ORDER_NOT_FOUND] action: prompt.for_confirmation message: 未找到该订单请确认订单号或换一个账号查询 dependencies: - auth.validate_user - order_service.query这个定义里有几个容易被忽略但很重要的点。trigger.conditions不是让模型感觉该用这个技能而是明确地给了匹配规则——意图是什么、上下文里必须有哪些槽位。模型先做意图匹配再检查槽位完整性缺了就走补槽流程而不是直接硬调。这个机制直接解决了模型乱填参数的问题。fallback部分也很关键。真实业务里没有百分之百成功的调用失败后怎么办必须提前想好。是重试、换参数、还是转人工把策略写死在技能里模型在异常场景下就不会自己创造出一些奇怪的行为。2.2 触发条件与前置校验防止乱入很多 agent 的翻车现场都出在不该调用的时候调用了。比如用户只是闲聊一句你们订单挺多的吧模型咔一下就去调订单统计接口既浪费资源又暴露隐私数据。这个问题的根因就是技能描述里没有约束触发条件。在我的实践里trigger.conditions至少要检查两件事意图匹配和槽位完整性。意图匹配用模型判断或者规则匹配都可以槽位完整性则是硬校验。以订单查询为例技能定义里注明必须要有user_id没有就进入补充询问的子流程而不是先用空参数调服务然后报错。还要注意preconditions的执行逻辑。有些技能依赖认证结果有些依赖前置数据状态。我见过一个项目模型先调了生成发票的技能再调查询发票的技能虽然功能上没错但流程上是反的多了一轮无效调用。把这些依赖和前置顺序写进技能定义调度层就能拦截这种顺序错误。2.3 description 怎么写才能让模型看懂技能描述的说服力远比大多数人想象的重要。LLM 对技能的理解基本就是靠 description 这段文字它写得越模糊模型就越容易瞎猜。我总结了一个实用的写法模板本技能用于[场景]下当用户[行为/意图]时完成[动作]。执行前需要确认[关键前提]执行后返回[结果形式]。如果遇到[典型异常]应[处理方式]。比较一下两种写法差的写法查询订单信息好的写法本技能用于售前咨询与售后跟进场景当用户请求查看自己的订单状态、物流进度或历史购买记录时使用。执行前必须确认用户身份已通过验证执行后返回结构化的订单卡片。如果用户提供的订单号不存在应引导用户确认输入或切换查询账号第二种写法信息密度高很多。它交代了适用场景、触发时机、前置条件、输出形式、异常兜底模型拿到这段话之后准确率提升是立竿见影的。3. 技能管理中枢注册、编排与运行时调度3.1 技能注册中心统一发现与热更新技能多了以后第一个要解决的问题是模型怎么知道有哪些技能可用。全量塞进 context 会撑爆 token 窗口不塞又怕模型不知道。我的做法是引入一个技能注册中心统一管理技能清单、版本、状态和索引。注册中心的核心接口长这样class SkillRegistry: def __init__(self): self._skills {} self._index defaultdict(list) def register(self, skill: Skill): self._skills[skill.name] skill # 为每个技能更新倒排索引方便按关键词召回 for token in self._tokenize(skill.description): self._index[token].append(skill.name) def search(self, query: str, top_k: int 10) - List[Skill]: query_tokens self._tokenize(query) scores defaultdict(int) for token in query_tokens: for name in self._index.get(token, []): scores[name] 1 ranked sorted(scores.items(), keylambda x: x[1], reverseTrue) return [self._skills[name] for name, _ in ranked[:top_k]]实际使用时模型并不会直接看到全部技能而是由调度层根据当前对话状态做一个粗召回只把最相关的 5 到 10 个技能描述放进上下文。这个动态加载策略把 token 消耗降了一个量级模型的选择准确率反而提高了因为它不需要从三十个候选里硬挑。注册中心还兼顾了热更新需求。业务侧改技能描述不需要重启服务注册中心检测到版本号变化后自动更新索引。这个能力在频繁迭代的早期阶段非常救命否则每一次调整都要走一次完整发布流程。3.2 编排引擎从让模型自由发挥到受控执行技能编排是 agent-skills 里最有价值也最难做的一部分。完全自由的 ReAct 模式Reason Act 交替循环在复杂任务里经常失控模型会在无关工具上反复横跳。我采用的折中方案是主链路用编排局部允许自由发挥。具体做法是把一个复杂任务拆成链路模板。比如退货退款任务明确写出三个阶段核验订单、生成退款单、通知用户。每个阶段绑定一个技能阶段与阶段之间有数据传递契约。模型要做的是在阶段内选择合适参数、处理边缘情况而不是决定整个流程的顺序。pipeline SkillPipeline( stages[ Stage(verify_order, skillquery_user_order, nextcreate_refund), Stage(create_refund, skillrefund.create, nextnotify_user), Stage(notify_user, skillmessage.send, terminalTrue), ], # 任何阶段失败都走 fallback 链 global_fallbackhuman_handoff )有些读者可能担心这种硬编码链路会不会反而限制了模型的泛化能力。我的经验是不会而且恰恰相反。链路模板管住的是主干流程——这部分本来就不该让模型自由发挥模型的价值在分支处理——用户中途改了需求、某个步骤拿不到数据、需要临时换一个等价方案。这些场景我用一个兜底技能dynamic_planning来处理它的作用是在主链路走不通时允许模型自己临时规划替代方案但会记录日志供人工审计。这种确定性流程 动态兜底的混合模式兼顾了稳定性和灵活性。纯编排太死板纯自由太飘中间态才是工程上最舒服的位置。3.3 运行时上下文管理让技能间丝滑传参技能之间不是孤立的。一个技能的输出常常是另一个技能的输入如果上下文管理做得不好模型每次都要在多个技能间反复记忆-提取-填充既费 token 又容易出错。我在 project 里的方案是给运行时维护一个结构化的context_store技能执行完以后输出会按照 schema 存入一个共享的上下文对象。下游技能定义里可以通过${field_path}的方式引用上游结果而不是要求模型从对话历史里自己翻。context ContextStore() result await run_skill(query_user_order, {user_id: U12345}) context.set(order, result) # 下游技能直接引用上下文字段 await run_skill(refund.create, { order_id: ${order.order_id}, amount: ${order.pay_amount} })这个机制有几个实打实的好处。第一token 消耗下降因为不需要把完整历史都塞进每次调用的上下文第二数据一致性变好不会出现模型记错了上一个技能返回的订单号这种低级错误第三可观测性变强因为每一步数据流都是显式的排查问题的时候一眼就能看到某个字段是从哪个技能来的。4. 实测从 63% 到 88% 的任务完成率是怎么抠出来的4.1 一个客服机器人的技能化改造实录前面说的都是方法论我拿一个真实项目来串一遍。这个项目是一个电商客服机器人改造前的架构非常朴素一个大 prompt 34 个 function 定义模型自由选择工具。线上表现就是任务完成率 63%用户经常被绕晕说一个问题模型答三句无关内容。改造第一步是盘点技能。我把 34 个 function 按业务域拆成了 12 个技能单元订单查询、物流跟踪、售后申请、退款进度、发票处理、优惠券查询、商品咨询、库存查询、人工转接、满意度回访、话术安抚、工单创建。拆分的依据不是函数属于哪个模块而是用户会在什么场景下发起什么目标。第二步是给每个技能补齐定义。这一步最耗时因为要把描述、触发规则、failback 全部写清楚。我踩过一个坑一开始 description 写得太业务化比如本技能用于查询订单模型识别率还是上不去。后来改成当用户表达出想了解自己购买的商品现在什么状态、发了什么快递、大概什么时候到这类诉求时使用效果一下子就起来了。这背后其实是 LLM 对行为意图的理解要比对抽象业务标题的理解好得多。第三步是上线小流量验证。先拿 10% 的流量跑了两周对比改造前后的指标。结果很理想任务完成率从 63% 提升到 88%平均对话轮数从 9.2 降到 4.7人工转接率从 31% 降到 12%整个系统的 token 消耗也明显下滑。核心收益在于不再有冗余的工具调用和重复信息确认。4.2 四个典型的线上问题与排查实录改造过程中遇到过几个典型问题我整理出来大家遇到类似情况可以直接照方抓药。问题一技能召不回现象是有些意图明明该走某个技能模型却完全没提到它。排查后发现是技能检索的倒排索引出了问题——description 里的关键词和用户实际说法差距太大。比如技能里写的是退款流程用户说的是我不想要了想退掉两者没有重叠词索引召回就断了。解决方法是给 description 增加同义表达区或者改用向量召回。我最终采用了关键词索引 向量召回的混合检索小规模技能库可以直接用 embedding 离线算好在线检索取 top-k 再合并排序。问题二技能误触有个场景是用户问你们周六上班吗模型居然去调了订单查询技能。查日志发现这个技能 description 里有周末两个字跟用户问题里的周六产生了语义关联被模型误判了。这个问题的根子在触发条件写得不够严。我把技能的触发条件从纯意图匹配改成意图匹配 槽位校验双保险同时在 description 里明确排除项比如加上一句仅当用户询问已有订单相关信息时使用企业营业时间等通用咨询请走 faq 技能。问题三技能 A 拿到结果后技能 B 拿不到这在多技能链路里非常常见。技能 A 返回的数据结构是嵌套的技能 B 引用order.pay_amount但 A 实际返回的字段名是payment.total引用路径对不上传参就断了。后来我在运行时增加了Schema 校验 自动映射层。每个技能执行完以后会把输出和定义里的output_schema做一次比对字段不匹配时自动生成提示帮助下游技能准确定位。同时给每个下游引用加了一个 fallback 表达式比如${order.pay_amount ?? order.payment.total}一个拿不到就换另一个。问题四失败兜底策略太弱早期所有技能的 fallback 都是兜底话术 转人工导致 30% 以上的任务被当成异常转出。这个比例太高了说明很多失败其实是可以通过参数修正解决的。我把 fallback 策略细化成四级参数修正重试、换等价技能、通知用户补充信息、最后才是人工转接。四个级别按成本从低到高排列模型优先尝试低成本方案。改完以后转人工率降到了 12%用户整体满意度反而上升了因为很多小问题在低级别就解决了响应反而更快。4.3 一套实用的技能效果评估指标最后说说怎么量化评估技能体系的效果总不能一直凭感觉。我目前在用的指标分三层技能层调用准确率、参数合法率、单次技能执行成功率、平均响应耗时流程层任务完成率、平均任务轮数、完全自动解决率、兜底触发率体验层用户满意度评分、一次解决率、平均对话时长其中我最推荐关注完全自动解决率和兜底触发率这两个指标。前者代表系统的真本事后者告诉你系统在哪些地方撑不住。每周把这些指标按技能维度拆开看哪个技能是短板就一目了然然后针对性优化描述、编排或者 fallback 策略。5. 踩坑总结与避坑指南5.1 三个容易反复踩的坑光是把技能定义写好还远远不够。我在多个项目里反复踩过同一类坑这里集中写出来你们直接避开。第一个坑技能粒度要么太粗要么太细。太粗一个技能塞五六个完全不同的行为模型根本不知道它应该触发哪个太细技能数量爆炸检索召回和模型选择难度指数级上升。我的经验法则是一个技能只做一件可以在 10 秒内向用户描述清楚的事。能一句话说清楚的事才是一个好粒度的技能。第二个坑描述里塞太多业务黑话。业务方喜欢在 description 里写本技能用于处理 SAL 工单的 SLA 超时转派但用户根本不会说SALSLA这种词。模型训练数据里这类词的语义关联很弱匹配准确率自然上不来。正确做法是描述里同时写用户侧的表达和系统侧的专业术语两者标记清楚。第三个坑忽视了技能版本管理。技能是会迭代的有时候改一个描述就会影响线上行为。我见过一个团队改完技能描述以后没有做灰度验证全量上线后任务完成率直接掉了 5 个点。所以技能每改一次都要走测试集回归 小流量灰度的流程哪怕只是一个字段的说明。5.2 技能测试集被严重低估的投入技能体系的测试和普通接口测试不一样你没法完全用单测覆盖模型理解这件事。我建议每个技能配一个专门的测试集里面放 20 到 50 条用户真实说法覆盖正常表达、模糊表达、边缘场景、异常表达四类。有了测试集以后每次改技能定义跑一遍全量测试算一下意图识别准确率和参数抽取准确率的变化再决定是否上线。这是技能体系开发里性价比最高的一件事。我目前在为一个技能维护 30 条测试样本迭代一轮大概花 15 分钟但为线上减少的故障排查时间是按小时算的。5.3 向系统设计再迈一步如果把 agent-skills 的思路再往外推一步你会发现它其实是一种面向模型的任务分解范式。任何一个业务场景只要把能力结构化、把约束写清楚、把失败路径设计好模型的稳定性就会有质的提升。这套东西不依赖某个特定模型你可以用在 GPT 上也可以用在国内的大模型平台上核心逻辑是通用的。我最近还在尝试把技能定义从 YAML 升级成更像契约的形态在里面增加数据权限标签和审计要求让每个技能执行都能追溯到是谁的数据、什么目的、结果用在哪里。这套加上以后系统的合规性会好很多后续做精细化权限管理也有据可依。6. 最后分享一个小技巧让模型自己审查技能定义这个技巧是我在一次调试中偶然发现的与其反复人工检查技能定义写得好不好不如让一个强模型站在使用者的角度去审查描述是否可执行。做法很简单把技能定义发给一个不带记忆的模型让它扮演真实用户描述一个具体场景然后看这个模型能否仅凭技能定义做出正确触发决策。如果它触发了错误的技能或者没有识别出该触发的场景说明定义里一定有模糊或缺失的地方。这个方法相当于用模型做了一次免费的可用性测试成本几乎为零但能发现很多肉眼看不出的问题。我现在的迭代流程基本固定了写定义、跑测试集、让模型审查、小流量灰度、全量上线。每一步花费的时间都不长但叠加起来整个技能体系的稳定性和可维护性会高很多。如果你也正在被模型乱调工具折磨不妨照着这个思路把技能体系搭起来先从两三个高频场景开始跑通以后再慢慢扩展效果应该不会让你失望。
返回列表