Agent技能体系实践:从拆解、调度到调优的完整指南 做 Agent 相关项目这两年我总结出一个挺扎心的规律模型选得再好、提示词写得再花真正决定一个智能体能不能在业务里站住脚的往往是它到底掌握了多少技能skills以及这些技能被组织得够不够顺手。agent-skills 这个词最近在技术社区里出现频率越来越高但它不是某个单一工具或框架的名字而是一整套关于“怎么让 Agent 会干活”的实践思路技能拆解、注册、调度、评估。这篇文章不聊空概念我把过去踩过的坑和沉淀下来的套路全摊开从为什么要做技能体系到一个技能库从零到一怎么落地再到调优和排查。适合正在做 LLM 应用、AI Agent、自动化助手或者想把手头零散的提示词工程升级成可维护技能库的开发者。1. 先搞清楚Agent 技能和“工具”到底是不是一回事1.1 技能、工具、插件的边界很多项目早期只有三五个工具函数这时候谈“技能”显得多余直接在提示词里列一遍就完事了。等到工具数量破百、多个业务方共用同一个 Agent 底座时没有技能层就会乱成一锅粥。我的理解是这样的工具Tool是最小的可执行单元比如“查询天气”“发送邮件”技能Skill则是带业务语义、可复用的能力封装一个技能可以只对应一个工具也可以内部编排多个工具和中间逻辑插件Plugin更像是技能的完整集成包包含技能、配置、文档甚至前端 UI。技能层存在的意义是在模型和工具之间插入一个“业务翻译层”。没有这层翻译模型每次决策都得理解十几个乃至上百个函数的原始签名既容易出错也没法统一做权限管理和监控。有了技能层模型只需要理解“有哪些能力可用、每个能力适合什么场景”具体怎么调用底层函数由技能实现内部消化。这个抽象对维护者来说意义更大因为业务变了你只需要改技能实现不需要让模型重新学习协议。1.2 技能为什么容易翻车我一开始犯过一个错把技能直接当成普通函数来写定义好输入输出就扔给 Agent 用。结果线上跑起来模型要么选错技能要么参数传得乱七八糟。这里面的关键差异在于普通函数是代码走死路调用关系是确定的而 Agent 调用技能是模型在不确定情况下做决策本质是概率行为。你写了一个“发邮件”的技能模型却可能用它来“给用户发一条道歉消息”因为在你眼里这是两件事在模型眼里都属于“发消息”。所以技能体系不是一个“写好函数 注册上去”就完事的过程。你需要考虑的是模型能不能根据用户请求准确回忆起这个技能会不会在多个相似技能之间犹豫技能描述会不会误导模型执行失败后的错误信息能不能被模型消化这些问题每一个都能让一个看起来很完美的技能库在实际跑的时候翻车。后面我会逐一把解法写出来。1.3 谁真的需要引入技能体系判断自己要不要上技能体系不需要什么复杂的评估模型就回答三个问题工具数量是不是已经超过 20 个是不是有多个业务线在复用同一套 Agent 底座是不是需要在 Agent 之外做技能级权限控制或审计如果三个答案都是否建议先别折腾三五个工具直接写进提示词最省事。反过来如果至少两个答案都是“是”技能体系就是刚需。我自己见过太多项目前期为了图省事把所有工具函数堆在一个文件里后面每次改需求都提心吊胆加一个新工具要担心影响几个老流程。拆成技能库之后这些问题变成了常规 CRUD谁负责哪个技能就改哪个目录边界清晰得多。还有一个场景特别适合技能化你准备在同一套底座上给不同客户交付定制 Agent技能就是天然的“能力模块”按需装配而不是每个客户都重写一遍提示词。2. 技能体系怎么搭拆解、分类与注册2.1 拆技能原子性不是越细越好技能拆分是整套体系里最容易走极端的一环。有人把技能切成特别小比如“读取文件”“解析 JSON”“提取标题”每个都单独成技能结果 Agent 完成一个简单任务要编排五六个技能中间任意一步决策失误就全盘崩掉。另一些人则把技能做得特别大一个“处理订单”技能里塞了创建、查询、取消、退款全部逻辑模型根本不知道该在什么时机调用。我自己的经验是遵循“业务闭环最小化”原则一个技能应该能独立完成一件对用户有价值的事。以订单场景为例合理的技能切分是“创建订单”“查询订单”“取消订单”而不是把“序列化订单数据”“校验商品库存”这种内部步骤暴露给模型。理由很简单模型的规划能力是有限的技能粒度过细会让决策链变长出错概率指数上升粒度过粗则会让技能缺少明确触发条件模型不敢用或乱用。粒度判断可以做个类比一个技能就像是公司里的一个岗位岗位职责要清晰但不用细到“呼吸都要打报告”。如果你发现一个技能描述必须写满一屏那大概率是拆粗了如果技能描述里有一半句子在解释“内部子步骤”那大概率是拆细了。2.2 三类技能基础技能、复合技能、元技能在实际工程里我会把技能分成三层方便管理和编排。第一类是基础技能Base Skill直接映射到单个工具或外部 API比如“查询天气”“发送邮件”“读取日历”。这类技能的共同点是内部没有复杂的决策逻辑调用参数基本就是 API 参数维护成本最低。第二类是复合技能Composite Skill内部会编排多个基础技能或封装一段业务逻辑。举例来说“安排会议”这个复合技能内部要依次完成“查参会人空闲时间”“找可用会议室”“创建日历邀请”“发送通知邮件”。它在模型眼里就是一个整体动作但具体执行是多步骤的。复合技能把常用的业务路径固定下来好处是稳定坏处是灵活性下降所以不是所有流程都值得复合化只有那些调用模式相对固定、重复出现频率高的才值得。第三类是元技能Meta Skill它不直接操作业务数据而是操作其他技能。最典型的元技能就是“规划器”它负责把用户的大目标拆解为可执行的小步骤再逐一唤起对应技能。还有“技能检索器”当技能数量庞大时用来找出当前任务最匹配的几个技能。元技能的存在让 Agent 具备了“自我调度”的能力不至于在几十个技能面前不知所措。2.3 技能描述是给模型看的说明书技能描述的重要性怎么强调都不过分。模型不是靠代码注释来理解技能的它靠的是描述文本中关于“何时使用、如何使用、有何限制”的表述。我见过太多团队花了大量精力写技能实现结果描述就写一行“处理用户请求”这种描述等于没写。一个好的技能描述至少要包含四块内容触发场景、输入含义、输出说明、禁忌事项。拿“取消订单”技能举例描述可以这样写当用户明确表示要取消已经存在的订单时使用。 输入参数 order_id 为目标订单号reason 为用户填写的取消原因。 执行成功后返回订单的取消状态与退款金额。 注意仅限已支付且未发货的订单可取消用户仅问“能不能退”时不要调用应先引导确认。这段描述里模型能清楚地知道“什么情况下调用、需要什么参数、执行完会怎样、哪些雷区不能踩”。尤其最后一句禁忌事项能直接过滤掉一大批误调用。我在实际项目中还试过在描述里加入“反面示例”效果同样很好比如“不要用它来创建新订单”。这类否定式描述能显著提升模型选技能的准确率。描述也不是越长越好我测试下来一个技能描述控制在 150 到 250 字之间命中率最高太长反而会稀释模型注意力。2.4 技能注册表与技能仓库当技能数量多起来就需要一个集中管理的入口我把它叫技能注册表Registry。注册表的核心是保存每个技能的元信息名称、描述、参数 Schema、实现入口、版本号、所属业务线、权限级别。Agent 启动时系统会从注册表加载所有技能再注入到模型上下文中。听起来简单但有几个细节容易踩坑。第一个坑是技能名冲突。不同业务方可能都做了“查询库存”功能但内部逻辑完全不同如果注册表里出现两个同名技能模型会随机选一个。我建议所有技能名都带上业务前缀比如“wms_query_inventory”和“oms_query_inventory”从根上避免冲突。第二个坑是无主技能就是没人负责维护的技能一旦网上有人上传了半成品技能库不加审核直接塞进注册表迟早会出问题。第三个坑是权限粒度注册表应该记录每个技能所需的权限标签比如“只读”“写操作”“高权限”在调度器层做拦截而不是让模型自己判断能不能调高危动作。技能仓库的目录结构也建议规范化我目前使用的是这样的组织方式agent-skills-demo/ ├── skills/ │ ├── weather_query/ │ │ ├── SKILL.md │ │ ├── schema.json │ │ └── run.py │ ├── calendar_check/ │ │ ├── SKILL.md │ │ ├── schema.json │ │ └── run.py │ └── meeting_scheduler/ │ ├── SKILL.md │ ├── schema.json │ └── run.py ├── registry.py └── agent_core.py每个技能目录下SKILL.md 是给模型看的描述文档schema.json 是参数校验定义run.py 是具体实现。目录结构统一之后新增技能只涉及“新建目录 注册”删减技能也只涉及其对应目录互相隔离维护成本直线下降。3. 实操落地把技能库接到 Agent 主流程里3.1 先选实现方式Function Calling 还是提示词驱动技能要被模型调用目前主流有两种实现路线我两个都用过说说各自适用场景。第一种是 Function Calling函数调用这是 OpenAI、Claude 等模型原生支持的能力。你在请求里传入一个 functions 列表模型在生成回复时会返回一个结构化的 function_call 对象包含函数名和参数。这套机制的好处是稳定模型被专门训练过输出格式参数 JSON 基本不会语法错误坏处是依赖具体厂商 API一旦要切换底层模型兼容性得重新处理。第二种是提示词驱动Prompt 驱动把技能清单写进系统提示词里要求模型以固定 JSON 格式输出调用意图。然后程序解析 JSON去调用对应技能。优点是模型无关换哪家大模型都行缺点是模型偶尔会输出非法 JSON或者字段顺序不一致需要健壮的解析和重试逻辑。我现在的建议是如果你只在单一模型平台开发直接用 Function Calling省心如果要构建多模型可切换的底座或者需要动态调整技能清单而不重写提示词提示词驱动更灵活。还有一个折中线就是在底层做一层适配器把技能注册表统一转成某个模型的 Function 格式换模型只换适配器不影响技能实现本身。这个方案前期代码量多一点但换来的是长期自由度值得投入。3.2 技能定义JSON Schema 怎么写才顺手技能参数定义我统一用 JSON Schema不管底层走 Function Calling 还是提示词驱动它都适用。写 Schema 时最容易犯的错是把所有参数都设为必填结果模型为了满足校验不得不编造一个值。设计 Schema 的经验是能选填的尽量选填能用默认值解决的绝不设为必填枚举值能写就写避免模型自由发散。一个“查询订单”技能的 Schema 大概是这样的{ name: query_order, description: 根据订单号或时间范围查询用户的订单信息返回订单状态、金额和物流状态, parameters: { type: object, properties: { order_id: { type: string, description: 订单号形如 ORD-20250101-001选填但建议填写 }, phone_tail: { type: string, description: 用户手机号后四位用于多订单模糊匹配 }, date_range: { type: object, properties: { start: {type: string, format: date}, end: {type: string, format: date} } } }, required: [] } }注意几个细节每个字段的 description 都写清楚格式示例模型会照着示例生成参数 required 里尽量留空或只留真正的关键字段枚举值写在 enum 里能显著降低非法参数概率。我还习惯在 Schema 层做一次程序化校验模型返回的参数先过一遍校验器不通过就直接告诉模型“参数不合法请根据错误重新生成”而不是把错误参数传给技能实现。3.3 技能调度器路由、校验、超时调度器是技能体系的心脏它负责接收模型的调用意图经过校验后把请求分发给具体技能再把结果返回给模型。我实现调度器时会特别关注四个点。第一个是意图路由。模型给出的技能名可能和注册表不完全一致比如描述里写了“meeting_scheduler”模型却输出“schedule_meeting”调度器要做模糊匹配或者同义词映射。简单可靠的方案是维护一个别名映射表把易混淆的说法都归一化到正式技能名。第二个是超时控制。技能实现如果有外部网络请求必须设置超时我之前线上出现过一次事故某个技能依赖的 API 响应长达 30 秒Agent 一直等在那里用户不退出整个流程就卡死。后来所有技能的统一超时上限设为 10 秒超时就返回默认错误信息让模型换方案。第三个是错误信息的标准化。技能执行失败时返回给模型的信息决定了模型能不能从失败中恢复。一段好的错误信息要包含“失败原因 当前已完成的动作 建议的补救方案”。比如“会议室预订失败目标房间已被占用建议使用备选房间或更换时间段”而不是单纯返回“Error 500”。这段错误信息会被模型重新阅读写得越可行动模型恢复率越高。第四个是重试与兜底。不是所有失败都需要重试像“参数校验不过”这种可以重试一次带上更明确的提示“外部接口 500”这种重试没意义直接走兜底流程。我一般会给每个技能配置一两个兜底技能比如“会议安排”失败时兜底到“发送会议邀请草案”并转人工确认。3.4 接入 Agent 主循环一套最小实现现在把技能库接入 Agent 的 ReAct 主循环。ReAct 就是“推理-行动-观察”的循环模型先根据当前状态思考下一步要做什么然后调用技能观察技能返回结果再继续推理直到任务完成。这个循环是 Agent 技能体系的基本骨架。一个最小实现思路如下def run_agent(user_request, registry, max_steps10): messages [{role: user, content: user_request}] for step in range(max_steps): response llm_chat( messagesmessages, skillsregistry.get_skill_descriptions() ) intent parse_intent(response) if intent.action finish: return intent.final_answer if intent.action call_skill: result dispatcher.dispatch( skill_nameintent.skill_name, paramsvalidate_params(intent.params) ) messages.append({ role: tool, content: json.dumps(result, ensure_asciiFalse) }) return 已达最大步数任务可能未完成循环的核心是维护一个 messages 列表模型的每次决策和技能执行结果都会追加进去形成完整上下文。max_steps 一定要设上限没有上限的 Agent 会陷入“在无限循环里自我争论”既烧 token 又不解决问题。实操的时候我还会在每次循环里打印当前 step、选择的技能名、参数这组日志是后来排查问题最宝贵的依据。3.5 完整链路示例一次会议安排请求为了让你看清全貌我走一遍完整的链路。假设用户说“帮我约周五下午三点的周会叫上小明和小红会议室需要能投屏。”第一步Agent 主循环把四个技能描述注入模型上下文calendar_check查空闲、room_find找会议室、meeting_create建日程、notify_send发通知。模型分析用户请求后判定需要“安排会议”这个复合技能于是向调度器发起调用意图。第二步调度器识别会议安排是复合技能它内部开始编排子步骤先调用 calendar_check 检查小明和小红周五下午三点是否空闲同时调用 room_find 找一个带投屏设备的会议室。如果发现小明时间冲突复合技能会返回一条中间错误信息模型看到后主动问用户是否换时间而不是硬创建一个时间冲突的会议。第三步空闲和会议室都确认后调度器调用 meeting_create 创建日历邀请再调用 notify_send 把会议链接发给参会人。所有子技能的中间状态都会记录到上下文中模型全程能看到进展。第四步所有子技能执行完毕后复合技能向模型返回最终结果“周会预约成功会议室 A302已通知小明和小红。”模型据此生成给用户的自然语言回复。整个过程从用户视角看只是一句话的延伸但系统内部已经完成了四轮技能调度这就是技能体系的真正价值把复杂编排隐藏起来对外只暴露能力。4. 技能质量与迭代测试、评估、演进4.1 评估指标怎么定技能体系上线后必须有一套指标来回答“当前这套技能到底行不行”。我常用的指标有四个它们从不同维度反映技能库的健康度。技能命中率是最基础的指标指的是 Agent 在需要某个能力时是否能正确选中对应的技能。计算方式是统计测试集里模型选对技能的次数除以总次数。一开始我的命中率只有 70% 左右主要问题是两个相似技能描述重叠后来优化描述后提到 95% 以上。参数完整率衡量的是模型调用技能时传递参数是否齐全且合法很多问题都出在模型漏传非必填参数这会导致技能内部逻辑异常。执行成功率反映技能本身实现的稳定性包括外部依赖是否可靠、超时是否频发。任务完成率是整体指标指用户请求最终是否被完整解决它受前面三个指标共同影响也受 Agent 规划和上下文管理能力影响。这四个指标不是分开看的而是要组成一张趋势图。如果任务完成率降低先看执行成功率是不是也掉了如果是基本都是外部接口或技能实现问题如果执行成功率稳定但命中率下降那就是技能描述或新增技能影响了模型的判断。4.2 技能冲突与优先级技能越多冲突越不可避免。最典型的是两个技能边界重叠比如“查询订单”和“查询物流”当用户问“我的快递到哪了”模型可能选前者也可能选后者两者都算部分正确但返回的信息侧重点不同导致用户体验不一致。处理冲突有两个思路。第一个思路是“描述隔离”把技能描述的触发场景写得更精确靠自然语言划出边界。比如在“查询订单”里多加一句“如需查看物流轨迹请使用物流查询技能”这一句话就能把大部分误调用的请求分流出去。第二个思路是“技能优先级”在注册表里给技能加一个优先级字段当模型对多个技能的置信度相同时调度器强制执行高优先级技能。比如业务上更看重订单完整信息就可以把“查询订单”的优先级设高。我踩过一个典型的坑新增了一个“全渠道订单汇总”技能结果它的描述把查询类请求的名称都吸走了导致“查询订单”命中率从 92% 跌到 50%。当时排查了很久最后发现是“汇总”这个词和“查询”在模型语义空间里太接近。解决办法是在描述里加了一句“本技能仅用于多渠道数据汇总分析不返回单个订单明细”并降低了它的优先级命中率才恢复。4.3 回归测试与版本管理技能库本质上也是代码库只是多了一层“模型感知”的属性。它的测试比普通代码更复杂因为除了验证函数逻辑还要验证“模型描述与实现的一致性”。我现在的做法是建一个技能评测集里面放三部分内容真实用户历史请求、人工构造的边界请求、干扰请求。每次改动任何技能都要把评测集完整跑一遍比对命中率、参数完整率和完成率的变化。版本管理上每个技能单独维护自己的版本号尽量做到互不影响。一个疏忽很容易出问题有一次两个技能共用了同一个公共工具函数我在升级 A 技能时改了公共函数的签名结果 B 技能执行突然开始报错。后来我要求公共依赖必须单独抽个 common 包任何改动都要跑全量回归才把这个坑填上。发布策略上技能的更新不一定要跟 Agent 主版本同步特别是线上 Agent 在跑的时候推荐“灰度式发布”先在一小批流量上验证新技能跑一天没问题再全量放开。技能灰度比功能灰度更谨慎因为这个领域的故障往往不是崩溃型错误而是“模型选了个效果更差的技能”这种错误系统层面很难发现。4.4 控制 token 与上下文污染技能体系的一大隐性成本是 token。几十个技能描述全部都塞进每个请求单是一次请求就吃掉几千 token既慢又贵而且会让模型注意力被稀释。我做过一次实验工具箱里塞了 60 个技能描述之后模型准确率反而不如只塞 20 个的时候。解决思路有两种第一种是技能检索不把所有技能描述全量注入而是先根据用户请求做一次向量召回选出最相关的 5 到 8 个技能进入模型上下文。这就像图书馆先通过索引找书而不是把所有书都放在读者面前。第二种是技能分层把经常一起使用的基础技能打包成复合技能模型上下文里只需要出现复合技能内部子技能不暴露。比如“安排会议”只占一个描述位节省了下面四五个子技能的 token 开销。上下文污染还有一层意思旧一轮技能执行的输出会留在 messages 里如果中间步骤太多模型后面的推理会被无关信息干扰。我通常会设置一个上下文窗口策略保留最近的几轮关键结果更早的中间输出只保留摘要避免 Agent“越聊越糊涂”。5. 常见问题与排查技巧实录5.1 高频问题速查表把我在多个项目里遇到的高频问题整理成一张速查表照着排查大部分问题都能在十分钟内定位。症状可能原因解决方式相同请求下技能选得时对时错技能描述边界模糊精简描述增加触发场景和禁忌说明模型调用技能但参数大量缺失参数 Schema 必填设置太多减少必填项给字段加格式示例技能执行失败但 Agent 直接停止错误信息不可行动标准化错误返回包含原因和补救建议两个相似技能总被选错描述重叠度过高增加边界说明设置技能优先级指令理解很久才开始调用技能上下文里技能描述过多引入技能检索只注入最相关的几个新技能上线后老技能效果下降新技能描述吸引了原有触发词检查既有技能描述补充排他性说明一次任务中技能调用超过 8 次复合技能拆得不够粗将稳定流程封装成复合技能减少决策点日志里出现非法 JSON 输出提示词驱动模式解析失败增加 JSON 修复逻辑和重试机制5.2 几个让我记忆深刻的坑第一个坑是关于技能描述的语言。最初我用纯英文描述技能模型是 GPT 时代的产品英文输入理论上效果更好。但实际跑下来中文用户请求和英文技能描述之间始终隔着一层语义转换特别是那些中文业务词比如“已支付”“退款中”英文描述很难精确表达。后来我全部改成中文描述命中率直接提升了 6 个百分点。所以别迷信“英文更准”描述用的语言应该匹配目标用户的表达习惯。第二个坑是技能实现里的隐式依赖。有一个技能内部依赖另一个技能的执行结果但我没有在描述里说明这种前置关系结果模型在未调用前置技能的情况下直接调用它返回了一堆空数据。后来我在描述里明确写了一句话“调用前请确保用户已完成身份校验未校验时不调用”并且把这个约束做成调度器层面的依赖检查双保险才解决。第三个坑是过度打磨非核心技能。有一段时间我花了大把精力优化那些低频但有趣的小技能比如“每日运势”“星座查询”结果真正高频使用的“订单查询”反而出了问题没人及时发现。后来我建立了一个按调用量排行的监控页每天看一眼保证注意力都放在核心技能上。这个习惯帮我在一次大版本升级前提前发现了核心技能的性能劣化避免了线上事故。第四个坑是关于技能评测集的构建。早期我的评测集全是理想请求比如“帮我查天气”测下来命中率 98%漂亮得很。一到线上真实用户的话是“今天出门要不要带伞”“北京冷不冷”模型全懵了。真实用户说话会绕弯、会省略信息、会夹杂闲聊评测集里必须放大量这种“不标准”请求指标才有参考价值。建议每隔一段时间就从线上日志里抽一批新请求回来补充评测集让评测集跟着真实用户一起进化。5.3 一点个人心得做技能体系这件事真正难的不是技术实现而是保持“模型视角”。你要时刻提醒自己技能库的所有描述、Schema、错误信息最终是给一个概率模型看的不是给编译器看的。它不会因为你代码写得漂亮就正确调用但会因为你的描述清晰而减少犹豫。我每次新增技能都会把自己当成一个完全不懂业务的新用户对着技能描述问一句“我看到这段描述知道什么时候该用你吗”如果不太确定就继续改。这套技能体系上线跑了大半年迭代了几十次最大的体会是技能库是活的它更需要持续养护而不是一次性建完就放手。最后再分享一个小习惯每次发版本前我都会把技能评测集完整跑一遍哪怕只改了一个标点符号因为我知道模型对文本的敏感程度往往比人想象的高得多。