现在的大模型应用开发,已经慢慢从“单次问答”走向“多工具协同”和“智能体自主完成任务”。不少同学在看完各种 Agent 科普后,仍然会有一个困惑:模型能力再强,怎么让它真正帮我执行一个复杂任务?比如“帮我整理一个目录下的所有图片,按拍摄时间重命名并生成一份 Markdown 索引”,如果只靠提示词,大模型做不到,但如果你给它写好一个 Skill(技能),它就能非常稳定地完成。
这就是 Agent Skills 存在的意义。
这篇文章会从概念入手,讲清楚它和 Function Calling、Tools、MCP 之间的关系,然后带你在本地搭建一个最小可运行的 Agent Skill 项目,最后通过一个完整实战代码,走通“定义技能 → 注册技能 → 让 Agent 调用技能 → 输出结果”的闭环。文章较长,建议先收藏再慢慢看。
1. 为什么 Agent 需要 Skills
1.1 Agent 的“手”和“脑”
大模型本身擅长的是“思考”,也就是根据上下文生成文本。但它有几个天然短板:
- 不知道实时数据,比如今天的天气、最新股价。
- 不能调用外部系统,比如发邮件、操作数据库、调用支付接口。
- 执行过程不稳定,同一个任务换一种问法,可能流程就乱了。
为了解决这些问题,业界慢慢形成了“模型 + 工具”的架构。模型负责拆解任务、决定调用哪个工具,工具负责具体执行。
早期大家管这些工具叫 Function Calling 或者 Plugins,后来随着智能体框架的发展,出现了更细分的概念:Agent Skills。
1.2 Skills 是什么
Agent Skills 可以理解为“给 Agent 预装的一项专项能力包”。它不仅仅是一个 API 接口,而是一套完整的“技能描述 + 执行脚本 + 使用说明”。
典型结构如下:
skills/ └── image-sorter/ ├── SKILL.md # 技能说明文档 └── sort_images.py # 实际执行脚本SKILL.md 告诉模型“这个技能是干什么的、什么时候该用、怎么用”,脚本负责真正干活。模型读 SKILL.md 后,决定要不要调用这个技能;一旦调用,就执行对应脚本。
用一句话概括:Tools 是单个动作,Skills 是完成一类任务的方法包。
1.3 Skills 与传统 Tools 的区别
很多同学容易把 Skills 和 Tools 混在一起。这里做一个简单区分:
| 维度 | Tools | Agent Skills |
|---|---|---|
| 粒度 | 单个函数/接口 | 一组脚本+说明文档 |
| 触发方式 | 模型根据函数描述调用 | 模型理解技能说明后触发 |
| 学习成本 | 需要写清楚函数入参出参 | 需要写清楚使用场景和步骤 |
| 可复用性 | 一般只能复用函数本身 | 整个方法包可复制到其他项目 |
| 典型例子 | 查询天气接口 | 完整的数据清洗方法包 |
Skills 更接近“教模型一套方法”,而不是“给模型一把螺丝刀”。
1.4 本文实战目标
接下来我们会做一个完整的 Agent Skill 实战案例,目标是让 Agent 具备一个自定义技能:“文件归档助手”。
需求场景:
- 用户告诉 Agent:把某个目录下的文件按扩展名分类。
- Agent 调用 file-archiver 技能。
- 技能脚本扫描目录,按扩展名创建子文件夹并移动文件。
- 技能脚本生成一份归档报告。
整个项目不需要额外的大模型服务也能运行,核心是为了讲清楚 Skills 的机制。最后我们还会接入一个大模型客户端,演示“用户自然语言 → Agent 决策 → 调用 Skill”的完整流程。
2. 环境准备与项目结构
2.1 运行环境说明
本文示例代码使用 Python 编写,依赖很少,重点在于理解 Skills 的组织方式。
推荐环境:
- Python 3.10+(3.8 及以上应该也能运行,但本文示例用 3.10 语法)。
- 操作系统:Windows / macOS / Linux 均可。
- 编辑器:VS Code 或者任意你习惯的 IDE。
- 可选依赖:OpenAI SDK 或 Anthropic SDK(用于最后的大模型联动示例)。
版本提示:大模型 SDK 更新速度很快,文中涉及的 SDK 库以常见稳定版本为例,如果你安装的版本更新,调用方式可能略不同,建议以官方文档为准。
2.2 安装 Python 依赖
创建项目目录并进入:
mkdir agent-skills-demo cd agent-skills-demo建议先创建虚拟环境:
python -m venv venv source venv/bin/activate # Windows 下使用 venv\Scripts\activate安装后续可能用到的依赖:
pip install openai如果只是先跑通文件归档技能本身,不需要任何第三方库,Python 标准库的os、shutil、pathlib就够了。
2.3 项目结构规划
我们要在一个小项目中体现 Skills 的完整生命周期,建议按下面的目录组织:
agent-skills-demo/ ├── skills/ │ └── file-archiver/ │ ├── SKILL.md │ └── archive_files.py ├── data/ │ └── sample_files/ # 测试目录 │ ├── readme.md │ ├── notes.txt │ ├── photo.jpg │ ├── report.pdf │ └── script.py ├── agent/ │ └── skill_runner.py # 技能加载与执行器 ├── demo_manual.py # 手动调用技能示例 └── demo_with_model.py # 接入大模型的完整 DEMO这个结构不算复杂,但已经能体现出“技能包”和“执行器”分开的思想。后面每个文件都会给出详细代码。
3. Agent Skills 的核心概念拆解
3.1 SKILL.md 规范
在 Anthropic 的 Claude Skills 文档中,一个 Skill 目录的核心是SKILL.md文件,它使用 Markdown 编写,描述技能的使用场景、输入参数、输出格式和注意事项。
一个规范的 SKILL.md 通常包含:
name:技能名称。description:技能作用描述,模型会根据这段文字判断是否调用。when_to_use:什么情况下应该使用这个技能。inputs:需要哪些输入参数。outputs:会返回什么结果。examples:使用示例。notes:注意事项。
这样设计的本质是:让模型在没有执行脚本的情况下,也能准确判断“什么时候该调用、调用了会得到什么”。
3.2 技能脚本的调用方式
技能脚本是真正干活的代码。它和普通 Python 脚本没有本质区别,但有几个设计上的要求:
- 支持命令行参数输入。
- 结果尽量输出为结构化文本(JSON 或纯文本)。
- 失败时要返回清晰的错误信息。
- 不要硬编码路径,路径从参数传入。
这样设计是为了让 Agent 可以通过“构造命令”的方式灵活调用技能,而不是把逻辑写死。
3.3 技能发现与加载机制
所谓技能发现,就是让 Agent 在运行前知道“我有哪些技能可以用”。常见实现方式有两种:
- 约定目录扫描:Agent 启动时扫描
skills/目录下的所有SKILL.md,把技能说明注入系统提示词。 - 工具注册表:通过代码显式注册技能,把技能名称、描述、调用函数放入一个注册表。
本文采用方式一,因为更贴近“Skills 作为独立包”的设计理念。
3.4 Skills 和 Function Calling 的关系
这里再补充一个容易被问到的点:Skills 和 Function Calling 有重叠,但定位不同。
Function Calling 是模型接口层面的能力,它让模型输出一个结构化调用请求。 Skills 是应用层面的封装,它包含说明文档和执行脚本。实际开发中,经常是“Skills 里的脚本被封装成 Function Calling 接口暴露给模型”。
你可以把 Skills 看成“技能的仓库”,把 Function Calling 看成“技能暴露给模型的通道”。
4. 从零实现一个 Agent Skill:文件归档助手
4.1 编写技能说明文档 SKILL.md
首先创建技能目录和说明文件。
文件路径:skills/file-archiver/SKILL.md
--- name: file-archiver description: 将指定目录下的文件按扩展名分类归档,并生成一份归档报告。适合整理下载目录、项目文档目录等场景。 --- # File Archiver Skill 将指定目录下的所有文件按扩展名移动到对应子目录中,并在目录下生成 archive_report.json 报告文件。 ## When to Use - 用户希望整理某个目录下的文件时。 - 需要按文件类型分类存放时。 - 希望生成文件清单报告时。 ## Inputs - directory: 要归档的目录路径,必填。 - dry_run: 是否只预览不实际移动,可选,默认 false。 ## Outputs - 归档后的目录结构描述。 - archive_report.json 文件内容,包含处理文件数和分类统计。 ## Examples 输入: python archive_files.py --directory /tmp/test_dir 输出: 归档完成。共处理 5 个文件,分为 3 类。 报告文件:/tmp/test_dir/archive_report.json ## Notes - 脚本不会处理子目录内的文件,只处理目标目录下的一层文件。 - 如果目标文件已存在,会重命名为 filename_1.ext 避免覆盖。 - 建议先使用 dry_run 模式预览效果。这份文档是给模型读的,所以要写清楚触发条件和输入输出格式。
4.2 编写技能执行脚本
文件路径:skills/file-archiver/archive_files.py
这个脚本接受命令行参数,完成三个动作:
- 扫描目录。
- 按扩展名归档。
- 生成报告。
完整代码如下:
#!/usr/bin/env python3 """ 文件归档技能脚本。 用法示例: python archive_files.py --directory /path/to/dir python archive_files.py --directory /path/to/dir --dry_run """ import argparse import json import shutil from collections import defaultdict from pathlib import Path def get_file_category(ext: str) -> str: """根据扩展名返回分类名。""" ext = ext.lower() if ext in {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg"}: return "images" if ext in {".md", ".txt", ".doc", ".docx", ".pdf"}: return "documents" if ext in {".py", ".js", ".java", ".go", ".ts"}: return "code" if ext in {".zip", ".rar", ".7z", ".tar", ".gz"}: return "archives" return "others" def archive_directory(directory: Path, dry_run: bool = False) -> dict: """ 按扩展名归档目录文件。 返回包含处理统计的字典。 """ if not directory.exists() or not directory.is_dir(): raise ValueError(f"目录不存在: {directory}") # 分类统计 category_map = defaultdict(list) moved_count = 0 for item in directory.iterdir(): # 只处理文件,跳过子目录和报告文件本身 if not item.is_file(): continue if item.name == "archive_report.json": continue category = get_file_category(item.suffix) category_map[category].append(item.name) # 创建分类子目录 target_dir = directory / category if not dry_run: target_dir.mkdir(exist_ok=True) # 目标路径:避免重名覆盖 target_path = target_dir / item.name if target_path.exists(): stem = item.stem suffix = item.suffix counter = 1 while target_path.exists(): target_path = target_dir / f"{stem}_{counter}{suffix}" counter += 1 shutil.move(str(item), str(target_path)) moved_count += 1 # 生成报告 report = { "directory": str(directory), "dry_run": dry_run, "file_count": sum(len(files) for files in category_map.values()), "moved_count": moved_count if not dry_run else 0, "categories": {k: len(v) for k, v in category_map.items()}, "files": dict(category_map), } if not dry_run: report_path = directory / "archive_report.json" report_path.write_text( json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8", ) return report def main(): parser = argparse.ArgumentParser(description="文件归档技能") parser.add_argument("--directory", required=True, help="要归档的目录路径") parser.add_argument("--dry_run", action="store_true", help="只预览不实际移动") args = parser.parse_args() try: report = archive_directory(Path(args.directory), dry_run=args.dry_run) print(json.dumps(report, ensure_ascii=False, indent=2)) except Exception as exc: print(f"归档失败: {exc}") raise SystemExit(1) if __name__ == "__main__": main()这里有几个细节值得说明:
get_file_category函数把常见扩展名归为五类,映射关系可以按实际需求调整。- 重名文件使用
_1、_2后缀避免覆盖,防止数据丢失。 dry_run模式只统计不移动,便于模型先向用户确认。- 报告统一输出为 JSON,方便上层解析。
4.3 测试技能脚本
先创建测试数据目录和几个文件:
mkdir -p data/sample_files cd data/sample_files echo "# 测试文档" > readme.md echo "一些笔记" > notes.txt touch photo.jpg echo "测试PDF" > report.pdf echo "print('hello')" > script.py cd ../..先以 dry_run 模式运行,确认分类结果:
python skills/file-archiver/archive_files.py \ --directory data/sample_files \ --dry_run预期输出类似:
{ "directory": "data/sample_files", "dry_run": true, "file_count": 5, "moved_count": 0, "categories": { "images": 1, "documents": 3, "code": 1 }, "files": { "images": ["photo.jpg"], "documents": ["readme.md", "notes.txt", "report.pdf"], "code": ["script.py"] } }确认没有异常后,正式归档:
python skills/file-archiver/archive_files.py \ --directory data/sample_files运行后查看目录结构:
tree data/sample_files预期结果:
data/sample_files/ ├── archive_report.json ├── code/ │ └── script.py ├── documents/ │ ├── notes.txt │ ├── readme.md │ └── report.pdf └── images/ └── photo.jpg到这一步,技能本身的逻辑已经完整了。
5. 实现技能加载器:让 Agent 能发现技能
5.1 技能加载器的作用
前面我们写了技能脚本,但 Agent 还不会自动发现它。现在写一个小型技能加载器,模拟 Agent 框架的“技能发现”机制。
它的职责:
- 扫描
skills/目录下的所有子目录。 - 读取每个目录中的
SKILL.md文件。 - 解析技能名称和描述。
- 提供执行技能的统一入口。
文件路径:agent/skill_runner.py
""" 技能加载器:扫描 skills 目录,提供技能发现与执行能力。 """ import json import subprocess from pathlib import Path class SkillRunner: def __init__(self, skills_root: str = "skills"): self.skills_root = Path(skills_root) self.skills = {} self._scan_skills() def _scan_skills(self): """扫描技能目录,读取每个技能的 SKILL.md 基本信息。""" if not self.skills_root.exists(): return for skill_dir in self.skills_root.iterdir(): if not skill_dir.is_dir(): continue skill_md = skill_dir / "SKILL.md" if not skill_md.exists(): continue # 简单解析 name 和 description name = skill_dir.name description = "" in_front_matter = False for line in skill_md.read_text(encoding="utf-8").splitlines(): if line.strip() == "---" and not in_front_matter: in_front_matter = True continue if line.strip() == "---" and in_front_matter: break if in_front_matter and line.startswith("description:"): description = line.split(":", 1)[1].strip() self.skills[name] = { "name": name, "description": description, "path": str(skill_dir), "script": str(skill_dir / (name + ".py")), } def list_skills(self) -> list: """返回所有技能的基本信息。""" return list(self.skills.values()) def get_skill_prompt_block(self) -> str: """生成一段可供注入系统提示词的技能描述文本。""" lines = ["可用技能列表:"] for skill in self.skills.values(): lines.append(f"- {skill['name']}: {skill['description']}") return "\n".join(lines) def run_skill(self, skill_name: str, args: list) -> dict: """ 执行指定技能。 参数: skill_name: 技能名称,例如 file-archiver args: 命令行参数列表,例如 ["--directory", "data/sample_files", "--dry_run"] 返回: stdout、stderr 和返回码。 """ if skill_name not in self.skills: raise ValueError(f"未找到技能: {skill_name}") script = self.skills[skill_name]["script"] cmd = ["python", script] + args result = subprocess.run( cmd, capture_output=True, text=True, encoding="utf-8", ) return { "stdout": result.stdout, "stderr": result.stderr, "returncode": result.returncode, } def run_skill_with_json(self, skill_name: str, args: list) -> dict: """执行技能并将 stdout 解析为 JSON。""" result = self.run_skill(skill_name, args) if result["returncode"] != 0: return { "success": False, "error": result["stderr"], } try: data = json.loads(result["stdout"]) return { "success": True, "data": data, } except json.JSONDecodeError: return { "success": True, "raw": result["stdout"], }这个加载器的核心是subprocess.run,它通过命令行调用技能脚本。这样的好处是技能脚本可以用任何语言编写,不一定非得是 Python,只要支持命令行参数即可。
5.2 手动调用技能测试
文件路径:demo_manual.py
from agent.skill_runner import SkillRunner def main(): runner = SkillRunner(skills_root="skills") print("=== 可用技能 ===") for skill in runner.list_skills(): print(f"- {skill['name']}: {skill['description'][:50]}...") print("\n=== 注入系统提示词的文本 ===") print(runner.get_skill_prompt_block()) print("\n=== 以 dry_run 方式调用 file-archiver ===") result = runner.run_skill_with_json( "file-archiver", ["--directory", "data/sample_files", "--dry_run"], ) print(result) if __name__ == "__main__": main()运行:
python demo_manual.py如果输出中包含“归档失败”或者 JSON 报错,先检查技能脚本路径是否与skills/file-archiver/file-archiver.py一致。这里要说明一个问题:我们的技能脚本起名是archive_files.py,但加载器默认脚本名是技能目录名.py,所以这里要么把脚本改名,要么修改加载器逻辑。
为了让项目一致,推荐把脚本名改为file-archiver.py。
修改命令:
mv skills/file-archiver/archive_files.py skills/file-archiver/file-archiver.py如果保持原脚本名不变,也可以修改SkillRunner中的逻辑,让每个技能在 SKILL.md 中声明脚本文件名。这更像真实框架的做法。这里为了简单,统一使用“技能目录名.py”作为脚本名。
再次运行,确认技能加载器正常工作。
6. 接入大模型:用自然语言触发 Skill
6.1 整体思路
现在到了最关键的一步:让大模型根据用户输入,自动决定是否调用 file-archiver 技能。
流程如下:
- 用户输入:“帮我把 data/sample_files 整理一下”。
- 系统把技能描述注入系统提示词。
- 模型返回一个结构化 JSON 调用请求,比如
{"skill": "file-archiver", "args": ["--directory", "data/sample_files"]}。 - 系统解析 JSON,调用 SkillRunner 执行技能。
- 把执行结果返回给模型,模型生成人类可读的总结。
6.2 使用 OpenAI SDK 实现
先安装依赖:
pip install openai然后编写demo_with_model.py。
这里需要说明,OpenAI SDK 不同版本调用方式略有差异,本文以 1.x 版本为例。如果你用的是 0.x 老版本,API 调用方式需要相应调整。
""" 接入 OpenAI 的 Agent Skill 演示。 流程: 1. 调用模型,把技能描述和用户请求一起发送。 2. 模型返回结构化 JSON,指定要调用的技能和参数。 3. 执行技能。 4. 将结果再次交给模型总结。 """ import json import os from openai import OpenAI from agent.skill_runner import SkillRunner client = OpenAI(api_key=os.getenv("OPENAI_API_KEY", "your-api-key")) SYSTEM_PROMPT_TEMPLATE = """你是一个智能助手。你有以下技能可以调用: {skill_prompt} 如果用户请求涉及可用的技能,你必须返回如下 JSON(不要返回其他文字): {{ "skill": "技能名称", "args": ["参数1", "参数2"] }} 如果用户请求不涉及任何技能,请直接回复普通文本。 """ def build_messages(user_input: str, skill_prompt: str): system_prompt = SYSTEM_PROMPT_TEMPLATE.format(skill_prompt=skill_prompt) return [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_input}, ] def extract_skill_call(content: str): """解析模型返回内容。如果包含技能调用 JSON,则返回对应结构。""" try: data = json.loads(content) if "skill" in data and "args" in data: return data except json.JSONDecodeError: pass return None def main(): runner = SkillRunner(skills_root="skills") skill_prompt = runner.get_skill_prompt_block() user_input = "请把 data/sample_files 目录按类型整理一下,先预览不要真移动" messages = build_messages(user_input, skill_prompt) response = client.chat.completions.create( model="gpt-4o-mini", # 或你账号可用的其他模型 messages=messages, temperature=0, ) model_content = response.choices[0].message.content print("模型返回:", model_content) skill_call = extract_skill_call(model_content) if skill_call is None: print("模型未调用技能,直接回复:", model_content) return skill_name = skill_call["skill"] args = skill_call["args"] print(f"\n执行技能:{skill_name}") print("参数:", args) # 这里注意:如果模型没有传 --dry_run,我们可以补上,防止误操作 if "--directory" in args and "--dry_run" not in args: args.append("--dry_run") print("已自动追加 --dry_run,安全预览。") result = runner.run_skill_with_json(skill_name, args) print("技能执行结果:") print(json.dumps(result, ensure_ascii=False, indent=2)) # 把执行结果交给模型总结 summarize_messages = [ {"role": "system", "content": "你是一个助手,请用简洁的中文总结技能执行结果。"}, {"role": "user", "content": f"技能执行结果如下:\n{json.dumps(result, ensure_ascii=False)}"}, ] summary_response = client.chat.completions.create( model="gpt-4o-mini", messages=summarize_messages, temperature=0, ) print("\n总结:", summary_response.choices[0].message.content) if __name__ == "__main__": main()6.3 安全说明
上面代码中,我先强制加入了--dry_run,这是很重要的一个工程习惯。
文件移动、删除、覆盖这类操作,在生产环境中风险很高。如果你让 Agent 直接操作真实目录,建议遵循以下原则:
- 默认先 dry-run。
- 需要用户二次确认后再真正执行。
- 操作前备份关键文件。
- 尽量使用测试目录验证。
6.4 没有 OpenAI Key 怎么办
如果你暂时没有 OpenAI API Key,也可以不运行这一段,前面第 4、5 章的本地技能本身已经能说明 Skills 的核心机制。
如果想要一个完全离线、不依赖外部 API 的演示方式,可以写一个“伪模型决策”函数,用关键词规则模拟模型判断:
def mock_skill_call(user_input: str): """模拟模型决策过程,不使用外部 API。""" if "整理" in user_input or "归档" in user_input: return { "skill": "file-archiver", "args": ["--directory", "data/sample_files"], } return None这种设计在学习阶段非常实用,能让你在没有 API 的情况下先跑通整个调用链。
7. 常见问题与排查思路
在实际开发和运行中,下面几个问题出现频率最高。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 技能脚本无法导入 | 项目根目录不在 sys.path | 在项目根目录运行,或用python -m agent.skill_runner |
| 技能脚本名称与目录名不一致 | 加载器默认拼接脚本路径 | 统一脚本名,或在 SKILL.md 中声明脚本名 |
| 模型返回的不是 JSON | 提示词约束不够强 | 增加 few-shot 示例,使用更低 temperature |
| dry_run 模式下仍然移动了文件 | 参数解析逻辑有误 | 检查脚本中 dry_run 分支,确保移动代码在 if 内 |
| Windows 下 subprocess 中文乱码 | 编码未指定 | 在 subprocess.run 中设置 encoding="utf-8" |
| 技能目录被当成普通文件处理 | 扫描逻辑判断错误 | 检查是否使用 is_dir() 过滤 |
| 模型调用了不存在的技能 | SKILL.md 描述不清楚 | 重新检查技能描述,降低描述歧义 |
| 文件重名导致覆盖 | 没有做重名处理 | 使用带序号的新文件名,避免覆盖 |
这里特别要说一下编码问题。在 Windows 下,Python 默认编码可能是 GBK,如果你在执行脚本时遇到中文乱码或编码异常,可以在调用脚本前设置环境变量:
# Windows PowerShell $env:PYTHONIOENCODING="utf-8"或者在 Python 代码里,在main()开头加:
import sys sys.stdout.reconfigure(encoding="utf-8")另外,排查任何 Agent Skill 问题时,建议先脱离大模型,手动运行技能脚本。如果脚本本身能正常工作,那问题基本出在模型提示词或参数解析环节。
8. 最佳实践与工程建议
8.1 Skill 命名与目录规范
- 技能目录名使用短横线分隔,例如
file-archiver、db-backup。 - 脚本名与目录名保持一致,减少加载器复杂度。
- 每个技能目录必须包含 SKILL.md,否则加载器应跳过并告警。
- 同一个技能目录里如果有多个脚本,确保只有一个入口脚本,其他作为辅助模块。
8.2 提示词与技能描述设计
SKILL.md 里的 description 是模型判断是否调用的关键,不要写得太模糊。
反面示例:
description: 整理文件的技能。正面示例:
description: 将指定目录下的文件按扩展名分类归档,并生成归档报告。适合整理下载目录、项目文档目录等场景。关键词是“什么时候用”和“效果是什么”。
8.3 参数安全与权限控制
Agent Skill 落地到生产环境时,一定要关注参数注入问题。
比如你的技能脚本接收目录参数,那用户可以让 Agent 访问服务器任意目录。建议:
- 配置白名单目录,只允许技能操作允许范围内路径。
- 对参数做路径标准化检查,避免
../穿越。 - 敏感操作增加确认机制。
- 对执行用户做最小权限控制,不要用 root 运行 Agent。
- 涉及数据库变更、删除操作时,先备份,再执行,最后验证。
8.4 结果输出与可观测性
技能执行结果不要只输出一句“完成”。建议:
- 输出结构化 JSON。
- 记录执行耗时。
- 记录操作前后的状态变化。
- 写入执行日志。
这样上层 Agent 才能更好地判断“这次执行到底成功没有”。
8.5 Skill 的测试方法
一个成熟的 Skill 应该配套测试:
- 单元测试:测试核心函数。
- 集成测试:模拟命令行调用。
- 干跑测试:在测试目录中完整执行一次。
建议为每个 Skill 维护一个tests/目录。学习阶段也许觉得麻烦,但一旦技能数量多了以后,回归测试能帮你省下大量时间。
9. 学习路线与进阶方向
9.1 本文掌握要点
到这里,你已经完成了 Agent Skills 从概念到实战的闭环:
- 理解了 Skills 和 Tools、Function Calling 的区别。
- 掌握了 SKILL.md 的结构和编写规范。
- 用 Python 实现了一个文件归档技能。
- 写了一个技能加载器,实现了技能自动发现。
- 通过大模型接口实现了自然语言触发技能调用。
- 了解了生产环境下的安全和排错思路。
这套流程并不只适用于某一个框架。无论你以后使用 Claude Skills、OpenAI 的 Assistant 工具,还是 Dify、AgentScope 等国产框架,底层思想都很接近。
9.2 下一步可以学什么
- 学习 Anthropic 官方 Claude Skills 格式,它和本文结构非常接近。
- 研究主流 Agent 框架,比如 LangChain、CrewAI、Dify,看它们的 Tools 和 Skills 是怎么注册的。
- 尝试写一个“多技能协作”案例,比如一个技能负责收集数据,另一个技能负责生成报告。
- 学习 MCP(Model Context Protocol),理解 Agent 如何通过标准化协议访问外部数据源。
9.3 项目落地时的风险提示
最后,给你一个真实项目中的实用建议:先跑通最小闭环,再扩展技能数量。
很多开发者在学习 Agent 时会犯一个错误——一开始就追求技能数量,今天加一个天气查询,明天加一个数据库操作,最后发现模型经常选错工具。更好的做法是先把 1 到 2 个高质量技能打磨好,让模型的调用率稳定在较高水平,再逐步扩展。
存储、目录、文件操作类技能是练手首选,因为它们逻辑清晰、容易验证结果。等这一类技能稳定了,再去挑战数据库操作、外部 API 集成这些更复杂的技能。
希望这篇教程能帮你迈过 Agent Skills 的入门门槛。如果觉得有用,可以收藏备用;有任何问题,也欢迎在评论区交流。