资讯动态

Agent Skills实战:从原理到落地,构建可靠的大模型工具调用

发布时间:2026/10/21 13:42:04 来源:尧图企业网站定制
我第一次听到 Agent Skills 这个词的时候第一反应是“这不就是给模型加几个工具函数吗”。后来在实际项目里磕磕碰碰跑了两个月我才意识到这个理解错得有多离谱。Agent Skills 从设计思想到落地形态和简单挂几个 API 工具完全不是一回事。你如果只是把一堆函数丢给模型得到的往往是一堆不可控的调用、错乱的参数和说不清原因的失败。这篇文章我会从原理讲到实战再用一个完整的例子把整个链路串起来。内容包括技能体怎么设计、描述怎么写、参数怎么约束、多技能怎么编排、上下文怎么管理以及我实际踩过的坑。适合所有准备把大模型从“聊天玩具”推向“能干活的生产工具”的人无论你是做助手类应用、内部自动化流程还是在折腾某个跨平台系统这篇文章都能给你一条可以直接照着做的路径。1. Agent Skills 到底是什么为什么值得专门写一篇1.1 从“只能聊天”到“能够干活”的质变大模型的文本生成能力很强但在真实业务流程里它天然有三个短板拿不到实时数据、执行不了外部动作、没法可靠地完成多步操作。你问它“系统现在负载怎么样”它能根据常识编一个看起来很合理的答案但那不是真实状态。你让它“帮我把报表发给相关同事”它既打不开邮件客户端也不知道“相关同事”到底是谁。Agent Skills 要解决的就是这个问题。它把“模型之外的能力”以标准化、可复用、可被模型自主调度的小单元组织起来模型在推理过程中发现需要某种能力时调用对应的技能来完成。形象一点说普通对话模型像个只会上理论课的实习生Agent Skills 就像给实习生配齐了计算器、操作手册、通讯录和一套标准作业流程他需要什么就自己拿什么用。但这里有个关键区别虽然 Agent Skills 本质上是通过工具调用的能力实现的但它更强调“技能包”这个整体概念。一个成熟技能体不只是“一个函数一段描述”它包含使用说明、触发条件、参数约束、执行逻辑、返回格式规范。一个数量庞大的“裸工具库”变成一组分工明确、边界清晰、描述精准的“技能库”模型的调用准确率会明显提升。1.2 核心定义与三个特征我在实际工作中给 Agent Skills 下了一个比较实用的定义它是一段以“可复用、可组合、可被模型自主调度”为目标的封装能力单元在物理形态上通常是一个“元数据描述 具体执行逻辑”的最小集合。这个定义里有三个值得展开的特征。第一可复用。同一个技能可以在不同场景、不同项目、不同模型版本之间重复使用。比如一个“查询订单状态”的技能会员知识问答可以用客服工单系统可以用数据报表分析也可以用。技能体写好后维护成本远低于把同一套逻辑复制粘贴到各个流程里。第二可组合。复杂任务不是靠一个巨型技能完成的而是由多个简单技能拼接完成。比如“周末促销报告”这个复合任务可以拆成“查销售数据”“查库存数据”“生成图表”“汇总成文档”四个技能由模型判断顺序并调用。可组合的设计让技能体系具备很强的扩展性新增一种能力的时候不需要动旧技能。第三可被模型自主调度。这是 Agent Skills 最核心的一点。技能的触发不靠人工指令也不靠一堆 if-else 硬编码而是靠模型在推理过程中自主选择。这就要求技能的“对外表达”必须足够清晰——模型读得懂、选得准、调得对后面整个链路才会顺畅。1.3 用 Agent Skills 能解决哪些现实问题适合谁我先说说自己观察到的最典型的几类问题没有 Agent Skills 之前处理起来很头疼模型对实时数据产生幻觉编造不存在的数值多步骤任务断链做完第一步不知道第二步干什么工具调用逻辑硬编码在业务代码里新增一个工具要改一堆控制流同一套能力在不同项目里反复重复实现接口风格还各不相同。Agent Skills 把这些能力从业务逻辑里剥离出来变成独立可维护的单元模型只负责“决定用什么”技能体只负责“把这件事做对”。这种解耦在业务规则多变、需求迭代快的场景下收益非常明显。这套方案适合的人群也很明确正在做智能助手、内部自动化工具、客服机器人、数据分析助手的人被“工具调用失控”折磨过的开发者还有想把模型能力开放给非技术同学配置的团队。这里面的核心思想甚至不一定只在智能体项目里用任何“模型需要自主操作多个外部系统”的设计都可以参考。2. Agent Skills 的原理一条从意图到动作的完整链路2.1 整体架构与调用链路Agent Skills 的整体架构我习惯用“调度器 技能注册表 执行器”三层来看。调度器也就是 Agent 的核心通常由一个带着指令上下文的模型实例构成。它负责理解用户问题、拆解任务步骤、决定何时调用什么技能。技能注册表是一个全局的技能目录里面登记了所有当前环境可用的技能以及每个技能的元信息。执行器是真正跑技能逻辑的部分它接收模型生成的调用请求解析参数、执行对应操作、把结果返回给模型。一次完整的调用链路可以简化为五个环节模型意图识别 - 技能路由选择 - 参数生成 - 执行器执行 - 结果回填。我分开说。模型意图识别阶段模型从用户的自然语言中判断出“这里需要调用某种外部能力”。比如用户问“把上个月北京区的销售数据整理成表格发到群里”模型识别出至少三个动作查数据、整理表格、发消息。技能路由选择阶段模型在技能注册表的所有技能里选最合适的那个。这一步可以靠模型直接推理也可以靠独立的语义检索做初筛后再确认。参数生成阶段模型根据当前上下文填充技能所需的参数。比如“查销售数据”这个技能需要“区域”“时间范围”“数据粒度”三个参数模型从对话中提取出“北京区”“上个月”“按日汇总”。执行器执行阶段技能的真实代码开始跑。注意这个阶段模型本身不再参与所有处理由外部系统完成保证结果是真实可信的。结果回填阶段执行结果被重新交给模型模型把结果融合进自己的回答。如果结果不是模型想要的格式它可能还会继续调用其他技能直到整个任务被串起来。这五个环节里哪一个不顺畅最后的效果都会打折扣。我见过很多项目在第二阶段就翻车——技能太多描述写得又含糊模型不知道该选哪一个。2.2 技能选择机制模型是怎么决定“该用哪个”的技能选择机制是 Agent Skills 原理中最复杂也最有趣的部分。从实现方式上看大致有三条路线。显式路由就是把意图分类器放在模型前面先判断用户问题属于哪一类意图再映射到固定技能。这类方案的优点是稳定可控缺点是新意图要单独维护分类规则而且分类器理解不了太复杂的语义。语义检索路由预先为每个技能生成向量特征接用户的模糊问题先做相似度检索选出最相关的三到五个技能候选再交给模型做最后判断。优点是对新技能友好、召回能力强缺点是纯语义匹配偶尔理解不了“用户其实想调多个技能”的情况。推理式选择不给技能做预路由直接让模型看全部技能描述自主决定。这是最灵活的方式但也最依赖模型能力和技能描述质量。技能一多光把描述塞进上下文就会占掉大量空间。实际项目里我常用的是“粗筛 精排”的组合先用语义检索把几百个技能压到五到八个候选再让模型在这几个候选里做最终判断。粗筛负责控制上下文长度模型负责理解复杂意图两边各司其职。这个思路很好的平衡了效果、成本和控制力。2.3 为什么“描述写得好”比“代码写得好”更重要这条原则是我在整个 Agent Skills 实践中体会最深的。技能的代码逻辑写得再漂亮如果模型看不懂“什么时候该用它”这个技能就等于不存在。技能描述本质上是在教模型做决策。好的描述要回答四个问题这个技能做什么、适合什么场景、不适合什么场景、调用时要注意什么。我见过无数技能描述只有一句话比如“获取订单信息”模型在遇到“我昨天买的耳机发货了吗”这种问题时根本不知道该不该用它。更麻烦的是模糊描述带来的幻觉触发。模型会依据想象力把不相关的技能硬套上去。比如一个“获取订单信息”的技能用户问“最近有什么优惠活动”模型也可能调用它然后拿着空结果编一段活动说明。解决方案就是在描述里同时写清楚正向触发条件和反例排除条件这个技能只用于查询已存在订单的物流与状态不用于查询促销活动、优惠券或库存信息。参数描述也一样重要。模型不知道“app_id”是什么意思但如果你写成“应用在开放平台注册后生成的唯一标识格式为 32 位十六进制字符串”它就能在上下文中找到并填对。大部分参数错误根源都不是模型笨而是我们没给它足够清晰的填空线索。3. 实战准备先把“技能”这个抽象概念落地成看得见的东西3.1 运行环境与依赖的通用设计开始写技能之前先把运行环境安排好。Agent Skills 需要一个能稳定加载模型的环境一个技能注册中心以及一个执行沙箱。我不推荐把技能逻辑塞在 Agent 主进程里尤其是那些要执行外部命令、访问数据库、发请求的技能隔离不好很容易把整个系统拖崩。在实际项目里我一般用三层隔离核心对话进程处理模型交互技能执行进程跑外部操作两者之间通过消息队列或进程间调用通讯。技能注册中心可以是一个简单的配置文件目录每个技能一个独立文件夹包含描述文件和代码文件。一个标准结构类似下面这样skills/ ├── query_order/ │ ├── skill.yaml │ └── handler.py ├── check_stock/ │ ├── skill.yaml │ └── handler.py └── send_message/ ├── skill.yaml └── handler.py这样做的最大好处是热更新。改一个技能的描述或实现不需要重启整个 Agent 进程注册中心检测到文件变化后重新加载就行。生产环境下迭代技能会非常频繁这个能力省了我很多事。3.2 技能清单的规划与分类组织技能越多规划就越重要。我见过有些团队一口气上线五十个技能结果调用准确率惨不忍睹。这里面的问题不是模型不行而是技能之间的边界没有划清楚。我自己的实践是把技能按领域和依赖关系分层管理。领域决定了技能的职责范围依赖关系决定了技能之间的上下位结构。比如一个“客户服务”领域底下要有“身份校验”“订单查询”“售后登记”“工单创建”这些基础技能还可以有“客诉全流程处理”这种复合技能。复合技能内部会调用基础技能但它对模型只暴露一个总入口降低调度难度。技能命名的规则也要统一。我建议用“动作 对象”的格式比如“查询订单”“创建工单”“发送消息”避免“处理数据”“执行任务”这种过于宽泛的名字。模型看到“查询订单”就能大概猜出用途看到“执行任务”往往一脸茫然。还有一种很有效的组织手法把经常一起使用的技能编组。比如“销售周报”组里包含“汇总订单”“计算增长率”“生成图表”三个技能模型在识别到“周报”这个业务概念时可以直接调用整组技能而不是每次从几十个技能里重新翻找。这在处理固定流程型任务时效率非常明显。3.3 描述、参数与输入输出的规范化技能描述和参数定义我强烈建议用结构化格式来写。不要用自由文本因为自由文本模型读起来信息密度低你写完自己过两天都看不明白。下面这个例子是我常用的描述框架。name: query_order description: 查询用户订单的物流与状态信息。 triggers: - 用户询问订单什么时候发货 - 用户查询订单物流轨迹 - 用户反馈下单后未收到货 not_triggers: - 与促销活动、优惠券、库存相关的问题 - 创建新订单或退款 parameters: order_id: type: string description: 订单号通常以字母 OR 开头后接 10 位数字 required: true user_id: type: string description: 用户唯一标识 required: false return_format: type: object properties: status: type: string enum: [created, paid, shipping, completed, cancelled] logistics: type: array items: type: object properties: time: { type: string } content: { type: string }这套描述里triggers 和 not_triggers 是最容易被忽略的部分但恰恰是它们决定了模型在模糊场景下的行为。参数部分每个字段都要有清晰解释尤其是类型、格式、是否必填。return_format 这个设计很有用模型在调用前就大概知道会拿到什么结果后续生成回答的稳定性能提高一大截。认知负荷在这里有个重要原则参数越少越好可填可不填的一律不设。模型不是万能的它同时要处理用户语言、技能选择和参数填充参数一多出错率会指数级上升。能用三个参数解决的问题绝不设计五个参数的版本。4. 从零实现写第一个可复用的 Agent Skill4.1 第一步定义技能接口与描述文件理论讲再多不如动手写一个。我以一个常见的场景为例做一个“查询系统运行状态并生成摘要”的技能。这个技能在运维类助手、内部监控机器人、值班系统中非常实用。首先建立技能目录并编写描述文件。name: get_system_overview description: 获取当前系统运行状态包括 CPU 使用率、内存占用、磁盘水位、最近发生的异常日志并输出一行摘要。 triggers: - 用户问系统是不是卡了 - 用户要求查看服务器资源占用 - 用户汇报服务异常需要快速查看整体状态 not_triggers: - 查询具体某条业务数据 - 执行重启、扩容等变更操作 parameters: host: type: string description: 目标主机名或 IP 地址默认当前主机 required: false recent_minutes: type: integer description: 需要统计的最近时间窗口单位分钟默认 30 required: false return_format: type: object properties: summary: { type: string } cpu_usage: { type: number } mem_usage: { type: number } disk_usage: { type: number } anomaly_count: { type: integer } top_anomalies: { type: array }注意 host 参数设计成可选默认当前主机。这样模型在大多数情况下不需要生成这个参数减少一次出错的可能。recent_minutes 也一样给了默认值模型没把握时可以直接不传。4.2 第二步实现执行逻辑并接上外部数据描述文件只是“说明书”真正干活的是执行逻辑。这个技能的实现核心是调用系统命令或读取监控接口然后整合成结构化结果。下面是一个简化的实现示例。import json import subprocess import time def get_system_overview(hostNone, recent_minutes30): # 获取 CPU 使用率示例Linux 环境下读取 /proc/stat cpu_usage read_cpu_usage() # 获取内存信息 mem_usage read_memory_usage() # 获取磁盘水位 disk_usage read_disk_usage() # 获取最近指定时间窗口内的异常日志 anomalies query_anomalies(minutesrecent_minutes) # 生成一段简洁摘要方便模型直接引用 if anomalies: summary f系统运行状态整体一般CPU {cpu_usage:.0f}%内存 {mem_usage:.0f}%磁盘 {disk_usage:.0f}%最近 {recent_minutes} 分钟内有 {len(anomalies)} 条异常日志 else: summary f系统运行状态良好CPU {cpu_usage:.0f}%内存 {mem_usage:.0f}%磁盘 {disk_usage:.0f}%最近 {recent_minutes} 分钟内无异常 return { summary: summary, cpu_usage: cpu_usage, mem_usage: mem_usage, disk_usage: disk_usage, anomaly_count: len(anomalies), top_anomalies: anomalies[:5], }这里最需要注意的一点是返回结果里有了一个 summary 字段。summary 是一段已经格式化好的自然语言摘要模型拿到之后可以直接使用不需要再去做额外计算。为什么要这样做因为在长对话里模型对数字的“保持能力”并不稳定如果我们已经替它把结论算好能避免很多后续幻觉。4.3 第三步注册技能并验证调用链路写完执行逻辑之后把技能注册到 Agent 的技能中心。这一步我通常会写一个注册函数把描述文件里的内容解析成运行时对象然后加载到技能注册表。def load_skill_from_yaml(path): with open(path, r, encodingutf-8) as f: meta yaml.safe_load(f) handler import_handler(meta[name]) return register_skill(meta, handler)验证调用链路时我习惯按照三个层级来做直接调用函数验证执行逻辑正确在无模型环境下用预设参数触发技能模拟模型调用最后在真实对话环境里发测试问题看模型能否自主选中这个技能并正确传参。第三层验证最容易出问题。比如我问“最近系统卡不卡”模型可能直接根据上下文编造答案而没有触发技能。这时我会先检查技能描述是否被模型“看见”再看触发条件里有没有覆盖“系统卡不卡”这种口语化表达。如果都覆盖了还是不行很可能是技能注册顺序有问题模型识别到了错误的技能。4.4 落地验证清单照着逐项过一遍我在上线任何一个新技能前会强制走一遍这份验证清单项目里叫它“试跑四问”模型知道这个技能存在吗有没有办法看到它的完整描述模型知道什么时候调用它吗正向触发和反例排除是否清晰模型能正确填好参数吗每个参数有没有类型、格式和默认值兜底模型对返回结果能正确使用吗返回格式是否结构化且自带说明任何一问答“不确定”我都不会直接上生产。这个清单看起来简单但它确实拦住了我大量隐性 bug。有一次一个技能加载失败模型侧完全不知道但 Agent 还在运行每次用到那个技能时就兜回通用回答用户体感就是“这个助手怎么突然变笨了”。试跑四问里第一问就拦住这种问题。5. 进阶玩法多技能编排、状态记忆与输出治理5.1 多技能串行与并行编排单技能能力终究有限真实任务往往要多个技能配合。多技能编排里我分两种串行链和并行链。串行链是 A 技能的结果作为 B 技能的输入比如“查订单信息 - 查该商品库存 - 生成发货时间预告”每一步都依赖前一步输出。这种链路最大的风险是错一处全链崩。所以每个环节都要做结果校验比如订单查询结果为空就不要强行执行后续技能直接返回“没有查询到订单信息”。并行链是没有依赖关系的技能同时执行比如“查销售额 查退款率 查客户满意度”三个技能同时跑最后把结果合成一份报告。并行执行能节省大量时间但要注意上下文管理三个技能返回的结果同时塞给模型很容易超出上下文窗口。实际项目里我更推荐“半并行”模式先并行执行互不依赖的查询拿到结果后再让模型统一做下一步决策。这个模式比完全串行快又比一次性全部并行好控制上下文。5.2 上下文窗口管理与中间结果压缩Agent Skills 的一大隐藏问题就是每调一次技能返回结果都会占掉上下文空间。如果你做一个“历史对话 多技能调用”的助手几十轮下来上下文就满了模型开始丢三落四。我的应对策略是给每个技能设置返回结果的上限并在技能内部做摘要。比如“查订单”技能有 200 条记录时全部返回会让模型崩溃我会提前把结果整理成“订单号 状态 关键时间”的极简结构最多返回 20 条如果业务方确实需要完整清单就由另一个技能负责导出文件。让模型“拿摘要而不是拿数据”是上下文管理最重要的一条原则。另一个技巧是把技能调用的中间结果缓存在会话外部上下文里只保留一个重要总结。比如统计报表任务把“已获取销售数据共 12000 条”这样的轻量标记留在上下文里具体数据放到外部存储。模型后续处理时如果需要细节再按需调用补充技能。5.3 技能输出格式的强约束方法模型天然偏好生成自然语言但对下游系统来说结构化数据更好用。当技能结果是给另一个程序使用时我会对输出做格式强约束。一种可靠做法是在技能描述里明确指定返回格式的 JSON 结构并给每个字段写明用途。还有一种更强硬的办法就是执行器对返回结果做运行时校验不符合规范就强制转成统一错误结构。比如 get_system_overview 的结果必须包含 summary、cpu_usage、mem_usage 等字段缺失就返回{ error: 技能执行异常或返回格式不匹配, fallback_summary: 系统状态获取失败请稍后重试 }模型拿到 error 之后会如实告诉用户而不是硬着头皮编造数据。这个兜底机制看起来很简单但能防止大量幻觉产生。5.4 技能版本管理与灰度上线技能也是要演进的。我维护的 Agent 技能库里同一套业务逻辑经常会有 v1、v2 两个版本并存。v1 可能还在给旧会话使用v2 已经在给新会话做灰度验证。版本管理我分三层功能版本技能的业务逻辑发生变化时升级接口版本技能的参数和返回结构发生不兼容变化时升级同时保留旧接口一段时间描述版本只改描述文本、不改代码逻辑时升级这种升级最安全通常可以随时生效。灰度上线我用的方法是“流量分层”内部群先接入新版本跑三天看调用率和准确率没问题再切到种子用户群最后才全量。有一个项目因为着急全量上线新版技能结果新技能触发率比旧版低了三十个百分点整个助手在一个下午内退化成了问答机器。从那以后我再也不敢跳过灰度这一环。6. 常见问题与排查实录6.1 模型死活不调用技能这个问题的排查顺序很重要。先确认技能描述是否真的进了模型上下文不是代码里注册了就等于模型能看到。有些框架在技能数量多时会截断描述通过日志检查这一点最直接。如果描述在就看触发条件是否覆盖了用户的实际表述方式。用户不会说“请调用查询订单技能”他们会说“我东西怎么还没到”。描述里写“查询订单”可能不够要写“用户询问货物到达时间”这类生活化表达。再看反例是否误伤。我遇到过一个项目技能描述里写了“不处理订单取消咨询”结果用户问“我取消订单后钱什么时候退”模型就判定这不是该技能的范围转去调了一个完全不相关的功能。反例的边界一定不能写太宽。最后很关键的一点是技能数量。如果候选技能超过三十个哪怕描述写得再好模型的分类准确率也会下降。这时候别死磕描述先做路由粗筛把候选压下来。6.2 技能被调用了但参数填得乱七八糟参数问题的根源大部分在描述里没有给出格式样例。模型生成“订单号”时如果不知道订单号长什么样它就只能靠猜。我给每个参数加格式样例之后参数错误率肉眼可见地下降了一大截。另一个常见问题是没有兜底默认值。一个非必填参数如果不设默认值模型为了“填完整”就会从对话里猜一个。这种猜测在成本低时无所谓但如果是写数据库、发消息这类动作错误参数代价很大。设计原则是动作型技能参数必须全部校验查询型技能尽量给默认值。还有个野路子排查方法把模型生成的参数完整打印出来结合用户原话对齐能很快发现模型是在“摘取用户原话”还是在“凭空编造”。如果大量参数都是编出来的说明描述里的提示越狱式字段不够要加强字段释义和示例。6.3 多个技能之间混淆冲突技能一多最典型的问题就是这个技能像那个技能。我在处理“查询库存”和“查询订单”时两个描述里都出现了“商品”这个词导致模型经常选错。解决方案分两步。第一步是给每个技能定义反例边界明确说“本技能不处理什么”。第二步是压缩技能名和描述里的同质化词汇让差异点更突出。比如“查询库存”突出“剩余数量”“查询订单”突出“购买记录与物流”模型在语义空间里能找到明显的分割线。还有一种情况是技能职责分得不细一个技能覆盖了多种不同的业务动作。这种建议直接拆成多个小技能。有人担心技能多了会乱但事实是边界清晰的前提下小技能比大技能调用准确率高得多。6.4 响应变慢与成本失控技能调用变慢最常见的原因是技能本身逻辑太重比如查询接口超时、循环太多。另一个隐形原因是模型上下文被撑大每次请求的处理时间都会上升。我优化过的一个项目查询技能返回了 200 条完整记录模型每次生成回答都要处理几千字冗余内容响应时间直接翻倍。把返回结果截断成摘要后速度和成本都恢复到了正常水平。成本失控的另一个来源是模型多次重试调用同一个失败技能。技能执行器对错误应有熔断机制连续失败三次就停止重试返回固定错误信息。这个机制既能避免成本浪费又防止用户看到连续几次“系统错误”的糟糕体验。以下是一个问题速查表我在工作中直接贴在项目文档里现象可能原因优先排查方向模型不调用技能描述没进上下文 / 触发条件不匹配查看日志确认描述是否注入技能调用后结果乱返回格式不结构化 / 摘要缺失检查 return_format 与执行器结果参数频繁错误字段描述缺类型缺示例补充格式样例与默认值混淆两个技能描述语义空间相近 / 缺反例重写描述突出边界响应越来越慢返回结果撑大上下文截断返回给轻量摘要成本突增失败重试无熔断增加熔断与重试上限写在最后的一点个人心得Agent Skills 这套东西真正难的不是写执行代码也不是配置环境链路而是持续打磨“模型能不能看懂我的技能”。很多项目前期进展很顺一到技能规模变大、场景变复杂之后就开始失控。我自己从这些项目里学到的最重要一条经验是每个技能上线前都该问一句——“如果我是模型看到这段描述我能在 2 秒内决定要不要调用它吗”如果答案是否定的就该优化描述而不是改代码。再分享一个小技巧在项目启动阶段不要追求一次性建立大量技能先把 5 ~ 10 个最核心的技能做扎实。运行两到三周后通过日志统计每个技能的调用频率、成功率、异常特征再根据真实数据把高频场景拆出来做新技能。这个循环跑起来比闭门造车高效得多。后续你还可以尝试把一个比较复杂的“复合技能”拆成可组合的基础技能再让模型自己决定编排顺序这套向上的路径能玩很久。

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

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

免费获取报价 →
↑