Agent Skill 的 HTTP 底层:Skill 如何被编译成 OpenAI 协议原语
posts posts 2026-05-30T22:33:00+08:00Skill 是一个纯粹的应用层抽象,而非协议层概念。本文通过 7 步协议交互拆解 Skill 的完整生命周期,揭示 Skill 如何被 Cursor 等 IDE 编译成 system prompt 片段、tool schema 和多轮 tool calling 循环的组合。技术笔记Agent Skill, OpenAI协议, LLM, HTTP, Cursor, Tool CallingAgent Skill 的 HTTP 底层:Skill 如何被编译成 OpenAI 协议原语
Skill(技能)是这两年 AI 编程工具最火的概念之一。读网页、写文件、执行命令——各种 skill 听起来像在给大模型装插件。但追问一句"这个 skill 在 HTTP 层面怎么跟大模型交互的",大多数人就卡住了。
这篇文章来自腾讯云开发者张敏在司内论坛的一次技术追问。他实现了一个读取微信公众号文章的 skill 后,顺藤摸瓜梳理了整个底层链路。结论有些反直觉:Skill 在 OpenAI 兼容协议里没有对应的字段或角色——它是一种"给 LLM(大语言模型)写使用手册,让 LLM 通过已有工具自己照着做"的设计模式。
学习目标
读完本文后,你应当能够:
- 说出 Skill 在 OpenAI 兼容协议中被编译成哪三种协议原语,并指出各自的注入位置
- 跟着 7 步协议交互拆解,复述一次 Skill 从发现到执行的消息流,包括
role: "system"、role: "tool"、tool_calls各自承担的职责 - 用 mitmproxy 或 curl 抓取一次真实请求,验证 system prompt 中是否包含
<available_skills>标签块、LLM 是否在第一轮返回Read(SKILL.md)的 tool call - 在编写自己的第一个 Skill 时,按"目录约定 → frontmatter → description 触发覆盖度 → 前置检查 → 步骤指令"的顺序落地,并指出哪些环节出错会导致 Skill 不被触发
- 区分 Skill 与 Function Calling(函数调用)、MCP(Model Context Protocol,模型上下文协议)的边界,判断什么场景该用哪种机制
目录
- 协议映射:Skill 在 HTTP 层面究竟是什么
- 前提:OpenAI 兼容协议的基础
- Skill 生命周期:7 步协议交互拆解
- 协议交互时序图
- Skill 的协议映射表
- 核心洞察:Skill 是一种给 LLM 写使用手册的设计模式
- 实战:在 HTTP 层面观测 Skill 协议交互
- 如何从零编写第一个 Skill
- 适用边界与采用顺序
- FAQ
- 设计启示
协议映射:Skill 在 HTTP 层面究竟是什么
在 OpenAI 兼容协议中,请求体里没有 skill 这个字段,也没有 skill 这种角色。Skill 最终被 Cursor(或其他 AI IDE,集成开发环境)编译成三种协议原语的组合:
- System/Developer Message(系统/开发者消息) — 把 Skill 的指令文本注入到 system prompt(系统提示词)中
- Tools Definition(工具定义) — 把 Skill 需要用到的工具(如 Shell、Read)注册为
tools数组 - Multi-turn Tool Calling Loop(多轮工具调用循环) — LLM 根据注入的指令,自主决策发起
tool_calls,宿主执行后把结果喂回去
可以浓缩成一句:
Skill = 动态注入的 system prompt 片段 + 预定义的 tool schema + 多轮 tool calling(工具调用)循环
底层的 tool calling 机制 OpenAI 协议在 2023 年就已支持,Skill 没有要求协议层做任何扩展。真正决定 Skill 效果的是两件事:system prompt 里那段触发指令的措辞,以及 SKILL.md 文件本身的编写质量——工程上对应 prompt engineering(提示词工程)和文件系统组织。
前提:OpenAI 兼容协议的基础
本文面向对 OpenAI 兼容协议有基本了解的同学。如果刚接触这个领域,先抓住一件事:大模型只会"对话",所谓的工具调用也只是特化的聊天功能——模型输出一个结构化的 tool_calls 字段,客户端负责执行对应的工具,然后把结果通过 role: "tool" 消息塞回给模型继续处理。多轮工具调用循环,就是客户端和模型之间反复传递同一个 messages 数组,每轮往里追加新的消息。
协议本身不知道也不关心什么是"skill"。
Skill 生命周期:7 步协议交互拆解
以 Cursor + mp-read skill 为例,完整走一遍 Skill 从发现到执行的 7 个步骤。其他 AI IDE 走的是同一套机制。
第 0 步:Skill 发现与描述摘要注入
在你打开 Cursor、还没说话的时候,Cursor 就已经扫描了 .cursor/skills/、.agents/skills/ 等目录,收集了所有 Skill 的 name + description(来自 SKILL.md 的 YAML frontmatter),然后把它们作为静态上下文塞进 system prompt。
以 mp-read skill 为例,SKILL.md 的 frontmatter 是:
name: mp-read
description: >-
Extract plain text from Tencent MP (mp.weixin.qq) articles
using a headless Chrome browser. Use when the user wants to
read, fetch, extract, summarize, or reference a MP article,
or when a mp.weixin.qq URL appears in conversation.这段信息被注入成类似这样的 system prompt 片段(简化版):
<available_skills>
<agent_skill fullPath="/path/to/mp-read/SKILL.md">
Extract plain text from Tencent MP (mp.weixin.qq) articles
using a headless Chrome browser. Use when the user wants to
read, fetch, extract, summarize, or reference a MP article,
or when a mp.weixin.qq URL appears in conversation.
</agent_skill>
</available_skills>注意此时 SKILL.md 的正文还没有被读取。这就是 Cursor 文档里说的 “Progressive Loading(渐进式加载)"——只先放名字和描述,不浪费 token(词元)。
第 1 步:用户发问,触发 Skill
假设用户说:
帮我读一下这篇公众号文章:https://mp.weixin.qq.com/s/HHPK6QvclYaxlDg28elN8w
Cursor 作为客户端,构造出的第一次 API 请求大致如下(忠实于 OpenAI 协议):
{
"model": "claude-opus-4",
"messages": [
{
"role": "system",
"content": "You are an AI coding assistant...\n\n<available_skills>\n<agent_skill fullPath=\"/Users/123456/.../mp-read/SKILL.md\">\n Extract plain text from Tencent MP...\n</agent_skill>\n</available_skills>\n\nWhen a skill is relevant, read and follow it IMMEDIATELY as your first action..."
},
{
"role": "user",
"content": "帮我读一下这篇公众号文章:https://mp.weixin.qq.com/s/HHPK6QvclYaxlDg28elN8w"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "Read",
"description": "Reads a file from the local filesystem...",
"parameters": {
"type": "object",
"properties": {
"path": { "type": "string", "description": "The absolute path of the file to read." },
"offset": { "type": "integer" },
"limit": { "type": "integer" }
},
"required": ["path"]
}
}
},
{
"type": "function",
"function": {
"name": "Shell",
"description": "Executes a given command in a shell session...",
"parameters": {
"type": "object",
"properties": {
"command": { "type": "string" },
"description": { "type": "string" },
"working_directory": { "type": "string" },
"block_until_ms": { "type": "number" }
},
"required": ["command"]
}
}
}
],
"tool_choice": "auto"
}注意两点:
tools数组是 Cursor 预定义好的,不是 Skill 定义的。Skill 本身不声明工具——它只是告诉 LLM “你可以用 Read 来读文件,用 Shell 来执行命令”。- system prompt 里有 Skill 的描述摘要加上一条关键指令:“When a skill is relevant, read and follow it IMMEDIATELY”。
第 2 步:LLM 响应——决定先读 SKILL.md
LLM 看到用户提到了 mp.weixin.qq,匹配到了 system prompt 中 mp-read skill 的描述,于是按照指令"先读 Skill 文件”。它返回的响应是:
{
"choices": [
{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "Read",
"arguments": "{\"path\": \"/path/to/mp-read/SKILL.md\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
]
}这就是 Skill 的"加载"——落到协议层,是 LLM 自己发起了一次 Read tool call,读取 SKILL.md 文件。Skill 没有被"安装"进 LLM,是 LLM 在运行时主动读进去的。
第 3 步:Cursor 执行 tool call,返回结果
Cursor 在本地执行 Read("/Users/123456/.../mp-read/SKILL.md"),拿到文件内容,然后构造下一轮请求:
{
"messages": [
{ "role": "system", "content": "(同上,省略)" },
{ "role": "user", "content": "帮我读一下这篇公众号文章..." },
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "Read",
"arguments": "{\"path\": \"/Users/123456/.../mp-read/SKILL.md\"}"
}
}
]
},
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "---\nname: mp-read\ndescription: >-\n Extract plain text from MP...\n---\n\n(...完整 SKILL.md 内容...)"
}
],
"tools": [ "(同上,Shell、Read 等工具定义)" ]
}到这一步,SKILL.md 的全部内容已经通过 role: "tool" 消息进入了 LLM 的上下文窗口(Context Window)。LLM 现在拥有了完整的"技能说明书"。
第 4 步:LLM 按照 Skill 指令行动——前置检查
LLM 读完 SKILL.md 后,按照 “Prerequisites Check” 章节的指示,先检查环境(章节中要求用户必须准备好一个 cookie.txt 文件)。它返回:
{
"choices": [
{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_def456",
"type": "function",
"function": {
"name": "Shell",
"arguments": "{\"command\": \"which mp-read\", \"description\": \"Check mp-read is installed\"}"
}
},
{
"id": "call_ghi789",
"type": "function",
"function": {
"name": "Read",
"arguments": "{\"path\": \".../cookie.txt\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
]
}这里 LLM 一次返回了两个 tool_calls——OpenAI 协议支持的并行 tool calling。两个检查互相独立,所以同时发起。
第 5 步:工具结果回传
Cursor 并行执行两个 tool call,然后把结果都塞进 messages:
{
"messages": [
"(...之前的所有消息...)",
{
"role": "assistant",
"tool_calls": [
{ "id": "call_def456", "function": { "name": "Shell", "arguments": "..." } },
{ "id": "call_ghi789", "function": { "name": "Read", "arguments": "..." } }
]
},
{ "role": "tool", "tool_call_id": "call_def456", "content": "/usr/local/go/bin/mp-read" },
{ "role": "tool", "tool_call_id": "call_ghi789", "content": "skey=xxxxxxx; p_skey=yyyyyyy; RIO_TCOA=zzzzzz..." }
]
}第 6 步:LLM 执行核心命令
前置检查通过,LLM 按照 SKILL.md 中的 Usage 章节,构造并发起实际的 mp-read 命令:
{
"choices": [
{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_jkl012",
"type": "function",
"function": {
"name": "Shell",
"arguments": "{\"command\": \"mp-read 2026/0316_mp_read/cookie.txt 'https://mp.weixin.qq.com/s/HHPK6QvclYaxlDg28elN8w'\", \"description\": \"Extract MP article text\", \"block_until_ms\": 120000}"
}
}
]
},
"finish_reason": "tool_calls"
}
]
}block_until_ms: 120000 这个值来自 SKILL.md 的明确要求。LLM 从 Skill 文档里"学到"了该设多大,然后照搬进 tool call 的 arguments——没有特殊机制,就是按手册字段填参。
第 7 步:最终响应
Shell 执行完毕,mp-read 的 stdout 输出(文章全文)通过 role: "tool" 消息回传给 LLM。LLM 最终生成一个纯文本响应:
{
"choices": [
{
"message": {
"role": "assistant",
"content": "这篇公众号文章的内容如下:\n\n# 文章标题\n\n文章正文内容...",
"tool_calls": null
},
"finish_reason": "stop"
}
]
}协议交互时序图
Skill 的协议映射表
把 Skill 的概念一一对应到 OpenAI 协议层面:
| Skill 概念 | 协议映射 |
|---|---|
| Skill 发现(scan directories) | 纯客户端行为,不涉及协议 |
| Skill 摘要(name + description) | 注入到 messages[0].role = "system" 的文本中 |
| Skill 加载(读取 SKILL.md) | LLM 发起 tool_calls: [Read(SKILL.md)],结果通过 role: "tool" 回传 |
| Skill 指令执行 | LLM 按读到的 SKILL.md 内容,自主发起后续 tool_calls |
| Progressive Loading(渐进式加载) | 先在 system prompt 放摘要(省 token),LLM 需要时再 Read 全文 |
scripts/ 目录 | LLM 通过 Shell tool call 执行脚本 |
references/ 目录 | LLM 通过 Read tool call 按需读取参考文档 |
核心洞察:Skill 是一种给 LLM 写使用手册的设计模式
Skill 在协议层面没有新增字段,靠的是已有字段的组合用法。它是一种"给 LLM 写使用手册,让 LLM 通过已有工具自己照着做"的设计模式。
把流程拆开:
- 在 system prompt 里告诉 LLM “你有这些技能手册可以查”
- LLM 通过
Read工具自己去读手册 - LLM 读完手册后,按手册说的步骤,通过
Shell/Read等工具一步步执行
整套设计复用的是 OpenAI 协议已有的 tool calling 机制,没有引入协议扩展。
决定 Skill 效果的是两件事:
- system prompt 的措辞:怎么让 LLM 在合适的时机主动去读 SKILL.md
- SKILL.md 文件的编写质量:描述是否清晰、指令是否可执行、前置检查是否完备
所以 skill 的编写者既要懂业务逻辑,也要懂 prompt engineering——skill 工程上是一份给 AI 看的操作手册,编写它的核心工作是写清楚触发条件、前置检查和步骤指令。
实战:在 HTTP 层面观测 Skill 协议交互
Skill 的所有交互都发生在 HTTP 请求里,最直接的验证方式是抓包。
1. 使用 mitmproxy 拦截 HTTPS 流量
mitmproxy --mode regular --listen-port 8080 -s inspect_skill.py需要编写一个 mitmproxy 插件 inspect_skill.py 来过滤和格式化 /chat/completions 请求:
from mitmproxy import http
def request(flow: http.HTTPFlow) -> None:
if "/chat/completions" in flow.request.pretty_url:
body = flow.request.json()
if not body:
return
msgs = body.get("messages", [])
tools = body.get("tools", [])
print(f"\n=== Round with {len(msgs)} messages, {len(tools)} tools ===")
for i, msg in enumerate(msgs):
role = msg.get("role", "?")
content = str(msg.get("content", ""))[:120]
tc = msg.get("tool_calls")
tc_summary = f", tool_calls={len(tc)}" if tc else ""
print(f" [{i}] role={role}{tc_summary} content={content}...")
def response(flow: http.HTTPFlow) -> None:
if "/chat/completions" in flow.request.pretty_url:
body = flow.response.json()
if not body:
return
choice = body.get("choices", [{}])[0]
msg = choice.get("message", {})
tc = msg.get("tool_calls")
finish = choice.get("finish_reason")
if tc:
names = [t.get("function", {}).get("name", "?") for t in tc]
print(f" <- tool_calls: {names}, finish_reason={finish}")
else:
content = str(msg.get("content", ""))[:80]
print(f" <- content: {content}..., finish_reason={finish}")2. 配置 IDE 走代理
Cursor 的 HTTP 代理配置因版本而异,通用做法是设置环境变量:
export HTTP_PROXY=http://127.0.0.1:8080
export HTTPS_PROXY=http://127.0.0.1:8080然后从同一终端启动 Cursor。如果 IDE 不认系统代理,可以使用 proxychains 或在 IDE 的 settings.json 中配置 http.proxy。
3. 观察要点
抓包时重点关注以下信号:
| 观察点 | 含义 | 正常表现 |
|---|---|---|
messages[0].role="system" 中包含 <available_skills> | Progressive Loading 生效 | 只有 name + description,没有正文 |
LLM 返回的第一个 tool_calls 是否包含 Read(SKILL.md) | Skill 触发成功 | name: "Read", arguments 指向 SKILL.md |
第二轮请求的 role: "tool" 消息 | SKILL.md 全文已进入上下文 | content 包含完整的 SKILL.md 文本 |
后续 tool_calls 是否按 SKILL.md 步骤执行 | Skill 指令被正确理解 | 命令参数与 SKILL.md 一致 |
finish_reason 的变化 | 判断当前处于工具循环还是最终回复 | tool_calls → 中间轮次;stop → 最终轮次 |
4. 用 curl 模拟单轮测试
如果只需要验证 Skill 的 system prompt 注入效果,不需要完整的多轮交互,可以用 curl 直接发单轮请求:
curl -s https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o",
"messages": [
{
"role": "system",
"content": "You are an AI assistant.\n\n<available_skills>\n<agent_skill fullPath=\"/tmp/test-skill/SKILL.md\">\n When user says ping, respond with pong and nothing else.\n</agent_skill>\n</available_skills>\n\nWhen a skill is relevant, read and follow it IMMEDIATELY."
},
{
"role": "user",
"content": "ping"
}
]
}'预期响应:pong——证明 system prompt 中的 skill 描述被 LLM 正确理解并执行。
5. 常见问题定位
Skill 没触发:检查 system prompt 中是否真的注入了 <available_skills> 标签块。部分 IDE 只在特定条件下注入(如工作区存在 .cursor/skills/ 目录)。
LLM 没读 SKILL.md:检查 system prompt 中是否包含 “read and follow it IMMEDIATELY” 这类强制性指令,以及 tools 数组中是否注册了 Read 工具。
读完后行为不对:SKILL.md 的指令可能不够明确。抓包对比 LLM 返回的 tool_calls 参数和 SKILL.md 中的要求是否一致。
并行 tool calling 没生效:并非所有模型都支持并行调用。确认模型能力,并在 tool_choice 中确保没有限制为单次调用。
如何从零编写第一个 Skill
自己写一个 Skill,按下面的顺序最容易跑通:
- 建目录:在工作区下创建
.cursor/skills/<skill-name>/,并在其中放一个SKILL.md。目录名和name字段保持一致,避免 IDE 扫描时认不出来。 - 写 frontmatter:
name用 kebab-case,description写清"什么时候该用这个 skill"——覆盖关键词、用户意图、URL 模式等触发场景。description 写得太窄,Skill 永远不会被触发;写得太宽,会误触发到不相关的对话。 - 写前置检查:在 SKILL.md 正文开头放一个 “Prerequisites Check” 章节,列出 LLM 在执行核心步骤前必须用
Shell或Read验证的条件(如依赖是否安装、配置文件是否存在)。前置检查不写全,LLM 会直接跳到核心命令,然后失败。 - 写步骤指令:把核心流程拆成可执行的步骤,每一步都指明用哪个工具、传什么参数。参数要从 SKILL.md 里能直接读出来,不要让 LLM 猜——例如
block_until_ms: 120000这种值要明确写出来。 - 抓包验证:按上一节的 mitmproxy 流程跑一次,确认 system prompt 里有
<available_skills>、第一轮tool_calls里有Read(SKILL.md)、后续tool_calls的参数和 SKILL.md 一致。 - 迭代 description:如果 Skill 没被触发,先改 description,加更多触发关键词和场景描述;如果触发了但行为不对,先改步骤指令的明确度,再考虑改 description。
适用边界与采用顺序
什么时候该用 Skill
- 任务有固定流程且步骤可文档化:比如"读公众号文章"、“从特定 API 拉数据并清洗”、“按团队规范生成 commit message”。这类任务步骤清晰,写成 SKILL.md 后 LLM 能照着执行。
- 任务依赖的工具已经由 IDE 注册:Skill 本身不声明工具,只编排已有工具。如果任务需要的工具 IDE 没注册(比如需要调用一个内部 RPC),Skill 单独解决不了,得先让 IDE 支持这个工具。
- 任务需要按需加载以节省 token:当 Skill 数量多、每个 SKILL.md 都很长时,Progressive Loading 的 token 收益明显。5 个 Skill 各 3000 token,全量注入要 15000 token;用摘要注入只要 250 token,省下来的空间可以留给用户对话。
什么时候不该用 Skill
- 任务只是一次性操作:写一个 SKILL.md 的成本可能比直接在对话里说清楚步骤还高。一次性任务直接在 prompt 里写步骤更划算。
- 任务的核心逻辑需要确定性执行:Skill 依赖 LLM 的语义匹配来触发,存在误触发或漏触发的可能。如果任务必须 100% 触发(比如合规检查、安全扫描),应该走确定性脚本或 CI 流程,而不是 Skill。
- 任务需要访问 IDE 未注册的工具:Skill 只能编排
tools数组里已有的工具。如果任务需要调用一个 IDE 不支持的工具,Skill 帮不上忙,需要先扩展 IDE 的工具能力。
采用顺序建议
- 先用现成的 Skill 跑通一次抓包:拿一个最小 Skill(比如读文件、执行 shell 命令)做一次完整的 mitmproxy 抓包,确认你能在 HTTP 层面看到 7 步交互。这一步建立对协议映射的直观认识。
- 再写一个最小 Skill:选一个你自己业务里的小任务(比如"按团队规范格式化 JSON 文件"),按上一节的 6 步顺序写一个 SKILL.md,跑通后再考虑复杂场景。
- 最后考虑 Skill 的组织和复用:当 Skill 数量超过 5 个时,开始关注 description 的触发覆盖度、Skill 之间的边界划分、以及
references/目录的按需加载策略。
FAQ
Q1:Skill 和 Function Calling 是什么关系?
Function Calling(函数调用)是 Skill 的基础设施。Skill 本身不是一个独立的协议功能——它依赖 Function Calling 来实现 LLM ↔ 宿主之间的工具交互。区别在于:Function Calling 解决"怎么调用工具"的问题,Skill 解决"什么时候该用什么工具、按什么顺序、设什么参数"的问题。前者是协议层机制,后者是应用层编排。
Q2:为什么不直接在协议层支持 Skill?
因为没必要。Skill 需要的所有能力——文本注入、工具定义、多轮对话——OpenAI 协议早在 2023 年就全部支持了。在协议层新增 skill 字段反而会增加复杂度,且需要所有模型提供商同步跟进,得不偿失。这恰恰是工程设计中的好决策:用组合替代扩展。
Q3:多个 Skill 共存时,LLM 如何选择触哪个?
完全靠匹配。system prompt 中列出了所有可用 Skill 的 name 和 description,LLM 根据用户输入与各 Skill 描述的语义相似度来判断。所以 description 字段至关重要——它必须覆盖足够多的触发场景(关键词、用户意图、URL 模式等),否则 Skill 永远不会被触发。
Q4:Progressive Loading 具体省了多少 token?
以 mp-read 为例,其 SKILL.md 正文约 3000 token,而摘要(name + description)仅约 50 token。在每次对话的 system prompt 中只注入摘要,只有当 LLM 判定需要该 Skill 时才会 Read 全文。如果用户一次对话中从未提起公众号相关话题,那 2950 token 就省下了。这个数字随 Skill 数量线性累积——5 个 Skill 就是约 15000 token 的区别。
Q5:Skill 和 MCP(Model Context Protocol,模型上下文协议)有什么区别?
MCP 是一种标准化的客户端-服务端协议,定义了三方(Host、Client、Server)之间的通信规范,包括资源发现、工具注册、提示模板等,属于协议层抽象。Skill 则完全没有自己的协议——它就是 markdown 文件 + system prompt 注入 + tool calling 的组合,属于应用层模式。两者的共同点是都依赖 tool calling 作为底层执行机制,但 MCP 试图标准化工具接入方式,Skill 则完全依赖自然语言指令驱动。
Q6:自己编写的 Skill 需要配置工具吗?
不需要。Skill 本身不声明工具。工具(Shell、Read、Write、Grep 等)由 IDE 宿主统一注册到每轮请求的 tools 数组中。Skill 的职责是告诉 LLM:在什么情况下、用哪些已有工具、按什么步骤完成任务。你可以把工具理解为"操作系统提供的系统调用",Skill 就是"告诉程序怎么组合这些系统调用来完成特定任务的文档"。
练习题
练习 1:Skill 触发条件设计
你正在为一个代码审查工具编写 code-review skill。下面两段 description 哪一段更可能被正确触发?为什么?
A 版:
description: >-
Helps with code review tasks.B 版:
description: >-
Run code review on the current git diff.
Use when the user asks to review code, check pull request quality,
or wants coding feedback. Also trigger when the conversation
mentions PR, MR, code quality, or review.参考答案
B 版更可能被正确触发。原因:
- 覆盖触发场景:B 版列出了具体的关键词(PR、MR、code quality、review),而 A 版只有一句模糊的 “Helps with code review tasks”
- 明示触发时机:B 版用 “Use when…” 句式告诉 LLM 什么时候该用这个 skill
- 兜底触发条件:B 版加了 “Also trigger when the conversation mentions…",覆盖对话中隐含的触发场景
A 版的问题在于:LLM 可能无法将用户的 “帮我看看这段代码” 与 “code review tasks” 关联起来。
练习 2:用 curl 验证 Skill 协议交互
按文中「用 curl 模拟单轮测试」的方法,写一个完整的测试用例,验证以下场景:
用户说:“帮我用 mp-read 读这篇公众号文章:https://mp.weixin.qq.com/s/xxxxx”
要求:
- 构造完整的
curl请求体(包含 system prompt 中的<available_skills>块) - 预测 LLM 返回的第一个
tool_calls应该是什么 - 构造第二轮请求的
messages数组(包含 tool 返回结果)
完成后再用真实的 OpenAI API Key 跑一遍,对比预期和实际返回是否有差异。
练习 3:编写第一个最小 Skill
按文中「如何从零编写第一个 Skill」的 6 步顺序,为以下任务编写一个 SKILL.md:
任务:检查当前 Git 仓库是否有未提交的改动,如果有,提醒用户先提交再继续。
要求:
- 目录名和
name字段一致(用 kebab-case) description覆盖"检查 git 状态”、“是否有未提交改动"等触发场景- 正文包含 Prerequisites Check(检查是否在 git 仓库内)
- 步骤指令明确:用
Shell工具执行git status --porcelain,解析输出,如果有改动就提醒用户
写完后用 Cursor 或支持 Skill 的 IDE 实际运行一次,确认能被正确触发。
自测题
读完本文后,先自己想 30 秒再展开答案:
1. Skill 在 OpenAI 兼容协议的请求体中,对应哪三个字段或机制?
- System Message 文本注入 —
<available_skills>块和触发指令被注入到messages[0].content - Tools Definition — Skill 需要的工具(Read、Shell 等)被注册到
tools数组 - Multi-turn Tool Calling Loop — LLM 通过发起
tool_calls来读取 SKILL.md、执行步骤、返回结果
Skill 本身不对应协议中的独立字段。
2. 为什么说 Skill 是一种「给 LLM 写使用手册」的设计模式?
因为 Skill 不新增协议能力,而是把三件已有事情组合起来:
- 在 system prompt 里告诉 LLM「你有这些手册可以查」
- LLM 通过
Read工具自己去读手册 - LLM 读完手册后,按手册说的步骤通过
Shell/Read等工具执行
整个流程依赖的是 LLM 已有的 tool calling 机制,不是新能力。
3. Progressive Loading 节省 token 的原理是什么?举例说明。
原理:不在每次对话的 system prompt 中全量注入 SKILL.md 正文,只注入 name + description 摘要(约 50 token)。只有当 LLM 判定需要该 Skill 时才通过 Read 工具读取完整正文(约 3000 token)。
以 mp-read 为例:5 个 Skill 各 3000 token,全量注入要 15000 token;用 Progressive Loading 只要 250 token(摘要),省下 14750 token 留给用户对话。
4. 如果 Skill 没有被触发,应该按什么顺序排查?
排查顺序:
- 检查 system prompt 中是否有
<available_skills>块 — 没有说明 IDE 没扫描到 Skill 目录 - 检查
description是否覆盖了触发场景 — 太窄会导致不触发 - 检查 system prompt 中是否有触发指令(如 “read and follow it IMMEDIATELY”)— 没有则 LLM 可能不会主动读 SKILL.md
- 检查
tools数组中是否注册了Read工具 — 没有则 LLM 无法读取 SKILL.md - 用 mitmproxy 抓包确认 — 看实际请求体中
messages[0].content的内容
5. Skill 和 MCP 的核心区别是什么?
| 维度 | Skill | MCP |
|---|---|---|
| 层级 | 应用层模式 | 协议层规范 |
| 依赖 | 依赖 IDE 已注册的工具 | 定义自己的工具发现/调用协议 |
| 实现 | Markdown 文件 + prompt 注入 | 标准化的 Client-Server 通信 |
| 适用场景 | 编排已有工具完成特定任务 | 为 LLM 提供新的工具能力 |
两者都依赖 tool calling 作为底层执行机制,但 MCP 试图标准化工具接入方式,Skill 则完全依赖自然语言指令驱动。
进阶路径
读完本文后,按以下顺序动手操作:
第一步:跑通一次完整的 Skill 协议交互观测(预计 1-2 小时)
- 安装 mitmproxy:
pip install mitmproxy - 启动代理:
mitmproxy --mode regular --listen-port 8080 - 配置 Cursor 走代理(设置
HTTP_PROXY和HTTPS_PROXY) - 触发一个已安装的 Skill(如输入 “read this file” 触发 Read 相关 skill)
- 在 mitmproxy 界面中观察:
- 第一轮请求的
messages[0].content是否包含<available_skills> - LLM 返回的第一个
tool_calls是否包含Read(SKILL.md) - 第二轮请求的
messages数组是否包含role: "tool"且content为 SKILL.md 全文
- 第一轮请求的
验证标准:能完整说出从用户发问到 LLM 执行核心命令之间的所有消息轮次。
第二步:编写一个带前置检查的 Skill(预计 2-3 小时)
选一个你自己业务里的小任务,例如:
- 「按团队 ESLint 规则检查当前文件」
- 「生成符合团队规范的 commit message」
- 「读取腾讯云 API 文档并生成调用示例」
按文中 6 步顺序编写 SKILL.md,重点检查:
description能否覆盖 3 种以上触发场景- Prerequisites Check 是否列出了所有依赖
- 步骤指令中的每个参数是否都能从 SKILL.md 中直接读到
写完后用 IDE 实际运行,确认:
- Skill 在合适的时机被触发
- 前置检查确实被执行
- 核心命令的参数正确
第三步:为团队设计 Skill 组织规范(预计 1 天)
当团队 Skill 数量超过 5 个时,需要关注:
- 触发覆盖度矩阵:列出每个 Skill 的触发关键词、用户意图模式、URL 模式,定期检查是否有遗漏或重叠
- Skill 边界划分原则:一个 Skill 只做一件事(参考 Factor 10:小而专注的 Agent)
references/目录的按需加载策略:把大型参考文档(如 API 文档)放到references/,在 SKILL.md 中说明「需要时读取references/api.md」
交付物:一份团队 Skill 编写规范(可以直接存在团队 Wiki 里)。
第四步(可选):深入 OpenAI 协议层(预计 2-3 天)
如果你想知道 Skill 之外的协议层细节:
- 读 OpenAI 的 Function Calling 文档
- 读 Anthropic 的 Tool Use 文档
- 对比两者在
tool_calls格式、finish_reason处理、并行调用支持上的差异 - 用 curl 分别调用两个接口,观察返回格式的差异
设计启示
看完底层机制,Skill 在工程上是一个干净的设计。
它没有发明新协议,也没有依赖特殊 API,只是把三件早就存在的事情组合到一起:
- System prompt 注入(LLM 早就支持)
- Tool schema 注册(LLM 早就支持)
- 多轮 tool calling 循环(LLM 早就支持)
Skill 解决的是一个实际问题:LLM 面对复杂任务时,怎么知道该用什么工具、按什么顺序、设什么参数。以前这些信息要么散落在文档里,要么硬编码在提示词里,Skill 把它们收敛成一份可执行、可维护、可共享的操作手册。
编写层面,三件事决定 Skill 的成败:description 的触发覆盖度、前置检查的完备性、每一步指令的可执行性。它们分别对应:能不能在正确的时机被触发、触发后能不能跑通、跑通后能不能稳定复现。
本文由腾讯云开发者张敏原创,首发于腾讯云开发者公众号。原文链接:大模型的 Agent Skill 功能,在 LLM HTTP 底层交互流中是怎么承载的?
资料口径说明
- 协议版本:本文基于 OpenAI 兼容协议的 Function Calling 机制(2023 年支持),以及 Cursor 等 IDE 的 Skill 实现(2024-2026 年)。具体实现随 IDE 版本可能变化。
- 工具示例:文中提到的
mp-readskill 和mitmproxy抓包方法是示例,实际使用时可替换为其他 skill 或抓包工具。 - 协议细节:OpenAI 和 Anthropic 的 tool calling 格式有差异,文中以 OpenAI 兼容协议为主。实际开发时请参考对应平台的官方文档。
- 性能数据:文中提到的 token 数量(如 3000 token、50 token)为示例值,实际取决于 SKILL.md 的长度和 description 的措辞。
- 适用范围:本文的 Skill 设计模式主要适用于 Cursor、Claude Code 等支持 SKILL.md 的 AI IDE。其他 AI 工具可能有不同的 Skill 实现方式。
- 原文来源:本文首发于腾讯云开发者公众号,由张敏原创。如需引用,请注明原文链接。
优化说明
评分:100/100(原文章质量)
评估:文章质量极高,结构完整(学习目标、目录、练习题、自测题、进阶路径、设计启示),技术准确,可读性强,表达自然无明显 AI 味道。
优化内容:
- 添加了"资料口径说明"章节(6 项说明)
- 使用 humanizer 检查 AI 味道:表达自然,无明显模板腔
状态:✅ 已记录为满分文章,跳过优化
记录时间:2026-06-29