Copilot-提示词链路详解

Copilot 提示词链路详解:从敲键盘到屏幕出结果

本文用本工作区(test_skills)里真实存在的文件做例子,逐阶段拆解一条完整的请求链路。

可信度说明 :下面的 JSON 报文是按这类系统的通用架构示意,各家的字段名和细节会有差异;但"模型只接收文本 + 工具表"这一层架构是确定的,也是理解一切的钥匙。


第一部分 · 基础说明

在讲链路之前,先把三个最容易混淆的概念钉死。

1.1 模型(Model)

模型是一个函数:

复制代码
输入:一串 token
输出:下一个 token 的概率分布

没有 文件系统、没有目录、没有网络、没有"技能"、没有"记忆"。它唯一会的动作是:接着往下写

所谓"AI 助手",是把模型的这个能力用外层程序包装出来的产物。

1.2 什么是 Skill

Skill = 一份写在文件夹里的说明书,程序把它登记成"菜单项",模型按需翻阅。

物理形态(本工作区真实路径):

复制代码
.github/skills/week-plan/SKILL.md          ← 说明书正文
.github/skills/week-plan/assets/xxx.md     ← 附带的资产

它在.md 文件头部有一段 YAML(叫 frontmatter),程序只读这两行来做登记:

yaml 复制代码
---
name: week-plan
description: 'USE WHEN: 用户要求制定/生成/调整「一周学习计划」......'
---

关键认知:

问题 答案
模型知道"skill"是什么吗? 不知道skill 只是提示词里出现的一个普通英文单词
这个词谁定的? 人。源自 Anthropic 的 Claude Skills,VS Code Copilot 采纳了这个叫法和目录约定
模型看到 skill 的什么? 一开始只看到 name + description 两行字
正文什么时候进模型? 模型主动调 read_file 之后,才作为工具结果被追加进请求

结论:skill 是"程序侧的目录约定",不是"模型侧的能力"。

1.3 什么是 Agent

Agent = 一个"角色定义":换了人设、换了工具权限、可以换模型。

物理形态:

复制代码
.github/agents/plan-coach.agent.md

frontmatter 里最关键的是 tools------工具白名单

yaml 复制代码
tools: ['codebase', 'search', 'edit']   # 没有终端 → 物理上无法跑命令

Skill 和 Agent 的本质区别:

