阿里AI亮明账,腾讯AI蓄暗线
2026-05-19 3330089
2026-05-19 0
原址

s05_skill_loading.py 讲的是:给 AI Coding Agent 增加“按需加载技能”的能力。
核心思想是:不要把所有专业知识一次性塞进 system prompt,而是先只放技能名称和简介,等模型需要时再通过 load_skill 工具加载完整技能内容。 文件开头注释明确写了这个“两层注入”设计:第 1 层把技能名和简介放进 system prompt,第 2 层在模型调用 load_skill("pdf") 时返回完整技能正文。(GitHub)
普通 Agent 如果把所有规则、工具说明、PDF 处理方法、代码审查规范、MCP 构建指南都直接写进 system prompt,会有几个问题:
prompt 变长,浪费 token
模型注意力被稀释
不相关知识也会干扰当前任务
技能越多,系统提示词越膨胀
所以这个文件实现了一个更像 Claude Code / Cursor / Agent Skill 的机制:
这就是注释里说的:“Don’t put everything in the system prompt. Load on demand.” (GitHub)
可以理解成这 6 步:
objectivec 体验AI代码助手 代码解读复制代码启动程序
↓
扫描 skills/ 目录下所有 SKILL.md
↓
解析每个 SKILL.md 的 YAML frontmatter
↓
把技能名称 + 简介塞进 system prompt
↓
用户提出任务
↓
模型判断是否需要某个技能,需要就调用 load_skill(name)
↓
load_skill 返回完整技能说明
↓
模型根据完整技能继续完成任务
文件注释里规定了技能目录大概长这样:(GitHub)
objectivec 体验AI代码助手 代码解读复制代码skills/
pdf/
SKILL.md
code-review/
SKILL.md
每个技能一个目录,每个目录里有一个 SKILL.md。
比如仓库里确实有这些技能目录:agent-builder、code-review、mcp-builder、pdf。(GitHub)
SKILL.md 的结构通常是:
yaml 体验AI代码助手 代码解读复制代码---
name: pdf
description: Process PDF files...
---
# PDF Processing Skill
具体操作说明……
比如 skills/pdf/SKILL.md 里就有 name pdf 和 description Process PDF files...,后面才是完整的 PDF 处理说明。(GitHub)
ini 体验AI代码助手 代码解读复制代码load_dotenv(override=True)
if os.getenv("ANTHROPIC_BASE_URL"):
os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)
WORKDIR = Path.cwd()
client = Anthropic()
MODEL = os.environ["MODEL_ID"]
SKILLS_DIR = WORKDIR / "skills"
这段做了几件事:
代码
作用
load_dotenv(override=True)
读取 .env 文件里的环境变量
ANTHROPIC_BASE_URL 判断
如果使用自定义 base url,就移除 ANTHROPIC_AUTH_TOKEN
WORKDIR = Path.cwd()
当前运行目录作为工作区
client = Anthropic()
初始化 Anthropic 客户端
MODEL = os.environ["MODEL_ID"]
从环境变量读取模型名
SKILLS_DIR = WORKDIR / "skills"
默认从当前目录下的 skills/ 加载技能
这些初始化代码位于文件中部,负责准备模型客户端、模型 ID 和技能目录。(GitHub)
这是本文件最关键的类。
ruby 体验AI代码助手 代码解读复制代码class SkillLoader:
def __init__(self, skills_dir: Path):
self.skills_dir = skills_dir
self.skills = {}
self._load_all()
它的作用是:启动时扫描 skills/ 目录,把所有技能读进内存。
self.skills 是一个字典,用来保存所有技能。每个技能大概会被存成这样:
css 体验AI代码助手 代码解读复制代码{
"pdf": {
"meta": {...},
"body": "...完整技能内容...",
"path": "skills/pdf/SKILL.md"
}
}
SkillLoader 会在初始化时调用 _load_all(),扫描 skills_dir 下面所有 SKILL.md 文件。(GitHub)
python 体验AI代码助手 代码解读复制代码def _load_all(self):
if not self.skills_dir.exists():
return
for f in sorted(self.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, "path": str(f)}
这段逻辑很重要:
步骤
含义
if not self.skills_dir.exists()
如果没有 skills/ 目录,就直接返回
rglob("SKILL.md")
递归查找所有叫 SKILL.md 的文件
f.read_text()
读取技能文件内容
_parse_frontmatter(text)
拆分 YAML 元信息和正文
meta.get("name", f.parent.name)
优先用 frontmatter 里的 name,没有就用目录名
self.skills[name] = ...
存入技能字典
也就是说,技能名可以来自两种地方:
makefile 体验AI代码助手 代码解读复制代码name: pdf
或者来自目录名:
bash体验AI代码助手代码解读复制代码skills/pdf/SKILL.md ↑ pdf
相关扫描和存储逻辑在 SkillLoader._load_all() 里。(GitHub)
python 体验AI代码助手 代码解读复制代码match = re.match(r"^---n(.*?)n---n(.*)", text, re.DOTALL)
这行正则的意思是:
yaml 体验AI代码助手 代码解读复制代码从文件开头开始匹配:
---
这里是 YAML 元信息
---
这里是正文
如果匹配不到 frontmatter:
arduino 体验AI代码助手 代码解读复制代码return {}, text
也就是:没有元信息,整个文件都当正文。
如果匹配到了:
csharp 体验AI代码助手 代码解读复制代码meta = yaml.safe_load(match.group(1)) or {}
return meta, match.group(2).strip()
也就是:
部分
作用
match.group(1)
YAML frontmatter
match.group(2)
技能正文
yaml.safe_load(...)
把 YAML 字符串转成 Python 字典
.strip()
去掉正文首尾空白
所以 SKILL.md 实际被拆成了两部分:
css 体验AI代码助手 代码解读复制代码meta:name、description、tags 等短信息
body:完整技能说明
这就是后面“两层加载”的基础。(GitHub)
python 体验AI代码助手 代码解读复制代码def get_descriptions(self) -> str:
"""Layer 1: short descriptions for the system prompt."""
这个方法生成放进 system prompt 的内容。
它不会返回完整技能正文,只返回类似这样的短描述:
less 体验AI代码助手 代码解读复制代码 - pdf: Process PDF files...
- code-review: Perform thorough code reviews...
代码里会读取每个技能的:
ini 体验AI代码助手 代码解读复制代码desc = skill["meta"].get("description", "No description")
tags = skill["meta"].get("tags", "")
然后拼成一行文本。(GitHub)
这就是第 1 层:
python 体验AI代码助手 代码解读复制代码def get_content(self, name: str) -> str:
"""Layer 2: full skill body returned in tool_result."""
这个方法就是 load_skill 背后的真正逻辑。
如果技能不存在:
kotlin 体验AI代码助手 代码解读复制代码return f"Error: Unknown skill '{name}'. Available: ..."
如果技能存在:
swift 体验AI代码助手 代码解读复制代码return f"" {name}">n{skill['body']}n"
也就是说,当模型调用:
scss 体验AI代码助手 代码解读复制代码load_skill("pdf")
工具会返回:
ini 体验AI代码助手 代码解读复制代码name ="pdf">
完整 PDF 处理说明……
这就是第 2 层:
相关逻辑在 get_content() 中。(GitHub)
ini 体验AI代码助手 代码解读复制代码SKILL_LOADER = SkillLoader(SKILLS_DIR)
SYSTEM = f"""You are a coding agent at {WORKDIR}.
Use load_skill to access specialized knowledge before tackling unfamiliar topics.
Skills available:
{SKILL_LOADER.get_descriptions()}"""
这段就是把技能简介注入系统提示词。
注意:这里注入的是 get_descriptions(),不是 get_content()。
所以 system prompt 里只有:
diff 体验AI代码助手 代码解读复制代码你是一个 coding agent
遇到陌生任务时可以用 load_skill 加载专业知识
当前可用技能:
- pdf: ...
- code-review: ...
不会一开始就把 PDF 的详细命令、代码审查清单、MCP 构建流程全部塞进去。(GitHub)
这个文件仍然保留了普通 Agent 工具:
工具
作用
bash
执行 shell 命令
read_file
读取文件
write_file
写文件
edit_file
替换文件中的指定文本
load_skill
按名称加载技能正文
工具处理函数在 TOOL_HANDLERS 里注册,其中 load_skill 对应:
objectivec 体验AI代码助手 代码解读复制代码"load_skill": lambda **kw: SKILL_LOADER.get_content(kw["name"])
也就是:模型调用 load_skill,实际执行的是 SKILL_LOADER.get_content(name)。(GitHub)
这是这个文件最值得理解的点。
普通工具是“做事”的:
体验AI代码助手代码解读复制代码bash → 执行命令 read_file → 读文件 write_file → 写文件 edit_file → 改文件
但 load_skill 不是直接做事,而是“给模型补知识”的:
体验AI代码助手代码解读复制代码load_skill → 返回一段专业操作说明,让模型变得更会做某类任务
所以它更像是:
体验AI代码助手代码解读复制代码知识工具 / 上下文注入工具 / 按需说明书加载器
举个例子:
用户说:
体验AI代码助手代码解读复制代码帮我处理这个 PDF
模型看到 system prompt 里有:
arduino 体验AI代码助手 代码解读复制代码- pdf: Process PDF files...
于是模型可以先调用:
json 体验AI代码助手 代码解读复制代码{
"name": "pdf"
}
然后 load_skill 返回完整 PDF 技能,比如如何用 pdftotext、PyMuPDF、reportlab、pandoc 等处理 PDF。skills/pdf/SKILL.md 里确实包含读取、创建、合并、拆分 PDF 的具体操作说明。(GitHub)
python 体验AI代码助手 代码解读复制代码def safe_path(p: str) -> Path:
path = (WORKDIR / p).resolve()
if not path.is_relative_to(WORKDIR):
raise ValueError(f"Path escapes workspace: {p}")
return path
这个函数用于保护 read_file、write_file、edit_file 这些文件操作。
它会把用户传入的路径拼到当前工作区,然后解析成绝对路径。
如果最终路径不在 WORKDIR 里面,就抛错。
比如:
bash体验AI代码助手代码解读复制代码正常:src/main.py 危险:../../etc/passwd
../../etc/passwd 解析后会逃出工作区,所以会被拦截。相关路径保护逻辑在 safe_path() 中。(GitHub)
scss 体验AI代码助手 代码解读复制代码dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
if any(d in command for d in dangerous):
return "Error: Dangerous command blocked"
它会拦截一些明显危险命令,比如:
bash 体验AI代码助手 代码解读复制代码rm -rf /
sudo
shutdown
reboot
> /dev/
然后用:
ini 体验AI代码助手 代码解读复制代码subprocess.run(..., shell=True, cwd=WORKDIR, timeout=120)
执行命令,最多跑 120 秒。输出会截断到 50000 字符。(GitHub)
不过这里要注意:这是教学版,不是生产级沙箱。
原因是:
它用了 shell=True
危险命令只靠字符串黑名单拦截
safe_path() 只保护文件读写工具,不保护 bash 命令
bash 理论上仍然能执行很多未被黑名单覆盖的危险操作
所以这个文件更适合学习 Agent 架构,不适合直接拿来跑不可信输入。
ini 体验AI代码助手 代码解读复制代码def agent_loop(messages: list):
while True:
response = client.messages.create(
model=MODEL,
system=SYSTEM,
messages=messages,
tools=TOOLS,
max_tokens=8000,
)
这里和前面的 Agent Loop 思路一样:
把历史消息 messages 发给模型
带上 system
带上 tools
等模型回复
如果模型要调用工具,就执行工具
把工具结果塞回 messages
继续循环
直到模型不再调用工具
判断是否继续的关键是:
kotlin 体验AI代码助手 代码解读复制代码if response.stop_reason != "tool_use":
return
如果模型不是因为调用工具而停止,就说明它已经给出最终回答了,循环结束。(GitHub)
go 体验AI代码助手 代码解读复制代码results.append(
{
"type": "tool_result",
"tool_use_id": block.id,
"content": str(output),
}
)
messages.append({"role": "user", "content": results})
这段会把工具执行结果包装成 Anthropic API 需要的 tool_result 格式,再作为一条新的 user message 追加到历史消息里。(GitHub)
所以完整交互像这样:
scss 体验AI代码助手 代码解读复制代码用户:帮我处理 PDF
模型:我要调用 load_skill("pdf")
程序:执行 load_skill,拿到 PDF 技能说明
程序:把技能说明作为 tool_result 返回给模型
模型:读完技能说明后,再继续完成 PDF 任务
ini 体验AI代码助手 代码解读复制代码if __name__ == "__main__":
history = []
while True:
query = input(" 33[36ms05 >> 33[0m")
这部分让脚本变成一个命令行聊天程序。
执行后会看到类似:
体验AI代码助手代码解读复制代码s05 >>
然后你输入问题,程序把你的输入追加到 history:
bash 体验AI代码助手 代码解读复制代码history.append({"role": "user", "content": query})
agent_loop(history)
最后从 history[-1]["content"] 里拿出模型回复并打印。(GitHub)
你前面看过的文件可以串起来理解:
文件
重点
s01_agent_loop.py
最小 Agent Loop:用户输入 → 模型回复
s02_tool_use.py
加入工具调用:模型可以调用 bash / read / write 等工具
s03_todo_write.py
加入任务规划 / Todo 管理
s04_subagent.py
加入子 Agent:主 Agent 可以把任务委托出去
s05_skill_loading.py
加入技能加载:模型可以按需加载专业知识
s05 的本质升级是:
体验AI代码助手代码解读复制代码从“能调用工具” 升级到 “能根据任务加载专业操作手册”
也就是:
ini 体验AI代码助手 代码解读复制代码Agent = LLM + Tools + Memory/Context + Planning + Skills
这里的 Skills 不是模型本身的能力,而是外部维护的一组专业说明书。
可以总结成一句话:
sql 体验AI代码助手 代码解读复制代码System Prompt 只放索引,完整知识按需加载。
更具体一点:
ini 体验AI代码助手 代码解读复制代码skills/ 目录 = 技能库
SKILL.md frontmatter = 技能索引
SKILL.md body = 完整技能说明
get_descriptions() = 注入 system prompt 的轻量索引
load_skill() = 按需读取完整技能
tool_result = 把技能正文塞回模型上下文
这个设计很像:
体验AI代码助手代码解读复制代码书架目录 + 按需取书
不是一开始把所有书都摊在桌子上,而是先告诉模型:
体验AI代码助手代码解读复制代码我有 PDF 技能、代码审查技能、MCP 构建技能……
等模型真的要处理 PDF 时,再把 PDF 那本“说明书”拿出来。
s05_skill_loading.py 就是在教你做一个更聪明的 AI Coding Agent: