Agent Skills:从工具调用到可复用技能系统,打造生产级智能体 📅 发布时间:2026/9/16 20:01:49 👁 浏览次数: 这两年做 AI 应用我越来越确信一件事同一个底座模型在不同团队手里效果差距可以拉到天壤之别。有人用一线大模型做出来的东西依然是“一问一答”的玩具有人却用同样的模型拼出了能自动跑完整条业务线的智能体。差别往往不在模型本身而在你怎么把模型能调用的能力组织成一个体系。这个体系在最近一年的 AI 工程化实践里慢慢收敛成了一个名字Agent Skills。Agent Skills 简单说就是把大模型完成特定任务时需要的外部能力——查数据库、调接口、操作文件、执行代码、走审批流——统一封装成标准化、可复用、可被模型理解和调度的“技能单元”。它解决的不是“模型能不能生成一段话”而是“模型怎么稳定地调用真实世界的工具并且把任务闭环跑完”。这篇文章我想从概念、设计、落地到排障完整讲一遍我自己在做 Agent 工程时的思路和踩坑记录适合正在做智能客服、自动化流程、Copilot 类产品或者想从“写 Prompt 调 API”升级到“做一整套 Agent 工程”的开发者参考。1. Agent Skills 的本质不是工具调用的换皮1.1 技能与工具调用到底差在哪很多人第一次接触 Agent Skills 时会问这不就是 function calling 吗把函数定义塞给模型模型按 JSON 格式传参然后执行函数返回结果。流程上确实很像但技能和工具调用在工程定位上完全不是一回事。工具调用Tool Calling本质是无状态的函数暴露你给模型一个函数名、一段参数描述模型生成参数你执行你把结果塞回上下文。它解决的是“让模型能按格式调函数”的问题。而 Agent Skills 解决的是“让模型能在复杂任务中正确选择和编排能力”的问题。技能单元里除了输入输出描述还应该包含什么时候该用这个技能、什么时候不该用的判定规则输入参数的完整约束、校验逻辑和默认行为技能执行失败后的错误码、重试策略和降级路径技能内部是否有依赖其他技能或外部服务的声明技能执行结果如何格式化、摘要化再回填给模型。简单类比工具调用是给你一把扳手技能是给你一整套“拧这颗螺丝”的操作规程包括用哪把扳手、用多大力、拧不动的备选方案。模型光有扳手不够它需要知道在什么场景下选什么工具、怎么判断是否成功、失败了下一步怎么办。1.2 技能的分层原子、组合、工作流我在实际项目里会习惯把技能分成三层因为不同层级的技能在维护方式、调试难度和复用方式上差异很大。第一层是原子技能Atomic Skill指单一、无状态、不可再拆的能力单元。典型例子是“查询实时天气”“读取文件内容”“调用某个内部 API”。原子技能的输入输出都尽量简单执行时间短失败原因明确。这一层是技能的基石数量会随着业务扩展膨胀得很快所以要格外重视命名规范和参数约束。第二层是组合技能Composite Skill指串联多个原子技能才能完成的业务流程。典型例子是“根据用户收货地址推荐附近门店”内部需要先调地址解析服务再调门店库存接口最后根据距离排序。组合技能的价值在于把多步编排“封装成一次调用”让模型不需要理解底层细节只要表达意图即可。这样能显著降低模型在长链路中的出错概率。第三层是工作流技能Workflow Skill指跨系统、跨角色的长流程任务通常会包含人工审批、异步等待、状态持久化。典型例子是“发起一笔报销流程”“创建一条发布工单”。工作流技能一般需要额外的状态存储和任务调度支持不能像前两层那样同步返回往往要走异步回调或轮询。这一层最复杂也最容易出问题但如果做好了恰恰是 Agent 从“聊天助手”升级成“数字员工”的关键。1.3 为什么“技能化”是 Agent 落地的分水岭我自己带团队做 Agent 项目时有个很明显的感受没有技能化抽象之前每个 Agent 都是“Prompt 一堆工具函数”的快餐代码复制粘贴严重改动一个逻辑要全局搜索调用点。一旦技能化之后能力边界、版本、测试、权限都变成了可管理对象。技能化带来的第一个收益是可测试性。每个技能都能独立做单元测试模型调度是另一套测试两层解耦后定位问题快得多。第二个收益是可观测性。技能层可以统一埋点谁在什么上下文下调用了什么技能、传了什么参数、返回了什么结果、耗时多少全部有日志可查。第三个收益是可复用性。一个技能可以在多个 Agent 里共用比如“查询订单状态”这个技能客服 Agent 能用售后 Agent 能用运营数据分析 Agent 也能用。所以我的结论很明确如果你只是做个 Demo直接 function calling 就够了但如果你想把 Agent 推到生产环境技能化是绕不开的一步。2. 设计一套技能系统选型和规范比写代码更重要2.1 先分清两种范式声明式技能与代码式技能在做技能系统前你先要决定采用哪种范式。我见过团队在这上面反复横跳所以想单独拿出来说说。声明式技能Declarative Skill的核心思路是你只定义输入输出的 Schema、描述文本和校验规则模型负责根据意图生成调用参数系统负责执行并返回结果。它的优点是实现成本低、模型可控性强、逻辑透明适合参数简单、结果能够被文本化的场景比如查数据、算指标、发通知。代码式技能Code-based Skill的核心思路是技能本身就是一段可执行代码或插件模型只负责表达“我想做什么”执行器在内部承载完整逻辑甚至内部可以继续调用其他技能或模型。它的优点是能处理复杂、非结构化的操作比如操作浏览器、处理 Excel、写代码并调试。缺点是可解释性偏弱调试困难对执行环境的安全要求更高。对比一下两种范式的适用情况维度声明式技能代码式技能实现成本低写 Schema 和短逻辑即可高需要维护独立执行环境模型参与度高模型主导参数生成低模型只传意图适用场景参数清晰、结果可结构化的任务操作复杂、环境依赖多的任务调试难度较低链路短较高链路长且状态多典型代表查天气、查订单、发消息写文件、操作浏览器、跑脚本实际项目中两者通常混用。我推荐的做法是对外统一暴露“技能”概念内部用不同执行器适配两种范式底层框架不感知差异。这样模型侧规范统一工程侧又能按需选择。2.2 技能描述怎么写模型才看得懂技能描述是技能系统的灵魂。我见过很多团队花大量时间写代码技能描述却只写一句“查询订单”结果模型在真实对话里根本不知道该在什么时候用它或者在可用技能变多后频繁选错。技能描述至少要回答四个问题这个技能解决什么问题用一两句话说明业务目标不要写技术术语要写模型能理解的用户意图什么时候必须用列出触发条件例如“当用户提到订单号或查询物流信息时”什么时候不要用明确负面条件例如“用户只是询问退货政策时不要调用订单查询”调用时需要注意什么例如“如果订单号缺失必须向用户确认后再调用”。我习惯在技能描述里再加一段 few-shot 示例给出用户话术到技能参数的映射样例。实测下来增加两到三个示例可以明显提升模型在真实场景下的参数抽取准确率。模型本质上是在做“文本匹配”任务你给它看的示例越贴近真实分布它的表现就越稳。2.3 技能 Schema 的工程规范参数、输出与依赖技能 Schema 是技能的“API 契约”既要给模型看也要给执行器用。我建议用 JSON Schema 作为统一格式因为模型对它的理解已经非常成熟工程侧解析和校验的生态也很完整。一个合格技能 Schema 至少包含名字、描述、输入参数定义、输出结构定义、错误码定义、依赖声明。参数定义里不建议只写类型还要写清枚举值、默认值、格式约束和参数之间的联动关系。比如“寄件地址”和“收件地址”在某些业务里可以缺省但“寄件时间”一旦填了就必须是未来时间这些规则最好都写进 Schema做二次校验时能过滤掉大量模型幻觉参数。我整理了一个简单的“查询订单”技能 Schema 片段只为了展示结构实际项目中可以按需增删字段{ skill_name: query_order, description: 查询用户订单的当前状态包括商品名称、物流轨迹、预计送达时间。当用户询问订单状态、物流进展时使用。, input_schema: { type: object, properties: { order_id: { type: string, description: 订单号通常是数字和字母组合, minLength: 6, maxLength: 32 }, include_logistics: { type: boolean, description: 是否返回物流轨迹明细默认 false, default: false } }, required: [order_id] }, output_schema: { type: object, properties: { status: { type: string, enum: [pending, shipped, delivered, cancelled] }, items: { type: array, items: { type: string } }, logistics_trace: { type: array, items: { type: string } } } }, error_codes: [ORDER_NOT_FOUND, ORDER_ID_MISSING, SERVICE_UNAVAILABLE], dependencies: [order_service] }不要小看这个 Schema它同时承担了模型的调用约束、执行器的入参校验、调用方对返回结果的解析依据三重职责。Schema 不严谨后续所有环节都会埋雷。3. 实操落地从零封装一个可复用的技能模块3.1 最小化技能注册与加载框架理论讲完直接进入代码。我尽量保持代码精简只突出核心结构方便你迁移到自己的项目里。技能模块的核心抽象我习惯定义为一个基类每个具体技能继承并实现注册信息、执行逻辑、校验逻辑。Python 伪代码如下from abc import ABC, abstractmethod from typing import Any, Dict, Optional import json class BaseSkill(ABC): # 技能唯一标识 name: str # 技能描述给模型选技能用的 description: str # 输入参数 JSON Schema input_schema: Dict[str, Any] {} # 输出 JSON Schema output_schema: Dict[str, Any] {} abstractmethod def execute(self, params: Dict[str, Any], context: Optional[Dict[str, Any]] None) - Dict[str, Any]: 执行技能核心逻辑返回统一格式的结果字典 pass def validate_input(self, params: Dict[str, Any]) - Dict[str, Any]: 参数校验基类提供默认实现子类可覆盖补充 # 实际项目中这里可以接入 jsonschema 库做严格校验 return params def to_skill_spec(self) - Dict[str, Any]: 生成给模型看的技能规范注册到模型配置里 return { name: self.name, description: self.description, input_schema: self.input_schema, output_schema: self.output_schema, } class SkillRegistry: def __init__(self): self._skills: Dict[str, BaseSkill] {} def register(self, skill: BaseSkill): if skill.name in self._skills: raise ValueError(fSkill {skill.name} already registered) self._skills[skill.name] skill def get(self, name: str) - Optional[BaseSkill]: return self._skills.get(name) def all_specs(self) - list[Dict[str, Any]]: return [skill.to_skill_spec() for skill in self._skills.values()] registry SkillRegistry()这个框架看着简单但它是整个技能系统的地基。注册表统一维护所有技能模型侧配置一次全量技能描述执行侧通过技能名拿到具体执行器。这样的好处是新增技能时改动只落在独立的技能文件里不会污染业务代码。3.2 请求进入 Agent 后的调度链路框架有了接下来是调度链路。我一般把链路拆成五步意图接收、技能匹配、参数抽取与校验、技能执行、结果回填。意图接收阶段模型先理解用户的话术判断这是一个普通对话还是需要调用技能。技能匹配阶段模型从注册表里的技能列表中选择最合适的候选这一步本质是“技能检索 语义匹配”当技能数量超过二十个时我会额外引入一层关键词标签或者向量检索做预筛把候选集缩小到三到五个再交给模型精排。参数抽取与校验阶段模型生成 JSON 参数后执行器先做格式校验和业务规则校验校验不通过直接返回错误码不要带着错误参数闯进执行逻辑。技能执行阶段走具体执行器内部可能调用内部 API、查询数据库或者操作文件。结果回填阶段把执行结果摘要化后拼装成模型可读的文本再交给模型生成最终回复。简化后的调度代码如下def handle_agent_request(user_message: str, registry: SkillRegistry, llm_interface): # step 1: 模型判断是否需要技能并抽取参数 plan llm_interface.run_with_tools( user_messageuser_message, toolsregistry.all_specs() ) if not plan.get(need_skill): return llm_interface.chat(user_message) skill_name plan[skill_name] params plan[params] skill registry.get(skill_name) if skill is None: return 抱歉我暂时无法处理这个操作。 try: # step 2: 参数校验 validated_params skill.validate_input(params) # step 3: 执行技能 result skill.execute(validated_params, context{user_message: user_message}) # step 4: 结果摘要回填 summary summarize_result(result) return llm_interface.chat(f技能执行结果摘要{summary}请根据摘要继续回答用户。) except SkillExecutionError as e: # step 5: 失败处理 return handle_skill_error(e, llm_interface)这只是一个非常简化的骨架但链路里最关键的一点是技能执行的结果不要原封不动地塞回上下文而是先做摘要化处理。理由很简单如果技能返回的是几十行 JSON模型在生成回复时会被大量无关字段干扰既增加 Token 消耗也容易答非所问。摘要化既能省钱又能提升回复质量。3.3 上下文管理与技能记忆别让模型“上一秒学会下一秒就忘”技能系统的上下文管理是生产环境中最容易翻车的地方。很多团队的 Agent 一跑长对话就“失忆”不是模型不行而是技能调用结果的存储和引用方式不对。技能调用结果不应该只保存在对话历史里还应该以结构化方式存入短期记忆。我具体做法是每次技能执行成功后把结果按技能名和参数做 Key-Value 缓存同时把结果摘要写进对话上下文。当模型后续需要引用前面某次查询结果时它可以直接从上下文摘要中拿而不用重新触发技能。这样既降低 Token 消耗也减少重复调用外部服务的压力。缓存策略上要设 TTL比如订单状态查询五分钟内相同订单号直接命中缓存超过五分钟则重新查询。另外技能结果被引用后如果用户更新了条件旧缓存要及时失效。基础规则是“技能传入参不变且 TTL 未过期才允许命中缓存”。上下文里的技能调用历史也要做截断。对话超过一定轮数后早期技能调用的详细参数可以丢弃只保留“用户曾经查过订单 A”这种级别的摘要。模型不需要记得完整的订单 JSON它只需要知道事实和上下文细节需要时再调技能。3.4 把技能做成插件层版本化、灰度与权限技能系统跑稳之后下一个问题就是协作和发布。如果一个团队有多个 Agent 共用一个技能注册表技能变更会直接影响所有调用方这时就必须做版本化和隔离。我的做法是技能注册表里每个技能都带版本号Agent 配置里声明自己依赖的技能版本范围。发布新技能版本时先在一个低流量 Agent 上灰度观察错误率和请求耗时稳定后再全量放开。版本回滚也要能做到秒级最简单的方式是注册表里保留最近三个版本切换只是改一个配置项。权限隔离同样重要。不是所有技能都能给所有 Agent 调用比如“删除用户数据”这种高风险技能只允许内部管理 Agent 调用而且每次调用都要二次确认。我通常会在技能 Schema 里增加allowed_roles或者permission_level字段执行器调用外部服务前先做身份鉴权防止模型被恶意提示词诱导去执行越权操作。插件层的设计原则很简单技能注册表只做登记和调度不写业务逻辑业务逻辑全部收口在技能执行器内部对外暴露的只有标准化的 Schema 和错误码。这样即使团队从三个人扩到三十个人也不会因为协作问题把技能系统搞乱。4. 技能系统运行后我遇到的四个典型问题4.1 模型参数抽取不稳定问题多半出在 Schema 设计技能上线初期我遇到最多的问题是模型调用技能时参数填得乱七八糟。比如用户说“帮我看看上海的天气”模型把“上海”塞到城市字段没问题但有时候会把“明天”解析成日期格式时写成2025-01-01有时候又写成明天两个汉字导致执行器解析失败。排查了一段时间后我发现问题根源不在模型而在 Schema 太粗。日期字段没有给出明确的格式约束和示例值模型只能凭感觉生成。后来我在参数描述里加了两条一是明确枚举和格式二是提供一个真实示例。比如日期字段描述改成“目标日期格式必须是 YYYY-MM-DD例如 2025-01-01当用户说‘明天’时由模型计算后填入具体日期”。改完之后参数抽取准确率明显提升。另一个坑是布尔字段。模型经常把“是”“需要”解析成true把“否”解析成false看起来没问题但当用户说“不确定”时模型会乱猜。我后来把所有可能缺失的布尔字段都加了default同时在校验规则里规定没有明确表达时一律走默认值避免模型替用户做决定。4.2 技能太多模型反而选不准技能注册表从五个涨到三十个之后我遇到了一个新的头疼问题模型开始频繁选错技能。用户明明问的是“退货运费谁承担”模型却调用了“查询订单”技能用户想“修改收货地址”模型却调用了“查询门店”技能。一开始我以为是模型能力不够后来统计了一遍技能描述才发现问题在于多个技能描述里都出现了“订单”“地址”“查询”这些高频词模型在语义匹配时被干扰了。解决思路有两个方向。第一个是技能描述做减法把描述里跟业务目标无关的修饰词全部删掉尽量用“当用户想 X 时使用”的句式减少歧义。第二个是引入候选集预筛在让模型做最终选择前先用关键词或者向量检索把全量技能缩小到五六个候选。预筛逻辑可以很简单给每个技能打两到三个业务标签用户消息先匹配标签命中标签的技能才进入模型选择范围。实测在技能数超过二十个之后加预筛能显著提升选择准确率。4.3 执行失败后Agent 要学会体面地降级技能执行不可能永远成功外部 API 不稳定、数据库超时、参数语义偏差都可能导致失败。最初我的 Agent 在技能失败时会直接把“系统异常”抛给用户这种体验基本没法看。后来我设计了一套三级降级策略。第一级是重试对网络超时、服务暂时不可用这类错误自动重试一次往往就能解决。第二级是换路径同一个目标如果有多条技能实现路径比如“查天气”既可以走实时接口也可以走缓存实时接口失败就自动切到缓存。第三级是主动澄清如果参数不完整或校验不通过Agent 不要硬试而是用自然语言向用户确认缺失信息。把“系统失败了”变成“我没有找到你的订单号可以再提供一下吗”体验差距非常明显。降级策略要做成技能执行器内部的标准流程而不是靠模型临场发挥。因为模型在失败场景下的表现非常不稳定与其赌模型的临场应变不如把降级规则写死在代码里。4.4 安全与权限不要在技能层面对大模型过度信任最后聊一个容易被忽视的问题安全。模型本质上是概率生成器它的输出不一定符合业务安全规则。技能系统必须在执行层做权限门禁不能把“模型已表达意图”默认成“用户已授权”。比如“删除订单”“修改价格”“发送营销短信”这类高风险操作我要求技能执行器拒绝在单轮对话内执行必须走带外确认流程例如通过验证码、二次弹窗或者审批流确认。模型在这种场景下只负责生成待确认的操作内容不负责直接执行。另一个安全细节是外部服务的请求参数校验。模型生成的 URL、SQL 语句、文件路径一律不能直接拼接执行必须经过白名单校验。URL 只允许访问内网域名白名单SQL 只允许预编译参数化查询文件路径只允许访问配置的基础目录。别觉得这是小题大做生产环境里模型被诱导读取敏感文件的案例已经不少了。问题现象可能原因解决动作参数格式不稳定Schema 缺少格式约束和示例在描述中增加格式说明和示例值技能选择准确率下降技能数量多且描述语义重叠增加标签预筛缩小候选集外部接口超时导致回复失败缺少重试和降级策略实现重试、缓存降级、主动澄清策略模型调用高风险操作缺少权限门禁Schema 增加权限字段执行器增加白名单校验5. 从技能到“会成长的 Agent”一点方向性思考文章写到这最后聊一点我对技能系统后续演进的看法。现在的技能体系大多是静态设计——技能列表由开发人员手工维护模型只能从已有技能里选。但这个模式在技能数量进一步膨胀后一定会遇到瓶颈未来的方向可能是让 Agent 能自己创建、沉淀新技能。比如客服 Agent 在处理了一百个“查发票”的请求后能不能自动从对话日志里提炼出一个“发票查询”技能的 Schema能不能把三个常被串联调用的技能自动合并成一个组合技能这需要技能系统有更好的埋点、更结构化的日志、以及一个“技能提炼”的后处理流程。短期内手工维护仍然是主流但底层的数据积累现在就要开始做。我个人在实际操作中的体会是技能系统的的核心不是代码写得多漂亮而是协议定得够不够干净。技能注册表、Schema、错误码、权限模型这些设计好了后面的扩展自然顺理成章。最后再分享一个小技巧给每个技能录一段“使用日志”不仅记录参数和返回结果还记录模型当时的决策上下文。这比任何评测集都更能帮你找到模型的盲区也能为以后技能自动沉淀打基础。