AI Agent技能库搭建实战:让大模型从“能聊”到“能干”
如果你最近也在折腾AI Agent大概会产生一种很微妙的感觉大模型什么都能聊但真让它干活的时候总像隔着一层纱。它能告诉你“我可以帮你写脚本”可你真让它去操作文件、调用接口、按固定流程跑一轮数据分析时它又经常卡在第一步。这层纱其实就是“技能”缺失。我第一次意识到这件事是在一个自动化运维项目里。模型把 Shell 命令生成得头头是道可一旦涉及“读取上一步结果、判断端口状态、再决定是否重启服务”这种多步闭环它就乱了节奏。我意识到问题不在模型智商而在它身边没有任何可依赖的、稳定封装的执行单元。后来我花了大约三周时间搭了一个叫“agent-skills”的个人技能库把平时 Agent 常用的能力全部拆成独立技能模块统一接入执行调度层。效果非常直接Agent 从“嘴上都会”变成了“手上稳了”同类任务的成功率提升了一截排错时间也大幅缩短。这篇文章就以我搭建与使用“agent-skills”的完整经验为主线聊聊技能库的定位、目录结构与技能描述规范、执行器设计、上下文分配策略、踩坑记录以及一套可以直接抄作业的最小实现方案。不管你是在做个人助理 Bot、垂直领域问答还是自动化流程编排这篇文章都值得你耐心看完。1. agent-skills 到底是什么一个独立的技能库该解决什么问题1.1 大模型“聪明但手笨”的真相大模型本质上是一个概率推理引擎它擅长的是把 tokens 串成看似合理的序列但它并不天然具备“做一件事”的能力。拿写自动化脚本来举例模型能生成一段看起来没有任何语法毛病的 Python 代码但这段代码在真实机器上能不能跑通、有没有权限、会不会把环境变量搞坏它并不知道。这就像让一个从没摸过方向盘的理论大师帮你倒车入库他能讲清楚所有物理学原理却大概率会把车蹭上墙。Agent 系统之所以在真实业务里经常翻车不是模型选得不够强而是缺少一层“可编程的肌肉记忆”。所谓 agent-skills就是把这层肌肉记忆显式地做出来每个技能对应一个目标明确、输入输出可控的执行单元Agent 在对话中先判断“该调用哪个技能”再由技能模块去真正操作外部世界。这样模型只负责理解和决策不负责手写每一步低级操作。1.2 一套“技能库”在工程上包含哪些东西我理解的 agent-skills 不是一段代码而是一个完整的工程目录通常包含四层内容技能定义层描述每个技能的名称、用途、输入参数、输出格式、使用约束一般用 YAML 或 JSON 维护方便模型理解。技能实现层每个技能背后真正干活的逻辑可能是 Python 脚本、Shell 命令、API 请求封装甚至是一段人工审核流程。执行调度层负责把模型意图映射到具体的技能模块并完成参数校验、输入组装和结果回传。评估与观测层记录技能调用的成功率、响应时间、失败原因逐步迭代。这种分层方式的价值在于它把“模型如何理解技能”和“技能如何被真实执行”解耦了。你完全可以在不调整模型的前提下通过新增一个技能文件来扩展 Agent 的能力边界。1.3 它和 Function Calling、MCP 有什么区别很多人会问一个问题大模型平台已经有 Function Calling社区也在推 MCP为什么还要自己造一个技能库我的理解是这三者并不冲突它们处在不同层次。Function Calling 是模型接口层的能力它要求你预先声明函数列表模型负责从列表里挑一个函数并生成参数。但它不关心函数内部怎么做也不负责状态管理。MCP 是模型上下文协议它定义了一套客户端与服务端之间发现工具、调用工具的规范解决的是“工具怎么被通用地暴露给模型”的问题。而 agent-skills 更偏应用层它是面向真实任务的一套“行为包”。你可以把它视为一种既有技能封装策略也可以把它理解成一个包含工具、提示词、校验逻辑和降级策略的组合。实际工程里这三者往往是叠加使用的底层用 Function Calling 做模型与函数的桥接中间用 MCP 统一工具接口再往上一层用 skills 管理复杂业务技能。如果想快速验证效果第一步其实不需要上 MCP先把技能目录定好把执行器写稳就已经能解决大部分问题了。2. 拆解一个技能库的目录结构与技能定义规范2.1 目录设计怎么分文件才不容易失控刚开始搭 agent-skills 时我犯过一个很经典的错误把所有技能脚本丢进同一个文件夹文件按“技能名.py”平铺。后果就是不到一周几十个文件堆在一起没有任何层次调用关系混乱得连我自己都不想维护。后来我重新设计了目录结构才把这件事变得可持续。我采用的目录设计大概长这样agent-skills/ ├── core/ # 核心框架代码不轻易改动 │ ├── executor.py # 技能执行器主流程 │ ├── registry.py # 技能注册与发现 │ ├── context.py # 上下文管理与窗口预算 │ └── state.py # 交互状态存储 ├── skills/ # 所有技能定义与实现 │ ├── file_ops/ │ │ ├── skill.yaml # 技能元数据 │ │ └── main.py # 技能实现 │ ├── web_search/ │ ├── data_analysis/ │ └── task_reminder/ ├── scripts/ # 通用辅助脚本 ├── tests/ # 技能测试与回归用例 ├── logs/ # 运行日志 └── config.yaml # 全局配置核心思路是两件事第一把框架代码和技能实现分开前者稳定后者高频迭代第二每个技能拥有一个独立目录技能元数据和实现代码放一起新增技能时不需要改动核心框架。这套结构最大的好处是“增量友好”我后面每次加新技能基本只需要复制一个目录模板、改描述和实现不会碰乱已有部分。2.2 一份高质量技能描述文件应该长什么样技能描述文件是整个 agent-skills 里最容易被低估的部分。很多项目技能执行成功率不高问题不是代码写错了而是描述文件写得不够好模型压根不知道该在什么时机调用、传什么参数。一份标准 skill.yaml我会包含以下字段name: file_read description: 读取指定文本文件内容适用于查看日志、配置文件、代码文件等场景。 version: 1.2.0 author: your_name trigger: when: 用户请求查看、读取文件内容或需要分析某个已知路径的文件 not_when: 用户仅提到文件名但未给出路径时先调用 path_search 定位如果文件体积超过50MB应提示用户改用流式读取 input_schema: type: object required: - file_path properties: file_path: type: string description: 文件的绝对路径要求用户提供或已通过上下文获取 encoding: type: string default: utf-8 description: 文件编码方式仅当文件非 UTF-8 时需指定 output_schema: type: object required: - content - truncated properties: content: type: string description: 文件内容默认最多返回前300行 truncated: type: boolean description: 内容是否因为长度限制被截断 examples: - user: 帮我看看 /var/log/app.log 最后有没有报错 call: file_read args: file_path: /var/log/app.log result: content: 2025-06-01 ERROR ... truncated: false fallback: - permission_error: 返回权限不足提示并调用 check_permission 技能 - not_found: 返回文件不存在提示并调用 path_search 技能为什么“examples”字段这么重要因为模型对技能的调用往往是通过语义匹配实现的描述文字再多也不如给两三个典型例子来得直观。我实测下来加了 example 之后模型选错技能的概率明显下降尤其对“file_read”和“file_tail”这类容易混淆的技能例子能把边界讲清楚。2.3 技能之间的依赖与冲突管理技能多了之后一定绕不开依赖问题。比如“数据分析”技能可能依赖“文件读取”和“代码执行”而“代码执行”又依赖一个受控的沙箱环境。我会在 skill.yaml 里显式声明依赖关系并在执行阶段做两件事依赖预检执行技能前先检查依赖技能所需的前置条件和资源是否就绪。冲突仲裁当多个技能都想处理同一请求时根据优先级规则决定谁先执行。比如“代码执行”和“SQL查询”都能处理数据但如果用户明确提到“写一段脚本处理”就应该优先选“代码执行”而不是让模型自己去猜。这里的优先级规则不需要写得很复杂一个简单的 order 字段就能解决大部分冲突priority: order: 20 conflicts_with: - name: sql_query strategy: 询问用户确认3. 技能执行器把定义变成真正能跑的行为3.1 执行器要拆成哪几个模块如果说技能目录是 agent-skills 的骨架执行器就是它的心脏。我设计执行器时没有把所有逻辑塞进一个大函数而是拆成了五个小模块各司其职路由解析接受模型输出的意图和参数将其映射到具体技能名称。上下文组装从历史会话、用户画像、全局配置中提取当前技能所需的上下文。行为编排判断是否需要调用子技能、是否需要询问用户澄清、是否满足依赖条件。输出校验技能执行完后校验输出是否符合 output_schema。回调与重试执行失败时根据 fallback 策略做重试或降级处理。拆完模块之后执行器本身变得很薄大部分逻辑只是“按顺序调用上面五个模块”。这让排错变得非常方便技能调用失败时我只要看日志里卡在哪一步就能迅速定位是路由问题、参数问题还是技能实现问题。3.2 语义路由的坑与可行方案很多 Agent 项目喜欢用“语义匹配”来做路由也就是把用户请求编码成向量和每个技能的描述做相似度计算取最高分作为选中技能。这个思路对简单场景是有效的但在技能数量超过二三十个后纯向量匹配的准确率会明显下降尤其是两个技能描述相似时模型很容易选错。我的解决方案是“三层路由”第一层规则匹配如果技能声明了 trigger 关键词或正则先走规则命中就直接调用。第二层向量召回把所有技能描述向量化用相似度选出 Top3 候选。第三层LLM 重排把候选技能的描述、示例和用户请求拼成一段 prompt让模型从候选里选一个并给出理由。这样做会多花一点 token但换来的是非常稳定的路由准确率。实际项目中三层路由的准确率基本能稳定在 95% 以上相比单层向量匹配提升非常可观。3.3 技能失败时的降级策略技能库永远会有失败的时候关键是怎么让失败不拖垮整个对话。我设计了一套三级降级一级降级同类型技能替换比如“读取文件”失败时先试试“获取文件摘要”至少给用户部分信息。二级降级纯模型兜底如果技能硬失败且没有替代方案让模型基于已有上下文做一次推测性回答但必须明确告知用户这是推测。三级降级用户澄清如果连推测都没把握就触发反问请用户确认路径或意图。看起来很简单但很多人会忽略一个细节降级不是无条件的。如果某个技能已经连续失败三次我会在上下文里标记该技能“暂不可用”后续路由阶段直接跳过它避免模型反复选中、反复失败最终把用户惹毛。4. 上下文窗口的分配与长期记忆的接入4.1 技能库不该把全部上下文塞给模型做一个 Agent 系统不操心 token 成本是不可能的。技能库如果设计得不好默认行为是把所有技能描述一股脑塞进系统提示词让模型任挑。这会有两个后果一是 token 开销巨大二是上下文被无关技能干扰模型反而更容易选错。我的做法是做一个“上下文预算”机制。把一次完整调用想象成一笔预算系统提示词只放基础信息和当前可能用到的技能摘要完整技能描述放在外部索引里等模型判断需要某类技能时再按需把对应描述加载进来。这样能节省至少 40% 的系统提示词 token 开销而且因为干扰减少选路准确率反而提升了。4.2 长期记忆与用户画像如何被技能调用Agent 的另一个痛点是记忆。比如用户上周让你监控过一个接口这周回来说“上次那个接口最近有异常吗”如果你没有长期记忆Agent 完全不知道“上次那个接口”指的是什么。我在 agent-skills 里给“记忆”也做成了技能。有一个记忆写入技能负责在对话中自动抽取用户身份信息、偏好、常用路径并存储到状态库另有一个记忆检索技能负责在对话开始时拉取相关记忆拼接到上下文里。技能调用时如果需要记忆不是直接读数据库而是通过状态接口查询这样彻底避免了“每个技能都自己写一套记忆读取逻辑”的重复造轮子问题。实际使用中记忆技能的收益比我想象中更大。它让 Agent 从一个“每次都是从零开始的无状态接口”变成了“记住了长期用户习惯的虚拟助理”用户的信任感会明显提升。4.3 多技能并发的冲突控制一个复杂任务往往需要并发调用多个技能。比如用户说“把这份报告上传到对象存储然后发消息给团队群”实际上涉及文件处理、API 调用、消息通知三个技能而且它们有先后顺序。我在执行器里做了一个非常轻量的状态机每个技能实例有 idle、running、waiting、done、failed 五种状态。当技能需要依赖另一个技能的结果时它进入 waiting 状态等待上游事件完成才能继续。所有技能实例的状态变化都会写入日志方便事后追踪整条调用链。这套状态机大约只花了我半天时间实现却让技能编排的可靠性提升了一个量级。5. 实操过程从零搭一套可以复用的 agent-skills5.1 先定边界只做你最需要的技能很多人搭技能库时会陷入“堆技能”的冲动恨不得一次性把网上所有工具都封装进去。我的建议是第一次搭只做 7 到 12 个技能覆盖你日常最高频的场景就够了。我的 MVP 技能包长这样技能名称用途依赖file_read读取文本文件内容无file_write写文件创建备份无shell_run执行白名单内的 Shell 命令file_readweb_search联网检索信息无http_request调用外部 HTTP 接口无sql_query查询结构化数据库config.yamldata_analyze对表格数据进行统计分析file_readtask_reminder创建定时任务提醒无memory_put写入长期记忆state.pymemory_get查询长期记忆state.py这个清单看起来不多但覆盖了个人助理场景下绝大部分需求。等到基础跑通后再加技能你会发现新增技能的成本非常低。5.2 搭建过程的完整步骤我按下面的顺序搭建可以保证每一步的结果都可验证不会攒到最后一次性排查一堆问题。第一步初始化目录骨架。按第二节的目录结构建好文件夹配置全局 config.yaml把日志和测试目录预留出来。第二步实现状态管理模块。用一个社区常见的 key-value 方式管理会话状态和长期记忆先只做内存版等跑通后再接 Redis 或数据库。第三步实现技能注册与路由。写一个 registry 模块能够自动扫描 skills 目录下的 skill.yaml 并注册技能。路由阶段先做“规则优先 向量召回”不用急着上 LLM 重排。第四步实现三个第一批技能file_read、shell_run、http_request。这三个技能基本是你后续所有复杂能力的底座。先不要做复杂业务先把“读”“跑”“调”三件事做稳。第五步实现执行器的上下文组装、输出校验和降级逻辑。这一步把执行器从“仅能触发技能”升级为“能处理失败和边界情况”。第六步写测试用例。重点不是测技能本身的逻辑而是测“模型意图到正确技能”的映射是否稳定。第七步接真实模型做端到端联调。我建议先用小模型跑再把模型规模逐步提升观察路由准确率的变化。5.3 一个最小可运行的执行器代码示例下面给出一段极简执行器代码核心目的是展示路由、注册和降级是怎么串起来的。为便于理解我把模型调用部分用伪接口代替。# core/executor.py import json from typing import Dict, Any from core.registry import Registry from core.context import Context class Executor: def __init__(self, registry: Registry): self.registry registry def _route(self, user_request: str, context: Context) - str: # 第一层规则匹配这里用关键词简单演示 if 读取 in user_request or 查看文件 in user_request: return file_read # 第二层向量召回 第三层LLM重排在这里省略 candidates self.registry.semantic_recall(user_request, top_k3) return self.registry.llm_rerank(user_request, candidates) def execute(self, user_request: str, context: Context) - Dict[str, Any]: skill_name self._route(user_request, context) skill self.registry.get(skill_name) if skill is None: return {status: failed, error: skill not found} # 依赖预检 for dep in skill.dependencies: if not context.has(dep): return {status: need_more_info, missing: dep} # 参数组装 try: args skill.extract_args(user_request, context) except KeyError as e: # 参数缺失降级到用户澄清 return {status: need_clarification, field: str(e)} # 真正执行 try: result skill.run(args, context) return {status: ok, result: result} except PermissionError: # fallback处理 fallback skill.fallback.get(permission_error) return {status: failed, fallback: fallback, error: permission denied}这个示例虽然简化了很多细节但已经能让你体会到执行器的核心机制路由、依赖预检、参数校验、降级处理都暴露出来了。实际工程里你只需要逐层把向量检索、LLM 重排、上下文预算和更细的校验逻辑填入对应位置即可。5.4 测试与评估没有回归测试的技能库就是定时炸弹技能库迭代非常快没有回归测试的话改一个新功能很可能会悄悄弄坏一个旧技能。我的测试思路分三层单元测试每个技能自身逻辑是否正确输入输出是否符合 schema。路由测试把历史对话整理成测试集验证模型把不同说法映射到正确技能的比例。端到端测试构造几个典型用户场景模拟完整对话链路观察最终结果是否满足预期。我通常会在每次新增或修改技能后跑一遍路由测试集用准确率和失败率两个指标做回归判断。如果路由准确率低于一个阈值就不允许上线。这套评估机制虽然朴素但能拦住大多数改动引入的回归问题。6. 踩坑实录agent-skills 使用中最常见的几个问题6.1 技能描述写得太像“人看的文档”模型根本不调用这是一个非常容易犯的错。最开始我写的技能描述偏向传统 API 文档把重点放在参数类型与返回结构上结果模型在对话中经常忽略这些技能。后来我才醒悟过来:技能描述的服务对象是模型而模型更像一个“需要看例子猜意图”的新人你必须提供典型对话和触发条件。解决方法是把描述重心从“接口说明”切换到“场景触发”。每次写描述时我都问自己用户说什么话时模型应该想到这个技能然后把这个说法写进 trigger 字段并配上两三条对话例子。改完之后技能调用率肉眼可见地提升。6.2 技能之间共享了很多隐式状态踩了“状态不同步”的坑我的技能库早期要处理一个场景先做数据查询再生成报告最后发送通知。这三个技能都需要知道“当前任务ID”和“查询时间范围”而它们各自去读配置、各自维护状态结果经常出现前一步更新了状态、后一步读取不到的情况。后来我把所有共享状态统一收拢到一个 state 模块里所有技能的读写都走同一个接口并且规定“先写后读”的依赖顺序。这样做之后状态同步问题基本绝迹。如果你也在做多技能编排强烈建议从一开始就使用集中式状态管理。6.3 只测单技能、不测链路上线后才暴露编排问题单技能测试通过不代表端到端就能跑通。技能之间的参数格式可能不兼容技能 A 的输出未必正好是技能 B 期望的输入。如果直接把两者串联经常会有隐蔽的 bug 到最后才爆发。我的经验是建立几个固定场景的端到端测试用例每次修改任何环节后都跑一遍。虽然有点繁琐但我已经记不清这套用例帮我拦下过多少次线上事故了。编排层的可靠性是靠一次次回归测试磨出来的。6.4 权限和沙箱一开始没有做严格后续补课成本很高做技能库时很多人会觉得“先能跑就行权限后面再加”。我一开始也是这么想的于是让 shell_run 技能支持任意命令。结果某次联调时一条命令差点把环境配置清掉吓出一身冷汗。后来我不得不把 shell_run 改造成命令白名单模式只放行常见、安全的命令凡是不在白名单里的命令必须走人工确认或专门开发的技能。这个教训我一直记着技能执行力越强权限控制就越重要千万不要在这件事上偷懒。还有就是文件类技能要限制可读写的目录范围网络请求技能要设置目标域名白名单或审批流。6.5 缺少可观测性定位问题像大海捞针有一次用户反馈某技能偶尔失败但我在日志里看不到任何线索只能靠反复复现去猜。就是因为早期我把日志写得过于简陋只记录了“调用了哪个技能”。后来我给每个技能调用增加了 trace_id并记录完整的输入参数摘要、路由路径、各阶段耗时和错误信息。这样再出问题时我只要查一个 trace_id整条链路就一目了然定位效率提升了数倍。如果你也在搭这类系统请务必在第一天就规划好“可观测性”。不需要一开始上分布式追踪平台只要在日志里加上 trace_id、阶段耗时和关键变量就能省下大量排查时间。6.6 技能越加越多模型反而更容易“选择困难”技能数量增长到三十个以上时路由准确率曲线会开始下滑。这是正常的因为它给了模型更多选项而每个技能描述都会占用一定上下文注意力。我用了两个办法缓解一是按场景给技能分组比如“文件操作组”“数据处理组”“提醒通知组”先让模型选组再选具体技能二是给核心高频技能更高的路由优先级让模型会更倾向于先考虑它们。通过这两步技能库规模扩展后路由准确率依然能保持稳定。结束语我自己搭完这套 agent-skills 并跑了近两个月之后最大的感受是真正让 Agent 变“好用”的不是又多接了一个大模型也不是堆了多少炫酷的工具而是把最基础的那些操作打磨到足够稳定、足够容易被模型理解和调用。这就像给一个很聪明但没有工作经验的新人配了一套标准的操作手册他才能把聪明劲儿用在真正需要判断力的地方。如果你正准备给自己的 Agent 补上“技能”这层肌肉记忆我的建议是先别急着抄大而全的方案挑两三个日常最痛的操作按文中的目录结构和执行器思路搭一个最小版本。跑通之后再逐步加技能、加记忆、加评估你会发现 Agent 的能力边界会你想象中更快地扩展。后面有机会我还会专门写一写技能评估数据集怎么构建以及长记忆存储怎么从内存版平滑迁移到持久化存储咱们下次再聊。