在很多技术团队里,AI Agent 的落地都卡在同一个地方:模型很强,Agent 很笨。
强的是对话能力,笨的是具体干活。你让它改代码,它能改得像模像样;你问它“我们项目的发布流程是什么”“这份日志里的高频报错集中在哪几个接口”,它就容易开始一本正经地编。把所有规则都塞进提示词,提示词会越来越长、越来越不可维护;把能力直接做成工具,又涉及服务、协议、鉴权,太重了。夹在提示词和工具之间的这一层,正是 Agent Skill 要解决的位置。
最近我注意到 ConardLi / garden-skills 这个项目。名字很有画面感:garden 是花园,skills 是技能。表面看,它是一个把 AI Agent 技能整理成“花园”的仓库;往深一层想,“garden”这个词本身也传达了这类项目真正想表达的理念:技能不是一次写死、永不变化的静态文件,而是需要像植物一样被持续照料、修剪、培育的资产。这个判断,也是这篇文章想展开的核心观点——AI Agent 真正从“能聊天”走向“能干活”,靠的不只是模型升级,而是把技能当成一套可积累、可维护、可组合的工程资产。
这篇文章会从问题出发,讲清楚 Skill 是什么、和 Prompt / Tool 有什么区别,再以 garden-skills 这类技能集合为切入点,演示如何上手使用别人的技能、如何从零构建一个自己的 Skill、如何验证它真的被 Agent 调用,最后给出常见问题排查和工程上的最佳实践。如果你是正在做 Agent 应用,或者准备把 Agent 引入团队协作的开发者,这篇文章应该能帮你省下不少试错成本。
1. garden-skills 真正要解决的问题
一个 Agent 项目要想真实投入使用,首先要回答的问题就是:你的 Agent 会做什么、不会做什么?
大部分团队在第一版都会把答案写在提示词里。做一个客服助手,就把客服话术写进 system prompt;做一个代码助手,就把代码规范、打包命令、发布流程写进上下文;做一个数据分析助手,就把公司数据字典、报表口径写进前置说明。这种方案在 Demo 阶段没有任何问题,一旦进入真实项目,问题就开始集中爆发。
第一个问题是提示词膨胀。业务规则一多,提示词轻松写到几千字,模型越往后越容易忽略靠前的约束。更麻烦的是,不同业务规则之间存在优先级冲突,靠自然语言很难做清晰的裁决。你告诉它“回复要简洁”,又告诉它“必须给出完整排查过程”,模型每次都在两种要求之间摇摆。
第二个问题是不可复用。这个项目的客服话术,换一个项目完全用不了;A 团队辛苦总结的日志排查经验,B 团队不知道,又要从零积累。团队内部的“会干活的知识”基本处于口口相传状态,组织记忆力非常弱。
第三个问题是不可测试。提示词写得好不好,只能靠人工反复试。你很难对一段提示词做单元测试、做版本对比、做回归验证。一旦提示词被多人修改过,最后连谁改了什么、为什么改都说不清楚。
这些痛点不是模型能力提升就能自动解决的。模型理解力再强,也需要有人把某个任务的标准做法、工具调用方式、结果校验方法组织成一个可复用的单元。这部分工作,就是 Skill 的定位。
从项目命名来看,garden-skills 想做的更像是把零散的 Agent 技能集中管理起来,形成一套可以通过“目录结构 + 描述文件 + 脚本”复用的技能花园。相比单条提示词,它把“完成某类任务的经验”整体打包,让 Agent 在遇到对应场景时自动选择使用。更值得留意的是,“花园”这个隐喻还强调了一层意思:技能库不维护就会荒废,需要持续修剪、补种、淘汰。真正拉开团队差距的,往往不是第一个 Skill 写得多好,而是后续能不能把技能库持续养下去。
2. 基础概念:Skill、Tool、Prompt 到底有什么区别
想用好 garden-skills 这类项目,先要把一个基础概念理清楚:Skill 不是 Prompt,也不是 Tool。很多人会把三者混在一起,导致整个 Agent 工程的架构边界非常模糊。
2.1 三个概念的边界
先用一句通俗的话概括:
- Prompt 是“告诉模型该怎么想”的文本。
- Tool 是“让模型能做什么”的外部功能。
- Skill 是“教模型怎么把一件复杂事做完”的能力包。
举一个更贴近开发的例子。假设你要让 Agent 帮忙做代码评审:
用 Prompt 实现,就是写一段几百字的评审规则:“请检查代码风格、异常处理、日志规范……”这是最轻量的方式,但换个团队、换个语言,这段 Prompt 基本作废。
用 Tool 实现,就是开发一个调用静态检查服务的接口,比如接入 ESLint、SonarQube。它解决的是“能不能自动跑检查”的问题,但不负责“检查结果出来后怎么组织评审意见”。
用 Skill 实现,就是在一个目录里同时放评审规则、检查命令、报告模板、历史评审示例。Agent 在拿到一个 pull request 时,先判断“这个任务应该调用 code-review 技能”,然后加载技能里的文档,按里面的步骤执行检查,最后按模板输出评审结论。
2.2 一张表看懂区别
| 维度 | Prompt | Skill | Tool |
|---|---|---|---|
| 本质 | 文本指令 | 能力包(文档 + 脚本 + 资源) | 外部可调用功能 |
| 触发方式 | 用户或系统注入 | 模型按需判断调用 | 用户、应用或 Agent 框架调用 |
| 可复用性 | 低 | 高 | 中 |
| 可测试性 | 低 | 高 | 高 |
| 典型内容 | 规则、语境、示例 | SKILL.md、脚本、参考文档 | API、命令、SDK |
| 维护成本 | 低,但易失控 | 中等,结构清晰后可维护 | 较高,涉及服务治理 |
| 适合场景 | 对话控制、临时约束 | 复杂任务的完整方法论固化 | 系统交互、副作用操作 |
这句话值得重复一遍:不要用 Prompt 去做本该由 Skill 做的事,也不要为了一个简单判断去写一个 Tool。分层,是技能体系的第一课。
2.3 模型是如何决定调用哪个 Skill 的
这是很多新手最容易忽略的机制:Skill 不是由用户手动指定执行的,而是由模型根据任务的语义描述自动判断的。
每个 Skill 的 SKILL.md 里都有一个 description 字段。模型在读到一个用户请求时,会把这个请求和各个 Skill 的 description 做匹配,匹配度高才加载对应的 Skill 内容。也就是说,Skill 的 description 写得好不好,直接决定了 Agent 会不会在正确的时机使用它。这一点在后面的实操部分还会反复出现。
3. 一个 Skill 的标准结构:从 SKILL.md 到脚本
要判断 garden-skills 这类仓库里的技能好不好用,首先要学会看一个 Skill 的内部结构。目前主流 Agent 平台的 Skill 约定比较接近,一个 Skill 就是一个独立目录,核心文件是 SKILL.md,其余文件按功能组织。
3.1 常见目录结构
log-analyzer/ ├── SKILL.md ├── scripts/ │ ├── analyze.py │ └── requirements.txt ├── assets/ │ └── report_template.md └── references/ └── examples.md各文件职责如下:
- SKILL.md:技能说明书,也是技能入口。包含元信息(name、description)和执行指南。
- scripts/:具体执行任务的脚本。Skill 里最核心的可执行部分,通常放在这里。
- assets/:模板、数据文件、静态资源。比如报告模板、配置文件。
- references/:参考文档、示例、最佳实践。用于给模型提供“怎么把活干好”的上下文。
3.2 SKILL.md 到底写什么
SKILL.md 是整个技能的入口。模型先读它,再决定要不要加载 scripts 和 references。一个推荐的写法是:
--- name: log-analyzer description: 分析应用日志文件,提取高频异常、统计错误码分布,并生成 Markdown 格式的巡检报告。当用户希望排查线上问题、分析日志、定位错误时使用。 --- # 日志分析技能 ## 适用场景 - 用户提供了日志文件路径。 - 用户希望从日志中发现异常趋势或高频报错。 ## 执行步骤 1. 使用 scripts/analyze.py 分析日志文件。 2. 根据脚本输出结果生成巡检报告。 3. 报告必须包含:时间范围、异常数量、Top 错误、改进建议。 ## 依赖 - Python 3.9 及以上 - 使用命令:python3 scripts/analyze.py --help 查看参数说明这里有两个关键点。
第一,frontmatter 里的 name 和 description 是模型判断是否调用技能的依据。description 要写清楚“什么时候用”,而不是“技能是什么”。比如“当用户希望排查线上问题、分析日志、定位错误时使用”就比“这是一个日志分析工具”更容易被模型命中。
第二,正文里的执行步骤不一定要写成脚本逻辑,但一定要写清楚边界。比如“报告必须包含哪些内容”“哪些情况属于异常”“输出格式是什么”。这些规则会被模型当成行为约束,直接影响最终输出质量。
3.3 脚本在 Skill 里的角色
底层逻辑很简单:模型读文档做判断,脚本做确定性计算。Skill 之所以比纯 Prompt 可靠,是因为它能调用脚本,把日志解析、数据聚合、格式校验这些确定性动作交给代码完成。模型只在“判断场景、编排步骤、组织输出”这些需要语义理解的地方发挥作用。
所以在设计 Skill 时,一个核心原则是:能在脚本里实现的逻辑,就不要让模型自由发挥。
4. 上手实践:如何使用 garden-skills 这类技能集合
下面进入实操。由于不同仓库的目录结构可能不同,具体命令以该仓库 README 为准。这里以 garden-skills 一类典型的技能集合为例,演示通用接入流程。
4.1 第一步:获取技能集合
把仓库克隆到本地:
git clone https://github.com/ConardLi/garden-skills.git cd garden-skills克隆后先不要急着复制文件,先看目录结构:
ls -la一般来说,技能集合会有一个集中存放技能的目录,比如skills/。也有仓库会把每个技能放在独立子目录里,并配一个总览 README。先找到存放技能的根目录,再进入下一步。
4.2 第二步:阅读每个技能的说明书
进入一个技能目录后,重点看 SKILL.md 的 frontmatter 部分,确认它解决什么问题、依赖什么环境。
cat skills/log-analyzer/SKILL.md这一步非常重要。很多使用技能集合的人会栽在这里:看到目录很多就直接复制,结果有些技能依赖 Python 3.11,有些依赖 Node.js 20,复制完一运行就报错。先花五分钟看依赖说明,能省下后面一整天的排查时间。
4.3 第三步:把技能安装到 Agent 的 skills 目录
不同 Agent 工具对 Skill 的安装位置定义不同。以 Claude Code 为例,全局技能通常放在~/.claude/skills/,项目级技能放在.claude/skills/。
mkdir -p ~/.claude/skills cp -r skills/log-analyzer ~/.claude/skills/验证一下目录是否完整:
ls -la ~/.claude/skills/log-analyzer只要能看到 SKILL.md 和 scripts 目录,说明安装成功。如果你使用的是自研 Agent,可能需要在代码里指定 skill 的加载路径,具体以框架文档为准。
4.4 第四步:测试技能是否能被自动触发
安装完成后,重启 Agent 进程,然后直接提出一个匹配该 Skill 描述的任务。
分析一下 server.log 里最常见的 5 个错误,并整理成一份报告如果 Agent 正确加载了 log-analyzer 技能,它会先执行脚本分析日志,再按 SKILL.md 里的报告模板输出结果。如果响应里完全看不到技能相关中间过程,说明可能没有触发成功,需要回到 SKILL.md 的 description 描述上检查。
5. 从零构建一个“仓库健康体检” Skill:完整示例
理解现有技能的结构之后,真正有价值的能力是设计自己的 Skill。这一节我们从一个实际场景出发,手把手构建一个“仓库健康体检”技能。它适合作为团队内部代码仓库准入检查的辅助工具。
5.1 场景定义
代码仓库里经常出现这类问题:README 缺失、.gitignore 没配、依赖没有锁定、大文件被误入库。每次人工检查都要开着 GitHub 页面一个个点开看,费时费力。我们把这个检查过程封装成一个 Skill,让 Agent 在收到“帮我看下仓库是否规范”这类请求时自动执行。
5.2 创建目录结构
repo-health/ ├── SKILL.md └── scripts/ ├── check_repo.py └── requirements.txt先创建目录:
mkdir -p repo-health/scripts5.3 编写 SKILL.md
文件路径:repo-health/SKILL.md
--- name: repo-health description: 对本地代码仓库进行常规健康体检,检查 README 是否存在、.gitignore 是否配置、依赖是否锁定、历史提交大小等。当用户希望评估仓库规范程度、准备开源、或做代码库准入检查时使用。 --- # 仓库健康体检技能 ## 适用场景 - 用户提供一个本地仓库路径。 - 用户希望了解仓库在工程规范上的缺失项。 ## 执行步骤 1. 使用 scripts/check_repo.py 扫描仓库。 2. 脚本会输出每个检查项的状态:OK、WARN、FAIL。 3. 根据脚本结果生成简要报告,并给出改进建议。 ## 输出要求 - 按检查项逐条列出结果。 - 每项给出明确结论:通过、警告或失败。 - 失败项必须给出可执行的修改建议。5.4 编写检查脚本
文件路径:repo-health/scripts/check_repo.py
#!/usr/bin/env python3 """仓库健康检查脚本:输出检查项结果与改进建议。""" import os import subprocess import sys from pathlib import Path def repo_root() -> Path: if len(sys.argv) > 1: return Path(sys.argv[1]).resolve() return Path.cwd() def check_git(root: Path) -> dict: return { "item": "Git 仓库", "status": "OK" if (root / ".git").exists() else "FAIL", "message": "已初始化" if (root / ".git").exists() else "当前目录不是 Git 仓库", } def check_readme(root: Path) -> dict: names = ["README.md", "README.rst", "README.txt", "REAMDE"] for name in names: if (root / name).exists(): return {"item": "README", "status": "OK", "message": f"存在 {name}"} return {"item": "README", "status": "WARN", "message": "缺少 README 文档"} def check_gitignore(root: Path) -> dict: p = root / ".gitignore" if not p.exists(): return {"item": ".gitignore", "status": "WARN", "message": "缺少 .gitignore"} content = p.read_text(encoding="utf-8", errors="ignore").strip() if not content: return {"item": ".gitignore", "status": "WARN", "message": ".gitignore 为空"} return {"item": ".gitignore", "status": "OK", "message": ".gitignore 已配置"} def check_license(root: Path) -> dict: for name in ["LICENSE", "LICENSE.md", "COPYING"]: if (root / name).exists(): return {"item": "LICENSE", "status": "OK", "message": "存在许可证文件"} return {"item": "LICENSE", "status": "INFO", "message": "未检测到许可证(内部仓库可忽略)"} def check_dependency_lock(root: Path) -> list: results = [] markers = [ ("package.json", ["package-lock.json", "yarn.lock", "pnpm-lock.yaml"]), ("requirements.txt", ["poetry.lock", "Pipfile.lock"]), ("go.mod", ["go.sum"]), ] for manifest, locks in markers: if not (root / manifest).exists(): continue found = [lock for lock in locks if (root / lock).exists()] if found: results.append( { "item": f"依赖锁定 ({manifest})", "status": "OK", "message": f"存在 {found[0]}", } ) else: results.append( { "item": f"依赖锁定 ({manifest})", "status": "WARN", "message": f"{manifest} 存在但未找到锁文件", } ) return results def check_large_files(root: Path, limit_mb: int = 10) -> dict: large = [] for dirpath, _, filenames in os.walk(root): if ".git" in Path(dirpath).parts: continue for name in filenames: p = Path(dirpath) / name try: size = p.stat().st_size if size > limit_mb * 1024 * 1024: large.append((str(p.relative_to(root)), size)) except OSError: continue if large: detail = "、".join(f"{p} ({round(s/1024/1024, 1)}MB)" for p, s in large[:5]) return { "item": "大文件检查", "status": "WARN", "message": f"超过 {limit_mb}MB 的文件: {detail}", } return { "item": "大文件检查", "status": "OK", "message": f"未发现超过 {limit_mb}MB 的文件", } def format_result(r: dict) -> str: icon = {"OK": "[OK] ", "WARN": "[WARN]", "FAIL": "[FAIL]", "INFO": "[INFO]"}.get( r["status"], "[INFO]" ) return f"{icon} {r['item']}: {r['message']}" def main(): root = repo_root() print(f"检查仓库: {root}\n") results = [check_git(root), check_readme(root), check_gitignore(root), check_license(root)] results.extend(check_dependency_lock(root)) results.append(check_large_files(root)) for r in results: print(format_result(r)) print("\n检查完成。") if __name__ == "__main__": main()这个脚本用纯标准库写成,不需要额外安装第三方依赖,所以 requirements.txt 可以留空,也可以只写一行注释。脚本会依次检查 Git 仓库状态、README、.gitignore、许可证、依赖锁文件和大文件,并输出带状态的检查结果。
5.5 解释关键设计
这个 Skill 的设计里有一个很容易被忽略的点:脚本输出的是结构化文本,而不是让模型自己去算结果。真正决定“这个仓库是否规范”的判断逻辑全部在代码里,模型只负责把检查结果组织成报告、补充改进建议。这样做的好处是结果稳定、可复现,不会出现模型“看错”文件大小这种低级错误。
还有一点值得说明:SKILL.md 的 description 里写了“准备开源、或做代码库准入检查时使用”,这是在帮模型划清触发边界。如果没有这句,模型可能在你随口说“帮我看下这个项目”时也触发体检,反而显得多余。
6. 运行结果与效果验证
6.1 独立运行脚本
先在命令行里直接运行脚本,验证逻辑本身是否正确:
cd repo-health python3 scripts/check_repo.py .预期输出示例:
检查仓库: /path/to/repo-health [OK] Git 仓库: 已初始化 [WARN] README: 缺少 README 文档 [OK] .gitignore: 已配置 [INFO] LICENSE: 未检测到许可证(内部仓库可忽略) [WARN] 依赖锁定 (package.json): package.json 存在但未找到锁文件 [OK] 大文件检查: 未发现超过 10MB 的文件 检查完成。只要打印出上述内容,说明脚本本身没问题。如果报错,优先检查 Python 版本和当前目录。
6.2 验证 Skill 是否被 Agent 正确加载
把 repo-health 复制到 Agent 的 skills 目录:
cp -r repo-health ~/.claude/skills/然后重启 Agent 进程,向 Agent 提问:
帮我检查一下 /path/to/my-project 这个仓库规范不规范如果技能被正确触发,你会看到 Agent 先运行脚本,再基于脚本输出撰写报告。如果 Agent 只是泛泛回答“需要检查 README、.gitignore……”而没有实际执行脚本,说明技能没有被加载,重点检查 frontmatter 格式和目录位置。
很多人在这个环节遇到的坑是:改了 SKILL.md 或脚本后,Agent 仍然使用旧逻辑。这是因为部分 Agent 工具对 skills 目录有缓存,修改后必须重启进程,或者执行工具提供的 reload 命令。
7. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| Agent 完全没有调用技能 | description 写得不清晰,模型不知道何时使用 | 查看 SKILL.md 的 description 字段 | 用“当用户希望……时使用”明确触发条件 |
| 技能被调用了,但输出不稳定 | 正文执行步骤不够具体 | 检查 SKILL.md 中的执行步骤和输出要求 | 补充明确输出格式、必须字段、禁止行为 |
| 脚本执行报错 | 依赖环境不匹配 | 独立运行脚本,查看报错堆栈 | 在 SKILL.md 的依赖节注明版本要求 |
| 技能目录复制后不生效 | 放错目录或缺少 SKILL.md | 检查 skills 目录下是否存在 SKILL.md | 按 Agent 工具的规范放置,并重启进程 |
| 修改技能后仍走旧逻辑 | 技能被缓存 | 查看是否有热加载或缓存机制 | 重启 Agent 进程或执行 reload |
| 技能被过度调用,Token 成本上升 | description 触发范围过大 | 观察哪些请求会触发技能 | 收窄 description,只在真正相关时触发 |
| 脚本输出内容重复出现 | Skill 的正文要求模型重复输出脚本结果 | 检查 SKILL.md 是否要求“逐字复述脚本输出” | 改为“基于脚本输出生成摘要报告” |
排在第一位的最常见问题依然和 description 有关。这是一个需要反复强调的点:模型判断技能是否可用的唯一依据,就是 frontmatter 里的 description。这个字段写得太笼统,模型不知道该不该用;写得太具体,模型又会过度匹配。理想的写法是包含“任务类型”和“触发场景”两个要素。
8. 最佳实践与工程建议
8.1 命名规范:动词开头,一眼看懂
技能目录名和 name 字段建议用动词或“对象 + 动作”的风格,例如log-analyzer、code-reviewer、release-note-generator。避免使用agent-001这类无法表达语义的名字。名字里的信息量直接决定了后续维护时团队成员的查找效率。
8.2 description 是技能的灵魂
写 description 时,把自己想象成一个搜索引擎:用户请求就是搜索词,description 就是页面标题和摘要。它必须覆盖可能的表达变体,比如“分析日志”“看下报错”“排查线上问题”可能指向同一个技能。建议在 description 中同时出现动词和业务场景词,并测试至少三种不同的用户说法,确认都能命中。
8.3 坚持自包含原则
一个 Skill 目录应该包含它运行所需的全部内容:脚本、模板、参考文档、依赖说明。不要出现“先到某个内网盘下载数据文件”这种外部依赖。技能一旦依赖外部环境,可移植性就会断崖式下降。团队之间共享技能时,最理想的状态是“复制目录即可用”。
8.4 把确定性逻辑放进脚本
模型擅长的是理解、判断、生成文本;不擅长的是精确计算、解析结构化数据、判断文件大小。凡是能做确定性处理的逻辑,尽量放到脚本里。这样既能提高输出稳定性,也能减少模型在无关细节上的 Token 消耗。
8.5 最小权限与安全边界
Skill 里的脚本会在 Agent 运行环境中执行。必须保持最小权限原则:脚本不应该读取无关目录,不应该拥有比当前用户更高的权限,更不应该把密钥、令牌写死在脚本或 SKILL.md 里。在团队内部共享技能时,建议在评审清单里加一项“敏感信息扫描”。涉及删除文件、修改配置、发布变更等高风险操作,需要明确要求模型在执行前向用户确认,并在测试环境验证之后再使用。
8.6 用 Git 管理技能版本
技能本质上是一份代码资产,应该纳入版本管理。每次修改 SKILL.md 或脚本都走 git,让团队能回溯“这个技能的判断逻辑是什么时候改的”。更高阶一点的做法是,给每个 Skill 配一个冒烟测试脚本,比如用一个很小的示例数据验证脚本能跑通、输出格式符合预期。技能库越大,冒烟测试的收益越明显。
8.7 给技能设定退出条件
一个训练有素的技能,不仅要知道什么时候执行,还要知道什么时候不执行。在 SKILL.md 里显式写明“以下情况不需要使用本技能”,能有效避免模型在无关场景里强行套用。这个细节看似简单,实际效果非常明显:它直接降低了误触发率和无效 Token 消耗。
9. 总结与后续学习方向
回到文章开头的判断:AI Agent 的竞争力,正在从“模型智商”转向“工程化技能”。
garden-skills 这类技能集合项目给我们的启发,不在于某一个技能写得多漂亮,而在于它把“技能需要被持续建设”这件事变成了一种可见的工程实践。你 clone 一个技能库、试用几个技能,只是第一步;真正有价值的是建立一套属于自己的技能维护流程:技能的提出、评审、测试、发布、淘汰,每一环都应该有明确规则。
读完这篇文章,你可以按下面顺序做一次完整的实践:
- clone 一份 garden-skills 或其他技能集合仓库,阅读 2 到 3 个技能的 SKILL.md,理解它们的描述写法。
- 把其中一个技能安装到你的 Agent 环境,验证是否能在真实请求中触发。
- 按照第 5 节的示例,为你的团队构建第一个自己的 Skill,内容可以是你们最常做的重复性任务。
- 给这个 Skill 配一个冒烟测试,然后提交到团队仓库。
接下来值得深入的方向还有不少:Skill 与 MCP 服务如何配合、多 Agent 场景下技能如何共享、技能触发准确率如何评测、如何用版本化方式管理技能集。这些话题都建立在同一个基础上:先动手写出第一个真正能用的 Skill。
一个项目里最有价值的能力,往往是那些被组织起来、能反复使用的经验。智能体时代也是一样。