Featured image of post 第九话|Skill:把「方法」从 Agent 代码里拆出去

第九话|Skill:把「方法」从 Agent 代码里拆出去

MCP 把工具实现拆成了可发现、可调用的协议。Skill 做的是同一件事的下一个对象:把「怎么做对一件事」的方法,从 Agent 代码里拆成可发现、可版本化的文件。

系列 GYA(Get Your Agent) 第 9 / 13 篇
主题 AgentLLM教程Skill工程化

第八话结尾留了一个问题:工具能即插即用了,方法怎么复用?

那一篇我们用 MCP 把「工具怎么被发现、怎么被调用」做成了协议,Server 和 Client 各自解耦。查天气、读数据库、调别人家的工具,都不必再为每一项能力重写适配代码。

但一个任务从来不只是「缺一个工具」。

让 Agent 检查一篇文章,它找得到文件检查工具,却不知道先查 frontmatter 还是先查标题;它发现了错误,却不知道能不能直接改原文;它做完几步,却没有一条「什么算做完」的验收线。工具是有了,但「应该按什么方法把事情做完整」这套东西,还散落在每次手写的 prompt 里、散落在循环代码里、散落在写这个 Agent 的人的脑子里。

工具能被协议接入了,方法却还没有自己的载体。这就是第八话留下的那个问题。

MCP 拆的是「能力」,Skill 拆的是「方法」。两者是同一个动作,作用在不同的对象上。

Skill 不是把 prompt 写得更长,也不是给 Agent 再加一个配置文件。它把一套经过验证的做法,拆成能被发现、按需加载、逐条执行、可被脚本验收的资源。

一、方法没有载体,是真正的复用缺口

先看一个已经打通工具的场景:Agent 要检查一篇 Markdown 文章的结构。

它有工具,MCP 也能把工具发现、调用都串起来。可真正动手时,每一步都卡在「工具给不了答案」的地方:

任务里的问题工具能不能回答
检查哪个文件能,传路径即可
先查 frontmatter 还是先查 H1不能,这是顺序
失败后继续还是停下不能,这是分支
发现了错误能不能直接改不能,这是权限边界
什么结果算「检查完成」不能,这是验收标准

顺序、分支、权限、验收——这四样东西,工具一样都给不了。它们不属于「能力」,属于「方法」。

现在这个方法是存在哪里的?大概率是每次写一段很长的 prompt,让模型「认真、全面地检查文章,先看 frontmatter,再数 H1,最后确认有没有总结」。这段 prompt 每次都要重新写一遍,写的人不同、措辞就不同,改的时候也找不到一份权威版本。

这和第八话开头那个「N 个模型 × M 个工具」的集成问题,是同一类问题:能力可以被协议统一,方法却一直是私有的一次性产物。 你写的「检查文章的方法」,绑死在这一段 prompt 里、绑死在这一次任务里,换个人、换个 Agent、换一天,就没了。

要复用的不是「检查」这个动作,而是「怎么检查」。方法得有自己的载体。

二、Skill 只做一件事:把方法从执行者身上拆出来

MCP 做的事,是把「工具怎么实现」从「调用方的代码」里拆出来,变成 Server 暴露的协议。Skill 做的事,是把「方法」从「执行者的代码」里拆出来,变成文件。

拆完之后,职责是这样分开的:

谁知道什么不知道什么
Agent 循环 / Host怎么发现 Skill、怎么匹配、怎么按步骤执行这个方法具体要检查什么
SKILL.md这个 Skill 叫什么、解决什么、步骤顺序规则怎么用代码实现
references/要检查哪些规则规则怎么逐条校验
scripts/怎么用代码逐条校验什么时候该用、结果怎么上报

注意这张表里的「不知道」那一列。一个拆得干净的 Skill,每一层都只管自己那一件事,既不替上层做决定,也不把下层的实现细节背到自己身上。

一个过于抽象的 Skill 往往没有可执行性:

# Markdown 质量检查

请认真、全面地检查文章。

这句话没有任何可以执行、可以验收的东西。「认真」「全面」不是步骤,是愿望。

拆出来之后长这样:

---
name: markdown-quality
description: Check Markdown articles for required frontmatter, one H1 title, and a closing summary.
steps:
  - ref: references/checklist.md
  - run: python3 scripts/validate.py {file}