Skill Agent
回答 这类任务怎么做 来做、能碰什么
工具权限 无控制力 核心就是控制工具
约束性质 文本建议(可以不听) 工具白名单(物理拦截
类比 岗位 SOP 手册 岗位本岗 + 门禁卡

1.4 三个概念速查

概念 载体 触发方式 模型能"绕开"吗
Prompt .github/prompts/*.prompt.md 只能手动 /名字 能(纯文本)
Skill .github/skills/<名>/SKILL.md 描述匹配,自动 能(纯文本)
Agent .github/agents/*.agent.md 手动切模式 / 当子代理 部分不能(工具被摘掉就真没有)
MCP MCP server 配置 模型按需调用 不能(真接口)

第二部分 · 具体例子

2.1 场景设定

用户在 Chat 里说:

复制代码
帮我制定一周学习计划,主题 MySQL 索引原理,每天 60 分钟

2.2 工作区里的真实文件(完整内容)

.github/skills/week-plan/SKILL.md

markdown 复制代码
---
name: week-plan
description: 'USE WHEN: 用户要求制定/生成/调整「一周学习计划」「7天学习安排」「学习排期」「周计划」。按内置模板生成计划并写入 study.md。'
---

# 一周学习计划生成器

## 何时使用

用户提到"一周学习计划""7 天安排""学习排期""周计划"时启用本技能。

## 步骤

1. 读取 `assets/plan-template.md`,它是**必须遵守**的输出格式定义。
2. 收集缺失信息:学习主题、每天可投入时间。用户没给就默认 60 分钟。
3. 按模板生成 7 天计划。
4. **写入** `study.md`(覆盖写入)。
5. 回复里只给"写入确认 + 3 行摘要",不要重复整张表。

## 硬性规则

- 第 7 天固定为"复习 + 自测"。
- 每天的验收标准必须可客观判断(例:"能默写 B+ 树三层结构")。
- 禁止"了解""熟悉""掌握"这类无法验证的动词。

## 资产

- `assets/plan-template.md` --- 输出格式模板

.github/skills/week-plan/assets/plan-template.md

markdown 复制代码
# 一周学习计划:{{TOPIC}}

> 每天投入:{{MINUTES}} 分钟 | 生成时间:{{DATE}}

| Day | 主题 | 具体任务 | 验收标准 |
| --- | ---- | -------- | -------- |
| 1 |      |          |          |
| 2 |      |          |          |
| 3 |      |          |          |
| 4 |      |          |          |
| 5 |      |          |          |
| 6 |      |          |          |
| 7 | 复习 + 自测 |      |          |

## 打卡

- [ ] Day 1
- [ ] Day 2
- [ ] Day 3
- [ ] Day 4
- [ ] Day 5
- [ ] Day 6
- [ ] Day 7

.github/agents/plan-coach.agent.md

markdown 复制代码
---
description: '学习计划教练:只处理学习计划相关请求,工具受限(不能跑命令、不能装依赖),适合被当作子代理调用。'
tools: ['codebase', 'search', 'edit']
---

# 角色

你是"学习计划教练"。你**只**处理学习计划相关的请求。

## 工作方式

1. 先一次性问清楚:学习目标、截止时间、每天可投入时间、当前水平。
2. 用 `search` / `codebase` 看看仓库里有没有旧计划,避免重复造。
3. 生成计划后写入 `study.md`。
4. 结尾给一句"如果只做一件事,先做哪件"的建议。

## 边界

- 不写代码、不执行命令、不安装依赖。超出范围就直说"这不在我的职责内"。
- 你的工具列表里没有终端,所以就算你想跑命令也跑不了------这是刻意设计的。

第三部分 · 完整调用逻辑(逐阶段)

模型服务 磁盘 .github/ Copilot 程序 你 模型服务 磁盘 .github/ Copilot 程序 你 #mermaid-svg-M2g9fXrze55vcJty{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-M2g9fXrze55vcJty .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-M2g9fXrze55vcJty .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-M2g9fXrze55vcJty .error-icon{fill:#552222;}#mermaid-svg-M2g9fXrze55vcJty .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-M2g9fXrze55vcJty .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-M2g9fXrze55vcJty .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-M2g9fXrze55vcJty .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-M2g9fXrze55vcJty .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-M2g9fXrze55vcJty .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-M2g9fXrze55vcJty .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-M2g9fXrze55vcJty .marker{fill:#333333;stroke:#333333;}#mermaid-svg-M2g9fXrze55vcJty .marker.cross{stroke:#333333;}#mermaid-svg-M2g9fXrze55vcJty svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-M2g9fXrze55vcJty p{margin:0;}#mermaid-svg-M2g9fXrze55vcJty .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-M2g9fXrze55vcJty text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-M2g9fXrze55vcJty .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-M2g9fXrze55vcJty .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-M2g9fXrze55vcJty .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-M2g9fXrze55vcJty .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-M2g9fXrze55vcJty #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-M2g9fXrze55vcJty .sequenceNumber{fill:white;}#mermaid-svg-M2g9fXrze55vcJty #sequencenumber{fill:#333;}#mermaid-svg-M2g9fXrze55vcJty #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-M2g9fXrze55vcJty .messageText{fill:#333;stroke:none;}#mermaid-svg-M2g9fXrze55vcJty .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-M2g9fXrze55vcJty .labelText,#mermaid-svg-M2g9fXrze55vcJty .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-M2g9fXrze55vcJty .loopText,#mermaid-svg-M2g9fXrze55vcJty .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-M2g9fXrze55vcJty .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-M2g9fXrze55vcJty .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-M2g9fXrze55vcJty .noteText,#mermaid-svg-M2g9fXrze55vcJty .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-M2g9fXrze55vcJty .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-M2g9fXrze55vcJty .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-M2g9fXrze55vcJty .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-M2g9fXrze55vcJty .actorPopupMenu{position:absolute;}#mermaid-svg-M2g9fXrze55vcJty .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-M2g9fXrze55vcJty .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-M2g9fXrze55vcJty .actor-man circle,#mermaid-svg-M2g9fXrze55vcJty line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-M2g9fXrze55vcJty :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 输入"帮我制定一周学习计划..."① 扫描 skills / agents / instructions② 只回 frontmatter(name + description)③ 第 1 次请求(system + user + tools)④ tool_call: read_file(SKILL.md)⑤ 读 SKILL.md 正文⑥ 正文(全量 skill 内容)⑦ 第 2 次请求(追加 tool 结果)⑧ tool_call: read_file(plan-template.md)⑨ 第 3 次请求⑩ tool_call: write_file(study.md)⑪ 第 4 次请求⑫ 最终文本⑬ 渲染成 Markdown

阶段 ① ②:扫描磁盘,只读"目录"

程序扫描 .github/ 下的约定路径,只提取 frontmatter,正文一个字节都不读。

这一步的产物是一份"菜单":

复制代码
- week-plan: USE WHEN: 用户要求制定/生成/调整「一周学习计划」「7天学习安排」「学习排期」「周计划」。按内置模板生成计划并写入 study.md。
- agent-customization: **WORKFLOW SKILL** --- Create, update, review...

为什么只给菜单不给全文? 因为 token 要钱,而且塞进去的信息越多,模型注意力越稀释。这就是 skill 被称为"按需加载"的原因。

阶段 ③:第 1 次请求(完整报文示意)

jsonc 复制代码
POST https://<模型服务地址>/v1/chat/completions

{
  "messages": [
    {
      "role": "system",
      "content": "你是 VS Code 中的 AI 编程助手。\n\n## 可用技能\n当用户请求与某个技能的 description 匹配时,先用 read_file 读取它的 SKILL.md 正文,再严格按正文执行。\n\n- week-plan: USE WHEN: 用户要求制定/生成/调整「一周学习计划」「7天学习安排」「学习排期」「周计划」。按内置模板生成计划并写入 study.md。\n- agent-customization: **WORKFLOW SKILL** --- Create, update, review, fix, or debug VS Code agent customization files...\n"
    },
    {
      "role": "user",
      "content": "帮我制定一周学习计划,主题 MySQL 索引原理,每天 60 分钟"
    }
  ],

  "tools": [
    {
      "name": "read_file",
      "description": "读取文件内容",
      "parameters": { "type": "object", "properties": { "path": { "type": "string" } } }
    },
    {
      "name": "write_file",
      "description": "写入文件内容",
      "parameters": { "type": "object", "properties": { "path": { "type": "string" }, "content": { "type": "string" } } }
    },
    {
      "name": "grep_search",
      "description": "在文件中搜索文本",
      "parameters": { "type": "object", "properties": { "query": { "type": "string" } } }
    },
    {
      "name": "run_in_terminal",
      "description": "在终端执行命令",
      "parameters": { "type": "object", "properties": { "command": { "type": "string" } } }
    }
  ]
}

此刻模型"眼里的世界"

它看到的全部内容,就是上面那串 token。具体说:

模型看到的 模型看不到的
- week-plan: USE WHEN: 用户要求制定... 这行字 文件路径、目录结构
"帮我制定一周学习计划..." 这行字 SKILL.md 的正文
一份函数表(名字 + 参数格式) 什么叫"技能"、什么叫"文件夹"
磁盘上还有多少文件

重点:模型此刻完全不知道 week-plan 是一份"技能"。它只看到一段带 - 的列表文本,外加一列它可以选择去调用的函数。

阶段 ④:模型返回(第 1 次响应)

模型逐 token 生成,吐出的是一段文本,而这段文本恰好构成了一个合法结构:

jsonc 复制代码
{
  "role": "assistant",
  "content": null,
  "tool_calls": [
    {
      "id": "call_01",
      "name": "read_file",
      "arguments": { "path": ".github/skills/week-plan/SKILL.md" }
    }
  ]
}

这是整条链路里最颠覆直觉的一点

所谓"模型调用了技能",本质是模型生成了一段格式正确的文本,被程序解析成了函数调用。模型自己并没有"打开文件"的能力。

阶段 ⑤ ⑥:程序真的去读文件

程序收到 tool_call → 执行 → 从磁盘读出 SKILL.md 全文。

阶段 ⑦:第 2 次请求 ------ 全量 skill 内容在这里

这是回答"最后发送的全量 skill 是什么"的关键一步。程序把上一步的结果追加到 messages 末尾:

jsonc 复制代码
POST https://<模型服务地址>/v1/chat/completions

{
  "messages": [
    { "role": "system",  "content": "你是 VS Code 中的 AI 编程助手。\n\n## 可用技能\n- week-plan: USE WHEN: 用户要求制定/生成/调整「一周学习计划」...\n- agent-customization: ..." },

    { "role": "user",    "content": "帮我制定一周学习计划,主题 MySQL 索引原理,每天 60 分钟" },

    { "role": "assistant", "content": null,
      "tool_calls": [{ "id": "call_01", "name": "read_file",
                       "arguments": { "path": ".github/skills/week-plan/SKILL.md" } }] },

    { "role": "tool", "tool_call_id": "call_01",
      "content": "---\nname: week-plan\ndescription: 'USE WHEN: 用户要求制定/生成/调整「一周学习计划」「7天学习安排」「学习排期」「周计划」。按内置模板生成计划并写入 study.md。'\n---\n\n# 一周学习计划生成器\n\n## 何时使用\n\n用户提到\"一周学习计划\"\"7 天安排\"\"学习排期\"\"周计划\"时启用本技能。\n\n## 步骤\n\n1. 读取 `assets/plan-template.md`,它是**必须遵守**的输出格式定义。\n2. 收集缺失信息:学习主题、每天可投入时间。用户没给就默认 60 分钟。\n3. 按模板生成 7 天计划。\n4. **写入** `study.md`(覆盖写入)。\n5. 回复里只给\"写入确认 + 3 行摘要\",不要重复整张表。\n\n## 硬性规则\n\n- 第 7 天固定为\"复习 + 自测\"。\n- 每天的验收标准必须可客观判断(例:\"能默写 B+ 树三层结构\")。\n- 禁止\"了解\"\"熟悉\"\"掌握\"这类无法验证的动词。\n\n## 资产\n\n- `assets/plan-template.md` --- 输出格式模板"
    }
  ],

  "tools": [ /* 同第 1 次请求,原样重发 */ ]
}

