☰
AI Agent技能库搭建实战:让大模型从“能聊”到“能干”
2026/9/25 8:22:12 网站建设 项目流程

如果你最近也在折腾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-skills

5.1 先定边界:只做你最需要的技能

很多人搭技能库时会陷入“堆技能”的冲动,恨不得一次性把网上所有工具都封装进去。我的建议是,第一次搭,只做 7 到 12 个技能,覆盖你日常最高频的场景就够了。我的 MVP 技能包长这样:

技能名称用途依赖
file_read读取文本文件内容无
file_write写文件,创建备份无
shell_run执行白名单内的 Shell 命令file_read
web_search联网检索信息无
http_request调用外部 HTTP 接口无
sql_query查询结构化数据库config.yaml
data_analyze对表格数据进行统计分析file_read
task_reminder创建定时任务提醒无
memory_put写入长期记忆state.py
memory_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_k=3) 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 的能力边界会你想象中更快地扩展。后面有机会,我还会专门写一写技能评估数据集怎么构建,以及长记忆存储怎么从内存版平滑迁移到持久化存储,咱们下次再聊。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询