本系列将从零开始,用 Go 语言实现一个具备基本功能(工具调用、循环思考、MCP、Skill)的 Agent

代码仓库:https://gitee.com/lymgoforIT/learn-agent(chapter1对应第一部分的代码,以此类推)

本节目标:引入 Skill 概念,将其作为“工具集+执行流程”的封装,解决工具过载和流程不固定的问题。

第五部分:封装固化流程:创建你的第一个 Skill

引入 MCP 协议后,我们的 Agent 拥有了强大的工具扩展能力。但“能力越大,烦恼越多”。当工具库膨胀到一定程度时,两个核心问题暴露出来:

  1. 上下文爆炸与选择困难:为了让 LLM 知道有哪些工具可用,我们需要将所有工具的描述都塞进 Prompt。当工具有成百上千个时,这会消耗海量的上下文额度,甚至超出模型的上限。更糟糕的是,在海量工具的描述中,LLM 做出正确选择的难度也大大增加,就像让你在几百页的菜单中点菜一样。
  2. 流程不固定,效果不可靠:对于一些有固定步骤的复杂任务(例如“发布一个软件版本”),我们希望 Agent 能严格按照预设的流程来执行。但如果只提供一堆零散的原子工具,完全依赖 LLM 去“临场发挥”,其执行路径将充满不确定性,效果难以保障。

问题:如何既能让 Agent拥有丰富的潜在能力,又不必在每次对话时都背负所有工具的“认知包袱”?如何将一系列工具调用固化成一个可靠的“任务模板”?
解决方案:Skill。

Skill 是一个更高层次的抽象。我们可以把它理解为一个预先打包好的“任务攻略”或“标准作业流程 (SOP)”。一个 Skill 内部封装了:

  • 完成特定任务所需的工具子集。
  • 指导 LLM 如何一步步使用这些工具的详细指令 (Instructions)。

而暴露给 LLM 的,不再是这个 Skill 内部所有工具的繁琐描述,而只是一个关于这个 Skill 本身的、高度浓缩的一句话简介。

这种机制,本质上是一种懒加载 (Lazy Loading)

  1. 初始状态:Agent 启动时,只告诉 LLM 它有哪些“技能包”可用,以及每个“技能包”的一句话功能描述(例如,“code-reviewer:一个用于评审代码合并请求的技能”)。
  2. 按需激活:当用户的任务与某个 Skill 的描述相匹配时,LLM 会决定“激活”这个 Skill。
  3. 加载攻略:Agent 在收到“激活”指令后,才会将这个 Skill 内部详细的“任务攻略”(即详细的执行步骤和所需的工具集)加载到上下文中。
  4. 执行任务:LLM 根据刚刚加载的“攻略”,按部就班地调用工具,完成任务。

通过这种方式,Skill 巧妙地平衡了能力的广度与上下文的简洁性,让 Agent 变得更加通用和强大。

5.1 Skill 的文件结构

一个 Skill 通常以一个文件夹的形式存在,其核心是一个 SKILL.md 文件。这个文件由两部分组成:

  1. 元数据 (Frontmatter):文件开头,由 — 包裹的 YAML 块,定义了 Skill 的基本信息,其中最重要的是 name (唯一标识) 和 description (一句话功能简介)。
  2. 指令 (Instructions):文件的正文部分,用 Markdown 格式详细描述了该如何使用此 Skill,包括它的作用、触发时机、思考步骤、需要用到的工具等。

这是一个 SKILL.md 的示例:

---
name: my-skill
description: 用于处理某类固定任务。当用户提出相关需求或需要按标准流程输出时,应优先使用本技能。
---

# My Skill

## 作用
用于处理:
- 任务 A
- 任务 B

## 触发时机
当用户:
- 提到相关任务
- 希望结构化输出
- 希望按固定流程处理

......

5.2 实现 Skill 的加载与管理