---

差别不在后者更长,而在它把「方法」落成了三样能分别落盘、分别替换的东西:

  • description 是方法的名字和适用场景——发现阶段只靠它;
  • steps 是方法的顺序——先读规则,再跑校验,Host 逐条执行;
  • scripts/ 是方法里那些「结果只有对错」的确定性部分,交给代码而不是交给模型。

name + description 回答「什么时候该用我」,steps 回答「用我时按什么顺序做」,scripts 回答「哪一步不用模型猜、直接算」。三样东西拆开,才能各自独立演进。

方法从 Host 拆到 Skill 目录:Host 只做通用的事,检查什么、怎么检查都在 Skill 自己的文件里

三、方法拆出来了,怎么被找到、怎么被加载

一个 Agent 手里可能挂着几十个 Skill。如果每个任务一开始就把所有 Skill 的正文、参考资料、脚本全塞进上下文,上下文先被撑爆,匹配也变成大海捞针。

所以加载是分层的:发现靠一句话,加载靠匹配,执行才碰细节。

Skill 的渐进披露:发现只读 name + description,匹配后才加载正文,执行才跑脚本

第一层决定一切。description 写得太泛,匹配阶段就没了线索;把无关资料全塞进入口,每次执行又背上一堆用不上的上下文。一个能打的 description 更像服务注册中心里的服务名片——它不执行业务,但它决定调用方能不能先找到正确的入口。

这和 MCP 的 tools/list 是同一套思路:先只给「叫什么、能干什么」的元数据,等调用方真正需要了,再给细节。

四、动手:一个把步骤真正拆出来的最小 Host

职责拆得对不对,用一个能跑的最小实现来看:Host 自己不知道什么叫「Markdown 质量」,它只做发现、匹配、加载、按步骤执行这四件通用的事;「检查什么、怎么检查」全部写在 Skill 自己的文件里。

代码在 GYA 的 c09-skill-system,Java 21 等价实现在 GYA-Java 的 c09-skill-system。目录长这样:

c09-skill-system/
├── main.py                  # Host:只做发现/匹配/加载/执行
├── skills/
│   └── markdown-quality/
│       ├── SKILL.md         # 声明 name/description/steps
│       ├── references/
│       │   └── checklist.md # 要检查哪些规则
│       └── scripts/
│           └── validate.py  # 怎么逐条校验
└── samples/
    ├── good.md              # 合格样例
    └── bad.md               # 故意损坏的样例

Host 的关键代码只有一段——它把 SKILL.md 里声明的 steps 读出来,逐条执行:

# main.py —— Host 不知道"Markdown 质量"是什么,它只知道按 steps 执行
for step in metadata["steps"]:
    if "ref" in step:
        print(f"[load] {step['ref']}")       # 加载资源
    elif "run" in step:
        validator_cmd = step["run"]          # 拿到 Skill 自己声明的命令

for sample_name in ("good.md", "bad.md"):
    sample = SAMPLES / sample_name
    argv = validator_cmd.replace("{file}", str(sample)).split()
    print(f"[run] {' '.join(argv)}")          # 把 {file} 填成真实路径再执行
    subprocess.run(argv, ...)                # 校验结果由脚本给出

注意 validate.py 是 Skill 目录里自带的,{file} 是目标文件的占位符。Host 从头到尾不知道「要检查 frontmatter、要数 H1、要确认总结」——这些规则在 references/checklist.md 和 scripts/validate.py 里。Host 只是把 Skill 声明的步骤串起来跑。

运行环境只有 Python 3.9+,无第三方依赖,也不需要 API Key:

git clone https://github.com/renxin2024/GYA.git
cd GYA/c09-skill-system
python3 main.py

实际输出(只留关键行,完整输出见仓库该目录的 README):

[discover] markdown-quality → [match] → [load] → [run]
[validate] good.md -> PASS: frontmatter=ok h1=1 summary=ok
[validate] bad.md -> FAIL: frontmatter must be delimited by ---; missing closing section: ## 总结
[result] status=PASS

最后一行 status=PASS 不表示两份样例都合格。它表示 Host 的验收条件被满足:合格样例通过、故意损坏的样例被拦截。「验证器工作正常」和「被验证对象合格」是两回事。 把这两层分开,是写 Skill 时最基本的工程习惯——否则一个只会返回 PASS 的验证器,也会被当成「任务成功了」。

