从零开始构建 Agent 技能
Source: Dev.to
Agent Skills – How They Work & How to Integrate Them
View the complete implementation on GitHub
什么是 Agent Skills?
Agent skills 解决了一个简单的问题:当你尝试让代理擅长所有事情时,系统提示会变得臃肿。
与其把所有内容塞进一个巨大的提示中:
You're an expert at code review, git, file organization, API testing...
[2000 lines of instructions]
不如定义离散的、可复用的技能:
You have access to these skills:
- code-review: Reviews code for bugs and security
- git-helper: Git workflows and troubleshooting
- file-organizer: Organizes files intelligently
- api-tester: Tests REST APIs
Load them when needed.
每个技能都是一个自包含的 Markdown 文件,代理可以按需加载。
核心理念
技能 是存放在目录中的 Markdown 文件。它包括:
- YAML front‑matter – 包含
name、description以及可选的元数据。
(如果你对 front‑matter 不熟悉,请参阅这篇指南 ) - Markdown 正文 – 详细指令,充当临时系统提示。
当代理需要专业知识时,它会加载相应的技能:
User: "Review this code for SQL injection"
↓
Agent: "I need the code‑review skill"
↓
System: [Loads SKILL.md with security guidelines]
↓
Agent: [Follows those guidelines]
因此,技能是 结构化、模块化的提示,它们是可发现的,并且仅在需要时加载。
实际工作原理
1️⃣ 发现
扫描目录中的 SKILL.md 文件,仅解析它们的 front‑matter。
这会为你提供一个可用技能的列表,而无需加载完整内容,从而保持内存使用低。
skills/
├── code-review/
│ └── SKILL.md # name: code-review, description: …
├── git-helper/
│ └── SKILL.md
实现细节可在仓库的 discovery module 中找到。
2️⃣ 工具注册
将每个技能转换为 OpenAI function tool。LLM 将这些视为可调用函数:
{
"name": "activate_skill_code_review",
"description": "Reviews code for bugs, security, best practices"
},
{
"name": "activate_skill_git_helper",
"description": "Git workflows and troubleshooting"
}
描述至关重要——它指引 LLM 决定激活哪个技能。请尽可能具体、清晰。
3️⃣ 激活
当 LLM 调用技能函数时:
- 从磁盘加载完整的
SKILL.md内容。 - 将其作为 tool result(即临时系统提示)返回给模型。
- 让 LLM 继续执行,此时已受到该技能指令的引导。
这种惰性加载方式意味着即使你拥有 20 个技能,但只使用了 2 个,也只有这 2 个会被读取到内存中。
4️⃣ 执行
LLM 读取技能指令并像在系统提示中一样执行它们。任务完成后,技能指令会自然从上下文中消失,除非你显式保留它们以用于多轮交互。
什么是技能的样子
---
name: code-review
description: Reviews code for bugs, security, and best practices
version: 1.0.0
---
# Code Review Skill
You are an expert code reviewer.
* Identify potential bugs and security vulnerabilities.
* Suggest improvements for readability and performance.
* Provide concrete code snippets for fixes.
每个技能都遵循相同的结构:前置元数据(front‑matter)+ 详细的 markdown 正文。
快速回顾
| 步骤 | 发生的事情 |
|---|---|
| 发现 | 扫描目录,仅读取前置元数据。 |
| 工具注册 | 将每个技能注册为 OpenAI 函数。 |
| 激活 | LLM 调用函数 → 加载完整 markdown → 作为工具结果返回。 |
| 执行 | LLM 按照技能的指示完成当前任务。 |
使用这种模式,你可以让系统提示保持简洁,使专业知识模块化,并让代理动态获取所需的确切信息。祝编码愉快!
检查清单
安全
- 查询中的 SQL 注入
- 用户输入中的 XSS
- 身份验证绕过
质量
- 可读性
- 可维护性
- DRY 违规
性能
- N+1 查询
- 内存泄漏
- 低效算法
摘要: 简要评估
关键问题: 安全问题(如果有)
改进建议: 更好的代码建议
优点: 有效之处
Source: …
为什么这种模式有效
1. 上下文效率
与其一次性加载 10 KB 的指令,不如只加载 100 字节的元数据。完整指令只有在需要时才会出现。这在你按 token 计费时尤为重要。
2. 模块化
每个技能都是独立的。只需放入一个 SKILL.md 文件即可添加新技能——无需修改代码。想要移除技能?直接删除对应目录即可。
3. 清晰性
调试时,你可以准确看到是哪个技能被激活以及它提供了哪些指令。这比使用单一的大提示更容易排查问题。
4. 可复用性
在不同项目之间共享技能。别人的 api-tester 技能可以在你的代理中直接使用,零修改。技能成为了一套共享的专业知识库。
关键设计决策
惰性加载
不要 在启动时将所有技能加载到内存——这会违背初衷,因为你又回到了预先加载所有内容。
要 按需加载。在发现阶段解析 front‑matter,但在 LLM 实际请求之前,保持完整内容在磁盘上。
函数命名
为技能函数加上明确前缀,例如 activate_skill_code_review。这能让日志中的行为一目了然。当日志里出现 activate_skill_* 时,你就知道某个技能被激活了。
对话流程
顺序必须严格遵守。流程如下:
- 用户发送消息。
- LLM 返回
tool_calls(请求调用技能)。
关键:在对话中添加一条包含tool_calls的 assistant 消息。 - 添加一条 tool 消息,携带技能内容。
- LLM 继续执行技能指令。
- 最终响应。
如果跳过第 3 步,OpenAI 会拒绝你的请求。tool_calls 必须正确包含 type 字段以及嵌套的 function 对象。这是常见的坑。(详见 OpenAI 的工具文档)
多次工具调用的循环
技能可以串联。一个技能可能会激活代码执行,而代码执行又可能需要另一个技能。你的代理应循环处理,直至没有更多 tool_calls:
while True:
response = llm.chat(messages=messages, tools=tools)
if not response.get("tool_calls"):
break
handle_tool_calls(response)
每次调用都要传入 tools,即使在技能已激活后也是如此。否则,技能将无法使用其他工具(如代码执行)。(完整实现请参见此处)
实际考虑
技能范围
一个技能 = 一个领域。 保持聚焦。
好例子: code-review, git-helper, api-tester
坏例子: developer-tools(范围过大)
技能结构
使用清晰的章节并提供示例:
- 技能的功能
- 如何处理任务
- 预期的输出格式
- 良好结果的示例
一大段文字不起作用。结构化有助于 LLM 遵循指令。
错误处理
如果技能不存在怎么办?返回有帮助的错误,例如:
"Skill 'xyz' not found. Available: code-review, git-helper"
常见错误与故障排除
预先加载所有内容
问题:某些实现会在启动时加载所有技能,浪费内存和上下文 token。
解决方案:在发现阶段仅加载元数据。按需激活技能。
技能描述模糊
LLM 使用技能描述来决定激活哪个。请具体说明。
- ❌ “帮助编写代码”
- ✅ “审查 Python/JavaScript 代码的安全漏洞、PEP 8 合规性和性能问题”
请说明技能的功能、处理的任务类型以及关键能力。
工具调用格式错误
错误:Missing required parameter: messages[1].tool_calls[0].type
原因:OpenAI 需要特定的嵌套结构。tool_calls 必须包含 type 字段,并在 function 键下嵌套函数细节。
解决方案:使用正确的格式,即 type: "function" 并在 function 对象中嵌套细节。参见 OpenAI tools documentation。
激活技能后忘记包含工具
问题:激活技能后,LLM 无法使用其他工具(如代码执行)。
解决方案:在每次 LLM 调用时始终传入 tools。不要在激活技能后移除工具,因为技能可能需要它们。
技能缺乏结构
一大段文字不起作用。使用清晰的标题、项目符号、代码示例和预期输出格式。LLM 对结构化指令的遵循程度远高于散文。
何时使用技能
适用场景:
- 处理代码、git 和 DevOps 的多领域代理
- 具有专门工作流的代理
- 团队共享通用模式
- 遇到上下文限制的情况
不需要的情况:
- 单一用途的代理
- 提示词小且聚焦的代理
- 原型和实验
不要过度设计。如果系统提示简短且易于管理,通常不需要技能。
标准
SKILL.md命名约定- YAML front‑matter 架构
- 目录结构
- 最佳实践
遵循该标准意味着你的技能可以与其他实现一起使用。技能在项目和团队之间变得可移植。
构建你的第一个技能
- 创建目录
mkdir -p skills/my-first-skill - 创建
SKILL.md,并包含 YAML 前置元数据和 Markdown 指令。 - 将
SkillsManager集成到你的代理中 – 请参阅 GitHub 仓库获取完整代码。 - 测试它,让你的代理使用该技能并验证其已激活。
就是这样。添加新技能不需要修改代码——只需放入一个
SKILL.md文件。
底线
Agent 技能是带有加载机制的结构化提示。该模式之所以有效,是因为:
- 只加载所需内容,使上下文保持精简。
- 技能相互独立,使代理模块化。
- 支持技能复用,能够在不同项目之间共享技能。
- 通过清晰的激活日志,简化调试过程。
你可以在一个下午内构建出可运行的实现。核心的 SkillsManager 只有约 130 行 Python 代码。(查看实现)
- 从一个技能开始。
- 看看它是否有帮助。
- 在此基础上扩展。
完整的可运行实现已在 GitHub 上提供。可将其用作参考或你自己的代理的起点。
资源
- AgentSkills.io – 官方规范
- Claude Skills – Anthropic 的技能示例
- Open Agent Skills – 社区技能库
- Working Implementation – 本教程的完整代码
- WTF is Frontmatter? – 了解 markdown 文件中的 front‑matter/元数据
- Anatomy of a Prompt – 使用结构化方法编写有效 AI 提示的指南
- OpenAI Function Calling – 官方 OpenAI 工具文档