Agent 技能系统:按需加载领域知识 @ Lin | 2025-11-28T14:21:26+08:00 | 3 分钟阅读 | 更新于 2025-11-28T14:21:26+08:00

1、问题

Agent 常常需要遵守很多额外知识,比如:

  • Git 工作流
  • 测试规范
  • 代码审查清单
  • 特定领域的操作手册

如果把这些内容全部塞进系统提示词,会让 prompt 非常臃肿,而且大部分知识和当前任务没有直接关系。

阅读前提

建议先理解前面几节里的两个核心概念:工具是按需调用的,子 Agent 是按需派发的。技能系统其实延续的是同一类思路,只不过这次按需加载的是“知识”而不是“执行”。

2、两层注入

这一节的解决方案是 SkillLoader + 两层知识注入。

第一层放在系统提示词里,只保留技能名称和简要说明,成本很低。
第二层通过工具按需加载完整技能内容,成本较高,但只在需要时使用。

整体结构如下:

Layer 1: system prompt 中放技能目录
Layer 2: tool_result 中注入某个技能全文

本节架构图

flowchart TB
    subgraph L1["启动层"]
        A[Agent 启动]
    end

    subgraph L2["提示层"]
        P[系统提示词]
        D[技能目录摘要]
    end

    subgraph L3["决策层"]
        L[模型判断是否需要额外知识]
        T[load_skill]
    end

    subgraph L4["知识层"]
        S[读取 SKILL.md]
        C[注入完整技能说明]
    end

    A --> P
    P --> D
    D --> L
    L --> T
    T --> S
    S --> C
    C --> L

3、技能目录结构

原教程里,每个技能对应一个目录,目录下有一个 SKILL.md

skills/
  pdf/
    SKILL.md
  code-review/
    SKILL.md

SKILL.md 里既有 frontmatter,也有正文说明。

4、SkillLoader

SkillLoader 会递归扫描所有 SKILL.md 文件,并读取元信息与正文:

class SkillLoader:
    def __init__(self, skills_dir: Path):
        self.skills = {}
        for f in sorted(skills_dir.rglob("SKILL.md")):
            text = f.read_text()
            meta, body = self._parse_frontmatter(text)
            name = meta.get("name", f.parent.name)
            self.skills[name] = {"meta": meta, "body": body}

然后它能提供两种输出:

  • 获取所有技能简介
  • 按名称获取某个技能完整内容

5、把技能接入系统

第一层描述被放进系统提示词中:

SYSTEM = f"""You are a coding agent at {WORKDIR}.
Skills available:
{SKILL_LOADER.get_descriptions()}"""

第二层则作为普通工具接入:

TOOL_HANDLERS = {
    "bash": ...,
    "read_file": ...,
    "write_file": ...,
    "edit_file": ...,
    "load_skill": lambda **kw: SKILL_LOADER.get_content(kw["name"]),
}

这意味着模型先知道“有哪些技能”,需要时再调用 load_skill 获取完整内容。

6、这一步比直接堆 Prompt 更好在哪里

  • 系统提示词更轻
  • 知识可以按需注入
  • 技能之间相互独立,便于维护
  • 后续新增技能只需要新增目录和 SKILL.md

7、可以尝试的 prompt

What skills are available?
Load the agent-builder skill and follow its instructions
I need to do a code review -- load the relevant skill first
Build an MCP server using the mcp-builder skill

更完整的可运行示例

这个版本把技能扫描、frontmatter 解析和按名称读取正文串了起来,已经可以直接作为本地技能加载器使用。

from pathlib import Path

class SkillLoader:
    def __init__(self, skills_dir: Path):
        self.skills = {}
        for file in sorted(skills_dir.rglob("SKILL.md")):
            text = file.read_text(encoding="utf-8")
            meta, body = self._parse_frontmatter(text)
            name = meta.get("name", file.parent.name)
            self.skills[name] = {
                "meta": meta,
                "body": body.strip(),
                "path": str(file),
            }

    def _parse_frontmatter(self, text: str):
        if not text.startswith("---"):
            return {}, text
        _, fm, body = text.split("---", 2)
        meta = {}
        for line in fm.splitlines():
            if ":" in line:
                key, value = line.split(":", 1)
                meta[key.strip()] = value.strip()
        return meta, body

    def get_descriptions(self) -> str:
        lines = []
        for name, item in self.skills.items():
            desc = item["meta"].get("description", "")
            lines.append(f"- {name}: {desc}")
        return "\n".join(lines)

    def get_content(self, name: str) -> str:
        if name not in self.skills:
            return f"Skill not found: {name}"
        item = self.skills[name]
        return f"# {name}\n\n{item['body']}"

loader = SkillLoader(Path("./skills"))
print(loader.get_descriptions())

本节完整 demo 目录结构

技能系统最适合用独立目录组织:

demo-s05/
├── agent.py
├── skill_loader.py
└── skills/
    ├── code-review/
    │   └── SKILL.md
    └── mcp-builder/
        └── SKILL.md

这样后续新增技能时,只需要继续往 skills/ 下添加目录和 SKILL.md,主程序不用大改。

8、补充说明

技能系统要真正落地,关键不是把知识塞进 SKILL.md,而是把技能边界划清楚。

一个技能最好只解决一类明确问题。例如“代码审查”“MCP 服务开发”“PDF 提取”都可以是独立技能,但不要把多个领域知识混在一个技能里,否则模型加载以后仍然会被无关信息干扰。

此外,技能文档最好同时包含三层内容:适用场景、执行步骤、失败时的处理办法。只有这样,模型在加载技能后才能既知道“什么时候用”,也知道“怎么用”,还知道“遇到问题怎么办”。

与下一节的衔接

当技能越来越多、会话越来越长时,另一个问题就会暴露出来:上下文窗口会越来越紧张。下一节会从知识治理继续往前走,进入长上下文压缩。

9、小结

这一节把“额外知识”从固定 Prompt 中拆了出来,变成可动态加载的技能系统。

从此之后,Agent 的知识不再是一次性灌进去的,而是可以按任务场景逐步加载。

© 2019 - 2026 Lin 的博客

Powered by Hugo with theme Dream.

avatar
关于我

Lin 的 ❤️ 博客

记录一些 🌈 生活上,技术上的事

  • 🛠️ 技术阵地: 深耕 Java 开发 ,目前向 AI 应用领域探索与拓展。这里主要记录我从后端架构到 AI 赋能的进阶之路、技术思考以及日常的 debug 血泪史。始终致力于写出优雅且没有 bug 的代码(尽量)。
  • 🌈 生活侧写: 偶尔脱下极客的外衣,我也会在这里分享生活中的灵光一现。不管是好用的效率工具,还是周末的一场骑行,都是构成我真实生活的一部分。