注意三个细节

  1. 每次请求都要把历史全部重发一遍。 模型是无状态的------它不记得上一次请求。所谓"对话记忆",是程序每次把历史拼进去造出来的假象。
  2. role: "tool" 这条消息就是"全量 skill 内容"。 到这一刻,模型才第一次知道这份 skill 要它干什么。
  3. tools 数组每次都原样重发。 模型"能做什么",取决于这次请求里带了哪些函数签名。

阶段 ⑧:模型返回(第 2 次响应)

现在模型读到了 SKILL.md,正文第 1 条写着"读取 assets/plan-template.md",所以它接着调:

jsonc 复制代码
{ "role": "assistant", "content": null,
  "tool_calls": [{ "id": "call_02", "name": "read_file",
                   "arguments": { "path": ".github/skills/week-plan/assets/plan-template.md" } }] }

阶段 ⑨ ⑩:第 3 次请求 → 写文件

程序再把模板全文作为 role: "tool" 追加,第 3 次 POST。模型读到模板里的表格列定义,于是调用写文件:

jsonc 复制代码
{ "role": "assistant", "content": null,
  "tool_calls": [{ "id": "call_03", "name": "write_file",
                   "arguments": {
                     "path": "study.md",
                     "content": "# 一周学习计划:MySQL 索引原理\n\n> 每天投入:60 分钟 | 生成时间:2026-09-13\n\n| Day | 主题 | 具体任务 | 验收标准 |\n| --- | ---- | -------- | -------- |\n| 1 | B+ 树结构 | ... | ... |\n..."
                   } }] }

