Agent技能体系实战:从定义、注册到编排的完整指南

发布时间:2026/10/14 8:01:52
Agent技能体系实战:从定义、注册到编排的完整指南 做Agent开发这么久我越来越确定一件事真正决定一个Agent上限的不是底层模型的聪明程度而是你愿不愿意在技能Skills体系上花心思。模型大家都能调Prompt大家都会写但一套把工具、逻辑、知识沉淀成可复用技能的方法才是拉开差距的地方。这篇文章就围绕“agent-skills”这个主题把我实操中关于技能抽象、定义、注册、编排和排查的经验完整拆一遍。1. 技能体系的价值与整体设计定位1.1 为什么Agent需要技能体系而不是单纯堆API先看一个最朴素的场景。我最早做Agent的时候思维很简单——模型需要什么工具我就给它塞什么。天气API、数据库查询、日历接口、邮件服务全部注册进去让模型自己选。结果很快踩了两个坑。第一个坑是上下文爆炸。工具描述写得含糊模型频繁选错工具每次调用失败还会把错误信息带回上下文一轮对话下来光工具相关的token就占了大半。第二个坑是复用性极差。同一个“根据用户历史行为推荐优惠券”的逻辑在客服机器人和营销助手里各写了一份改动一个策略两个地方都要动。后来我看了一些开源项目的做法意识到核心问题是工具只是能力的原子单位而Agent需要的是能力的封装单位。工具描述的是“我能做什么”技能描述的是“在什么情况下、按什么步骤、用哪些工具、达成什么目标”。这个抽象层级差一层维护成本和鲁棒性就完全不一样。所谓agent-skills就是把一组相关的工具、调用逻辑、参数约束、错误处理甚至Prompt片段打包成一个自包含的模块让Agent像人类掌握一项技能一样按需调用。它的价值不是减少代码量而是把“模型该怎么做”这件事从对话编排中固化下来变成可测试、可复用、可版本管理的资产。1.2 技能设计的三个核心原则在动手定义技能之前先把三个原则想清楚后面能省一大堆返工的麻烦。第一技能要面向目标而不是面向工具。不要建一个“调用数据库查询接口”这样的技能而要建“查询用户订单状态”“统计商品销量趋势”这样的技能。因为Agent的真实任务是达成目标不是调接口。面向工具建技能本质上还是在裸用工具只是包了一层壳。第二技能的粒度要能够独立验证。一个技能应该能单独被测试——输入一组参数得到确定的结果。如果你的技能内部还依赖了“上一次对话的上下文”或者“某个全局状态”它就会变得不可控。技能最好是无状态的所有依赖都通过参数显式传入。第三技能的边界要窄而清晰。每个技能只做一件事并且做得很彻底。比如“解析PDF发票”是一个技能“解析PDF发票并写入财务系统”就是两个技能。它们可以编排在一起但不要揉成一个。揉在一起的问题在于后续你想换掉解析引擎或者改写入逻辑时会发现牵一发而动全身。1.3 技能与工具、插件、工作流的区别这几个概念经常被混着说我自己的理解是分层的。工具是最底层的能力单元比如“发送HTTP请求”“读取文件”“执行SQL”。插件是工具的集合通常围绕某一个第三方系统或数据源比如微信插件、飞书插件、数据库插件。技能则是建立在这些之上的编排单元它内部可以使用多个工具或插件并且包含了决策逻辑和步骤编排。工作流和技能的差别在于工作流通常是预定义的、线性或带条件分支的步骤链路更像是一个剧本而技能更像是演员的表演能力它知道在什么情境下怎么表现。一个Agent可以拥有多个技能然后在对话中自由选择串联这比只能运行固定工作流要灵活得多。在项目里我习惯的划分方式是技能描述文件包含触发条件、参数规范、执行步骤叫Skill技能背后的具现执行逻辑通过工具调用来完成。两者组合成一个完整的agent-skill。2. 技能定义、注册与参数规范2.1 一份结构化技能定义的完整框架一份好的技能定义核心目的不是给程序看而是给模型看。模型要依据这个描述来判断“当前任务是否该调用这个技能”“参数该怎么填”。所以描述语言必须清晰、无歧义、信息密度高。我常用的技能描述文件结构是name: order_status_query description: - 根据用户提供的订单号或手机号查询订单的当前状态、物流节点与预计送达时间。 当用户咨询我的订单到哪了快递什么时候到发货没有等问题时使用。 如果用户提供的参数不足不要调用本技能而是主动追问订单号或手机号。 version: 1.2.0 author: platform-team tags: [order, logistics, query] parameters: type: object properties: order_id: type: string description: 用户订单号格式为15位数字可带前缀字母 pattern: ^[A-Za-z]?\\d{15}$ phone: type: string description: 用户下单手机号后四位可用于模糊匹配 required: [order_id] steps: - resolve_customer: 根据order_id查询用户身份 - query_order: 调用订单中心接口获取详情 - enrich_logistics: 补充物流节点信息 - format_response: 按模板输出结果 error_handling: order_not_found: 提示用户核对订单号并提供人工客服入口 network_error: 提示稍后重试记录失败日志这个结构里我把description放在极其重要的位置。因为它其实就是一份给模型的“使用说明书”写得具体还是笼统直接决定模型能否正确触发技能。我发现一个规律描述里包含具体示例和触发场景的技能触发准确率比泛泛描述的要高很多。比如描述里带当用户咨询快递什么时候到模型就知道要调这个技能而只是写查询订单状态模型有时会选择其他技能。2.2 参数Schema的设计与校验细节参数定义是技能落地最容易出问题的环节。Agent模型本质上是概率生成它填参数不会像人写代码那样严格遵守类型约束所以Schema设计要刻意“降低模型犯错的可能性”。几个实操建议一是尽量使用字符串类型承载业务数据而不是布尔或数字。模型生成布尔字段时可能输出true、True、true甚至1。每次校验都要做兼容很烦。干脆都用字符串按需自行转换。二是必填字段要少。每增加一个必填参数就多一次校验失败的可能。能用默认值解决的就不要让模型填。比如分页大小固定20条不设置参数项直接写在执行逻辑里。三是在描述里写明格式要求而不是只靠正则兜底。比如订单号字段描述里写明“15位数字”同时Schema里的pattern再加上正则校验双重保险。模型被正则弹回去会重试消耗一次调用但如果描述写清楚了它一次就能填对。2.3 技能注册中心与加载机制技能定义好之后需要注册才能被Agent运行时发现。注册中心的实现方案很多可以是一个JSON文件清单也可以是一个数据库表甚至是对象存储上的一组YAML文件。核心是两部分技能元数据索引和执行代码的位置。我比较推荐“文件即注册”的模式——每个技能一个目录目录下放着skill.yaml描述文件和实现代码加载器扫描目录自动注册。好处是版本管理天然支持Git分支和Tag就能完成技能版本的回滚和灰度。上线一个新技能就是合并一个PR的事。# 技能加载器核心逻辑伪代码 class SkillRegistry: def __init__(self, skill_dirs: list[str]): self.skills {} for directory in skill_dirs: for skill_path in Path(directory).glob(*/skill.yaml): skill_meta load_yaml(skill_path) executor load_executor(skill_path.parent / executor.py) skill Skill(metaskill_meta, executorexecutor) self.skills[skill_meta.name] skill def get_skill(self, name: str) - Skill: return self.skills.get(name) def list_skills(self) - list[str]: return list(self.skills.keys())加载器在Agent启动时跑一次把技能清单交给模型做参考。后面每次模型决定调用某个技能时按name直接索引到执行器。注册中心还需要提供路由信息比如这个技能负责哪个域、是否需要特殊授权这些元数据可以供上层的调度器做决策。2.4 版本管理技能一定会迭代这是必然的我对团队的要求是任何技能上线第一天就要有版本号。哪怕你只打算写给自己用也要养成习惯。原因是技能的行为会被Agent当作“既定事实”一旦行为变化所有依赖它的对话流都受影响。版本管理有几个层面要考虑。元数据层面的version字段自不必说更重要的是行为兼容性的记录。我通常会在技能目录里放一个CHANGELOG.md记清楚每个版本改了什么行为。别小看这个动作当线上Agent出现异常排查“是不是技能升级导致的回归”时这个文件能救你一命。灰度策略上比较粗的实践是目录名里带版本号比如order_query_v1、order_query_v2然后在注册中心做流量分配。细一点的可以接配置中心按用户ID哈希做小流量实验。对大厂之外的大多数场景目录名加版本号已经够用了。3. 从零搭建一套可用的agent技能库3.1 项目目录结构与骨架搭建我建议直接把技能库当成一个独立项目来管理别和Agent主服务混在一起。技能库做成一等公民后续加技能、修缺陷都更清晰。一个我试过很多次的目录结构agent-skills/ ├── skills/ │ ├── order_status_query/ │ │ ├── skill.yaml │ │ ├── executor.py │ │ ├── templates/ │ │ │ └── response.md.j2 │ │ └── tests/ │ │ ├── test_executor.py │ │ └── fixtures/ │ ├── coupon_recommend/ │ │ ├── skill.yaml │ │ ├── executor.py │ │ └── tests/ │ └── ... ├── registry.py ├── runner.py ├── pyproject.toml └── README.md每个技能目录里skill.yaml负责“告诉模型怎么用”executor.py负责“把事情办成”tests目录保证“改动不破坏已有行为”。templates目录存放回复模板因为技能输出格式的稳定性对用户体验很重要。3.2 技能执行器的接口设计与实现执行器的接口设计是整个技能库的地基。我踩过一次坑是早期接口五花八门有的技能接收dict有的接收kwargs有的直接在executor里访问全局数据库连接。后来统一成标准输入输出模型世界清静了。# 统一执行器接口 from pydantic import BaseModel class SkillInput(BaseModel): parameters: dict context: dict # 包含用户ID、会话ID、时区等基础上下文 class SkillOutput(BaseModel): status: str # success | failure | need_more_info data: dict {} message: str error_code: str | None None class BaseSkillExecutor(ABC): 所有技能执行器继承此基类 def __init__(self, skill_meta: dict): self.meta skill_meta self.timeout skill_meta.get(timeout, 10) abstractmethod async def execute(self, skill_input: SkillInput) - SkillOutput: ...统一接口的最大收益是上层调度器可以无差别对待所有技能。无论是查订单、算优惠还是生成文案对Agent运行时来说都是“参数进结果出”。这个抽象让上层逻辑变得极简也让技能测试变成一件机械、可批量执行的事。3.3 技能内部编排多工具调用的执行链一个技能内部往往要串多个工具。以“查询订单状态”为例实际的调用链是这样的先用订单ID查用户身份然后调订单中心拿订单详情再去物流系统补充物流节点最后用模板组装回复。这个编排逻辑写在executor里我推荐的方式是每个环节拆成独立函数环节之间用异常处理衔接任何一步失败都走统一的错误分支。async def execute(self, skill_input: SkillInput) - SkillOutput: params skill_input.parameters order_id params.get(order_id) try: customer await self.order_api.resolve_customer(order_id) if not customer: return SkillOutput( statusfailure, error_codeORDER_NOT_FOUND, message未找到该订单对应的用户信息 ) order await self.order_api.get_order(order_id) logistics await self.logistics_api.track(order.tracking_no) return SkillOutput( statussuccess, data{ order_status: order.status, logistics_nodes: logistics.nodes, eta: logistics.eta, } ) except TimeoutError: return SkillOutput( statusfailure, error_codeNETWORK_TIMEOUT, message查询超时请稍后重试 ) except Exception as exc: logger.exception(unexpected error) return SkillOutput( statusfailure, error_codeINTERNAL_ERROR, message服务异常已记录日志 )注意到一个细节技能内部并不直接打印给用户的内容而是返回结构化的data和message由上层Agent统一组织语言。这么做的好处是同一套技能数据可以在不同前端用不同话术呈现比如微信渠道话术更口语化APP渠道更正式。如果技能把话术都写死了复用性就会下降。3.4 技能调用编排让模型做决策的运行时机制一个Agent拥有几十个技能之后如何决定在某个对话场景下调用哪个技能这里有两种主流的做法。一种是纯模型决策把所有技能的元信息全部塞给模型由模型自行选择。这种方式灵活但上下文开销大而且技能一多模型会“眼花”经常选错。我试过60个技能的全量注入调用准确率直接跌到70%以下。另一种是分层路由先有一个粗粒度的意图路由把对话分流到某一个技能域再在域内让模型选择具体的技能。比如用户说“我的快递到哪儿了”先路由到“订单物流域”域内只有两三个技能模型选中的概率就大大提升。在agent-skills的体系里我推荐第二种。具体实现上可以在注册中心给每个技能打上domain标签路由层维护domain到技能集合的映射。这一步改进让我的技能触发准确率从70%多提高到了接近95%。代价是路由层本身也要消耗一次模型调用但对复杂技能库来说是值得的。3.5 让技能可见Agent上下文中的技能描述降噪技能数量上来之后怎么给模型展示技能列表是一门学问。全量展示一定会让模型产生选择困难我摸索出来的比较有效的做法是三层降噪。第一层只展示当前domain下的技能。第二层每条技能只展示名称、一句话能力描述和核心参数要求其他细节留给模型触发后再加载。第三层技能描述提前做过同义改写把触发场景的常见说法都写进去比如“快递”“包裹”“物流”“到哪了”都归到订单查询技能下面。这样做的效果很直观——模型看到的技能列表短而精描述又直接对应常见的用户表达触发的准确率上来了同时上下文占用的token大幅下降。之前一段复杂对话可能要消耗4000多token在工具描述上现在压缩到不到500对话成本也随之下降。4. 技能组合编排从单技能到多技能协作4.1 技能之间的显式调用链设计单个技能能解决的问题始终有限真实业务往往是“先查一下状态再决定下一步动作”。这就需要技能之间有协作机制。我的做法是在技能描述里显式声明后继技能next_skills而不是让模型自由发散。比如“取消订单”技能声明依赖“查询订单状态”技能的输出如果订单已发货则跳转“退货申请”技能。这样做的好处是链路可控失败率低缺点是灵活性下降了。灵活性和可控性之间需要平衡我的取舍标准是核心链路显式编排边缘情况自由发挥。用户问“我的订单怎么了”这是一条高频核心链路必须编排得滴水不漏。但如果用户接了一句“这快递也太慢了”那就让模型自由决定要不要调用投诉建议技能。关注核心放开边缘。4.2 条件分支与技能间数据传递技能编排中最难处理的问题是数据传递。一个技能的输出怎么变成下一个技能的输入我建议在编排层定义一个共享状态对象SharedState链条上的每个技能都可以读写其中的字段但必须遵循声明的数据契约。class SkillChainContext: 技能链共享状态 def __init__(self, initial: dict): self.data deque(initial.get(params, {})) self.history [] self.errors [] def set(self, key: str, value: Any): self.data[key] value def get(self, key: str, defaultNone): return self.data.get(key, default)运行时每个技能在执行前声明它需要的输入字段和要写入的输出字段链式调度器依次校验。如果前一个技能没有产生后一个技能需要的字段调度器应该抛出缺失异常而不是闷着头跑下去。这个设计可能看起来略重但对生产环境是值得的。因为Agent链条一旦加长排查“用户为什么收到错误回复”时能清晰地看到是哪个环节缺了数据。我Debug过最痛苦的线上问题80%都出在技能间数据张冠李戴。4.3 技能编排的并行与降级策略不是所有技能都必须串行。比如用户投诉处理既要查订单、又要查物流、还要查历史客服记录这三个查询彼此独立并行发起能显著降低整体响应时间。并行化的代价是复杂度。你需要在编排层管理并发、超时和部分失败。我的实践是只有稳定且无依赖的查询类技能才做并行涉及写入类操作一律串行。并行技能如果其中一路超时默认走容错分支用缓存数据或简化回复兜底不让整个链路失败。降级策略是另一个加分项。比如主技能“智能推荐优惠券”依赖实时算法一旦算法服务抖动可以直接降级到“默认规则推荐”技能。这个降级逻辑写在编排配置里不需要改代码。做这个设计时要把业务方的预期管理好降级不等于失败是可控的体验降级。5. 常见问题与排查技巧实录5.1 模型不触发技能或者触发错了技能这是最常遇到的故障咨询群里的高频问题。排查思路按顺序走。第一步看技能描述是否覆盖了用户的常见表达。我遇到过一次用户说“我的快递走哪了”技能描述里写了“查询订单状态”“物流进度”偏偏漏了“走哪”这种口语化说法模型直接懵了。解决方式就是在description里把同义说法补全。第二步看技能列表是不是太长。如果模型可选技能超过30个建议马上做domain路由。我说的那个准确率降到70%的例子就是血泪教训。第三步看技能之间的边界是否清晰。两个技能都包含“查询”描述又相似模型大概率会选错。处理办法是刻意在描述里写清差异点比如“本技能专门处理已发货订单的物流跟踪”和另一个“查询订单基本信息”的技能区分开。5.2 技能执行报错错误信息被模型“编”成答案这类问题隐蔽性极强。技能执行器返回了一个错误模型没有如实告知用户失败而是顺着用户的话编了一个看似合理的答案。比如订单没查到模型却说“您的订单预计明天送达”。根治的办法是在技能返回错误时让错误信息足够醒目并且在技能描述里写明“如果技能返回失败必须告知用户查询不到结果绝对不允许自行推测或编造答案”。还要在编排层做拦截技能返回failure时不允许模型直接向用户输出内容只能走预设的错误话术模板。5.3 参数传递链路断裂技能联动失败串行技能链里前一个技能输出字段名对不上后一个技能的输入是家常便饭。典型的场景是前一个技能输出的是order_id后一个技能输入的参数名叫order_no对不上。这类问题的排查效率取决于日志。我在链条每个环节都打印结构化日志记录入参出参的关键字段。排查时直接看链条日志一眼就能看出哪个环节断了。数据契约校验也是提前拦截的好方法把字段名不一致的问题在配置期爆出来而不是运行期。5.4 技能响应超时拖垮整个Agent对话技能超时的问题通常和外部API延迟有关。解决思路分三层。第一层外部调用必须设超时我的经验是数据库查询3秒、外部HTTP接口5秒、算法调用10秒太长会影响整体体验。第二层超时后要降级不能干等且超时路径必须记录日志方便后续排查。第三层考虑缓存。同一个用户在短时间内反复触发“查订单”技能结果几乎不会变加个5分钟的缓存能削减大量外部依赖压力。5.5 观察与攻击面定期检查技能执行质量最后建议把技能的线上质量当成持续运营项而不是上线就完事。我给每个技能都加了调用量、成功率、平均延迟三项指标每周复盘一次。哪些技能被频繁触发但成功率低哪些技能几乎没有触发过都是重要的信号。成功率低的技能优先查外部依赖是否稳定。很少触发的技能要么是描述不够显眼要么是业务场景本身就不需要。前者需要优化描述后者说明这个技能本就不必存在。删除低效技能和增加高效技能一样重要技能库也会腐化定期修剪是对所有使用者负责。我个人在这几轮迭代里最大的体会是技能体系的建设不是一锤子买卖它需要持续观察、持续修正像经营产品一样经营你的技能库。代码层面的实现并不复杂真正难的是对触发场景的敏感度、对边界情况的判断力、以及对线上监控数据的解读能力。如果你的技能库能稳定跑到95%以上的准确率、99%以上的成功率Agent的整体体验一定不会差。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询