十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Agent技能系统设计:从技能封装到路由编排的落地实践

Agent技能系统设计:从技能封装到路由编排的落地实践 最近这半年大模型圈子里关于 Agent 的讨论已经从“能不能跑通”转向了“怎么生产化落地”。我自己在跟进和复现一些智能体项目时发现一个被反复提及但又很少有人讲透的模块agent-skills。说白了它就是给智能体准备的一套“可插拔技能库”让模型不再是一个只会聊天的空壳而是能按需调用工具、执行动作、完成实际任务的能力集合。这个方向解决的核心问题很直接裸的 LLM 只会“说”不会“做”。你让它帮你查天气、订机票、操作表格、调接口它要么胡编要么告诉你“作为AI我没有这个能力”。而接入了agent-skills之后模型就相当于有了一整套“手和脚”知道在什么场景用哪个技能、参数怎么填、结果怎么处理。这篇文章我就结合我自己跑通的实践路径把这个东西从设计思路到落地细节完整拆一遍希望能给正在做 Agent 开发的朋友一些能直接上手的参考。1. 内容整体设计与思路拆解1.1 为什么把能力做成“技能”而不是“函数”最早我在做工具调用的时候第一反应就是直接把所有能力注册成函数让模型在 function calling 里选。这个方案在接口数量少的时候非常爽十几个函数模型基本不会选错。但一旦业务复杂起来上百个工具、互相之间有依赖、输入参数动辄几十个字段你再让模型从一整个函数表里挑它就开始犯迷糊了不是你调错了就是参数传漏了甚至有时候模型自己都在纠结到底该用哪个。agent-skills的思路是把工具调用往前再推一步不把能力扁平地暴露给模型而是对能力进行“技能化封装”。每个技能内部可以包含多个步骤、多轮工具调用、一套明确的输入输出协议甚至包含异常处理的兜底逻辑。模型面对的不再是海量底层函数而是一组语义清晰、边界分明的“技能”它只需要回答“现在应该激活哪个技能”剩下的执行细节由技能自身完成。这个设计和真实世界的分工逻辑很像。你去餐厅吃饭不需要知道后厨有几个灶台、切配是谁、掌勺是谁你只需要告诉服务员“来一份招牌菜”就行。技能就是这个“招牌菜”的工序单Agent 就是那个服务员它负责听懂需求、判断推荐哪道菜、然后把菜端到你面前而至于菜怎么做那是后厨内部的事情。1.2 与 Prompt 工程、RAG 和 Workflow 的边界很多人在搭 Agent 时容易把技能系统和 Prompt、RAG、Workflow 搞混这里我做个简单区分Prompt 工程是让模型“说好话”靠的是提示词引导本质上是语言层面的约束。RAG 是让模型“有知识”通过检索外部文档补充上下文。Workflow 是让系统“走流程”适合固定的、可预测的业务步骤。而agent-skills是让模型“会干活”它解决的是动作选择和能力编排的问题。我自己理解的核心差异在于Prompt 和 RAG 是输入侧的增强它们决定模型“想什么”技能系统是输出侧的增强它决定模型“做什么”。在真实场景里这四者并不冲突往往还会叠加使用——先通过 RAG 检索知识再让 Agent 基于技能库决策执行最后套一个 workflow 来约束关键节点的顺序。但如果我们只想要一个“会干活”的助手agent-skills是最关键的骨架。1.3 技能化方案选型时我在纠结什么在做技能系统的技术选型时我考虑过要不要直接上业界已有的 agent 框架。当时摆在我面前的大致有三条路直接用 LangChain 的工具机制、用当时还算新鲜的 MCPModel Context Protocol规范、自研一套轻量的技能管理结构。LangChain 的工具机制胜在生态成熟、社区案例多但问题在于它的工具本质上仍然是“一函数一工具”的扁平结构尽管可以做复杂的tool装饰器但对多步骤组合能力、技能版本管理、动态启停支持并不直观。MCP 的设想很好它是把工具、资源、提示词统一成一套协议未来可扩展性很强但当时生态还不够完善很多能力要靠自己造轮子而且对技能之间的编排和聚合支持也比较原始。最终我选择自研技能层但底层兼容函数调用和 MCP 两种出口。这样既保留了对现有工具生态的兼容又能在技能层做更细粒度的管理和编排。相当于我建了一层“控制层”下面接什么都行。2. 技能体系的核心细节解析与实操要点2.1 技能的三层结构意图、参数与执行体我设计技能体系时把每个技能拆成三层意图层、参数层、执行体。这三层各管各的事互不越界组合起来就是一个完整可用的技能单元。意图层解决的是“什么时候该用这个技能”。对 LLM 来说意图层本质上是技能的描述文档它需要把技能的适用场景、能力边界、触发条件讲清楚。这一步至关重要因为模型没有“直觉”它只能靠文字描述来判断当前用户的问题应该路由到哪个技能。描述写得模糊模型就容易误召回。参数层解决的是“技能执行需要什么输入”。这里不只是写几个字段名还要明确字段类型、取值范围、必填可选、相互依赖关系。最好的做法是把参数定义成 JSON Schema 结构既方便模型生成结构化输入也方便执行端做校验。执行体解决的是“技能拿到参数后怎么完成任务”。执行体可以是一段 Python 函数、一个 HTTP 调用、一条 SQL 查询甚至是一段让子 Agent 去跑的指令。关键是执行体必须足够健壮能处理输入异常、超时、第三方接口返回不一致以及部分失败的情况。三层分离的好处是它能独立演进。比如你改了一个技能的执行体只要输入输出协议不变上面两层完全不用动如果你觉得某个技能的描述不够准确也只动意图层不影响底层实现。这个解耦到后期维护时会特别省心。2.2 技能清单、技能库和技能路由的关系搭建完基础结构后我加了两层管理机制技能清单和技能路由。技能清单是静态的“目录”它把所有可用技能的名称、意图描述、参数协议、版本号、启用状态统一登记起来。每次大版本迭代后清单会重新生成一次确保 Agent 看到的永远是最新可用的技能集合。技能路由是动态的“分诊台”它接收用户的当前任务和上下文结合技能清单决定激活哪些技能以及调用的顺序。路由的判断依据不光是意图匹配还包括上下文信息、历史行为偏好、当前对话中的实体比如用户提到了“昨天的订单”路由就要把时间参数解析出来传给订单查询技能。这里有个容易忽略的细节技能路由不一定只能让 LLM 来做。对于那些确定性强的规则比如包含“退款”关键词就路由到售后技能直接用规则引擎跑会更快更准。我的方案是“规则优先、模型兜底”先让规则引擎过一遍没有匹配到明确规则时再把决策权交给 LLM。这样既保证了低频但确定的场景能秒级响应又让长尾的、意图模糊的请求不至于无处安放。2.3 技能的元信息为什么这么重要在整个技能系统的所有组件里我踩过最大的坑是“低估了元信息的重要性”。一开始我以为把执行体写好、能跑通就可以了内置的意图描述随便写两句话就上线结果模型在路由测试里频繁撞车查天气的技能有时候被用来查日期查日期的技能有时候被用来设置闹钟。后来我痛定思痛把技能的元信息当作一等公民来对待要求每个技能上线前必须过一遍“元信息审核”给技能起一个一眼能看懂的名字如weather_query就比get_weather_info_v3更不容易被误解描述里必须显式写出“能干什么”和“不能干什么”两部分尤其是“不能干什么”能有效减少误调用关键字段要提供枚举值或示例值模型参考示例生成的参数明显比凭空生成的要规范得多。这套经验后来我总结成一句话技能的执行体决定了能力的上限技能的元信息决定了能力的可用下限。元信息写得差模型再强也容易翻车元信息写得好哪怕模型能力弱一些至少它能找对门。3. 实操过程与核心环节实现3.1 技能定义的结构化设计先从最底层说起。我在项目里用 JSON 来定义技能每个技能对应一个结构化的 JSON 文件。为什么选 JSON 而不是直接用 Python 类因为技能定义需要被不同模块共享——前端用来生成配置界面后端用来注册执行体模型侧用来做意图路由。JSON 是跨语言的通用格式能天然满足这个需求。下面是我在项目里实际使用的一个技能定义模板{ skill_id: query_orders, name: 订单查询, version: 2.1.0, description: 根据用户提供的订单号或时间段查询订单状态、金额、物流信息。只能用于查询不能用于创建或修改订单。, tags: [order, query, e-commerce], enabled: true, input_schema: { type: object, properties: { order_id: { type: string, description: 订单号长度一般以数字开头, examples: [20250115001, 20240930088] }, start_time: { type: string, format: date-time, description: 查询起始时间, examples: [2025-01-01 00:00:00] }, end_time: { type: string, format: date-time, description: 查询结束时间 } }, required: [order_id] }, output_schema: { type: object, properties: { order_status: { type: string }, total_amount: { type: number }, tracking_number: { type: string } } }, actions: [ { step: 1, action: call_ext_api, endpoint: https://api.example.com/orders/query }, { step: 2, action: parse_response, parser: standard_order_parser } ], fallback: { retry_times: 2, timeout_ms: 3000, on_failure: return_friendly_error } }这里actions数组是可扩展的它不只是调用外部 API还可以是“调用内部函数”“发起另一个子技能”“执行一段 Python 脚本”。把执行体描述成步骤序列有一个额外的好处可观测性变强了。每一步的执行耗时、输入输出都能被记录下来排查问题的时候一眼就能定位。3.2 编写技能执行体的实操要点定义文件只是“接口契约”真正干活的是执行体。以我的订单查询为例执行体是一个 Python 类类的核心方法接收input_schema校验后的字典返回output_schema约束的结果。我在实现执行体时踩过几个细节坑第一超时控制一定要在技能层处理不能在 HTTP 客户端层处理。因为技能是面向业务场景封装的它知道“这个接口我最多等 3 秒超过就降级返回值”而底层的 HTTP 客户端不知道这个业务约束只能设置一个通用的全局超时。第二外部接口返回的数据格式可能不稳定。比如正常情况下返回的 JSON 是{status: success, data: {...}}但偶尔会变成{code: 0, result: {...}}。我在执行体里加了一个“响应归一化”的适配层各种返回格式先转换成统一的SkillResult对象再交给上一层。这样后续不管换成什么上游服务技能内部的逻辑都不用大改。第三部分失败的处理要有“降级思维”。比如订单详情接口返回了但物流接口超时了这时候不能整体报错而是返回订单基础信息物流字段置为“暂不可用”。用户看到部分结果比看到一个错误弹窗要好得多这也是技能系统比裸函数调用更适合生产环境的原因之一。from skills.base import BaseSkill, SkillResult class QueryOrdersSkill(BaseSkill): skill_id query_orders def execute(self, params: dict) - SkillResult: order_id params.get(order_id) start_time params.get(start_time) end_time params.get(end_time) # 构造查询请求 payload {order_id: order_id} if start_time: payload[start_time] start_time if end_time: payload[end_time] end_time try: raw_resp self.http_client.post( https://api.example.com/orders/query, jsonpayload, timeoutself.timeout ) resp_data self.normalize_response(raw_resp) except Exception as exc: return SkillResult.fail( codeQUERY_EXTERNAL_TIMEOUT, message订单查询服务暂不可用请稍后重试 ) # 归一化字段名称 normalized self.rename_fields(resp_data) return SkillResult.success(datanormalized)3.3 技能路由的核心实现技能路由是决定用户体验的关键环节。我的做法是分两条路径并行拿结果一条是规则引擎另一条是模型分类。规则引擎我用的是一套简单的关键词加权匹配比如“查订单”“看看我的单”“订单到哪了”都会命中query_orders。每条规则根据命中词的确定性给一个分数超过阈值就直接返回该技能转发速度基本在毫秒级。模型分类则走正常的 LLM 推理把技能清单中每个技能的description和“不能干什么”的描述拼成上下文要求模型返回skill_id和参数解析结果。模型的优势是能理解模糊指令比如用户说“我上次买的东西什么时候能到”模型能推断出要调用“订单查询”并主动补上订单状态和物流跟踪参数。两条路径的结果会做一个融合如果规则引擎命中且分数极高直接采信规则结果模型结果只做参考如果规则未命中采信模型结果如果规则和模型结果冲突取模型结果并记录一次“冲突日志”方便后续优化规则权重。这种“规则兜底、模型兜复杂、冲突留痕”的架构让我在很长时间内都能稳定迭代每攒一批冲突日志就抽时间把高概率的规则固化到规则引擎里让系统的确定性越来越高模型承担的比例越来越小。3.4 技能间的编排与上下文传递单个技能能解决的问题有限实际业务往往是多个技能串成一个完整流程。比如“帮我把上周五的订单取消掉”这句话背后至少涉及三个技能订单查询先找到订单号、订单取消执行取消操作、消息通知把结果反馈给用户。我在技能编排上引入了一个轻量级的状态机上下文对象在技能之间流转前一个技能的输出字段自动映射到后一个技能的输入参数。映射规则是基于字段名的自动匹配加上少量人工编写的“映射覆盖规则”。比如query_orders返回的order_id会自动传递到cancel_order的target_order_id字段不需要额外写胶水代码。这个设计的核心思路是“让技能保持原子性让编排层负责组合”。技能自己只管自己的一亩三分地不关心前面是谁调用我、后面要去哪里。这样每个技能都能独立测试、独立复用。比如订单查询技能既可以被“订单状态播报”的助手用也可以被“售后自动处理”的流程用完全不需要改一个字。4. 技能评估、反馈与持续优化4.1 离线评测集是技能迭代的“安全网”技能系统迭代最怕什么最怕改动了一个路由描述结果牵扯出一堆隐藏的误调用。我在项目早期就吃过这个亏优化了query_orders的描述措辞上线后发现return_order技能的命中率掉了近三成。后来我意识到技能系统必须有离线评测机制任何改动上线前都要跑一遍回归。我构建的离线评测集包含几百条真实用户的匿名化 query每一条都标注了“期望路由到的技能”和“期望解析出的参数”。评测时有三个核心指标路由准确率期望技能和实际路由技能一致的占比这个指标主要卡的是“选没选对动作”参数完整率期望参数中被正确解析出来的占比它衡量的是“动作的关键输入有没有找全”参数准确率解析出的参数值里和人工标注完全一致的占比衡量的是“输入值对不对”。每次改动技能描述、调整路由 prompt 或更新意图示例我都会在评测集上跑一遍三率都不低于上一次才允许上线。这个流程很笨但它是我目前找到的“最防呆”的办法能挡住绝大多数回归性错误。4.2 线上反馈闭环从用户行为里自动学习离线评测解决的是“已知问题不复发”但要发现新问题必须靠线上反馈。我的做法是在技能执行链路上埋了一层反馈采集分三类第一类是显式反馈也就是用户点了“有帮助/没帮助”或者主动说“这不是我要的”。这类信号最直接权重最高。第二类是隐式反馈通过行为间接判断比如用户拿到搜索结果后立刻反复修改 query、连续点击好几个不同技能或者请求了多次某个技能却一直不进入下一步这些都暗示前面的路由可能不准确。第三类是系统异常反馈比如技能执行超时、返回空结果、参数校验失败。一个技能如果频繁出现“校验失败”往往意味着它在真实输入分布下暴露了意图层描述覆盖不到的边界情况这时候需要补描述或调参数协议。这些反馈会每周汇总一次生成一份“技能健康榜”按调用量、成功率、用户反馈分排序排在末尾的技能进入“待优化池”。优化动作有三种改描述、加示例、重新设计参数协议。整个闭环跑起来之后技能库的“体感质量”是肉眼可见地在涨。4.3 技能冲突和重复能力的消解随着技能数量增长一个很难避免的问题是“技能打架”两个技能都能处理同一类请求但执行路径不同结果也可能有差异。比如我早期既有一个query_weather又有一个travel_guide_query后者内部也会查天气。用户问“北京明天天气怎么样”两个技能都命中但返回内容的详略程度不一样。对于这种情况我的处理策略是“能力下沉”把基础的天气查询能力下沉为一个weather_report子技能travel_guide_query作为父技能调用子技能而不是自己重新实现一套。这样在路由层模型只需要识别“用户是不是在问旅行建议”如果是则走父技能如果不是则直接走天气子技能。冲突自然消解而且底层能力还因为复用变得更健壮。我也建议在新技能开发前先强制过一遍现有技能清单确认“要不要建新技能还是扩展现有技能”。特别是在团队协作的场景下没有这层约束很容易出现两三套重复实现后面维护成本会直接爆炸。我在项目里就是靠一个简单的 PR 评审 checklist 来卡这个事。5. 常见问题与排查技巧实录5.1 技能路由不准怎么办路由不准是技能系统中最常见的问题但它的根因往往不是模型笨而是意图层描述和用户真实表达存在错位。我遇到过一个典型案例技能描述里写的触发条件是“用户想了解订单配送进度”但用户实际会说的话是“我的东西怎么还没送到”“快递到哪了”“等了好几天了”这些表述在原始描述里没有一个词能命中。排查这类问题我总结出一套三步法第一步拉出所有漏召回和误召回的线上样本不要只看评测集真实线上用户的表达方式永远比评测集更多样第二步逐条对比“用户表达”和“技能描述”找出模型误解的词汇和句式这就是新的“触发信号”第三步把发现的新表达补充到技能描述或示例库里重新做离线评测通过后再上线。这套方法虽然听起来琐碎但每跑一轮路由准确率都会有实打实的提升。技能系统的效果不是一劳永逸的而是靠持续小步快跑磨出来的。5.2 同一个技能在不同对话里效果忽好忽坏我调试时还发现一个隐蔽问题同一个技能单独调用表现很好但在长对话里经常被调错。后来定位到原因是上下文干扰——对话里的历史信息比如用户之前提到过旅行后面问天气时会倾向于路由到旅行技能或者角色设定文本会污染模型的判断。解决思路是给路由模型喂“精简后的核心上下文”不把整段历史记录全部灌进去。我做了两层裁剪第一层是时间裁剪只保留最近几次轮次第二层是语义裁剪从历史记录中抽取与当前 query 最相关的实体和话题标签再和技能清单拼在一起送给模型。这个改造上线后路由稳定性提升了一个档次。另外还有一个容易踩的暗坑系统提示词里如果写了太多业务规则会稀释模型对技能描述的注意力。我的原则是路由场景的提示词尽量精简只保留“你是一个路由分发器请从以下技能中选择最合适的并解析参数”剩下的判断全部交给技能描述本身。5.3 技能执行超时与并发冲突技能执行走到真实业务环境后首当其冲的两个问题是超时和并发。超时的根因多半不在技能自身而是下游依赖服务偶发性变慢。我加的防护是两层超时叠加技能层设置一个业务超时往下游调用时再设置一个更紧的传输超时。这样即使下游“吊死”技能层也能快速失败并走降级逻辑。并发冲突则出现在“多个技能同时操作同一份数据”的场景。比如用户同时触发订单修改和订单查询修改还没提交完查询就读到了旧数据返回了过期状态。这类问题我在技能编排层面加了“资源锁”的机制同一业务资源同时只允许一个写操作技能运行读操作技能会排队拿到最新的数据快照。这样保证技能系统在并发场景下的数据一致性不至于给用户造成前后矛盾的信息。写在最后的小经验agent-skills这个方向我断断续续实践了小半年最大的感触是它不是一个“搭完就完事”的功能模块而是一个需要持续运营的能力体系。你搭好骨架只是开始后面每一个技能描述怎么打磨、路由规则怎么迭代、评测集怎么扩充才是真正决定智能体实操体验的核心。如果你也正在做这方面的开发我的建议是先不要追求大而全的技能库而是把一个高价值场景做深做透。拿一个真实业务沉淀出五个以内的核心技能通过离线评测和线上反馈把路由准确率打磨到九成以上再横向扩展。这样的节奏虽然慢一点但每一步都踩得实后面扩展时不会因为地基松动而反复返工。最后再分享一个我经常用的小技巧每次给技能库新增一个技能顺手把旧技能的线上失败案例拿出来重新跑一遍看看会不会被新技能“抢单”。很多路由问题不是新技能本身有 bug而是旧技能的错误命中被掩盖了。保持这个习惯你的技能库会越用越稳越迭代越有底气。
返回列表