资讯动态

Agent技能化:从工具调用到能力编排的实战指南

发布时间:2026/10/15 0:56:47 来源:尧图企业网站定制
先说结论如果只能给正在做 AI Agent 的朋友一条建议我会说尽早把技能skills当成一等公民来设计。所谓 agent-skills不是把一堆工具塞给模型让它自己猜而是把这个代理能干什么事、什么条件下干、怎么干、干坏了怎么处理拆成一个个独立、可描述、可测试、可组合的能力单元。这个项目名字看起来只是两个单词拼在一起实际背后牵扯的是整个 Agent 的能力边界、稳定性、可观测性和复用性。我最早接触这个概念时也被一个简单的误区骗过Agent 嘛会调用工具不就行了。工具这个词听起来跟技能差不多但真正跑起来你会发现工具只是技能的躯壳技能才是一整个系统工程。哪个场景该用哪个工具、参数怎么从用户的话里抽出来、调用失败后是重试还是换方案、多个技能同时触发时谁优先这些问题不在一开始想清楚后面每个版本迭代都是在补窟窿。这篇文章就围绕 agent-skills 这个方向把我实际搭建、调优、接线上线过程中沉淀下来的设计思路、代码结构、踩坑记录全部分享出来。不管你是刚接触 Agent 开发还是已经在用各类框架做多智能体希望这篇东西能帮你少走至少一个月的弯路。1. 先搞清楚agent-skills 到底在解决什么问题1.1 一个巨大 Prompt 的幻觉很多人第一版 Agent 都有一个共同画面写一个巨长的 system prompt里面密密麻麻列了二十个工具的用法、参数格式、注意事项然后指望模型每次都能精准选对。实测下来这个方案在 5 个工具以内还能勉强撑住超过 10 个就开始现原形了。模型会频繁选错工具、把参数填错、对着工具说明里的例子生搬硬套甚至在一个任务里连环调用七八次同一个错误工具越错越离谱。根本原因不是模型笨而是你把决策和执行的边界搞混了。工具层只告诉模型我有什么技能层要告诉它我什么时候该用什么、用了期望得到什么。这两者的信息密度完全不是一回事。我见过一个调账单类的 Agent工具列表里有查余额、查流水、查账单、查优惠四个工具描述几乎一样模型每次都挑第一个。把四个工具重构成账单查询这一个技能再在里面做参数分流之后准确率直接上了一个档次。所以 agent-skills 要解决的第一件事是给 Agent 一张真正有语义、有边界的能力清单而不是一堆孤立的函数接口。1.2 技能化拆解之后带来的改变当我们把能力从工具列表升级成技能库之后实际变化是结构性的不只是代码组织形式变了。首先是可测试性。一个技能有明确的输入、输出、触发条件和失败处理我可以像单测一个函数那样去测它。以前测工具调用要靠端到端对话来赌模型心情现在技能层可以独立验证回归成本低很多。其次是可复用性。同一个技能可以在不同 Agent 之间搬来搬去。比如意图确认技能用在客服机器人和用在审批助手里都是一套逻辑只是话术模板不同。技能库越攒越厚开发新 Agent 的速度会明显变快。最后是可观测性。技能调用天然是一条带元数据的日志单元谁会触发、参数是什么、结果成功还是失败、耗时多久全部有迹可循。之前用裸工具的时候出了问题只能翻一堆零散的函数调用日志排错排到怀疑人生。我个人理解agent-skills 的本质是给 Agent 做能力抽象让大模型不用关心每个工具的内部实现只需要理解技能描述这个接口契约。后面所有的调度、编排、纠错都建立在抽象之上。1.3 什么样的人适合这套思路如果你只是做个玩具 Demo让模型调一两个 API那技能系统属于过度设计直接写 function calling 就完事了。但如果你在做这几类事情技能化几乎是必经之路一是面向不确定用户需求的产品型 Agent比如客服、助手、办公自动化二是需要长期迭代、多人协作维护的 Agent 项目三是 Agent 要跨多种场景复用能力的平台型架构。这三种情况里技能库相当于 Agent 的操作系统没有它每一轮新需求都会变成给 prompt 打补丁最后补丁越打越多整个系统变成一个谁都不敢动的黑盒。判断标准也很简单当你发现改一个工具的描述会影响另外三个无关场景的效果时说明该上技能化了。2. 技能系统的整体设计从工具调用到能力编排2.1 技能、工具、工作流三个概念别混动手之前先把概念对齐不然团队里开会都会互相吵。工具tool是最底层的一个可执行动作比如调用某个 HTTP 接口执行一段 SQL发一封邮件它只干一件事输入输出都是确定性的。技能skill是面向目标的能力封装由一个或多个工具组合而来带有触发条件、适用场景、输入输出契约和失败策略。工作流workflow则是多个技能的编排产物解决的是先做什么后做什么的流程问题。举一个生活化的例子。工具是拿起刀切菜开火技能是做一道西红柿炒鸡蛋工作流是做一桌三菜一汤的宴席。刀的用法不会因为做菜谱系不同而改变但技能可以换、可以组合工作流则负责统筹资源。我之前见过一个团队把三者混在一起一个技能里塞了完整的业务流程结果技能之间大量耦合改一个环节要动三个技能。拆开之后再重构思路一下子就清楚了。2.2 我们最终采用的分层结构实际落地时我把系统分成四层交互层负责接收用户意图做初步判断决定是直接回复还是进入技能调度。技能路由层根据用户请求和技能描述做匹配选出最合适的技能并抽取参数。技能执行层真正运行技能内部的逻辑包括调用多个工具、组装结果、做内部校验。反馈层把执行结果转换成用户可读的回复同时记录日志埋点。这套分层的核心思想是决策和执行分离。路由层只负责做选择题哪个技能最合适执行层只负责做执行题这个技能内部怎么跑两者之间通过统一的技能描述和输入输出契约通信互不越界。选择这种设计一是因为模型的不确定性集中在做选择题上把决策点收敛到一个小模块排查问题范围小二是因为执行层的代码是可单测、可回归的哪怕模型抽风执行层仍然稳三是将来想换更强的模型或者换路由策略只动路由层就够了技能库不受影响。2.3 为什么不做端到端让模型自己编排也考虑过更大胆的方案不定义技能直接把所有能力和业务规则都丢给模型让它在对话里自由发挥、自由编排。这种做法在场景单一、翻车成本低的场合确实省事但我们最终没选它理由很现实。第一不可控。自由编排意味着同一件事这次走流程 A下次走流程 B用户感受到的服务质量波动很大。第二不可测。没有一个确定的技能边界就没法针对关键路径做回归测试上线完全靠抽样对话验证。第三成本高。模型每次都要从全部能力里重新做规划Token 消耗和延迟都上去了对规模化部署不友好。所以我一直觉得Agent 项目要在模型的聪明和工程的可控之间找平衡点。技能化就是在可控这边多压一些筹码把模型的发挥空间收敛到选择与参数抽取上而不是放任它自由发挥。3. 核心细节技能定义、元数据与输入输出契约3.1 一份可落地的技能规范技能的定义是整个系统的地基这块做得不好后面全白搭。我们最终沉淀了一份通用的技能描述结构用 JSON Schema 来表达核心字段包括这几个字段作用备注name技能唯一标识命名要语义化如 query_billdescription技能适用场景的详细描述这是给模型看的务必写清楚什么时候用、什么时候不用trigger_conditions触发条件辅助路由判断的规则提示input_schema输入参数定义每个参数的类型、必填、枚举、示例output_schema输出结构定义明确返回值给后续环节用error_strategy失败策略重试、降级、报错、人工介入tags分类标签用于检索、权限控制、统计有人会觉得这套字段太重了尤其小项目没必要。我的体会是description 和 input_schema 是保命字段绝对不能省tags 在技能超过二十个后价值极大error_strategy 在关键业务技能上是刚需。其余字段可以按需裁剪。3.2 描述质量最容易翻车的地方必须单独强调一下 description 的写法因为模型能不能选中正确的技能50% 靠它。我踩过最典型的坑是描述太抽象。比如此技能用于查询账单模型面对用户说我这个月花了多少钱时往往就是犹豫不决。后来我把描述改成当用户询问任意时间范围内的消费金额、账单明细、关联订单费用时使用支持按月份、按类目筛选如果用户只是问余额请勿使用。加了正例、反例、边界之后选错率立刻降下来了。写描述有几个实操技巧值得分享。一是明确写什么时候不要用负例和正例同等重要能帮模型做排除。二是写清楚参数之间的约束关系比如当 start_time 为空时必须使用最近自然月。三是控制篇幅描述不是论文用两三句话讲清场景边界即可太长模型反而不抓重点。另外强调一点所有描述都要从用户视角出发不要写实现细节。模型看到调用 HTTP GET /api/v2/bill/detail这种描述是没感觉的它需要的是用户想知道花了多少钱这一类目标语言。3.3 参数抽取与校验拦截脏数据的闸门技能定义的输入参数不是摆设它是模型和真实世界之间的闸门。用户一句我想看去年冬天的账单模型要抽出 start_time、end_time还可能要推断去年冬天等于几月到几月这属于参数解析的活。参数抽取我建议直接交给模型做但校验必须交给代码做。原则是模型负责把自然语言转成结构化参数代码负责判断参数是否合法、是否缺失、是否冲突。缺参数时不要默默补默认值要先走一次补齐追问的流程不然很容易在用户没确认的情况下做出错误动作。字段层面要设计好哪些参数必填、哪些可选、枚举值给出合理范围、参数之间有无依赖关系。这些约束写进 input_schema让模型在生成参数时就能看到比生成之后再用规则纠错效率高得多。我在生产环境里还加了一层兜底校验对关键参数执行类型检查和范围检查比如金额必须大于零、时间范围不能超过 366 天。这类规则看起来蠢但能挡掉不少模型幻觉导致的脏数据。4. 实操过程从零搭一套可用的 agent-skills 库4.1 目录结构与注册机制下面这套是我实际在用的组织方式不依赖任何特定框架自己用文件系统就能实现。核心思想是一技能一目录所有技能通过统一注册表加载。skills/ __init__.py registry.py query_bill/ skill.py schema.json test_cases.json README.md send_email/ skill.py schema.json test_cases.json README.md schedule_meeting/ skill.py schema.json test_cases.json README.md每个技能目录里skill.py 是执行逻辑schema.json 是上面说的技能定义test_cases.json 是预置的测试用例README 写给人看的说明。注册机制上registry 启动时扫描 skills 目录读取每个目录下的 schema.json 和 skill.py把技能描述汇总成一份清单。这份清单最终注入到路由模型的消息里让模型知道当前 Agent 有哪几项能力、各自适用什么场景。# registry.py 核心逻辑示意 import json import importlib from pathlib import Path class SkillRegistry: def __init__(self, skills_dir: str skills): self._skills {} self._load_all(skills_dir) def _load_all(self, skills_dir: str): for path in Path(skills_dir).iterdir(): if not path.is_dir(): continue schema_path path / schema.json if not schema_path.exists(): continue schema json.loads(schema_path.read_text(encodingutf-8)) module importlib.import_module(f{path.name}.skill) self._skills[schema[name]] { schema: schema, runner: module.run, } def list_descriptions(self) - list: return [self._skills[k][schema] for k in self._skills] def execute(self, name: str, **params): skill self._skills.get(name) if not skill: raise KeyError(fskill {name} not found) return skill[runner](**params)用文件系统做技能目录最大的好处是版本的边界清晰每个技能可以独立演进谁动了哪个技能git 历史里一目了然。等技能数量多起来之后再考虑上数据库管理和可视化后台也不迟。4.2 写出第一个真正的技能拿一个最简单的查账单技能举例先定义 schema.json再写执行逻辑。{ name: query_bill, description: 当用户询问任意时间范围内的消费金额、账单明细时使用。支持按月份筛选也支持自定义起止日期。如果用户只是询问账户余额请勿使用该技能。, input_schema: { type: object, properties: { start_time: { type: string, format: date }, end_time: { type: string, format: date } }, required: [start_time, end_time], constraints: end_time 不得早于 start_time时间跨度不得超过 366 天 }, output_schema: { type: object, properties: { total_amount: { type: number }, items: { type: array } } }, error_strategy: { on_invalid_args: ask_missing, on_timeout: retry_once } }skill.py 里只干三件事解析并校验参数调用账单服务接口把结果标准化后返回。def run(start_time: str, end_time: str): # 1. 参数校验 if end_time start_time: raise ValueError(end_time cannot be earlier than start_time) # 2. 调用底层账单服务 raw bill_service.query(start_timestart_time, end_timeend_time) # 3. 标准化返回 return { total_amount: raw[total], items: [{id: x[id], amount: x[amount], time: x[time]} for x in raw[records]], }这个技能看起来简单但已经包含了契约的完整闭环给模型的描述、给代码的参数约束、给下游的标准输出。这样的技能攒到几十个系统依然不乱套。4.3 技能的自测与验收技能写出来不能直接上生产我习惯每个技能带一份 test_cases.json里面存几条典型的输入输出用例。注册表里可以顺便实现一个 smoke test每次改动跑一遍。test_cases.json 大概长这样[ { name: 正常查询, input: { start_time: 2025-01-01, end_time: 2025-01-31 }, expect_total_positive: true }, { name: 时间倒置, input: { start_time: 2025-03-01, end_time: 2025-01-01 }, expect_error: end_time cannot be earlier than start_time } ]验收标准我给三条输入输出符合 schema非法输入能稳定报错而不是静默吞掉性能上单次执行延迟在预期范围内。满足这三条技能才能进主流程。5. 技能编排与调度让每个请求找到对的人5.1 让模型选技能还是让规则选技能技能多了之后第一个要面对的问题是路由一个用户请求进来怎么决定调用哪个技能业界主流做法有两条路。一是纯规则路由写一堆 if-else 或者关键词匹配好处是稳定可控、延迟低、不花额外 Token坏处是遇到模糊表达就死板覆盖面有限。二是让模型做语义路由把技能描述列表喂给模型让它返回技能名和参数灵活度高但存在误选风险也增加一点延迟。我的实践是二者混合先用一个轻量规则层做硬过滤比如非法时间、敏感词、明确的禁止词直接拦截命中不了规则的模糊请求再交给模型做语义路由。混合路由的好处是快路径和慢路径分离高频确定请求不用等模型低频模糊请求也能被兜住。路由层返回的不仅是一个技能名还应该包含置信度和抽取好的参数。置信度低于某个阈值时不调技能改为澄清追问。这个设计帮我挡掉了相当多误操作宁可多问一句也不要执行错。5.2 冲突、降级与并发不能只考虑选得对除了选哪个技能还要考虑选了之后怎么执行得稳。技能冲突是常见麻烦。多个技能都能解决用户请求时不能只靠模型猜要在描述和路由逻辑里明确优先级。我的做法是给技能配一个 priority 字段路由返回多个候选时按优先级排序同时结合置信度做加权。比如改地址和查订单同时触发时改地址涉及写操作必须显式跟用户确认不能因为置信度高就直接改。降级策略同样重要。写操作和读操作要区别对待读操作技能失败可以静默降级写操作技能失败必须暴露给用户走人工确认或稍后重试。这个原则看起来简单但我在实际项目里见过因为把读和写混在一起降级把用户订单状态改坏的事故。并发方面技能执行要做好超时控制。每个外部调用都要有明确的超时上限不能无限等。技能引擎里建议统一维护一个并发执行框架支持串行、并行、带超时的批量执行避免每个技能各自写一套协程逻辑。5.3 可观测性技能调用的日志与 trace技能系统上线后最值钱的就是日志和 trace。每个技能调用必须记录技能名、路由置信度、输入参数、输出摘要、耗时、错误信息、用户会话 ID。这不只是为了排查问题更是为了持续优化技能描述和路由策略。我习惯给每次调用打一个全局 trace_id从用户请求进来一直到技能返回全程透传。排障时按 trace_id 一查整条链路上模型怎么选的、参数怎么变的、哪个环节超时一目了然。没有这套埋点之前我排查一个误选问题要翻半小时日志有了之后五分钟内定位。另外一个被低估的点是反事实观测除了真实调用的技能路由层还可以把候选技能 Top-3 以及没选中的原因记录下来。这些数据攒多了可以分析哪些场景模型频繁纠结反过来优化技能描述形成闭环。6. 常见问题与排查技巧实录6.1 模型总是选错技能这是出现频率最高的问题。选错技能的原因我先按概率排序描述不清晰、技能之间边界重叠、参数示例误导、模型版本变更。排查思路是这样先把路由失败的样本整理出来看模型到底选了哪个技能、为什么这么选。如果模型在 A 和 B 之间反复横跳说明 A、B 的描述边界不清要在描述里明确什么时候用 A 不用 B。如果模型选了 A 但参数明显不对多半是 input_schema 里的示例不够需要补几条用户典型说法对应的参数示例。还有一个容易被忽略的点技能列表太长。超过大概二十个技能之后哪怕是强模型准确率也会明显下滑。这时候不要盲目扩列表想想能不能合并同类技能或者按场景分组做两级路由先选场景组再选具体技能。6.2 参数解析失败与幻觉参数参数解析失败是第二大类问题。用户说法里的信息模型没抽出来或者抽出了根本不存在的值。我的经验是参数抽取要给模型足够的提示锚点。比如上周下个月最近三个月这类模糊时间词最好在描述里明确换算方式甚至提供一个统一的时间解析函数让技能内部处理。把从自然语言到标准参数的转换责任尽量压在代码侧模型只需要原样传递关键片段解析确定性会高很多。幻觉参数方面一个有效手段是参数白名单校验。对枚举字段、固定选项字段做硬校验模型输出不在白名单里就直接拒绝并要求重抽。对开放文本参数至少要过一遍长度和格式检查避免脏数据污染下游系统。处理原则很简单能确定的参数直接执行有怀疑的参数去追问确定不了又必须用的参数按最保守方案处理。宁可让流程慢一步也不要带着错误参数往前冲。6.3 技能之间的隐性依赖第三个容易翻车的地方是隐性依赖技能 A 表面独立内部却依赖技能 B 跑过的上下文。最典型的是先查询后操作的业务模式比如必须先查到订单号才能执行退货申请。我的建议是把这类依赖显式化。要么合成一个复合技能把先查后操作的逻辑固化在里面要么在技能描述里写明前置条件并在路由层检查上下文里是否有必要的信息缺了就触发前置技能。另外技能执行也要考虑幂等性。重试机制虽然好但写操作重试前一定要确认上一次调用到底有没有成功。给每次写操作生成一个操作幂等键重试时带上同一个键避免重复下单、重复扣款这类问题。这是我们上线早期踩过一次坑换来的教训。下面把高频问题整理成一张速查表方便直接对照现象可能原因处理手段选错技能描述边界不清、技能重叠重写描述加负例拆分或合并技能参数缺失用户表达简略、描述未提示加追问流程描述中补充典型表达示例参数幻觉模型自行脑补缺失信息硬校验 白名单 关键项追加确认调用超时外部服务慢、无超时控制统一超时配置读操作并行写操作兜底重复操作重试机制未做幂等加幂等键写操作重试前核对状态效果变差模型版本升级或描述被改建立回归测试集每次变更跑全部技能用例6.4 上线后最值得做的两件事最后补两个偏运维的经验。第一是技能级灰度。不要在路由层一次性把新技能开放给全部流量可以先定义一个小流量分组比如只有 5% 的请求能路由到新技能观察准确率和用户反馈没问题再放量。第二是定期的技能健康度盘点。每月看一遍所有技能的调用量、成功率、平均延迟、误触发率。长期没人调用的技能大概率描述有问题或者需求不存在该优化优化该下架下架。技能库不是越肥越好精准才是关键。7. 最后分享一点个人体会技能体系这件事做之前觉得是个技术选型问题做之后发现它其实是个产品和工程的连接器。没有技能层产品和研发沟通 Agent 能力时全靠大概能做什么这种模糊表述有了技能库之后能力边界就在那里摆着新增需求要动哪些地方影响范围有多大评估成本会低非常多。我自己的体会是不要一开始就追求技能库的规模先老老实实把 10 个核心技能做好把描述、校验、日志、回归这套流程跑顺再往外扩。技能系统的价值不在第一天而在六个月后你还能轻松改一个技能而不影响全局的时候才真正体现出来。如果你也在做 Agent建议从今天开始先挑一个使用频率最高的能力试着把它变成技能。跑通这一个后面的路就顺了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