Skill Loading - 按需知识加载机制

3278 字
16 分钟
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 有什么区别?

维度SkillTool
触发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 层级回答 完整链条:

  1. 输入:核心任务内容 + 无关 Skill 内容(一层注入场景)
  2. 权重分配:Transformer 注意力权重归一化(总和 = 1)
  3. 稀释效应:无关内容权重累加 → 核心任务权重被稀释(从 1.0 降到 0.6-0.7)
  4. Token 生成:每个 token 生成时参考被稀释的权重
  5. 混入无关内容:权重分配偏向无关内容 → 输出混入无关信息
  6. 输出跑偏:本应输出”错误处理建议”,变成”检查文件大小”(PDF Skill 污染)

两层注入解决:第一层轻量导航(20 tokens/Skill),第二层按需加载(不注入无关 Skill),核心权重不被稀释。 :::

本章目标:让每个核心概念都从 L0 升级到 L2。理解 L05,不只是记住代码,而是掌握”如何在循环上按需加载知识”的设计原则。

六、复习可视化#

Skill Loading 流程#

flowchart TB subgraph Init["初始化阶段"] I1["skills_dir 路径"] I2["sorted(rglob 'SKILL.md')"] I3["递归扫描所有 Skill"] end subgraph Parse["解析阶段"] P1["读取文件内容"] P2["检测 frontmatter"] P3["_parse_frontmatter()"] P4["三层兜底处理"] P5["返回 meta + body"] end subgraph Register["注册阶段"] R1["name = meta.get 'name'"] R2["fallback: f.parent.name"] R3["self.skills[name] = ..."] end subgraph Layer1["第一层注入"] L1A["get_descriptions()"] L1B["生成描述列表"] L1C["写入 system prompt"] L1D["常驻,不参与压缩"] end subgraph Layer2["第二层注入"] L2A["模型调用 load_skill"] L2B["get_content(name)"] L2C["生成 Skill 内容"] L2D["返回 tool_result"] L2E["按需,可压缩释放"] end I1 --> I2 --> I3 I3 --> P1 P1 --> P2 P2 -->|"有 frontmatter"| P3 P2 -->|"无 frontmatter"| P4 P3 -->|"解析成功"| P5 P3 -->|"解析失败"| P4 P4 --> P5 P5 --> R1 R1 -->|"有 name"| R3 R1 -->|"无 name"| R2 --> R3 R3 --> L1A L1A --> L1B --> L1C --> L1D R3 --> L2A L2A --> L2B --> L2C --> L2D --> L2E style L1D fill:#e8f5e9,stroke:#333 style L2E fill:#e8f5e9,stroke:#333

一层 vs 两层注入对比#

flowchart LR subgraph OneLayer["一层注入(错误)"] O1["所有 Skill 内容"] O2["直接塞 system prompt"] O3["常驻 50000 tokens"] O4["注意力稀释"] O5["输出跑偏"] end subgraph TwoLayer["两层注入(正确)"] T1["Skill 描述列表"] T2["system prompt 常驻 2000 tokens"] T3["Skill 内容"] T4["tool_result 按需"] T5["可压缩释放"] T6["认知质量保证"] end O1 --> O2 --> O3 --> O4 --> O5 T1 --> T2 T3 --> T4 --> T5 T2 --> T6 T5 --> T6 style O5 fill:#ffebee,stroke:#333 style T6 fill:#e8f5e9,stroke:#333

容错性设计流程#

flowchart TB subgraph Check["检查 frontmatter"] C1["text.startswith '---'?"] C2["有 frontmatter"] C3["无 frontmatter"] end subgraph Parse["解析 YAML"] P1["split '---' 2"] P2["yaml.safe_load()"] P3["格式畸形"] P4["YAML 错误"] end subgraph Result["结果"] R1["返回 meta + body"] R2["返回 {} + text"] R3["返回 {} + text"] R4["返回 {} + text"] end C1 -->|"是"| C2 --> P1 C1 -->|"否"| C3 --> R2 P1 -->|"len >= 3"| P2 P1 -->|"len < 3"| P3 --> R3 P2 -->|"成功"| R1 P2 -->|"失败"| P4 --> R4 style R2 fill:#fff3e0,stroke:#333 style R3 fill:#fff3e0,stroke:#333 style R4 fill:#fff3e0,stroke:#333

注意力稀释机制#

flowchart TB subgraph Normal["正常情况"] N1["核心任务内容"] N2["权重分配"] N3["核心权重 = 1.0"] N4["输出精准"] end subgraph Diluted["稀释情况"] D1["核心任务内容"] D2["无关 Skill 内容"] D3["权重分配"] D4["核心权重 = 0.6"] D5["无关权重累加 = 0.4"] D6["每个 token 参考被稀释权重"] D7["混入无关内容"] D8["输出跑偏"] end N1 --> N2 --> N3 --> N4 D1 --> D3 D2 --> D3 D3 --> D4 D3 --> D5 D4 --> D6 --> D7 --> D8 D5 --> D6 style N4 fill:#e8f5e9,stroke:#333 style D8 fill:#ffebee,stroke:#333

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
Skill Loading - 按需知识加载机制
https://firefly.cuteleaf.cn/posts/learn-claude-code/05-skill-loading/
作者
AltumSisy
发布于
2026-04-11
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
AltumSisy
Hello, I'm AltumSisy.
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
32
分类
3
标签
25
总字数
68,347
运行时长
0
最后活动
0 天前

文章目录