Agent 开发入门(五):解决 Prompt 臃肿问题,实现技能按需加载

1 阅读5分钟

Agent 开发入门(五):解决 Prompt 臃肿问题,实现技能按需加载

本文是「从零构建 Coding Agent」系列的第五篇,适合已经掌握基础 Agent 开发,想进一步优化系统架构的开发者。

先问一个问题

当你的 Agent 越来越强大,需要处理的任务越来越多样时,你有没有遇到过这样的困境:

  • 做代码审查需要一套审查清单
  • 做 Git 操作需要一套提交约定
  • 做 MCP 集成需要一套专门步骤

如果你把这些知识全部塞进 system prompt,会发生什么?

语言模型的「知识过载」问题

语言模型(LLM)的上下文窗口是有限的。

当你把所有可能用到的知识都塞进 system prompt,会出现两个严重问题:

  1. Token 浪费:大部分 token 都浪费在当前用不到的说明上
  2. 维护困难:prompt 越来越臃肿,主线规则越来越不清楚

这就是技能加载系统要解决的核心问题:把"长期可选知识"从 system prompt 主体里拆出来,改成按需加载

技能加载的核心设计:两层模型

用一个图来表示技能加载的工作原理:

system prompt
  |
  +-- Skills available:
      - code-review: review checklist
      - git-workflow: branch and commit guidance
      - mcp-builder: build an MCP server

当模型需要时:
load_skill("code-review")
   |
   v
tool_result
   |
   v
<skill name="code-review">
完整审查说明
</skill>

关键点只有两个:

  1. 轻量目录常驻:技能目录信息常驻 system prompt
  2. 重内容按需加载:完整技能内容只在需要时加载

几个必须搞懂的概念

Skill(技能)

一份围绕某类任务的可复用说明书,告诉 Agent:

  • 什么时候该用它
  • 做这类任务时有哪些步骤
  • 有哪些注意事项

Discovery(发现)

"发现有哪些 skill 可用"的过程,只需要轻量信息:

  • skill 名字
  • 一句描述

Loading(加载)

"把某个 skill 的完整正文真正读进来"的过程,这才是昂贵的操作。

最小实现

1. 技能目录结构

skills/
  code-review/
    SKILL.md
  git-workflow/
    SKILL.md
  mcp-builder/
    SKILL.md

2. 技能文档格式

使用 frontmatter 格式存储元数据:

---
name: code-review
description: 代码审查清单
---

# 代码审查指南

## 安全性
- [ ] 检查 SQL 注入
- [ ] 检查 XSS 漏洞
- [ ] 检查敏感信息泄露

## 代码质量
- [ ] 命名规范
- [ ] 代码风格
- [ ] 注释完整度

3. 核心代码实现

@dataclass
class SkillManifest:
    name: str
    description: str
    path: Path

@dataclass
class SkillDocument:
    manifest: SkillManifest
    body: str

class SkillRegistry:
    def __init__(self, skills_dir: Path):
        self.skills_dir = skills_dir
        self.documents: dict[str, SkillDocument] = {}
        self._load_all()
    
    def _load_all(self) -> None:
        if not self.skills_dir.exists():
            return
        
        for path in sorted(self.skills_dir.rglob("SKILL.md")):
            meta, body = self._parse_frontmatter(path.read_text())
            name = meta.get("name", path.parent.name)
            description = meta.get("description", "No description")
            manifest = SkillManifest(name=name, description=description, path=path)
            self.documents[name] = SkillDocument(manifest=manifest, body=body.strip())
    
    def describe_available(self) -> str:
        if not self.documents:
            return "(no skills available)"
        lines = []
        for name in sorted(self.documents):
            manifest = self.documents[name].manifest
            lines.append(f"- {manifest.name}: {manifest.description}")
        return "\n".join(lines)
    
    def load_full_text(self, name: str) -> str:
        document = self.documents.get(name)
        if not document:
            known = ", ".join(sorted(self.documents)) or "(none)"
            return f"Error: Unknown skill '{name}'. Available skills: {known}"
        
        return (
            f"<skill name=\"{document.manifest.name}\">\n"
            f"{document.body}\n"
            "</skill>"
        )

4. 系统提示配置

SKILL_REGISTRY = SkillRegistry(SKILLS_DIR)

SYSTEM = f"""You are a coding agent at {WORKDIR}.
Use load_skill when a task needs specialized instructions before you act.

Skills available:
{SKILL_REGISTRY.describe_available()}
"""

5. 加载技能工具

TOOL_HANDLERS = {
    "load_skill": lambda **kw: SKILL_REGISTRY.load_full_text(kw["name"]),
    # 其他工具...
}

TOOLS = [
    # 其他工具...
    {
        "name": "load_skill",
        "description": "Load the full body of a named skill into the current context.",
        "input_schema": {
            "type": "object",
            "properties": {"name": {"type": "string"}},
            "required": ["name"],
        },
    },
]

新手最容易犯的 5 个错

1. 把所有 skill 正文永远塞进 system prompt

# ❌ 错误
SYSTEM = f"""You are a coding agent.

{all_skill_text}  # 这会让 prompt 变得非常臃肿
"""

# ✅ 正确
SYSTEM = f"""You are a coding agent.

Skills available:
{SKILL_REGISTRY.describe_available()}
"""

2. skill 目录信息写得太弱

如果只有名字,没有描述,模型就不知道什么时候该加载它。

3. 把 skill 当成"绝对规则"

skill 更像"可选工作手册",不是所有轮次都必须用。

4. 把 skill 和 memory 混成一类

  • skill 解决的是"怎么做一类事"
  • memory 解决的是"记住长期事实"

5. 一上来就讲太多多源加载细节

先理解"轻量发现,重内容按需加载"的核心思想。

技能、记忆、全局规则的边界

概念作用特点
Skill可选知识包只有在某类任务需要时才加载
Memory跨会话记忆系统记住的长期事实或偏好
CLAUDE.md全局规则更稳定、更长期的系统规则

一个简单判断法:

  • 这是某类任务才需要的做法或知识:skill
  • 这是需要长期记住的事实或偏好:memory
  • 这是更稳定的全局规则:CLAUDE.md

实际应用案例

案例:代码审查任务

用户输入:"请审查这个 PR,重点关注安全性和代码质量"

智能体执行流程

  1. 分析任务:需要代码审查
  2. 调用工具:load_skill("code-review")
  3. 加载技能:获得完整的代码审查清单
  4. 执行审查:按照清单检查代码
  5. 生成报告:基于审查结果给出反馈

案例:Git 操作任务

用户输入:"帮我创建一个新分支并提交这些更改"

智能体执行流程

  1. 分析任务:需要 Git 操作
  2. 调用工具:load_skill("git-workflow")
  3. 加载技能:获得 Git 工作流指南
  4. 执行操作:按照指南创建分支、添加文件、提交更改
  5. 完成任务:返回操作结果

为什么这很重要

因为 Agent 之所以「专业」,不是因为它天生什么都会。

而是因为它能在正确的时间,找到并使用正确的知识。

就像一个优秀的工程师:不是把所有手册都记在脑子里,而是知道什么时候该查什么手册。

下一章预告

有了技能加载系统,你的 Agent 已经具备了按需获取知识的能力。下一章我们将探讨如何让 Agent 与外部系统更深度集成,构建更加强大的自动化工作流。


一句话总结:技能加载系统的核心,不是"多一个工具",而是"把可选知识从常驻 prompt 里拆出来,改成按需加载"。


如果觉得有帮助,欢迎关注,我会持续更新「从零构建 Coding Agent」系列文章。