阶段 ⑪ ⑫:第 4 次请求 → 最终文本

程序追加写入结果,第 4 次 POST。这次模型不再需要工具,直接输出纯文本(按 SKILL.md 第 5 条要求,只给摘要不给全表):

jsonc 复制代码
{ "role": "assistant",
  "content": "已按 `week-plan` 技能写入 `study.md`。\n\n- 覆盖 7 天,第 7 天为复习 + 自测\n- 每天 60 分钟,含具体任务与可量化验收标准\n- 如需调整节奏,告诉我主题或时长即可重生成。" }

阶段 ⑬:渲染

程序把这段文本渲染成 Markdown 显示给你。

全流程代价

轮次 请求内容要点 messages 条数
1 system + user 2
2 + assistant(tool_call) + tool(SKILL.md 全文) 4
3 + assistant + tool(模板全文) 6
4 + assistant + tool(写入结果) 8

你看到的"一个回复",是 4 次独立请求拼出来的。 这就是 Agent 和普通问答的分水岭。


第四部分 · 类比:访问百度

浏览器 / 网络世界 Copilot 对话
你在地址栏敲 baidu.com 你在 Chat 输入框打字
浏览器查 DNS:域名 → IP 程序扫 .github/:skill 名 → 要注入的文本
浏览器组 HTTP 请求 程序组 JSON(system + messages + tools)
发包到服务器 HTTPS POST 到模型服务
服务器内部处理 模型逐 token 推理
返回 HTML 流式返回 token
浏览器渲染页面 程序渲染成 Markdown
域名 / IP 是网络设施的抽象 skill / agent 是官方起的名字
------ 工具调用循环 ← 关键差异

