做一个 Agent 功能时,很多人第一反应是把所有工作流塞进系统提示词里。一开始只有几行,后来变成几百行,再后来变成了几千行:角色设定、格式要求、工具用法、领域知识、示例对话全部混在一起。每次改一个小功能都要小心不要破坏别的行为,模型推理时间和费用也肉眼可见地往上涨。这个场景,做过 Agent 项目的开发者应该都不陌生。
Agent Skills 的出现,本质上就是为了解决这种“单体提示词越来越难维护”的问题。它不是给你一个全新的模型,也不只是多一个工具,而是把 Agent 的能力从“写在提示词里的碎碎念”改造成“按需加载的模块化技能包”。这个改变看起来不大,但它同时影响了开发方式、复用方式、协作方式,还有 Agent 系统的可观测性。
这篇文章会从零开始讲清楚 Agent Skills 是什么、它的目录结构怎么写、SKILL.md 怎么设计,再带你亲手做一个可以运行的技能调度示例,最后给出常见坑和工程建议。读完你至少能完成两件事:第一,把一个重复使用的能力改造成标准技能包;第二,在一套本地 Demo 里验证技能能不能被正确触发和执行。
1. 为什么需要 Agent Skills:它到底解决了什么问题
先说最直观的痛点。你负责维护一个内部问答 Agent,需求不断叠加:要能查数据库、要会解析 Excel、要按固定模板输出日报、要处理 Markdown 表格。如果你是传统做法,大概率会把这些要求全部写进 system prompt。最早是 200 行,后来是 800 行,再后来连你自己都说不清某段规则是给哪个模块用的。
这里的问题不只是“字太多”。当提示词越来越长时,有几个非常具体的副作用:
- 模型很容易被无关指令干扰,关键约束反而不被遵守;
- 每次小改动都要重新评测全局效果,回归成本高;
- 多个 Agent 之间想复用同一套能力,只能复制粘贴;
- 非算法同学想参与 Agent 行为配置,几乎不可能。
这些问题不是靠“更好的模型”就能自动解决的。只要 Agent 的行为还是一坨扁平的文本指令,它的可维护性就一定会随规模增长而恶化。
Agent Skills 的核心判断是:把 Agent 会做的事情拆成技能目录,每个技能自带说明和资源,Agent 在运行时按需加载。系统提示词只保留基础身份和全局规则,具体怎么做一件任务,交给技能里的 SKILL.md 去描述,需要执行的脚本、模板、参考数据都放在技能文件夹内部。
这种做法的价值在于三层:
- 开发层面:技能是自包含的,开发、测试、上线可以独立进行;
- 复用层面:同一个技能可以被不同 Agent 和项目引用,天然具备组件化属性;
- 协作层面:只要会用 Markdown 写说明,业务同学也可以参与技能的定义和维护。
所以这篇文章适合三种读者:正在做 Agent 但被提示词工程折磨的人;想把已有工具能力沉淀成可复用模块的人;以及刚接触 Agent 开发、想知道下一步该学什么的人。如果你只是想了解概念,本文前几章足够;如果你想手动跑通,请重点看第 4 章到第 6 章。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Agent Skills 的核心概念与运行原理
2.1 技能包长什么样
看 Agent Skills 官方设计之前,先理解一个非常简单的类比:它很像手机里的 App。你的手机不需要把所有 App 的功能都内置在系统里,你需要地图时打开地图,需要付款时打开支付工具。Agent Skills 也是一样,它不是把所有能力都塞进系统提示词,而是把一个个能力打包成独立技能,模型看到用户请求后判断“该用哪个技能”,再把这个技能的说明和资源加载进来。
一个标准技能包通常包含两部分:
- SKILL.md:给模型看的说明文档,描述这个技能什么时候用、怎么用;
- 附属资源:脚本、配置文件、模板、示例数据等实际执行需要的东西。
SKILL.md 是技能的核心。它通常以 YAML frontmatter 开头,包含 name 和 description 两个关键字段,后面是正文指令。name 是技能标识,description 的作用非常大:模型就是根据这段描述来判断当前请求是否需要调用这个技能。描述写得越模糊,模型越容易误触发;写得越具体,触发准确率越高。
2.2 Agent Skills 与传统方式的区别
要理解 Agent Skills 的原理,最好把它和两种传统方案放在一起对比。
第一种是“全写进 system prompt”。这种方式把使用说明直接暴露给模型,模型始终看得到这些内容。优点是实现简单,缺点上文已经说过:不可维护、不可复用、还会稀释重点指令。
第二种是 Function Calling / 工具调用。开发者先定义一个 JSON Schema,模型判断需要时返回一个函数调用请求,然后由你写的代码去执行。这套机制的优点是结构化、稳定,缺点是每个函数都要单独定义参数结构,写起来繁琐,而且它更适合“单个操作”,不太适合表达“多步骤工作流”或“领域知识+脚本结合”的复合能力。
Agent Skills 可以看作两者的中间形态:它保留了提示词的自然语言表达力,又把资源和步骤组织成可加载的独立单元;它不完全替代 Function Calling,但能承载更复杂的“知道怎么做”的能力。下表是一个快速对比:
| 维度 | 传统 System Prompt | Function Calling | Agent Skills |
|---|---|---|---|
| 能力组织 | 集中写在提示词 | 按函数拆分 | 按技能包拆分 |
| 描述方式 | 自然语言 | JSON Schema | Markdown + YAML frontmatter |
| 是否包含资源 | 否 | 否 | 是,可带脚本和模板 |
| 模型的加载负担 | 全部加载 | 按需调用 | 按需加载 |
| 复用能力 | 差,靠复制 | 一般 | 好 |
| 适合场景 | 简单固定对话 | 单步 API 调用 | 多步骤任务、领域工作流 |
2.3 触发机制:模型自主决策
理解 Agent Skills,还必须理解“触发”这件事。在传统工具调用中,触发逻辑通常由开发者显式调用;在 Agent Skills 体系中,很多实现是让模型在运行时根据用户请求和技能描述自动决定“要不要加载技能”。
这意味着 description 写得是否准确,直接决定了技能会不会被启用。比如你写了一个“把 JSON 格式化”的技能,描述是 Format JSON data nicely.,那用户说“帮我美化一下这段 JSON”时模型大概率能匹配上;但如果描述写得过于抽象,比如 Tools for data processing.,模型可能想不起来要加载它。
所以这里真正容易踩坑的地方是:很多人把 SKILL.md 当作文档写,只写“做了什么”,没有写“什么时候做、输入输出是什么、边界在哪里”。这会导致技能文件存在但模型根本不会用。
3. 环境准备与技能目录结构
在动手写代码之前,我们先约定一个本地环境。本文的 Demo 不依赖特定云平台,只要有一台能运行 Python 的电脑即可,操作系统不限。我更推荐 Windows 上使用 WSL 或 Git Bash 来跑命令,但直接在 PowerShell 里执行也没问题。
请先确认本机 Python 可用:
bash复制python3 --version
如果输出类似 Python 3.10.x 或更高版本,就可以继续。接下来我们创建一个干净的实验目录:
bash复制mkdir agent-skills-demo
cd agent-skills-demo
mkdir -p skills/json_formatter
这里的核心目录结构是:
text复制agent-skills-demo/
├── skills/
│ ├── json_formatter/
│ │ ├── SKILL.md
│ │ └── format_json.py
│ └── file_summarizer/
│ ├── SKILL.md
│ └── summarize.py
└── main.py
我建议每个技能都独立放在一个子目录中,目录名使用小写字母和下划线,例如 json_formatter,不要用空格或中文目录名,否则在跨平台调用时会有很多不必要的麻烦。
这个结构看起来简单,但它有一个重要的设计原则:技能是自包含的。也就是说,技能需要的所有脚本、模板、参考数据都应该放在它自己的目录里,而不是散落在项目其他地方。这样做的好处是,以后你想把这个技能复制到另一个 Agent 项目,只需要复制整个文件夹。
顺带一提,如果未来要在 ChatGPT 或类似平台中分享技能,通常也是把一个技能文件夹压缩成 zip 再上传。所以养成“一个技能一个文件夹”的习惯,后面迁移和分享都会顺畅很多。
4. 从零实现第一个技能:JSON 格式化
现在我们从零写一个最实用的技能:JSON 格式化。它逻辑简单,但足以展示 SKILL.md 和脚本之间怎么配合。
4.1 编写 SKILL.md
文件路径:skills/json_formatter/SKILL.md
markdown复制---
name: format_json
description: 将用户提供的 JSON 字符串或 JSON 文件格式化为带缩进的可读形式。当用户提到“格式化 JSON”“美化 JSON”“JSON 缩进”“pretty print”等需求时,使用本技能。
---
# JSON 格式化技能
## 输入
用户会提供 JSON 字符串,或者提供一个 JSON 文件路径。
## 执行步骤
1. 如果用户提供了文件路径,先读取文件内容。
2. 使用 `python3 format_json.py` 处理输入内容。
3. 将脚本输出的结果作为最终回复返回给用户。
## 输出
处理后的 JSON 文本,使用 4 个空格缩进,并按 key 排序。
这段描述虽然短,但包含了几个值得注意的细节:
description写出了触发场景,并列出常见说法;- 正文里有明确的输入、步骤、输出描述;
- 它还指定了使用哪个脚本、脚本的输入方式。
如果你去看一些真实技能设计文档,会发现它们比这个更长,但结构是类似的:先说明“我是什么”,再说明“什么情况用什么输入”,最后说明“怎么输出”。
4.2 编写技能脚本
文件路径:skills/json_formatter/format_json.py
python复制import json
import sys
def format_json_text(text: str) -> str:
try:
data = json.loads(text)
except json.JSONDecodeError as e:
raise ValueError(f"无效的 JSON 输入: {e}")
return json.dumps(data, indent=4, ensure_ascii=False, sort_keys=True)
def main():
# 支持两种输入:命令行参数传 JSON 字符串,或管道传入
if len(sys.argv) > 1:
raw = " ".join(sys.argv[1:])
else:
raw = sys.stdin.read()
result = format_json_text(raw)
print(result)
if __name__ == "__main__":
main()
这个脚本不依赖第三方库,只使用标准库 json。它做了三件事:接收输入、解析、格式化输出。当模型决定使用这个技能时,它会读取 SKILL.md 中的指引,然后通过约定的命令执行脚本。
这看起来很简单,但要注意一个关键点:SKILL.md 中的命令必须和实际脚本完全一致。如果你在文档里写 python3 format_json.py <input>,但脚本实际上是从标准输入读取的,那么 Agent 在执行时就会失败。技能包的自包含特性,决定了 SKILL.md 和脚本之间必须像 API 文档和 API 实现一样严格同步。
4.3 手动验证脚本
先不急着接入 Agent,我们手动跑一遍脚本,确认它本身是可靠的:
bash复制cd skills/json_formatter
echo '{"name":"agent","version":1}' | python3 format_json.py
预期输出:
json复制{
"name": "agent",
"version": 1
}
一切正常。这里其实就体现了 Agent Skills 工程化的一个基本思想:先保证资源可用,再让模型去调用它们。如果脚本本身失败,模型怎么调都不会成功;但如果脚本本身可靠,模型出错的概率就会下降很多。
5. 完整实战:实现一个本地技能调度 Demo
上一节的技能是孤立的,要让 Agent 能真正“看见”它并使用它,还需要一个调度机制。官方 SDK 可能提供内置的 skills 加载能力,不同 SDK 的写法会略有差异。为了不依赖具体平台,这一节我们写一个简单但完整的本地 Demo,模拟“模型决定加载技能”的过程。
我们不真的调用大模型,而是用一个关键词匹配来决定触发哪个技能。这样做的意义在于:让你理解技能调度的骨架。把这段骨架换成真实模型调用,你就能在项目中使用 Agent Skills 了。
5.1 设计目标
我们希望用户输入一句话,程序通过分析这句话,决定加载 skills/ 下的哪个技能,然后执行对应脚本,最后把脚本输出返回给用户。
这是一个最小可用的“技能调度器”,它包含三个组件:
- 技能注册表:扫描
skills/目录,读取每个SKILL.md的 frontmatter; - 选择器:根据用户输入和技能描述,选择一个技能;
- 执行器:调用技能文件夹下的脚本并返回结果。
5.2 官方 SDK 调用方式示意
在真实项目里,如果你使用 OpenAI 这类平台的 SDK,调用方式通常类似下面的代码(由于 SDK 一直在更新,这里只写思路,参数名以官方文档为准):
python复制from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-5", # 可用模型以官方列表为准
input="帮我把这段 JSON 格式化: {\"id\":1,\"name\":\"demo\"}",
skills=["skills/json_formatter"]
)
print(response.output_text)
这段代码表明:你将技能目录传给平台,平台负责解析 SKILL.md 并在合适的时候加载脚本。这个方向是正确的。不同 SDK 版本的参数形式可能变化,但底层思路都是“技能目录 + SKILL.md 描述 + 模型自主触发”。
现在,我们用本地 Demo 复刻这个思路。
5.3 实现技能调度 Demo
文件路径:agent-skills-demo/main.py
python复制import re
import subprocess
import sys
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent
SKILLS_DIR = BASE_DIR / "skills"
def parse_frontmatter(skill_md: str):
"""解析 SKILL.md 的 YAML frontmatter,提取 name 和 description。"""
match = re.match(r"^---\s*\n(.*?)\n---", skill_md, re.DOTALL)
if not match:
return {}, skill_md
front_text = match.group(1)
meta = {}
for line in front_text.strip().splitlines():
if ":" in line:
key, value = line.split(":", 1)
meta[key.strip()] = value.strip()
body = skill_md[match.end():].strip()
return meta, body
def scan_skills():
skills = {}
for skill_dir in SKILLS_DIR.iterdir():
md_file = skill_dir / "SKILL.md"
if not md_file.exists():
continue
content = md_file.read_text(encoding="utf-8")
meta, _ = parse_frontmatter(content)
if not meta.get("name"):
continue
skills[meta["name"]] = {
"dir": skill_dir,
"description": meta.get("description", ""),
"scripts": list(skill_dir.glob("*.py")),
}
return skills
def choose_skill(skills: dict, user_input: str):
"""根据用户输入中的关键词,选择最合适的技能。"""
for name, skill in skills.items():
desc = skill["description"].lower()
if any(kw in user_input.lower() for kw in desc.split()):
if any(kw in user_input.lower() for kw in ["json", "格式化", "美化"]):
return name
return None
def run_skill(skill_dir: Path, user_input: str):
"""执行技能目录下的第一个 Python 脚本,并把用户输入作为参数传入。"""
scripts = list(skill_dir.glob("*.py"))
if not scripts:
return "错误:技能目录下没有可执行的 Python 脚本"
script_path = scripts[0]
# 这里只做演示;生产环境中应严格限制命令执行权限
result = subprocess.run(
[sys.executable, str(script_path)],
input=user_input,
capture_output=True,
text=True,
encoding="utf-8",
)
if result.returncode != 0:
return f"脚本执行失败:{result.stderr}"
return result.stdout.strip()
def main():
print("本地 Agent Skills 调度 Demo")
print(f"技能目录:{SKILLS_DIR}")
skills = scan_skills()
print(f"已扫描到技能:{', '.join(skills.keys()) or '无'}\n")
if not skills:
return
user_input = input("请输入你的请求:")
selected = choose_skill(skills, user_input)
if not selected:
print("没有匹配到技能。")
return
print(f"已选择技能:{selected}")
skill = skills[selected]
output = run_skill(skill["dir"], user_input)
print("脚本输出如下:")
print(output)
if __name__ == "__main__":
main()
这段代码有几个地方需要解释:
第一,scan_skills() 负责扫描目录并读取 SKILL.md 的 frontmatter。这就是技能注册的过程。真实实现里,SDK 往往还会解析更丰富的字段,比如 allowed-tools、license,但我们这里只关注最小字段。
第二,choose_skill() 是简化版的模型决策。真实系统中,这个决策由大模型根据 SKILL.md 的描述来完成;这里我们用关键词匹配来演示流程。你能看到,决策依赖的是 description 字段,这再次说明描述质量有多重要。
第三,run_skill() 执行脚本。这里使用 subprocess 来模拟 Agent 调用本地脚本。在实际项目中,这往往会通过沙箱或白名单工具执行,目的都是同一个:让模型通过标准方式使用技能提供的资源。
5.4 在 Demo 中添加第二个技能
只有第一个技能还不够,我们再加一个 file_summarizer 技能,顺便验证多技能并存的效果。
文件路径:skills/file_summarizer/SKILL.md
markdown复制---
name: summarize_file
description: 读取文本文件并统计行数、单词数、字符数。当用户要求总结文件或分析文本文件统计信息时使用。
---
# 文本统计技能
## 输入
用户提供文件内容或文件路径。
## 执行步骤
1. 将文本内容传给 `summarize.py`。
2. 脚本会输出行数、单词数、字符数。
## 输出
一个包含统计结果的简短说明。
文件路径:skills/file_summarizer/summarize.py
python复制import sys
def main():
text = sys.stdin.read()
lines = len(text.splitlines())
words = len(text.split())
chars = len(text.replace("\n", ""))
print(f"行数: {lines}, 单词数: {words}, 字符数: {chars}")
if __name__ == "__main__":
main()
现在扫描技能时,目录里会有两个技能。你可以手动输入“统计一下这个文件内容”,程序会尝试匹配描述中的关键词。当前的匹配逻辑写得比较粗,这只是教学演示,真实场景中请直接使用大模型决策。
6. 运行结果与效果验证
让我们实际运行一次 Demo,看看完整流程。打开终端,进入项目根目录:
bash复制cd agent-skills-demo
python3 main.py
输入一个请求,比如:
text复制请输入你的请求:帮我格式化 JSON {"token":"abc","count":2}
预期看到类似输出:
text复制本地 Agent Skills 调度 Demo
技能目录:.../agent-skills-demo/skills
已扫描到技能:format_json, summarize_file
请输入你的请求:帮我格式化 JSON {"token":"abc","count":2}
已选择技能:format_json
脚本输出如下:
{
"token": "abc",
"count": 2
}
如何判断这个 Demo 是否成功?看三个信号:
- 技能注册是否成功:输出的技能列表里是否包含
format_json; - 技能选择是否正确:用户请求包含“JSON”和“格式化”,选择器应当选中
format_json; - 技能执行是否正确:输出的 JSON 是缩进后的可读形式。
如果第 2 步失败,说明选择逻辑有问题,可以去检查 choose_skill() 中的匹配条件。如果是真实模型决策,这类问题通常出在 SKILL.md 的 description 不够具体。
再测试第二个技能:
bash复制echo -e "hello world\nhello agent" | python3 main.py
注意,这个输入方式是基于管道传入,demo 中用 input() 读取用户输入,所以请直接运行后手动输入:
text复制请输入你的请求:请统计这段文本的行数和单词数
如果选中 summarize_file,脚本会读取用户输入并输出统计结果。因为脚本是从 stdin 读取的,所以统计的对象其实是整个请求文本。这里虽然统计结果“不是特别有意义”,但它完整展示了加载、选择、执行、返回的链路。真实项目中,你当然会设计更合理的输入传递方式,例如把用户附件文件路径传给脚本。
如果运行中 scan_skills() 找不到任何技能,优先排查:skills/ 目录是否在当前工作目录下、SKILL.md 是否存在、frontmatter 是否以 --- 开头和结尾。细节问题我们在下一节集中整理。
7. 常见问题与排查思路
下面是 Agent Skills 实践中最常见的几类问题,可以从上往下排查。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 扫描技能时没有任何技能被注册 | skills/ 目录路径不对,或 SKILL.md frontmatter 解析失败 |
打印 SKILLS_DIR 实际路径,确认文件存在;检查 frontmatter 是否有 --- 和字段 |
修正目录位置;重写 frontmatter,确保 name 和 description 字段存在 |
| 模型从不触发某个技能 | description 写得太抽象,或触发条件描述与实际表述不匹配 |
把技能说明展示给模型或同事看,问他们“什么请求会触发这个技能” | 重写 description,尽量列举用户可能说的原话,例如“格式化 JSON”“美化 JSON” |
| 技能被错误触发 | 多个技能 description 重叠,或描述过于宽泛 | 查看选择器的匹配日志;对比各技能描述 | 明确技能边界;为每个技能增加“不要用于什么场景”的说明 |
| 脚本执行失败 | SKILL.md 中命令与脚本参数不一致,或脚本依赖缺失 | 手动在终端执行脚本命令;查看异常堆栈 | 统一命令格式;在技能说明中写明运行方式和依赖 |
| 上传技能包后解压失败 | zip 压缩时把外层目录也压进去了 | 检查 zip 内部结构,确认 SKILL.md 是否在压缩包根目录 | 先进入技能文件夹,再对内部文件进行压缩 |
| 技能调用存在安全风险 | 脚本会执行不受信任的输入或系统命令 | 审查技能涉及的 subprocess、shell 调用 | 限制脚本权限;不要给技能写入密钥;生产环境使用最小权限沙箱 |
在这张表里,我想特别强调第一个问题。很多第一次写 SKILL.md 的人,会把 YAML 的缩进写错,导致 name 字段读不出来。YAML frontmatter 中通常不需要缩进,顶格写字段名即可。如果你用了复制粘贴导致的隐藏字符,也会出现解析异常。遇到这类问题,优先打印解析后的 metadata 来定位,而不是凭空猜。
8. 最佳实践与工程建议
Agent Skills 的工程化程度,往往决定了它在真实项目里好不好用。以下几条是从实践角度看最有价值的经验。
8.1 把 description 当作“产品文案”来写
description 不是给自己看的技术注释,而是模型判断是否加载技能的决策依据。它应该写清楚三件事:什么时候用、输入是什么、不用在什么场景。一个反例是 description: JSON tools.,这等于没写。一个合格的反例是 description: 处理与 JSON 相关的所有问题,包括格式化、校验、解析、转换。当用户提到 JSON、格式化、美化、pretty print 时使用。 它给出了触发场景,但没有过度承诺。
8.2 一个技能只做一件事
技能包要足够内聚,才能被稳定复用。如果你发现某个技能在描述里写了“既能格式化 JSON,又能统计文件,还能转换 Excel”,说明它太大了,应该拆成三到四个独立技能。技能的拆分原则和代码模块拆分没有本质区别:单一职责、接口清晰、便于测试。
8.3 脚本调用要有白名单和安全边界
SKILL.md 里的指令最终会被模型转换成实际执行动作。如果技能脚本内允许任意命令执行,相当于给了模型一把万能钥匙,这在生产环境是严重的风险。建议做三件事:
- 技能只提供可枚举的脚本入口,不暴露 shell 拼接能力;
- 脚本运行在受限用户、限定目录、极限超时的环境中;
- 不要在技能包中存放密钥、token 或数据库连接串,需要敏感信息时通过运行时的环境变量注入。
安全这件事在 Agent Skills 里容易被忽视,因为技能看起来只是“文档+脚本”,但在 Agent 手里,文档描述是不可信的输入,脚本执行是真实动作。你必须在技能设计阶段就假设输入可能被恶意构造,然后从脚本侧做校验。
8.4 为技能配置日志和评测
传统提示词很难做单元测试,技能包却可以。你可以为每个技能准备几组标准测试输入和预期输出,每当 SKILL.md 有改动就运行一遍,确认模型能据此正确加载和执行。最起码,要在技能调用入口打日志,记录“何时加载了哪个技能,输入是什么,脚本输出是什么,耗时多少”。有了这些日志,技能被误触发或者执行失败时,你才能在几分钟内定位问题。
8.5 善用版本管理
技能是代码,也是配置。把 skills/ 目录纳入 Git 管理,每次修改 SKILL.md 或脚本都走 MR/PR 评审。对于有多个环境的需求,可以用带版本号的技能目录名,例如 json_formatter_v2,但注意别真的把版本写进目录名,更好的方式是在 SKILL.md frontmatter 中维护 version 字段,让 CI 在扫描时做校验。这样能避免出现“明明改了描述,线上还是旧行为”的尴尬。
8.6 先让一个真实任务跑通,再推广
如果你所在团队刚开始引入 Agent Skills,不建议一次性把所有流程都改造成技能。更稳妥的做法是:挑一个高频、有明确边界的任务先做,比如“按模板输出周报”或“解析回收站上传的文件”,全程跟踪它的触发率和成功率。确认整套链路稳定后,再逐步把其他能力沉淀成技能。这个节奏符合组件化的通用演进规律:先局部验证,再扩大范围。
9. 总结与后续发展方向
Agent Skills 真正的价值不是“多了一种写提示词的方式”,而是改变了 Agent 工程中能力的组织形态。它通过 SKILL.md 把“模型何时加载技能”和“技能如何执行”分离,让开发、复用、测试、协作都变成组件化操作。
这篇文章完成了四件事:
- 解释了 Agent Skills 的核心原理和它与 System Prompt、Function Calling 的区别;
- 带你搭好本地技能目录结构,并实现了 JSON 格式化技能;
- 实现了一个不依赖任何云平台的本地技能调度 Demo,让你亲眼看到注册、选择、执行全链路;
- 列出了典型问题和工程建议,重点覆盖了 description 设计、单一职责、安全边界、日志与版本管理。
从 Demo 走向真实项目时,你只需要把第 5 节中的选择器替换成真实大模型 API 调用,再补上日志、错误处理和安全沙箱,一个可用的小型 Agent 技能系统就会成型。
接下来值得深入的方向有三个:一是研究你自己的业务中哪些高频流程适合沉淀成技能,建议从人均重复性最高的任务开始;二是关注官方 SDK 对 skills 能力的最新写法,因为相关接口和参数仍在快速演进,请不要根据本文的示意代码直接上生产;三是把技能评测和观察性体系建设起来,Agent 能力越丰富,越需要一个可靠的方式知道它为什么这么干。
如果你正准备入坑 Agent 开发,我的建议只有一句话:别急着做“全能 Agent”,先做几个质量过硬的技能,把它们组合起来。这一条路,比堆长提示词稳得多。
