Agent技能体系agent-skills:让大模型从会说到会做

发布时间:2026/10/21 18:33:39
Agent技能体系agent-skills:让大模型从会说到会做 最近在重构一套多智能体协作系统时我发现一个特别扎心的现象同样的一个工具函数在服务A里被硬编码进Prompt在服务B里重新封装了一遍到了服务C又换了个参数格式。三个服务各写各的谁也没法直接复用。模型经常“会说不会做”——不是它不会调API而是我们压根没给它一份结构化的、可检索的“能力说明书”。我们内部把这套能力标准化方案叫做agent-skills它不是什么新框架的专有名词而是一套把LLM可执行能力抽成标准化模块的设计思路。这篇文章会完整拆解agent-skills的落地过程技能的最小描述单元长什么样、注册中心怎么管生命周期、多技能之间怎么路由和组合、以及我在实测中踩过的一堆坑。整体偏工程实践适合正在用大模型做提效工具、想把零散函数变成体系化能力的开发者也适合刚接触多Agent编排、想搞懂“技能注册”到底在解决什么问题的同学。1. 为什么我的Agent总是“会说不会做”三个让我下决心做技能体系的痛点先说说我为什么非要折腾这套东西。如果你只是接一个模型接口让模型做做文本总结那确实不需要技能体系。但一旦你的Agent要真正去操作业务系统、查数据库、发通知、做计算事情就开始失控了。失控的方式很典型1.1 痛点一工具代码散落在各个服务里越复制越乱最早我们做智能助手时每个Agent服务里都有一把私有函数。有的叫query_inventory有的叫getStockInfo干的其实是同一件事但参数名、返回结构、错误码全不一样。到了第二个项目开发同学图省事直接把上一个服务的函数拷过来改两下就用结果文档没跟代码走Prompt里写的调用示例早就过时了。这种散落不只是维护成本高的问题更致命的是——你没法让多个模型统一使用这些能力。一个能力没有标准定义没有统一注册入口模型每次面对的都是“开发者在Prompt里临时描述的一个函数”它只能靠猜。1.2 痛点二提示词和逻辑纠缠不清改一个参数要动一轮Prompt现在很多人写Agent还是这套套路在系统提示词里写上“你有以下工具可用1. 库存查询入参是sku_id……”再把JSON Schema贴进去。看起来能用实际上特别脆弱。只要接口参数稍微变一下比如超时时间从3秒改成5秒或者新增了一个筛选条件你就得回头改Prompt。Prompt一旦变长模型对工具的选择准确率就开始下降逻辑和文本耦合得越深系统就越难扩展。这个痛点在多Agent场景里会更明显一个Agent要给另一个Agent提供能力如果各家的能力只是写在各自的Prompt里跨Agent调用根本无从谈起。1.3 痛点三多Agent协作时能力边界是一笔糊涂账去年我们做了一个模拟运营系统里面有多个Agent角色一个管库存、一个管订单、一个管售后。刚开始每个Agent各写各的Prompt各调各的接口看似分工明确。一跑起来发现售后Agent需要查订单状态它不知道该调谁的哪个函数只能让用户“把订单号发我”。这种体验离“自动化”差得很远。我当时就在想如果所有Agent能共享一份“能力清单”每个技能都带着清晰描述、参数Schema、权限标签任何Agent都能通过清单发现和调用那跨Agent协作的问题就解决了一大半。这就回到了一个很朴素的思路把能力从Prompt里抽出来变成一个个可以注册、检索、编排的模块——这就是agent-skills的起点。技能体系不是银弹但它把“模型能不能调对工具”这个玄学问题变成了“我们有没有把技能描述写清楚”这个工程问题。2. 技能的最小描述单元从一段函数到一份可被LLM理解的Schema技能体系的核心不是代码而是“结构化的描述”。你要让一个语言模型理解“这个技能是干嘛的、什么时候用、参数怎么传、返回值是什么”不能靠注释必须靠一份机器可读、模型可检索、人也可维护的定义文件。2.1 技能描述应该包含哪些字段我把一个技能的最小描述单元拆成十一个字段不是越全越好而是每个字段都有明确目的字段说明为什么必须有id技能唯一标识如inventory.query_stock路由和日志追踪都靠它name人类可读的名称如“库存实时查询”模型在推理时依赖自然语言名称description一句话到两句话说清适用场景和边界条件模型决定“什么时候该用”主要看它tags能力标签如库存、只读、高并发支持系统侧路由和权限过滤input_schemaJSON Schema 严格定义入参减少模型幻觉参数静态校验依赖它output_schema返回结构的结构化定义调用方Agent能预知结果形态executor真正的执行函数或服务端点描述归描述执行归执行permissions所需权限域如inventory:read多Agent场景强制做权限隔离timeout_ms超时阈值防止某个慢技能拖垮整个编排version版本号支持灰度发布和回滚enabled是否启用动态摘除不健康能力你可能会问name和description是给模型看的input_schema是给参数校验用的那executor给谁用给运行时用。模型永远不直接看到executor的实现细节它只看到结构化描述真正执行由技能管理器统一调度。这个分离非常重要它保证了模型和真实系统之间有一层可控的防火墙。2.2 一个技能的完整定义示例下面是我实际使用的一个YAML定义功能是语义搜索内部知识库。写这个示例是为了让你直观看到每个字段长什么样id: knowledge.semantic_search name: 知识库语义搜索 description: | 当用户想要查询内部知识库中与某个主题相关的文档时使用。 支持模糊语义匹配不依赖精确关键词。适合问题类、经验类、 流程类查询。不适合做精确账务查询。 tags: [知识库, 搜索, 只读] version: 1.2.0 enabled: true timeout_ms: 3000 permissions: - knowledge:read input_schema: type: object required: [query, top_k] properties: query: type: string minLength: 2 top_k: type: integer minimum: 1 maximum: 10 default: 5 scope: type: string enum: [all, order, inventory, after_sale] default: all output_schema: type: object required: [items] properties: items: type: array items: type: object required: [doc_id, title, score] properties: doc_id: { type: string } title: { type: string } score: { type: number }配套的executor可以是一个函数也可以是一个HTTP服务地址。我更建议在初期用函数注册后期再拆成独立服务这样技能管理器可以统一处理超时和重试。2.3 描述文本和参数约束LLM能不能调对全看这两项很多人把精力全花在写executor上却把description写得很随意比如“用于知识库搜索”。这话太模糊了——“什么时候用、什么时候不用、边界在哪”全没讲。模型看到这种描述只好凭感觉。我调试的时候发现同一套技能描述写得具体不具体调用准确率能差30%以上。写description有个小技巧要同时写清楚“该用的场景”和“不该用的场景”。比如知识库语义搜索我会特意写明“不适合做精确账务查询”这样模型遇到一个需要精确数字的问题时就不会误选这个技能。这种“负样例”经常被忽略但实测特别管用。参数约束也一样。input_schema里的required和enum字段要尽量收紧。你给模型的自由度越大它越容易发挥“创造力”。把可枚举的值写成enum把必填项写成required既能降低参数幻觉也能让后端的参数校验变成一个纯机械动作。这里分享一个自查方法把技能描述定义好以后先不要接模型把描述的文本打印出来自己像第一次见面的人一样读一遍——如果三秒钟内你能准确说出这个技能什么时候该用、什么时候不该用那描述基本合格了。3. 技能管理器注册、校验、索引、热更新的统一出口有了技能定义之后下一步就是建一个统一的管理出口。我把它叫技能管理器Skill Registry它负责技能的注册、校验、索引和动态生命周期管理。所有Agent都只跟这个管理器打交道而不是直接去调函数。3.1 管理器的四个核心职责职责做的事我踩过的坑注册接收技能定义解析为内部运行时对象没有做格式校验结果脏数据进了索引校验校验Schema合法性和executor可调用性忽略了对执行器端点的连通性探测索引建立描述、标签、名称的三维度检索没有索引每次路由都在全量遍历生命周期管理处理启用、禁用、版本更新、热切换直接替换实例导致正在执行的请求出错这四件事听起来简单但每件都有细节。比如校验不只是校验YAML语法还要解析input_schema的合法性把minLength、maximum这些约束提前算好注册时就拦截无效配置而不是到运行时才报错。3.2 注册流程与代码骨架我写了一个简化版的管理器它足够支撑大部分业务场景import json import threading import yaml class SkillRegistry: def __init__(self): self._skills {} self._lock threading.RLock() def register(self, skill_def: dict, executor: callable) - None: skill_id skill_def[id] with self._lock: if skill_id in self._skills: # 版本逻辑单独处理这里先严格去重 raise ValueError(fskill {skill_id} already registered) self._validate(skill_def) skill_obj { definition: skill_def, executor: executor, } self._skills[skill_id] skill_obj def _validate(self, d: dict) - None: # 必填字段检查 for field in [id, name, description, input_schema]: if field not in d: raise ValueError(fmissing field: {field}) # input_schema 必须是合法 JSON Schema try: json.dumps(d[input_schema]) except TypeError as e: raise ValueError(finput_schema not serializable: {e}) def get(self, skill_id: str) - dict: with self._lock: return self._skills.get(skill_id) def search(self, query: str, tags: list[str] | None None) - list[dict]: # 简易检索实际可以替换成向量检索 with self._lock: results [] for skill_id, skill_obj in self._skills.items(): d skill_obj[definition] if not d.get(enabled, True): continue if tags and not set(tags).issubset(set(d.get(tags, []))): continue if query and query not in d[description] and query not in d[name]: continue results.append(d) return results def update(self, skill_id: str, new_def: dict, new_executor: callable | None None) - None: with self._lock: if skill_id not in self._skills: raise ValueError(fskill {skill_id} not found) old self._skills[skill_id] current dict(old) current[definition] new_def if new_executor: current[executor] new_executor self._skills[skill_id] current这段代码看起来像玩具但它包含了核心思路所有读写操作加锁保护注册时就做校验检索时可以按标签过滤。生产级实现在此之上再加日志、指标和配置热加载但骨架结构不会变。3.3 生命周期管理启用、禁用、版本和热更新技能不是一个“注册完就不管”的静态配置。线上服务随时可能出问题某个技能突然超时率飙升、新版本参数不兼容、合作方接口需要临时下线。这时候你不可能去找开发改代码发布你得有动态管理能力。生命周期我分了四个状态Registering注册中校验定义和executorEnabled正常对外提供服务Disabled手动摘除路由层直接跳过Updating更新版本切换期间请求正常热度切换是最容易出问题的点。我一开始直接在注册表里替换executor引用结果正在执行的请求拿到的可能是旧函数、也可能是新函数状态就乱了。后来改成“孤儿实例”方案更新时保留旧实例继续处理在途请求等旧请求全部结束后再物理移除。实测下来干净很多。这个过程的代价是内存里会短暂存在两个技能实例但对现代服务来说完全可接受。4. 技能路由与组合从单技能调用到多Agent协作的流水线技能定义和注册解决的是“有什么能力”的问题路由和组合解决“怎么用能力”的问题。这是agent-skills体系里最见功力的部分。4.1 命中路由LLM选技能和系统选技能怎么分工技能路由有两种思路很多团队会纠结选哪种。我的结论是两者不冲突各管一段路由方式谁在做决定优点缺点LLM路由模型根据描述和当前对话自主选择技能灵活能处理模糊意图延迟高可能选错成本随调用量上升系统路由按规则、标签、历史命中率直接决定技能确定性强、快不灵活无法覆盖所有语义变体我的做法是“系统先行LLM兜底”先让系统按用户意图分类、知识类型做一轮粗筛如果系统能确定命中某个技能就直接调如果系统拿不准再把技能描述列表交给LLM做细粒度选择。这样既保证了大部分请求的稳定性又保留了模糊场景的灵活性。比如用户说“最近仓库里的蓝牙耳机还有多少货”系统路由通过实体识别仓库、蓝牙耳机已经能直接锁定inventory.query_stock这个技能完全不需要模型参与。如果用户说“帮我看看耳机还能不能补货”意图模糊系统找不到精确技能才把候选技能列表交给模型裁决。4.2 组合编排串行、并行、条件分支单技能调用只解决单点问题真正的Agent场景往往需要多个技能按顺序或条件组合。我在编排层引入了一个轻量级的任务描述格式类似流水线定义workflow: report.daily_sales steps: - skill: inventory.query_stock as: stock_data - skill: order.query_sales as: sales_data params: start_date: ${{ date.yesterday }} - skill: report.generate params: stock: ${{ steps.stock_data.result }} sales: ${{ steps.sales_data.result }}这个编排的意思很直白串行执行两个查询技能把结果作为参数传给报告生成技能。实际跑起来时我会根据技能类型决定执行方式纯只读技能而且彼此无依赖并行执行等所有结果回来再继续有依赖关系的技能串行执行每一步拿到上一步输出再下一步条件分支用上一步返回的某个字段决定下一步走哪个技能并行执行对延迟的优化非常明显。我有一组流程原来串行要6秒改成并行后压到2.5秒。前提是技能必须是线程安全的这个细节我后面在坑里细讲。4.3 冲突处理重名技能、模糊描述、权限不一致技能一多冲突必然出现。我整理了三类常见冲突和对应策略供你参考冲突场景具体表现处理策略技能重名两个模块都注册了report.generate强制命名空间前缀按模块隔离广域功能用module.function全名匹配描述模糊两个技能描述高度相似路由无法区分在description里增加负样例和明确边界比如“本技能只处理已付款订单”权限不一致Agent请求的技能需要更高权限技能管理器按Agent身份做权限校验无权直接返回permission_denied不把错误暴露给模型权限不一致这个问题在初期最容易被忽略。我们一开始没做权限隔离结果一个只读Agent也能调用批量删除技能差点出事故。现在每个技能定义里的permissions字段都会和Agent的授权token强制绑定技能管理器在路由层做前置校验不通过就拦截根本不会走到executor那一步。5. 实测中踩过的坑和排查思路给正在做技能库的同学参考这个章节全是我在实际运行里踩过的坑不是理论推演。每个坑都花过真实的时间去定位和修复我把排查链路也写出来希望你遇到类似问题时能少绕弯子。5.1 坑一参数Schema写得太宽模型开始“瞎猜”有段时间我的知识库搜索技能命中率暴跌日志显示模型调用时传了一个search_type参数但我Schema里根本没定义这个字段。好家伙模型直接发挥了。因为后端的参数绑定是按定义严格处理的多出来的字段被忽略结果搜索行为完全不是模型预期的那样反馈自然差。修复方式就是收紧Schema所有可枚举的值全部用enum锁死非必填项立即标注没有默认值的字段不写default。改完之后参数幻觉明显减少。我现在的原则是Schema能收紧就收紧没有确定情况的字段一律不给模型发挥空间。5.2 坑二技能并发执行时状态污染这个坑藏得很深。我早期把技能对象做成单例所有请求共享同一个技能实例。技能内部为了性能缓存了一些数据在实例变量里结果并发一高多个请求互相覆盖返回结果完全错乱。这个问题在本地压测时不一定暴露因为并发量小一旦上线流量上去就炸了。排查链路是这样的先看到错误率飙升然后看异常堆栈发现是在技能执行器内部数据不一致最后定位到实例变量被并发修改。修复方案是技能实例不再全局共享改为每次请求时从技能管理器获取一个“干净的上下文镜像”所有可变状态都放进上下文中。这条我建议你在设计阶段就决定好不然后期重构很痛。5.3 坑三注册顺序影响路由结果系统路由刚上线时我按注册顺序遍历技能列表返回第一个描述命中的技能。测试时发现同样一句话有时选A技能有时选B技能后来发现是注册顺序变了。注册顺序完全是个偶然因素怎么能影响路由结果呢我后来把路由从“第一个命中”改成了“评分制”对候选技能的描述、标签、历史调用频率做加权评分取最高分。虽然简单但至少跟注册顺序无关了确定性好了很多。如果要做得更精细可以用向量检索对描述做语义匹配但业务量没到那个程度用加权评分已经足够。5.4 排查链路示例一次技能命中率下跌的定位过程这是某一次真实的问题排查我觉得很有代表性。现象是一个订单查询Agent的准确率从92%掉到70%幅度很明显。我开始怀疑是模型升级导致路由行为变化后来一看不是。一步步查下来查路由日志发现大量请求被路由到了order.summary而不是order.detail。这不是模型自己选的是系统路由根据关键词“订单详情”同时匹配了两个技能按照旧逻辑取第一个而order.summary排在前面。查技能定义两个技能的description都写了“查询订单信息”没有边界区分。查调用结果order.summary返回的是聚合数据而用户要的是单条订单项数据形态完全不符。定位到根因后修复分两步一是把两个技能的description边界写清楚明细查询强调“返回单条订单项”汇总查询强调“返回聚合统计、用于报表”二是在系统路由层增加了“意图置信度”判断置信度低于阈值时提升触发LLM判断路由而不是直接选第一个。这套双重调整之后准确率回到了94%以上而且比之前更稳定。这次排查的经验是先看路由日志再看技能定义最后才考虑模型问题——大多数时候是描述写得不够好而不是模型变笨了。最后再说一个我个人的体会做技能体系最花时间的不是写executor而是打磨description和Schema。你可以把技能描述当成产品文案来写每次上线前拿一组固定的探测问题跑一遍看模型会不会选错技能。这个动作看起来笨但它能救你于真实调用时的各种玄学问题。我现在的团队里技能上线前必须先过这个“探测问题集”也算是在这次重构里最值得的一笔投入了。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询