最大差异 :DNS + HTTP 是一次性请求-响应 ;Agent 是多轮循环,一圈一圈把上下文"喂胖"。


第五部分 · 由此推出的结论

5.1 模型全程只见过一维 token 流

文件、目录、skill、agent、MCP------这些概念只存在于程序那一侧

在模型眼里,它们全都塌缩成了两样东西:

  1. 文本(提示词里的字)
  2. 函数签名tools 数组里的 JSON Schema)

5.2 三个可直接验证的推论

现象 真正原因
skill 有时触发、有时不触发 概率采样,不是查表匹配
description 写得越准越容易触发 描述是菜单上唯一的线索,模型只能靠它判断
打开了 SKILL.md 就一定生效 文本已在请求体里,绕过了整条发现链路
Agent 里写了"不许跑命令"仍可能跑 那是文本(可被说服);摘掉 tools 才是物理拦截

5.3 三种约束的可靠度

目的 手段 可靠度
"尽量这么做" 提示词(instructions / skill body) ⚠️ 概率性
"绝对别做" 工具白名单(tools: [] ✅ 确定性
"必须拦下来" hooks / 审批弹窗 ✅ 确定性

把确定性要求寄托在提示词上,是这套体系里最常见的错误。


第六部分 · 常见误解

误解 事实
"模型认识 SKILL.md 这个文件" 不认识。它只认识被程序塞进来的文本
"skill 是模型的一种能力" 不是。是程序读文件 + 拼提示词的组合效果
"对话有记忆" 没有。每次请求都重发全部历史
"模型打开了文件" 没有。它生成了一段文本,程序替它打开了文件
"skill 是通用术语" 是 Anthropic 起的名字,VS Code 沿用了
"MCP 和 skill 是一类东西" 不是。MCP 是开放标准 + 真接口;skill 只是文本
"不写 tools 就没有工具" 反了。省略 = 默认全套tools: [] 才是没有任何工具

附录 A · 为什么第一次请求就要带 tools

A.1 先看你的理解对不对

你的描述:

我给模型提供了 tools,说明这个工具在哪个位置、是干啥的。模型去"点菜",比如它需要读取哪个,并返回一定的格式。返回这个格式之后,真正去读这个 skill 文本的是 Copilot 这个外部程序。外部程序读取完之后,把结果内容再次给到模型。

基本全对,只有一处要修正:tools 里没有"位置"信息。

jsonc 复制代码
{
  "name": "read_file",
  "description": "读取文件内容",         // ← 干什么
  "parameters": {
    "type": "object",
    "properties": { "path": { "type": "string" } }   // ← 参数长什么样
  }
}

模型拿到的只有三样:名字、用途、参数格式

不知道

  • 这个工具背后是本地磁盘还是网络请求
  • 实现代码在哪
  • 文件系统里有什么

那个 .github/skills/week-plan/SKILL.md 的路径,是模型自己写出来的字符串 ------它根据上下文(你的话、工作区结构提示、之前的工具结果)出来的。程序拿到这个字符串,再去磁盘上找。找不到就报错给它,它再猜一次。

A.2 为什么非得给

因为模型的唯一动作是生成文本,它没有任何执行能力。

那它怎么表达"我想读那个文件"?

复制代码
方案 A(自然语言):"我想读一下 week-plan 这个技能的正文"
方案 B(结构化):  {"name":"read_file","arguments":{"path":"...SKILL.md"}}

方案 A 程序没法可靠解析------"读一下""看看""了解一下"可能是一个意思,中英文还混着。方案 B 一个 JSON 解析就拿到确切意图。

所以 tools 的作用是:告诉模型"请用方案 B 的格式,这些是合法的函数名和参数格式"。

类比:给不会做菜的顾客一份菜单和点菜机。

餐厅 对应
顾客(模型) 不会做菜,只会点单
菜单 + 点菜机(tools 可点的菜 + 点单格式
厨房(Copilot 程序) 真正做事的人
端菜上桌(role: "tool" 消息) 把文件内容送回请求里

给菜单不是指望顾客进厨房,是让他能点菜

A.3 历史上真的没有 tools 这个字段

早期 Agent(ReAct 那套)就是纯文本约定 ,模型直接把下面这段字写出来,程序拿正则去抓:

复制代码
Thought: 我需要先看这个技能的说明
Action: read_file
Action Input: .github/skills/week-plan/SKILL.md

问题很明显:格式写飘了抓不到、各家约定不统一、模型可能自创不存在的 Action

所以后来标准化了------把"合法动作清单 + 参数格式"正式声明成 JSON Schema。

所以 tools 不是魔法,它是把"正则抓文本"升级成了正式契约

A.4 tools 的三重身份

身份 含义
文档 "你可以让我做这些事"
语法 "表达请求必须用这个格式"
白名单 "列表之外的,别想"

第三条正是 plan-coach 实验能生效的机制之一:终端工具不在数组里 → 模型没有这个词可用 → 即使它在正文里写"我需要跑个命令",也变不成合法的 tool_call

A.5 核心问题:模型有记忆吗?

没有。完全没有。

那你担心的第二点就来了:

第二次请求又是一个单独请求,它不记得第一次,这不是有问题了吗?

答案是:程序每次都把完整历史重发一遍。

设想 效果
如果模型有记忆 只发新增内容就够了
实际做法(无状态) 每次把全部往来记录重新发一遍

所以第 2 次请求发给模型的实际内容是这样的:

复制代码
[system]     你是 VS Code 助手......可用技能:- week-plan: USE WHEN: ......
[user]       帮我制定一周学习计划,主题 MySQL 索引原理,每天 60 分钟
[assistant]  (tool_call) read_file(".github/skills/week-plan/SKILL.md")
[tool]       ← SKILL.md 的全文(本次新增的)

模型看得到自己刚才说了"要读这个文件"。 所以它当然知道为什么会拿到这份内容。

不是它记住了,是聊天记录被整本当复印件重发了一遍

类比:不是打电话 (对方记得你上次说了啥),而是每次寄一个装着全部往来信件的档案袋------对方每次都是从头读完所有信,才回你这一封。

A.6 为什么这样设计

无状态是为了让模型服务能横向扩展:任意一轮请求可以落到任意一台机器上,不用维持会话。代价就是每次都要重传,token 消耗随轮次线性增长

A.7 由此解释的几个现象

现象 真正原因
长对话越来越慢、越来越贵 每次请求都重发全部历史
聊久了它"忘了"前面说的事 超出上下文窗口,被截断或压缩了
关掉 VS Code 再打开,对话还在 历史存在本地磁盘,不存在模型里
同一句话在不同会话结果不同 上下文不同,模型是"就地重算"的
工具调用的结果它"记得" 那次结果一直在历史里被持续重发

A.8 完整的一圈(点菜闭环)

  1. 程序把 工具菜单 + 历史 + 你的这句话 打包,POST 出去
  2. 模型生成文本 → 恰好构成 read_file 调用格式
  3. 程序解析出 tool_call
  4. 程序真的去磁盘把文件读出来(模型全程没碰过磁盘)
  5. 程序把「第 1 步的全部内容 + 模型的 tool_call + 文件内容」打包,再 POST 一次
  6. 模型这次看到了完整前因后果(因为它收到了整本记录)
  7. 重复 2--6,直到模型输出不带 tool_call 的纯文本
  8. 程序把这段文本渲染成 Markdown 给你看

你看到的"一个回复",实际是 N 轮独立请求拼出来的。


附录 B · token 成本与上下文增长

B.1 结论先行

因为历史每轮全量重发,所以:

维度 增长方式
单次请求的大小 线性增长(第 N 轮 = 前 N−1 轮之和 + 固定开销)
整轮对话的总消耗 平方级增长
体感 越聊越慢、越聊越贵
复制代码
总 token ≈ (N² / 2) × 每轮平均增量

对话长度翻倍,总消耗翻四倍。

B.2 单次请求:线性

第 N 轮请求实际发出去的内容是:

复制代码
[固定开销]  system + 技能菜单 + 工具 schema        ← 每轮都发
[第 1 轮]   你的话
[第 2 轮]   模型的 tool_call + 工具返回结果
[第 3 轮]   模型的 tool_call + 工具返回结果
   ⋮
[第 N 轮]   你刚打的字                              ← 唯一的"新内容"

新内容只有最后一行,前面全是重发。

B.3 算一笔账

用第 2 部分那个"制定一周学习计划"的例子,固定开销(system + 技能菜单 + 工具 schema)约 550 token:

轮次 本轮新增 本次请求实发 累计实发
1 你的话(20) 570 570
2 tool_call + SKILL.md 全文(540) 1110 1680
3 tool_call + 模板全文(290) 1400 3080
4 tool_call(写文件) + 结果(330) 1730 4810

实际新增内容合计约 1180 token,实发 4810------放大了 4 倍。

如果这样聊 20 轮,放大倍数会到十几倍。

B.4 为什么这个放大在 Agent 场景尤其严重

普通问答一轮只加几十 token。但 Agent 每轮要追加:

  • 一次 tool_call(几十 token)
  • 一次工具结果(可能是一整个文件,几千 token)

一次 read_file 的结果,可能比前面所有对话加起来还大。 这就是为什么"工具返回内容要截断/摘要"是很多 Agent 产品的核心优化点。

B.5 但真实世界有四层缓冲

机制 作用
提示词缓存(prompt caching) 服务端缓存前缀,重复部分按极低价格甚至免费计费。这是最重要的一层------表面 token 数不等于账单
上下文压缩 接近窗口上限时,程序把旧轮次摘要化或截断
子代理隔离 子代理在自己的上下文里干活,只回一个摘要给主对话。这正是 plan-coach 那个"适合被当作子代理调用"的用意
输入 / 输出分别计价 输出 token 通常贵得多;而重复重发的是便宜的输入 token

所以:真实成本远低于"表面 token 数",但增长趋势依然存在。

B.6 一个容易被忽略的固定开销

tools 数组每一轮请求都原样重发。

如果你装了 5 个 MCP server,每个暴露 20 个工具,那就是 100 份函数签名,每一轮都发一遍------哪怕这次压根不用它们。

操作 影响
装 1 个 MCP server 每轮多几十~几百 token 固定开销
装 5 个 MCP server 每轮多几千 token,每次请求都付
关掉不用的 server ✅ 立刻降低每轮固定开销

这是实打实的持续成本,拖慢的是每一次请求。 所以"少装几个 MCP server"是有性能收益的,不只是清爽。

B.7 实操结论

操作 效果 为什么
聊完一个话题就新开会话 ✅✅ 直接砍掉历史负担,把 N² 拉回 1
把重活丢给子代理 ✅✅ 主对话只留摘要,过程不进主上下文
关掉不用的 MCP server 降低每轮固定开销
别长时间开着无关文件 打开的编辑器每轮都作为附件进请求体
依靠 skill 的按需加载 只进 description,正文用时才读
只看"这次回复用了多少 token" 真正的大头是重复重发的前缀

一句话:省 token 的关键不是"少说话",而是"少让历史被反复重发"。

相关推荐
海盗12342 天前
微软技术日报 2026-09-11:VS Code 语音与定时自动化上线,Copilot 键终于可改映射
microsoft·自动化·copilot
ms365copilot2 天前
Excel Copilot挖掘数据洞察,不用精通函数也能做数据分析 (1)
数据分析·excel·copilot
白帽黑客-晨哥3 天前
Copilot 能换成本地吗?本地化 AI 编程助手可行性全解析
人工智能·copilot
MicrosoftReactor3 天前
技术速递|如何在不牺牲任务质量的前提下,让 AI 编码更具成本效益
人工智能·ai·github·copilot
Leinwin3 天前
Claude Fable 5.1 上线 Copilot Studio:能力定位、访问策略配置与 Work IQ 上下文
copilot
ms365copilot3 天前
Excel Copilot一键美化图表,无需手动调格式
excel·copilot·outlook
白帽黑客-晨哥3 天前
Copilot 能换成本地吗?本地化 AI 编程助手完全指南
数据库·人工智能·copilot
天天喝旺仔4 天前
AI 编程实战:用 Cursor + Claude Code + Copilot 把效率翻倍
ide·chatgpt·prompt·copilot·ai编程
ms365copilot4 天前
Copilot Planner Agent生成【机器人产品发布全计划】
机器人·copilot