从零开始构建 Agent 技能

发布: (2025年12月26日 GMT+8 16:20)
13 min read
原文: Dev.to

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 文件。它包括:

  1. YAML front‑matter – 包含 namedescription 以及可选的元数据。
    (如果你对 front‑matter 不熟悉,请参阅这篇指南 )
  2. 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 调用技能函数时:

  1. 从磁盘加载完整的 SKILL.md 内容。
  2. 将其作为 tool result(即临时系统提示)返回给模型。
  3. 让 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_* 时,你就知道某个技能被激活了。

对话流程

顺序必须严格遵守。流程如下:

  1. 用户发送消息。
  2. LLM 返回 tool_calls(请求调用技能)。
    关键:在对话中添加一条包含 tool_calls 的 assistant 消息。
  3. 添加一条 tool 消息,携带技能内容。
  4. LLM 继续执行技能指令。
  5. 最终响应。

如果跳过第 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 架构
  • 目录结构
  • 最佳实践

遵循该标准意味着你的技能可以与其他实现一起使用。技能在项目和团队之间变得可移植。

构建你的第一个技能

  1. 创建目录
    mkdir -p skills/my-first-skill
  2. 创建 SKILL.md,并包含 YAML 前置元数据和 Markdown 指令。
  3. SkillsManager 集成到你的代理中 – 请参阅 GitHub 仓库获取完整代码
  4. 测试它,让你的代理使用该技能并验证其已激活。

就是这样。添加新技能不需要修改代码——只需放入一个 SKILL.md 文件。

底线

Agent 技能是带有加载机制的结构化提示。该模式之所以有效,是因为:

  • 只加载所需内容,使上下文保持精简。
  • 技能相互独立,使代理模块化。
  • 支持技能复用,能够在不同项目之间共享技能。
  • 通过清晰的激活日志,简化调试过程。

你可以在一个下午内构建出可运行的实现。核心的 SkillsManager 只有约 130 行 Python 代码。(查看实现)

  1. 从一个技能开始。
  2. 看看它是否有帮助。
  3. 在此基础上扩展。

完整的可运行实现已在 GitHub 上提供。可将其用作参考或你自己的代理的起点。

资源

Back to Blog

相关文章

阅读更多 »

为 AI 分配角色和名称

不稳定性问题:对 AI 提同一个问题两次,你会得到不同的答案。不是错误的答案——只是前后不一致。不同的强调,不同的……