为了让 Agent 能够理解和使用 Skill,我们需要实现一个 skill.Registry (技能注册器)。它的职责是:

  • Discover(): 在 Agent 启动时,扫描技能目录,只解析所有 SKILL.md 的元数据部分,建立一个可用的“技能摘要”列表。
  • BuildSkillsPrompt(): 将上述“技能摘要”列表格式化成一段通顺的文本,用于注入到 LLM 的系统提示词中,告诉 LLM 有哪些 Skill 可用。
  • ActivateSkill(name): 当 LLM 决定激活某个 Skill 时,完整地加载该 Skill 的 SKILL.md 文件,包括其详细的指令部分。

同时,我们还需要创建一个 activate_skill 工具。LLM 就是通过调用这个工具来通知 Agent 它想要激活哪个 Skill 的。

由于这部分代码主要是文件操作和状态管理,我们在此省略其具体实现,聚焦于它如何与 Agent 的主流程结合。

我们修改 Agent 的 buildSystemPrompt 方法,将技能列表的提示词自动追加到系统提示词的末尾。

// buildSystemPrompt 构建系统提示词
func (a *Agent) buildSystemPrompt() string {
        prompt := a.systemPrompt
        if prompt == "" {
                prompt = `你是一个专业的AI Agent助手。你可以使用提供的工具来完成用户的任务。`
        }

        // 将可用技能列表追加到系统提示词中
        skillPrompt := a.skillRegistry.BuildSkillsPrompt()
        if skillPrompt != "" {
                prompt += "\n\n" + skillPrompt
        }
        return prompt
}

5.3 实战:让 Agent 激活并执行 Skill

现在,我们把所有东西串联起来。我们创建一个my-script技能,该技能负责执行script/目录下的所有脚本。

---
name: my-script
description: 我的每日脚本
---

# My Script
每日都执行一下`script/`目录下的脚本

我们在 main 函数中初始化 skill.Registry,发现并注册所有技能,同时将 activate_skill 工具也加入到工具箱中。

func main() {
    // ... 省略了 LLM Client, MCP Manager 的初始化代码 ...

    // 初始化技能注册器
    skillRegistry := skill.NewRegistry(config.SkillsDir) // config.SkillsDir 指向技能存放的目录
    if err := skillRegistry.Discover(); err != nil {
        log.Fatal().Err(err).Msg("发现Skill失败")
    }

    // 将与 Skill 相关的工具也注册到工具箱
    // 激活skill的工具
    toolRegistry.Register(tool.NewActivateSkillTool(skillRegistry))
    // 列出所有skill的工具
    toolRegistry.Register(tool.NewListSkillsTool(skillRegistry))

    myAgent := agent.NewAgent("MyAgent", "", 10, client, toolRegistry, skillRegistry)

    // 提出一个可以匹配到 "my-script" 技能的任务
    answer, err := myAgent.Run(context.Background(), "执行我的每日脚本")
    if err != nil {
        log.Fatal().Err(err).Msg("运行Agent失败")
    }
    fmt.Println(answer)
}

当 Agent 运行时,它会经历以下步骤:

  1. 思考:用户的任务是“执行我的每日脚本”。我可用的技能中,有一个叫 my-script 的,它的描述是“我的每日脚本”。这看起来很匹配。
  2. 行动:调用 activate_skill 工具,参数为 {“skill_name”: “my-script”}。
  3. 观察:activate_skill 工具执行成功,返回了 my-script 技能的详细指令:“首先,使用 ls script/…”。
  4. 思考:好的,我收到了详细的攻略。第一步是列出 script/ 目录下的文件。
  5. 行动:调用 shell 工具,参数为 ls script/。
  6. … Agent 会继续按照 Skill 的指令执行下去,直到任务完成。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第五部分小结

通过引入 Skill 这一更高层次的抽象,我们有效地解决了工具过载和流程固化的难题。Agent 现在能够通过“懒加载”的方式,管理和使用海量的、封装了复杂流程的“技能包”。

至此,我们的 Agent 在“大脑”层面已经基本成型。但从用户体验上看,它还只是一个后台程序。最后一部分,我们将为它加上一点“交互的魔法”,让它成为一个真正可用的命令行应用。

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