第六篇:技能加载 —— 别什么都往 System Prompt 里塞
技能加载 —— 别什么都往 System Prompt 里塞
把你的大脑想象成一个书包。你是想把所有课本都背着上学,还是只带下节课要用的那一本?
System Prompt 的悲剧
你写了一个 Agent。它在 system prompt 里描述了整个公司的内部知识:
SYSTEM = """You are a coding agent.
Knowledge:
- Database schema: 150 tables including users, orders, products...
- API docs: 23 REST endpoints with request/response formats
- Deployment: K8s config, Dockerfile, CI/CD pipeline
- Coding standards: 47 rules about naming, testing, linting
- Architecture: microservices, event bus, message queue...
- ...总共约 8000 tokens 的知识"""
然后你问它:“帮我改一下用户头像上传的逻辑。”
为了回答这个简单的问题,模型被迫阅读了 8000 tokens 的无关知识,然后才找到真正需要的那 200 tokens。
这不是聪明。这是自虐。
System prompt 不是冰箱,你不应该把所有东西都塞进去保鲜。它是模型启动时加载到上下文里的内容——每一 token 都在和你的实际对话抢位置。
s05 的解法:两层注入
s05 的解决方案优雅得离谱。就两层:
Layer 1 (cheap — ~100 tokens/skill):
System prompt 只放技能名称和一行描述
Layer 2 (on demand — 完整内容):
模型调用 load_skill("xxx") → 完整的技能文档注入进来
System Prompt 是这样写的:
Skills available:
- pdf: Process PDF files and extract text content [file, document]
- code-review: Review code for quality, security, and maintainability [code]
就这些。每个技能一行名字 + 一行描述,几十个 token 搞定。
然后模型在需要时调用加载:
TOOL_HANDLERS = {
...
"load_skill": lambda **kw: SKILL_LOADER.get_content(kw["name"]),
}
get_content 返回的是完整的技能文档:
def get_content(self, name: str) -> str:
skill = self.skills.get(name)
if not skill:
return f"Error: Unknown skill '{name}'. Available: {', '.join(self.skills.keys())}"
return f"<skill name=\"{name}\">\n{skill['body']}\n</skill>"
返回的内容类似:
<skill name="pdf">
# PDF Processing
This agent can process PDF files using the following approach:
1. Use `pdftotext` to extract text content
2. For scanned PDFs, use OCR via `tesseract`
3. Extract tables using `camelot` or `tabula-py`
Common commands:
```bash
pdftotext document.pdf - | head -100
```
整个过程就是模型问"你有这方面的知识吗?"→ Harness 回答"有,给你"→ 知识作为 tool_result 进入上下文。
SkillLoader 的设计
s05 的 SkillLoader 读的是 skills/<name>/SKILL.md 文件,用的是 YAML frontmatter:
class SkillLoader:
def __init__(self, skills_dir: Path):
self.skills_dir = skills_dir
self.skills = {}
self._load_all()
def _parse_frontmatter(self, text: str) -> tuple:
match = re.match(r"^---\n(.*?)\n---\n(.*)", text, re.DOTALL)
if not match:
return {}, text
meta = yaml.safe_load(match.group(1)) or {}
return meta, match.group(2).strip()
所以 skills/pdf/SKILL.md 的文件结构是:
---
name: pdf
description: Process PDF files and extract text content
tags: file, document
---
# PDF Processing
Detailed instructions for handling PDF files...
三个字段:
- name:技能名,模型调用
load_skill("pdf")时用的名字 - description:一行描述,放在 system prompt 里
- tags:可选的标签,辅助分类(在描述里带上了)
文件名不重要,SKILL.md 才重要。 这种设计允许你按任何目录结构组织技能:
skills/
├── pdf/
│ └── SKILL.md
├── code-review/
│ └── SKILL.md
├── deployment/
│ ├── SKILL.md
│ └── templates/
│ └── ...
_load_all 用的是 rglob("SKILL.md"),所以技能可以嵌套在任意深度的目录里。
为什么这个模式有效?
两层注入之所以有效,是因为它和模型的工作方式高度吻合:
模型的注意力是有限的。 研究表明,Transformer 的注意力在长上下文中会"稀释"。你把 8000 tokens 的数据库 schema 放在 system prompt 里,模型在处理你的代码时,那 8000 tokens 就像背景噪音。
按需加载让模型知道自己有什么可用。 系统 prompt 里的技能列表就像是"目录"。模型看到目录,知道有 PDF 处理能力。当它遇到 PDF 文件时,它会主动调用 load_skill("pdf")。
技能加载本身就是一次工具调用。 这意味着:
- 技能内容作为 tool_result 进入上下文 → 紧挨着使用场景
- 用完技能后,上下文压缩可以把旧技能内容替换掉
- 不同任务可以用不同的技能组合
一个对比:知识泛滥 vs. 按需注入
没有技能系统:
User: "帮我解析这个 PDF 文件里的表格"
→ Agent 的 system prompt 里有 8000 tokens 的知识,
包括 PDF 处理、数据库 schema、部署配置、编码规范……
→ 模型需要在 8000 tokens 的噪音里找到 PDF 处理的部分
→ 可能会漏掉关键细节
有技能系统:
User: "帮我解析这个 PDF 文件里的表格"
→ Agent 看到"pdf"这个技能名,调用 load_skill("pdf")
→ 完整的 PDF 处理指南进入上下文(tool_result)
→ 模型按照指南操作
→ 上下文里没有无关的数据库 schema 和部署配置
效率差距就是"在图书馆找一本书"和"书已经翻到你需要的页码"的差距。
这和 RAG 有什么区别?
你可能会问:“这不就是 RAG(检索增强生成)吗?”
对,底层思想是一样的——不要把所有知识都塞进 prompt,按需检索。但实现层面有两个关键区别:
1. 触发机制不同
- RAG 通常是系统自动检索,在用户 query 之后、LLM 调用之前,从向量数据库里捞一段相关内容拼进 prompt
- 这个 repo 的技能系统是模型主动触发——模型看到技能名,自己决定要不要加载
2. 知识被当成工具,而不是上下文的一部分
- 在 RAG 里,检索到的文本被动地混在 prompt 里
- 在这个系统里,
load_skill是一个工具调用,知识作为 tool_result 返回,模型主动消费它
区别的实质是:谁决定什么时候加载知识?
- RAG:系统提前决定
- Skill Loading:模型自己决定
又回到了那个核心思想:模型是司机。
结合其他 Session
技能加载和这个 repo 里的其他机制配合得天衣无缝:
s04 Subagent:父代理加载技能后,把任务和技能描述一起传给子代理
prompt = f"用以下方法处理这个PDF:\n{skill_content}\n\n文件:report.pdf"
run_subagent(prompt)
s06 上下文压缩:技能内容不会被压缩,因为它在 tool_result 里。但如果后续被 micro_compact 替换成了 [Previous: used load_skill],模型可以随时重新加载。
s07 Task System:创建任务时把需要的技能名写在任务描述里,Agent 认领任务后自动加载对应的技能。
这又是一个可组合性的例子。技能加载不是孤立的——它和其他机制一起,构成一个完整的 Agent 生态系统。
下集预告
s05 解决了"知识太多"的问题。s06 要解决的是另一个方向的问题——“对话太长”。
上下文总会满的——不管你怎么小心翼翼地管理 prompt,只要 Agent 一直工作,messages 就会一直增长。s06 的三层压缩策略,让 Agent 可以永远工作下去。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)