如果运行结果不同,按顺序排查:

  1. 确认当前目录是 GYA/c09-skill-system,否则 Host 找不到相对路径下的 Skill 和样例。
  2. 确认 Python 3.9+。
  3. 检查 SKILL.md 的 steps 里是否还保留着 ref: references/checklist.md 和 run: ... {file} 两条;Host 只认这两条声明,缺了谁都会判定为无效 Skill。

五、方法里,哪些交给脚本、哪些交给模型、哪些交给权限

一个方法里,脚本和模型的分工要先定清楚。定反了,会得到两种同样坏的方法:让模型去数 H1 的个数,是把该确定性的事交给概率;让脚本去判断文章观点清不清楚,是把该语义的事交给规则。

工作该交给谁原因
判断文章观点是否清楚模型需要语义理解和上下文判断
判断语气是否自然模型需要综合语言和读者语境
统计 H1 数量脚本结果是确定的,不该让模型猜
检查 JSON 能否解析脚本成功或失败有明确判定
检查文件是否存在脚本不需要模型猜测
删除文件、发消息、部署权限系统 + 人工审批正确性不等于被授权

这条边界,就是 scripts/validate.py 和「让模型读文章给意见」之间的那条线:模型处理语义,脚本锁住不该漂移的事实,权限系统控制不可逆的影响。 Skill 的价值正是在任务层把这三类职责串成一条可检查的方法,而不是把其中任何一类神化成万能方案。

六、Prompt、Memory、MCP、Skill,各自回答什么

这四个词经常同时出现在 Agent 架构里,但回答的是四个不同的问题:

能力保存或提供什么回答什么问题不能替代什么
Prompt当前任务的即时指令与上下文这次希望我怎样回答可复用工作流与长期事实
Memory过去的事实、偏好或经验上次我们决定了什么工具接入和任务方法
MCP可发现、可调用的工具与资源现在能做什么步骤、验收和权限策略
Skill完成一类任务的方法与边界这件事应该怎样做模型推理、外部工具、授权

在一个真实任务里,Memory 补充用户偏好,MCP 提供读写外部系统的工具,Skill 规定检查流程,Agent 循环推进步骤,脚本验证局部结果。它们协作,但不互相冒充。

把这四行连起来看,结论和开头一致:MCP 把「能力」做成了协议,Skill 把「方法」做成了文件。一个是「能做什么」的标准化,一个是「怎么做对」的标准化。

七、从 demo 到生产,方法还要补什么

把 SKILL.md 写出来只是起点。一个写错的方法,会让 Agent 更稳定地执行一套错误的流程,所以生产环境至少还要补:

  • 用真实样例验证它在该触发时会被发现(而不是靠 description 撞运气);
  • 用故意失败的样例验证验证器真的能拦住错误(bad.md 就是);
  • 为规则变化保留版本和变更原因,方法不是写完就不动的;
  • 把写文件、发消息、发布、部署这类高影响动作,放在明确的权限边界之后;
  • 记录每次执行轨迹,把反复失败的点沉淀为下一版规则。

这些是从这个几十行的 demo 推导出的工程实践,不代表 demo 已经具备生产能力。demo 只证明一件事:Host 不写「检查什么」,也能靠读 SKILL.md 里的步骤把检查跑完,「方法在文件里」这条边界是能落地的。

八、能力有了协议,方法也该有自己的载体

工具接入这件事,第八话用 MCP 收了口。方法复用这件事,靠的是把它拆成文件:经过验证的做法保存下来,下一次任务不必从零猜测。

当 Agent 既能调用工具、又能复用方法时,下一个问题自然浮现:这些「推理」和「行动」的方式本身,又是怎么一步步演化出来的?下一篇,我们沿着 CoT、ReAct、Toolformer、Reflexion 到 SWE-Agent 的线索,看 Agent 的推理范式是怎么被发明出来的。

参考资料


演示代码:Python 版在 GYA 仓库 c09-skill-system,Java 21 等价实现在 GYA-Java 仓库 c09-skill-system。按各自 README 的命令跑,不要复制这篇文章里零散的代码块。

留言

欢迎分享你的想法。评论提交后会在审核通过后显示。