AI智能体技能系统设计实战:Agent Skills完整落地指南
如果你正在做AI智能体大概率已经绕不开 Function Calling、Tool Use 这些东西了。而 agent-skills 这个项目本质上就是把零散的 Tool 升级成一套带描述、带参数、带执行逻辑、带兜底策略的完整技能单元让智能体既能听懂指令又能真正把活干完。我最近完整推进了这个项目从设计到落地的全过程过程中踩了不少坑也沉淀出一套可以复用的方法论。这篇文章不聊大道理就讲我怎么理解 agent-skills、怎么设计技能模型、怎么实现调用链路以及那些文档里不会写的坑是怎么排掉的。项目做得不多但每个环节都是真金白银堆出来的经验希望能帮你少走几步弯路。1. 技能的本质把“会说话”变成“会干活”1.1 大模型最擅长的是生成不是执行先想清楚一个问题大模型凭什么能把活干完答案是它本身不会执行任何动作它只会根据输入的上下文生成一段“接下来应该调用哪个函数、参数是什么”的结构化文本。真正去查数据库、调接口、改配置的是外部系统。这里就藏着一个关键认知你喂给模型的不是“能力”而是“能力的说明书”。模型必须知道存在哪些技能、每个技能是干什么的、需要什么参数、返回什么格式它才能做出正确的选择。如果说明书写得含糊模型就只能靠猜。agent-skills 这个词拆开看就是两个层面的东西agent 层负责感知任务需求、规划执行顺序、调用合适的技能skills 层负责把具体动作封装成标准化的可执行单元。也就是说agent 是大脑skills 是手脚。大脑负责决策手脚负责落地。我在设计这个项目时第一件事就是划清这条界限——大脑不要碰执行细节手脚不要做决策。一旦出现越界系统就会出各种莫名其妙的 bug。1.2 把 Prompt 当成代码写会烂得特别快没做技能系统之前很多团队喜欢把所有业务逻辑写进 System Prompt。最初几个需求确实跑得动等需求一多问题就全出来了Prompt 越来越长三四千字都打不住模型上下文空间被严重占用新增一个功能就要改 Prompt线上微调成本极高并且容易引发“回归”同一个能力在多个场景需要复用时只能复制粘贴改一处漏一处模型输出的稳定性全看运气稍微换一下措辞就可能触发错误分支。我自己跑过一个测试把某个客服场景的 20 条业务规则一股脑写进 Prompt结果模型经常在几条纠缠规则之间反复横跳答错之后怎么调措辞都压不住。后来把这些规则拆成了 5 个独立技能每个技能只负责一小块逻辑准确率一下就上来了。这背后其实是个注意力问题。技能越多单次推理时模型需要关注的无关信息就越少决策自然越准。1.3 技能系统到底解决了什么归根结底agent-skills 想解决三件事能力的复用一个技能可以在多个 agent 场景里被反复调用不用每次重写。能力的可观测性技能有名字、有描述、有参数约束、有返回结构每一次调用都可以被记录和追踪。能力的可测试性单个技能可以独立压测、单测、回归而不是只能整体跑黑盒。所以你看技能系统不是花架子它是把“大模型应用”从“实验性脚本”推向“工程化产品”的一座桥。没有这座桥项目规模一大就撑不住了。2. 整体设计思路技能建模是地基2.1 先盘点现有能力再做抽象做技能系统的第一步不是写代码而是盘点业务里到底有多少“动作”。我习惯用一张简单的表来梳理能力域能力名称高频场景输入要素输出结果依赖系统订单域查订单状态用户问物流到哪订单号订单状态物流轨迹订单服务订单域修改订单备注用户要求改备注订单号新备注成功/失败原因订单服务营销域计算优惠用户想用优惠券用户ID商品列表优惠后金额促销服务用户域查会员等级用户问自己有啥权益用户ID等级权益列表用户中心这里的经验是不要一开始就抽象大而全的通用接口而是从真实高频场景反推。哪个动作被反复提起就优先把它技能化。当某个技能描述开始出现“如果……就……否则……”这种绕弯的句式说明它需要再拆一层。2.2 技能描述怎么写才不被模型误解我个人认为技能描述是整个技能系统里最容易被低估的部分。描述写得好不好直接决定模型能不能选对技能。重点有这几个说明“能做什么”也要说明“不做什么”。比如“查订单”技能如果不在描述里写明“仅用于查询状态不处理退款”用户问退款问题时模型很容易误调用。写清楚触发条件包括关键词、用户意图、前置条件。参数类型和取值范围要精确。为布尔参数写明布尔里到底表达什么为枚举参数写明取值范围。描述保持简洁不要放置执行逻辑的历史包袱。描述是给大模型当导航用的不是存放旧逻辑的垃圾堆。举个例子初始技能描述写的是查订单。查询订单信息比如订单状态、物流、商品列表。这样的描述过于宽泛模型可能拿它当万能接口。优化之后当用户需要了解订单当前状态、物流进度时调用查订单技能。仅支持订单状态查询若用户表达退款、改签等需求请改用其他技能。效果会好很多。这类优化在项目里反复出现过属于投入产出比最高的改善手段。2.3 技能粒度怎么定拆太碎是灾难拆太粗是摆设拆分粒度是另一个反复折磨人的点。我一开始把“查询订单”和“查询物流”拆成两个技能结果模型经常在两个技能之间犹豫不决。后来把订单查询和物流查询合并成一个技能反而更顺了。我总结出的规律是技能不应该对应“单一接口”而应该对应“完整用户意图”。比如“查看我上次买的手机现在走到哪了”底层可能要查订单、查物流、查商品名称但模型只应该看到一个“查订单全貌”的技能。粒度太细模型需要多步协调每一步都可能出错累积下来的错误率很吓人粒度太粗技能内部塞一堆互不相干的逻辑复用时反而受限制。我现在的判断标准是如果一个技能被调用的场景高度重复并且输入的参数基本一致说明粒度合适如果同一个技能要加三个以上的“能力标志位”去区分不同行为说明该拆了。3. 核心实现从注册到调用的完整链路3.1 Schema 先行把参数模型定清楚先看一个我实际用过的技能 Schema 例子。别急着抄重点看结构from enum import Enum from pydantic import BaseModel, Field class OrderStatus(str, Enum): PENDING pending PAID paid SHIPPED shipped COMPLETED completed REFUNDED refunded class QueryOrderSkillParams(BaseModel): order_id: str Field( description订单号格式为 8 位字母数字组合。 ) include_items: bool Field( defaultTrue, description是否返回商品明细默认返回。 ) class QueryOrderSkillResult(BaseModel): status: OrderStatus Field(description订单状态) express_trace: list[str] Field( default_factorylist, description物流轨迹按时间正序排列。 )这套 Schema 的意义在于强类型校验。模型生成参数时经常手滑给订单号传个 null 或者传个长度超标的字符串pydantic 一拦就能快速反馈不至于让一个错误的参数流入后端把订单服务打挂。字段描述即提示词。每个字段的 description 都会在运行时拼接进发给模型的工具说明里。字段注释写得准确模型生成参数的准确率会显著提高。可序列化。技能执行结果可以被转成结构化数据作为下一次模型推理的上下文。3.2 执行体编写把“生成”变成“执行”我设计执行体的核心原则是执行体只做确定性的事情不做任何让模型理解成本变高的事情。换句话说技能内部可以查数据库、调接口、做计算但不要自己再套一层小 Prompt 让另一个模型做决策。每多一层模型调用延迟、成本、失败率都会叠加。我的习惯是执行体里的逻辑要么是纯代码要么只允许调用内部规则引擎这种确定性的东西。代码层面技能执行体我建议保持轻量def execute_skill(skill_name: str, params: dict) - dict: skill_registry { query_order_info: query_order_info, calc_discount: calc_discount, update_order_remark: update_order_remark, } if skill_name not in skill_registry: raise SkillNotFoundError(f技能 {skill_name} 不存在) return skill_registry[skill_name](**params)在引入复杂框架之前先用字典做注册表是最可靠的。我在项目早期试过动态加载模块、插件系统、事件总线等各种花花肠子最后发现一个简单的字典加几行分发逻辑反而是所有方案里最稳的。框架可以后加但第一版一定要能随时看清调用链。3.3 注册与发现机制让模型知道“有什么可用”注册的时机和入口也值得讲究。我建议把技能元信息在 agent 启动时集中加载并生成一份统一的能力清单。清单里每个技能对应一块结构化的工具描述模型推理时会看到全部或经过筛选后的子集。这里有个性能细节当技能数量超过一定阈值我自己测下来大概是 30 到 50 个把全部技能描述一次性塞进上下文不仅浪费 token还会让模型决策变慢。所以你需要一个“预筛”步骤。我的做法是给每个技能打标签比如{domain: order, requires: [user_id]}在把技能清单发给模型前先用规则或者一个小的 embedding 模型根据当前会话意图做一次粗筛只把相关的十来个技能放进上下文。这一步简单的规则匹配就能见效。比如用户消息里出现“退”“款”优先召回订单域和售后域的技能出现“优惠”“券”召回营销域技能。不必一开始就上复杂的意图分类模型。3.4 调用路由与异常兜底技能执行不是调用完就结束后面的异常处理才是真正体现工程成熟度的地方。我总结了一套兜底策略参数校验失败——把校验错误信息拼回上下文提示模型重新生成参数最多重试两次执行超时——设置单技能超时时间比如 10 秒超时后立即返回“技能不可用”的占位结果不让 agent 无限等待业务返回为空——结果为空时不给模型发挥空间直接返回一个明确的“未查询到数据”标记避免它脑补不存在的结论未知技能调用——路由层拦截记录日志返回可读错误。最容易被忽略的是第 3 条。模型在拿到空结果后经常倾向于自我发挥编造一个看起来合理的答案。明确告知“没有数据”既是给模型吃定心丸也是给它划定边界。3.5 上下文压缩别让历史记录撑爆窗口技能调用多了之后上下文里会堆积大量工具返回结果。有些结果对当前会话早已没有价值还白白占用 token 窗口。我处理的方式是在每次技能调用结束后做一次轻量摘要如果技能结果是列表型数据保留前 3 条其余截断并附注“共 N 条已截断”如果技能结果已经作为核心回复发送给用户将其压缩成一行状态摘要保留在短期记忆里如果同一技能在连续三轮内被反复调用只保留最近一次的结果。这套策略能让上下文占用率下降 50% 左右模型响应速度的提升非常明显。4. 实操过程从一个真实技能库的搭建流程说起4.1 场景定义先立一个小目标我在项目里选了个“订单助手”场景作为试点。目标很简单用户输入自然语言系统能识别意图调用技能返回真实业务数据并生成友好回答。把一个场景做成闭环比把十个场景做成半吊子重要得多。所以第一版我只做了三个技能查订单、查物流、算优惠。技能清单如下技能名输入参数输出说明query_orderorder_id状态商品列表查主订单query_expressorder_id物流轨迹查物流calc_discountuser_id, product_list优惠金额计算优惠三个技能串起来的链路非常简单先是 query_order 拿到订单主体再按需查物流遇营销需求时就调 calc_discount。4.2 最小闭环先跑通搭建顺序是先写最底层的服务接口再用 pydantic 包一层参数校验然后做技能注册和路由最后接一个大模型作为“决策器”。第一轮跑通的时候问题一堆但最大的收获是技术选型都被验证过了。我测试用的模型支持 function calling但我不想被某个单一供应商绑死所以在设计上留了兼容层把技能描述转成模型厂商的 tool schema 格式这样切换模型时只需要换一个适配器。如果项目周期紧张我个人建议别从零实现兼容层可以直接基于现成的 Agent 框架来做把技能作为工具注册进去。框架能帮你处理对话状态、模型调用、函数分发这些脏活。但框架的选择也要结合项目复杂度和团队熟悉度不要盲目追新。这个我在文末经验里还会展开。4.3 让模型“更会选技能”的迭代技巧跑通之后最耗精力的不是让技能正常执行而是让模型在混乱的用户表达里也能选中正确技能。举几个真实迭代例子感受一下用户说“我的快递啥时候到啊”最初模型选的是 query_order返回订单状态后并没回答物流问题。调整 query_order 的描述和 query_express 的触发条件问题就消失了用户说“帮我看看还能不能便宜点”模型无法正确关联到 calc_discount。后来我在 calc_discount 描述里增加了一句“包含所有与优惠、降价、折扣相关的场景”准确率明显改善用户说“取消订单”但系统还没做这个技能模型就会随机调用相邻技能并胡乱回答。后来我在技能清单外增加了一个“fallback”说明明确告知模型“当前范围不支持取消订单请礼貌告知用户”情况立刻收敛。所以技能系统的迭代不是写代码而是写描述、写边界、写兜底。模型行为不对先别急着改底薪结构先用描述调一调又快又安全。5. 常见问题与排查技巧实录5.1 模型选错技能问题到底出在哪里选错技能这事很多人第一反应是“模型不行”但我测下来的结论是八成出在描述和场景语义不匹配只有两成是模型本身能力不足。排查思路是先检查该技能描述里是否写清了触发条件和排除条件再检查是否有其他技能与它语义相近导致被同时召回最后再看模型推理日志里当时看到了什么而不是凭感觉猜。我在这个环节里最大的收获就是养成了看日志的习惯。无论多快的推理错误多离谱的行为都能在链路日志里找到依据。技能调用链的可观测性一定要从第一天就建立起来落后了再补会非常痛苦。5.2 参数校验失败的三种解法模型传参失败的概率其实远高于大多数人预期。尤其是枚举值模型经常把“pending”传成“等待中”之类的中文描述这会在校验层直接炸掉。我现在常用的处理方式有三种在描述里写示例每个字段 description 都带上真实示例比如“order_id例如 ORD20250101”模型照葫芦画瓢的成功率会高很多。在校验层做同义映射把“等待中”、“待支付”之类的常见别名映射回枚举值。失败后重试机制第一次校验失败把错误信息返回给模型让它自行修正参数再试一次。很多小问题模型自己就改对了。5.3 重复调用与循环调用模型在复杂任务中有时会在两个技能之间来回跳形成循环。最初我以为跑进了死循环后来发现是上下文信息不足模型每次做出的决策都一样于是来回尝试。我的规避办法限制单个会话的最大技能调用次数比如 6 次超出则强制结束转为人工兜底增加“已尝试过该技能”的历史标记让模型看到重复调用没有效果分析日志找出反复调用的套路针对性调整对应技能的描述。5.4 上下文爆炸技能返回的数据量一旦大了上下文很快就不够用。特别是批量查询结果带列表的场景一次会话就可能吃掉几万 token。建议从源头控制在 Schema 层面就支持分页或者数量限制参数比如max_items5避免后端一次性返回全量数据。同时配合前文提到的上下文压缩策略双管齐下就能把上下文占用压到可控范围。5.5 测试技能库的三种姿势技能库的质量不能只靠肉眼判断我把测试分成三层单元测试单个技能的参数校验、业务逻辑、异常分支纯粹跑 Python 层面的测试速度快、反馈直接评估测试准备几百条用户话术让 agent 走完整链路统计技能选择准确率、参数生成准确率、最终回复满意度。这个环节最接近线上真实效果回归测试每次修改技能描述或新增技能后跑一遍全量测试集重点观察旧场景有没有被新描述干扰。回归测试是很多人容易忽略的。改了一个技能的描述另一部分场景可能就受到牵连没有回归测试兜底迟早要踩线上事故。6. 结尾说几句真心话项目走到今天我最大的体会是做 agent-skills 这类系统核心难点不在技术栈而在对“大模型行为边界”的理解。你以为你在写代码其实你在给一只话痨鹦鹉准备一本清晰的操作手册。手册写得好它才接得住话、干得成事。另一个深刻的体会是永远别把全部希望押在一个单体模型上。我实际推进时发现不同模型对 function calling 的支持和表现差距明显有的在工具选择上确实更强但消耗也大有的模型虽然通用能力弱一些但在简单技能调用上反而简洁高效。所以技能层的业务逻辑尽量与模型解耦做好兼容适配这样切换模型时才能进退自如。最后分享一个小技巧给每个技能加一个“成功率”指标统计每次调用的失败率、超时率、空结果率定期清洗优化。这套数据不仅指导具体迭代方向还能在你和业务方对需求时拿得出硬指标用事实说话总比空口讲道理有用得多。agent-skills 这条路技术边界还在被不断拓宽但地基逻辑已经比较清晰了。希望我的这些经验能帮你搭建自己项目的技能体系时少踩几个前人已经踩过的坑。