
1. 从agent-skills这个标题能读出什么第一次看到agent-skills这个词我脑子里蹦出来的不是某个具体框架而是一类正在快速成型的东西把智能体agent的能力拆成一块块可插拔、可复用、可组合的技能单元。这个标题本身信息量不大正文和关键词都是空的但恰恰是这种空说明它更像一个概念容器——一个用来装智能体技能体系的容器。我打算基于这个标题结合当前智能体开发的常见实践把agent-skills这件事从概念到落地讲透。先说清楚它是什么。agent-skills 指的是围绕智能体构建的一套技能抽象层把查天气读文件发邮件调数据库做网页检索这类具体动作封装成一个个独立、可描述、可被智能体按需调用的技能模块。它解决的问题很直接——过去我们做一个智能体往往是把所有能力硬编码在一个大提示词或者一个大函数里改一处牵动全身复用基本靠复制粘贴。有了技能层之后能力变成积木智能体变成拼装台。它适合谁看三类人最该关注。第一类是正在做智能体应用但被提示词越写越长、逻辑越理越乱折磨的开发者第二类是想把内部工具、内部系统接入智能体却不知道怎么抽象接口的工程同学第三类是对智能体感兴趣、想动手做一个能真正干活的小助手而不是只会聊天的玩具的爱好者。不管你是哪一类这篇内容都会给你一套能直接抄作业的思路和代码骨架。我先把结论摆在这agent-skills 的核心价值不在于技能多而在于技能边界清晰、描述规范、组合可控。技能写得再多如果每个技能都含糊其辞、职责重叠智能体照样会乱调、错调、漏调。所以下面我会重点讲清楚技能怎么定义、怎么描述、怎么被选中、怎么组合、怎么排错这些都是我实际踩过坑之后总结出来的东西。2. 技能抽象层到底解决了哪些真实痛点2.1 硬编码智能体的三个典型崩溃现场在讲技能层之前得先讲清楚没有它的时候会出什么事。我见过太多智能体项目死在三个地方。第一个崩溃现场是提示词膨胀。一开始你写个智能体提示词两百字能查天气、能算数挺好。后来要加读文件、加发消息、加查数据库、加调用内部接口提示词涨到三千字。这时候模型开始记不住——前面定义的规则到后面就忘了工具描述互相打架调用格式时对时错。你改一个工具的描述可能把另一个工具的行为带偏。这不是模型不行是你把所有信息塞进一个上下文里超出了它稳定处理的边界。第二个崩溃现场是逻辑耦合。所有工具调用逻辑写在一个大函数里用一堆 if-else 判断该走哪条路。加一个新能力就要在函数里再插一个分支还要小心别破坏原有分支。测试的时候更痛苦你没法单独测读文件这个能力因为它和发消息的逻辑缠在一起。这种代码活不过三个月维护成本指数级上升。第三个崩溃现场是复用为零。你在 A 项目里写了个解析 CSV 并汇总的能力B 项目也需要怎么办复制粘贴。粘过去之后两边各自演化修了 A 的 bug 忘了修 B 的最后变成两份不一样的代码。团队里三个人做三个智能体就有三套几乎一样但细节不同的工具实现。2.2 技能层带来的四个结构性改变agent-skills 这套思路本质上是把上面三个问题一次性解决。它带来四个结构性改变。第一能力与智能体解耦。技能是独立模块智能体只是调用方。技能不知道谁在调它智能体也不关心技能内部怎么实现。这种解耦让技能可以单独开发、单独测试、单独部署。你甚至可以让不同的人负责不同的技能最后拼起来就能用。第二描述与实现分离。每个技能对外暴露的是一份说明书——叫什么名字、干什么用、需要什么参数、返回什么。智能体只看说明书来决定调不调、怎么调。实现细节藏在里面改实现不影响调用方。这一点极其关键因为智能体选技能靠的就是这份说明书说明书质量直接决定调用准确率。第三组合成为一等公民。技能可以串起来用。先检索资料再提取要点再生成摘要再发送结果。每个技能只干一件事串起来就是一条完整工作流。这种组合能力是硬编码方式很难优雅实现的。第四可观测、可迭代。每个技能的调用次数、成功率、耗时、错误类型都能单独统计。哪个技能老出错、哪个技能没人用、哪个技能耗时太长一目了然。有了数据优化就有方向而不是凭感觉改提示词。2.3 一个生活化类比技能层就像餐厅后厨我用一个类比帮你建立直觉。硬编码的智能体就像一个厨师既要切菜、又要炒菜、又要端盘子、又要收银忙起来必然出错。agent-skills 就像把后厨分工切菜的是一个岗位炒菜的是一个岗位摆盘的是一个岗位传菜的是一个岗位。每个岗位有明确的职责说明技能描述有明确的输入输出食材进、菜品出有明确的协作方式传菜员从炒菜岗取菜送到前台。厨师长智能体不需要会切菜它只需要知道要切菜就找切菜岗要炒菜就找炒菜岗。哪个岗位今天出菜慢单独去优化那个岗位就行不影响其他岗位。新开一家分店新项目把同样的岗位配置搬过去就能跑。这就是技能层的价值。3. 一个技能应该长什么样字段设计与描述规范3.1 技能描述的最小字段集要让智能体准确选技能技能描述必须包含几个核心字段。我实测下来下面这套最小字段集基本够用缺一个都会出问题。字段作用缺失后果name技能唯一标识无法精确引用容易重名冲突description一句话说明干什么、什么时候用智能体不知道何时该调它parameters参数名、类型、是否必填、含义调用时参数乱填或漏填returns返回结构说明智能体拿到结果不知道怎么用examples典型调用示例边界情况容易调错name我建议用动词开头的短横线命名比如read-file、search-web、send-message。别用fileTool这种含糊名字也别用doEverything这种大杂烩。名字本身就是给智能体看的信号动词开头能让它快速判断这是个动作。description是最容易被写烂的字段。很多人写读取文件这等于没写。好的描述要回答三个问题干什么、什么时候用、什么时候别用。比如读取指定路径的文本文件内容。当用户需要查看某个已知路径的文件内容时使用。不要用于搜索文件搜索请用 search-files。最后那句不要用于极其重要它帮智能体划清技能边界减少误调。3.2 参数设计里最容易踩的坑参数设计我踩过最深的坑是参数太多、太自由。一开始我设计一个查询数据技能参数有表名、字段列表、过滤条件、排序、分页、聚合方式……结果智能体经常填错因为它要一次性决定太多东西。后来我把它拆成三个技能query-table只做简单查询、filter-rows只做过滤、aggregate-data只做聚合。每个技能参数少、职责单一调用准确率立刻上去了。另一个坑是参数类型不明确。比如一个参数叫count智能体不知道是数字还是字符串有时候传5有时候传5。解决办法是在描述里写死类型并且在代码里做一次容错转换。别指望模型每次都传对类型自己兜底最稳。还有一个坑是必填和选填不分。所有参数都标必填智能体就会硬凑参数凑出一些没意义的默认值。正确做法是明确标出哪些必填、哪些选填选填的给出合理默认值。这样智能体在信息不足时知道可以省略而不是瞎编。3.3 用示例锚定技能行为examples这个字段很多人省掉我觉得特别可惜。示例是给智能体看的标准答案能极大降低调用错误率。一个技能配两到三个示例就够一个最典型的、一个带边界条件的、一个说明不该这么调的反例。比如send-message技能示例可以写典型调用是给指定用户发送一条文本消息边界情况是消息内容为空时应该报错而不是发送空消息反例是不要用它发送文件发文件请用 send-file。这三个示例一摆智能体对这个技能的理解就立体了。我实测过一个对比同一批技能加示例和不加示例调用准确率能差出两成以上。示例的成本很低收益很高没有理由不做。4. 技能怎么被智能体选中路由机制拆解4.1 全量塞进上下文 vs 按需检索技能选中的第一步是让智能体看到有哪些技能可选。这里有两种主流做法各有取舍。做法一全量塞进上下文。把所有技能的描述一次性放进系统提示词。优点是简单直接智能体一眼看全。缺点是技能一多上下文就爆了。我实测过技能描述超过三十个每个平均一百字就是三千字再加上对话历史和其他指令很容易逼近上下文上限而且模型在长列表里的选择准确率会下降。做法二按需检索。先把技能做成可检索的索引用户提问时先检索出最相关的几个技能再塞进上下文。优点是上下文占用小、可扩展性强。缺点是多了一层检索检索不准就会漏掉该用的技能。检索可以用关键词匹配也可以用向量相似度我一般两者结合先用关键词粗筛再用向量精排。我的建议是技能少于十五个用全量多于十五个用检索。这个阈值不是绝对的取决于你技能描述的长度和模型上下文大小。但核心原则是——别让智能体在一堆无关技能里做选择选择空间越大选错概率越高。4.2 技能描述质量决定路由上限不管用哪种路由方式技能描述的质量都是路由准确率的天花板。检索再准如果描述本身写得含糊照样选错。我总结了几条描述写作的硬规则。第一用用户会说的话来描述技能而不是用技术术语。用户说帮我看看这个文件写了啥技能描述里就该有查看文件内容这种说法而不是执行文件读取操作。第二技能之间要有区分度。如果两个技能描述读起来差不多智能体必然分不清。这时候要么合并要么把差异点写清楚。第三描述里带上触发场景。当用户提到……时使用这种句式能显著提升命中率。4.3 多技能冲突时的优先级处理实际使用中经常出现多个技能都看起来能用的情况。比如用户说整理一下这份数据read-file、parse-csv、aggregate-data都可能被选中。这时候需要一套优先级规则。我的做法是在技能描述里加一个隐式的优先级信号越具体的技能优先级越高。parse-csv比read-file具体因为前者限定了文件类型。当用户明确提到 CSV 时应该优先选parse-csv。这个信号通过描述里的限定词来传递比如仅用于 CSV 文件。另一种做法是在路由层做显式排序给每个技能配一个优先级权重冲突时选权重高的。但权重需要人工维护技能一多就难管。我更倾向用描述里的限定词来自动区分维护成本低。5. 从零搭一个技能系统可复现的实操骨架5.1 目录结构与技能注册先给一套我常用的目录结构简单清晰扩展方便。agent/ skills/ read_file/ manifest.json # 技能描述 handler.py # 技能实现 search_web/ manifest.json handler.py send_message/ manifest.json handler.py registry.py # 技能注册与检索 agent.py # 智能体主逻辑每个技能一个目录manifest.json放描述handler.py放实现。这种结构的好处是技能完全自包含复制一个目录就能搬走一个技能。manifest.json长这样{ name: read-file, description: 读取指定路径的文本文件内容。当用户需要查看某个已知路径的文件内容时使用。不要用于搜索文件。, parameters: { path: { type: string, required: true, description: 文件的完整路径 }, encoding: { type: string, required: false, default: utf-8, description: 文件编码默认 utf-8 } }, returns: { type: object, description: 包含 content 字段为文件文本内容 }, examples: [ 读取 /data/report.txt 的内容, 查看 config.json 文件 ] }handler.py就是一个普通函数def handle(path: str, encoding: str utf-8): with open(path, r, encodingencoding) as f: content f.read() return {content: content}注册器负责扫描skills目录加载所有 manifest建立索引import json import os class SkillRegistry: def __init__(self, skills_dir): self.skills {} for name in os.listdir(skills_dir): manifest_path os.path.join(skills_dir, name, manifest.json) if os.path.exists(manifest_path): with open(manifest_path, encodingutf-8) as f: manifest json.load(f) self.skills[manifest[name]] manifest def all_descriptions(self): return [ f{s[name]}: {s[description]} for s in self.skills.values() ]5.2 技能调用的完整链路一次技能调用的完整链路是这样的用户输入 → 路由选出候选技能 → 技能描述塞进提示词 → 模型输出调用意图技能名 参数→ 参数校验 → 执行 handler → 结果回填 → 模型生成最终回复。这里面参数校验是最容易被忽略但最重要的一环。模型输出的参数经常有类型错误、缺字段、多字段。我一般做三层校验第一层检查必填字段是否齐全第二层检查类型是否匹配并做容错转换第三层检查值是否在合理范围内。任何一层不过就把错误信息回给模型让它重试而不是直接抛异常。def validate_and_call(skill_name, params, registry): manifest registry.skills.get(skill_name) if not manifest: return {error: f未知技能: {skill_name}} # 必填校验 for pname, pdef in manifest[parameters].items(): if pdef.get(required) and pname not in params: return {error: f缺少必填参数: {pname}} # 类型容错 for pname, pdef in manifest[parameters].items(): if pname in params and pdef[type] string: params[pname] str(params[pname]) # 执行 handler load_handler(skill_name) return handler(**params)5.3 让技能支持组合调用单个技能能干活但真正有价值的是组合。组合有两种实现方式。方式一让模型自己串。模型先调search-web拿到结果后再调summarize再调send-message。这种方式灵活但依赖模型的多步推理能力步骤一多容易断链。方式二预定义工作流。把常见组合固化成复合技能比如research-and-report内部依次调用检索、摘要、发送。这种方式稳定但灵活性差。我的经验是两者结合高频、固定的组合做成复合技能低频、多变的组合让模型自己串。复合技能对外还是一个技能但内部是编排逻辑。这样既保证了稳定性又保留了灵活性。6. 实测中最容易翻车的几个地方6.1 技能描述里的隐形重叠技能描述重叠是最隐蔽的坑。表面上两个技能名字不同、描述不同但语义上有重叠区模型就会在这块区域反复横跳。我遇到过一个典型案例get-weather和get-forecast两个技能前者查当前天气后者查未来天气。描述里都写了查询天气结果用户问明天天气怎么样模型有时候调前者有时候调后者。解决办法是在描述里把时间维度写死。get-weather写仅用于查询当前实时天气get-forecast写仅用于查询未来某天的天气。加上仅用于这种强限定词之后重叠区就消失了。这个技巧我用了很多次屡试不爽。6.2 参数默认值引发的静默错误参数默认值是个双刃剑。设了默认值模型不传参时能兜底但兜底兜错了就是静默错误比报错还难查。我踩过一个坑send-message的channel参数默认值是default结果模型不传 channel 时消息全发到了默认频道用户以为发到了指定频道。查了半天才发现是默认值的问题。我的建议是默认值只用于真正安全的场景。如果某个参数不传会导致行为不确定就不要设默认值直接标必填让模型必须明确传。宁可报错让模型重试也不要静默走错分支。6.3 技能返回结果太长撑爆上下文技能返回结果直接塞回上下文如果结果很长会挤占后续对话空间。我见过一个read-file技能读了一个几万字的文件结果整个上下文被占满后续对话全乱套。解决办法是在技能层做结果截断和摘要。返回结果超过一定长度时只返回前 N 个字符加一个内容过长已截断的提示或者自动调用摘要技能压缩。这个阈值我一般设两千字左右具体看模型上下文大小。关键是别让单个技能的返回结果失控。6.4 错误信息不回流导致模型瞎猜技能执行失败时如果只返回一个笼统的执行失败模型不知道错在哪就会瞎猜、瞎重试。正确做法是把具体错误信息回流给模型比如文件不存在/data/report.txt、参数 count 必须是数字收到的是字符串。模型看到具体错误才能针对性修正。我一般把错误分成三类参数错误模型能自己修、环境错误模型修不了需要告知用户、权限错误需要用户介入。参数错误直接回流让模型重试环境错误和权限错误转成自然语言告诉用户。这样分工明确不会让模型在修不了的问题上死循环。7. 技能系统的迭代与观测7.1 每个技能都该有调用日志技能系统上线之后最重要的不是加新技能而是看清楚现有技能用得怎么样。我一般给每个技能记三类日志调用次数、成功率、平均耗时。这三个指标能回答大部分问题。调用次数告诉你哪些技能是热门的、哪些是没人用的。没人用的技能要么删掉要么描述有问题导致模型不知道何时用。成功率告诉你哪些技能老出错出错是参数问题还是实现问题。平均耗时告诉你哪些技能拖慢了整体响应需要优化。import time import logging def logged_call(skill_name, handler, params): start time.time() try: result handler(**params) status success except Exception as e: result {error: str(e)} status error elapsed time.time() - start logging.info(fskill{skill_name} status{status} elapsed{elapsed:.3f}s) return result7.2 用失败案例反推描述问题技能调用失败的时候别急着改实现先看是不是描述问题。我统计过自己的项目技能调用失败里大概六成是描述不清导致的选错技能或填错参数只有四成是真正的实现 bug。所以每次失败我都会问三个问题模型是不是选错了技能如果是描述哪里让人误解了模型是不是填错了参数如果是参数描述哪里不清楚把失败案例攒起来定期回顾你会发现描述问题有很强的模式。比如时间相关技能容易混文件相关技能容易混发送类技能容易混。针对这些模式统一优化描述写法比一个个改效率高得多。7.3 技能版本管理与灰度技能是会演进的。改一个技能的描述或参数可能影响所有调用它的智能体。所以技能需要版本管理。我的做法是 manifest 里加一个version字段重大变更升大版本兼容性变更升小版本。智能体引用技能时可以指定版本范围避免被不兼容的变更搞崩。灰度发布也很重要。新版本技能先让一小部分流量用观察成功率和耗时没有异常再全量。技能系统的好处就在这里——技能是独立的灰度可以做得非常细不影响其他技能。8. 关于 agent-skills 我个人的几点体会做了一段时间技能系统我最大的体会是技能设计是门减法的手艺不是加法。新手总想多加技能觉得技能越多能力越强。实际上技能越多路由越难冲突越多维护越累。真正好用的技能系统往往是十几个职责清晰、边界明确的技能而不是几十个含糊重叠的技能。第二个体会是描述比实现重要。实现写错了测试能发现描述写含糊了测试很难覆盖因为它是概率性的问题。我现在的习惯是写技能先写描述描述写不清楚说明这个技能职责没想清楚那就先别写实现。描述是技能的契约契约不清实现再好也白搭。第三个体会是别追求一步到位。技能系统是长出来的不是设计出来的。先做三五个核心技能跑通链路用起来之后根据实际调用数据再拆、再合、再优化。我见过太多人一开始就设计一套完美的技能体系结果实际用起来发现根本不是那么回事推倒重来。小步快跑用数据说话比纸上谈兵靠谱得多。最后一个实用技巧给技能写负面示例。就是在描述里明确写不要用它做 X。模型对否定指令的遵循度其实不错加上负面示例能显著减少误调。比如search-web里写不要用它查询本地文件本地文件请用 read-file。这一句话能省掉很多排查时间。