Skill Loading - 按需知识加载机制
L05 要回答一个问题:如何在 L04 的基础上让 Agent 按需加载知识,而不污染认知?
L04 给了我们骨架:上下文隔离 + 禁止递归 + 返回值机制。但真实 Agent 执行特定领域任务时需要专业知识——git 操作流程、代码审查规范、部署检查清单。这些知识全部预先塞进 system prompt 会稀释注意力。怎么做?
本章讲三个机制:
| 机制 | 解决什么问题 | 核心代码 |
|---|---|---|
| 两层注入 | 认知管理 + 生命周期控制 | 第一层 system prompt + 第二层 tool_result |
| SkillLoader | 知识扫描解析注册 | rglob("SKILL.md") + frontmatter 解析 |
| 容错性设计 | 宽容有边界的错误处理 | 三层兜底 + 关键错误暴露 |
这三个机制叠加在 L04 的 Subagent 上,循环本身不变。理解 L05,就理解了 Agent 如何”按需加载知识不稀释注意力”。
一、两层注入:认知管理机制
“用到什么知识,临时加载什么知识。”
这句话概括了 Skill Loading 的核心价值。不是预先塞进所有知识,而是让模型自己决定何时需要什么。看代码:
class SkillLoader: def get_descriptions(self) -> str: # 第一层:系统提示 lines = [] for name, skill in self.skills.items(): desc = skill["meta"].get("description", "") lines.append(f" - {name}: {desc}") return "\n".join(lines)
def get_content(self, name: str) -> str: # 第二层:tool_result skill = self.skills.get(name) if not skill: return f"Error: Unknown skill '{name}'." return f"<skill name=\"{name}\">\n{skill['body']}\n</skill>"两层注入,两种生命周期:
| 层级 | 内容 | 位置 | 能否被压缩 |
|---|---|---|---|
| 第一层 | Skill 描述列表 | system prompt | ❌ 常驻,不参与压缩 |
| 第二层 | Skill 完整内容 | tool_result(messages 中) | ✅ 按需加载,可压缩释放 |
两层注入的本质
两层注入不只是省 token,更是:
| 价值 | 描述 |
|---|---|
| 认知管理 | 第一层是导航(告诉模型有哪些 Skill),第二层是内容(具体怎么做) |
| 生命周期控制 | 第一层常驻,第二层可释放——容量不足时压缩 tool_result |
| 架构一致性 | 项目从小到大,无需重构架构——今天玩具明天生产 |
为什么两层能解决注意力稀释?
假设一层注入(所有 Skill 内容直接塞 system prompt):
100 个 Skill × 500 tokens = 50000 tokens 常驻↓Transformer 注意力权重归一化(总和 = 1)↓无关 Skill 内容权重累加 → 稀释核心任务权重↓每个 token 生成时参考被稀释权重 → 混入无关内容 → 输出跑偏例子:代码审查任务,输出本应是”检查错误处理”,却变成”先检查文件大小”(从 PDF Skill 污染)。
两层注入:
第一层:100 个 Skill × 20 tokens 描述 = 2000 tokens 常驻(导航)第二层:按需加载 1 个 Skill × 500 tokens = 500 tokens 可释放(内容)↓核心任务权重不被稀释 → 输出精准验证你的理解
Q1:如果 Skill 只有 100 tokens,还需要两层注入吗?
:::details 答案 需要,保持架构一致性。
两层注入的价值不只是省 token,更是:
- 生命周期控制:Skill 放 tool_result 可被压缩释放,放 system prompt 则常驻
- 架构一致性:项目会成长,今天 100 tokens 的 Skill 明天可能变成 5000 tokens
即使短 Skill 也应该两层注入,保持架构统一,避免未来重构。 :::
二、SkillLoader:知识扫描解析注册
两层注入是设计理念,SkillLoader 是实现机制。
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}三步流程:
| 步骤 | 代码 | 说明 |
|---|---|---|
| 扫描 | rglob("SKILL.md") | 递归扫描 skills 目录 |
| 解析 | _parse_frontmatter() | YAML 元数据与 Markdown 内容分离 |
| 注册 | self.skills[name] = ... | 字典注册,供两层注入使用 |
图书馆类比
SkillLoader 的设计可以用图书馆类比:
| 组件 | 图书馆对应 | SkillLoader 对应 |
|---|---|---|
| SKILL.md 文件 | 书架上的书 | 知识文件 |
| meta(frontmatter) | 索引卡片 | 第一层注入(导航) |
| body(内容) | 书的内容 | 第二层注入(内容) |
| get_descriptions() | 索引卡片柜 | 返回所有 Skill 描述列表 |
| get_content(name) | 借书 | 按需返回完整内容 |
先查索引(第一层),再借书(第二层)。不是把所有书搬到桌上。
统一入口原则
Skill 和 Tool 有什么区别?
| 维度 | Skill | Tool |
|---|---|---|
| 触发 | load_skill(name) | read_file(path) 等 |
| 机制 | 都走 dispatch map | 都走 dispatch map |
| 返回值 | 文本内容(知识指引) | 执行结果(能力输出) |
| 目的 | 告诉模型”怎么做” | 让模型”能做什么” |
本质差异:职责不同(知识注入 vs 能力执行),机制相同(都走 dispatch map)。
统一入口原则:Skill 只是另一种 Tool——都是模型主动请求,都走 tool_use,都不改 agent loop 逻辑。
验证你的理解
Q2:Skill 和 Tool 为什么共用 dispatch map?
:::details 答案 统一入口原则。
Skill 和 Tool 的本质差异是职责:
- Skill = 知识注入(返回文本,告诉模型”怎么做”)
- Tool = 能力执行(返回结果,让模型”能做什么”)
但机制相同:都走 dispatch map,都由模型主动请求,都返回 tool_result。
这样设计的好处:不改 agent loop 逻辑——load_skill 只是字典里的又一个 handler。 :::
三、容错性设计:宽容有边界
SkillLoader 的 frontmatter 解析有三层兜底:
def _parse_frontmatter(self, text: str) -> tuple[dict, str]: if not text.startswith("---"): return {}, text # 无 frontmatter → 空 meta + 全文 parts = text.split("---", 2) if len(parts) < 3: return {}, text # 格式畸形 → 空 meta + 全文 try: meta = yaml.safe_load(parts[1].strip()) or {} except yaml.YAMLError: meta = {} # YAML 解析失败 → 空 meta return meta, parts[2].strip()三层兜底:无 frontmatter、格式畸形、YAML 解析失败 → 全返回空 meta,Skill 仍能注册(用目录名)。
容错性设计的权衡
| 错误类型 | 频率 | 后果 | 处理 |
|---|---|---|---|
| 无 frontmatter | 高频 | 轻(Skill 仍能注册) | ✅ 兜底,用目录名 |
| 格式畸形 | 中频 | 轻(Skill 仍能注册) | ✅ 兜底,用目录名 |
| YAML 解析失败 | 中频 | 轻(Skill 仍能注册) | ✅ 兜底,用目录名 |
| 写错 name 字段 | 低频 | 重(Skill 找不到) | ❌ 暴露,让用户发现 |
宽容有边界:高频轻后果兜底,低频重后果暴露。
为什么不是”吞掉错误”?
三层兜底看起来像”吞掉错误”的反模式,但本质不同:
| 反模式 | SkillLoader 设计 |
|---|---|
| 所有错误都吞掉,用户不知道 | 关键错误(写错 name)会暴露 |
| 隐式失败,难以调试 | 显式失败:Skill 找不到时返回 Error |
| 系统行为不可预测 | 兜底行为确定:用目录名作为 fallback |
原则:显式失败优于隐式错误。但”显式失败”不代表”任何错误都崩溃”——关键错误暴露,次要错误兜底。
验证你的理解
Q3:容错性设计为什么不是”吞掉错误”反模式?
:::details 答案 宽容有边界。
三层兜底是主动设计,不是 Python 默认行为:
- 高频轻后果(忘记写 frontmatter)→ 兜底,用目录名
- 低频重后果(写错 name 字段)→ 暴露,Skill 找不到
反模式是”所有错误都吞掉”,SkillLoader 是”关键错误暴露”。用户写错 name 会发现 Skill 找不到——显式失败优于隐式错误。 :::
四、回顾:L05 在循环上叠加了什么
回到开头的问题:如何在 L04 的基础上让 Agent 按需加载知识,而不污染认知?
答案:
| 叠加机制 | 代码位置 | 循环的变化 |
|---|---|---|
| 两层注入 | system prompt + tool_result | 不变——只是注入位置差异 |
| SkillLoader | __init__ 扫描 + 解析 | 不变——启动时一次性注册 |
| 容错性设计 | _parse_frontmatter 兜底 | 不变——错误处理在 handler 层 |
循环本身仍是 L01 的 while True + stop_reason + messages[]。L05 只是在”知识层”叠加了按需加载机制。
与后续课程的关系
理解 L05 后,你会发现后续课程都在 Skill Loading 上叠加——循环本身始终不变:
| 课程 | 叠加什么 | 变化在哪 |
|---|---|---|
| L01 Agent Loop | 统一入口验证 | Skill 和 Tool 都走 tool_use |
| L02 Tool Use | 开闭原则 | 加 Skill 不改 dispatch map |
| L04 Subagent | 上下文隔离 | Skill 进入 Subagent messages,用完丢弃 |
| L06 Context Compact | 压缩释放 | tool_result(第二层)可被压缩释放 |
| L07 Task System | 持久化 | Skill + 任务状态保存 |
核心洞察:Skill Loading 是”按需知识”的起点。理解 L05,就理解了 Agent 如何”临时加载知识不稀释注意力”。后续课程都在扩展 Skill 机制——压缩释放、持久化、多 agent 协作。
五、心智模型升级:从 L0 到 L2
读完这篇文章,你的理解经历了怎样的升级?
L05 核心概念的理解升级路径
| 概念 | L0(表面) | L1(关联) | L2(深层) |
|---|---|---|---|
| 两层注入 | ”第一层描述,第二层内容" | "两层省 token,按需加载避免噪音" | "认知管理 + 生命周期控制 + 架构一致性。第一层常驻可导航,第二层按需可释放。架构一致性 > 短期省钱” |
| SkillLoader | ”扫描 SKILL.md 文件" | "递归扫描 + frontmatter 解析" | "图书馆机制——索引卡片(meta)+ 书架上的书(body)。三层兜底是主动设计,宽容有边界” |
| 容错性设计 | ”三层兜底让系统不崩溃" | "错误处理,Skill 仍能注册" | "宽容有边界——高频轻后果兜底,低频重后果暴露。显式失败优于隐式错误,但关键错误必须让用户发现” |
| 注意力稀释 | ”无关内容干扰模型" | "权重被稀释,输出可能跑偏" | "Transformer 归一化约束——无关内容累加稀释核心权重。每个 token 参考被稀释权重 → 混入无关内容。token 级别污染” |
如何检验你达到了 L2?
用这个问题自测:
注意力稀释具体怎么导致输出跑偏?请给出完整链条。
:::details L2 层级回答 完整链条:
- 输入:核心任务内容 + 无关 Skill 内容(一层注入场景)
- 权重分配:Transformer 注意力权重归一化(总和 = 1)
- 稀释效应:无关内容权重累加 → 核心任务权重被稀释(从 1.0 降到 0.6-0.7)
- Token 生成:每个 token 生成时参考被稀释的权重
- 混入无关内容:权重分配偏向无关内容 → 输出混入无关信息
- 输出跑偏:本应输出”错误处理建议”,变成”检查文件大小”(PDF Skill 污染)
两层注入解决:第一层轻量导航(20 tokens/Skill),第二层按需加载(不注入无关 Skill),核心权重不被稀释。 :::
本章目标:让每个核心概念都从 L0 升级到 L2。理解 L05,不只是记住代码,而是掌握”如何在循环上按需加载知识”的设计原则。
六、复习可视化
Skill Loading 流程
一层 vs 两层注入对比
容错性设计流程
注意力稀释机制
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!