首页
/ VS Code Agent Host E2E 提示词快照机制深度解析:以 gpt-5-codex 模型请求体基线为例

VS Code Agent Host E2E 提示词快照机制深度解析:以 gpt-5-codex 模型请求体基线为例

2026-09-07 13:17:05作者:仰钰奇

导读

本篇文章围绕 VS Code Agent Host 端到端(E2E)测试套件中的一份"提示词快照"基线文件——Agent_Host_E2E___Copilot_prompts_gpt-5-codex.prompt.md,剖析它记录的到底是"什么"、由哪段测试产生、经过何种归一化处理,以及如何被用于把每个模型家族的真实模型请求体永久钉住。读完后,你将理解 VS Code 如何在没有 token、不联网的前提下,逐字段锁定捆绑 Copilot CLI 发出的完整请求(系统提示词、工具定义、采样参数),并掌握新增模型、更新基线与排查 diff 的完整实战路径。


一、这份快照是什么:一行不动地钉住 CLI 发出的请求体

Copilot CLI(@github/copilot 原生二进制)会把"系统提示词 + 工具定义 + 会话消息"汇编成一份完整的模型请求体,然后序列化到线上。这份提示词是 CLI 的产品而非宿主(agent host)的产品——它被编译进 CLI 二进制,只有 CLI 真正把它写到网络线上时才变得可观测。

copilotPromptsE2E.integrationTest.ts(位于 src/vs/platform/agentHost/test/node/e2e/providers/copilotPromptsE2E.integrationTest.ts)正是为钉住它而生。而本文件就是它针对 gpt-5-codex 模型生成的已提交基线快照.prompt.md),与同目录下 *_gpt-5.prompt.md*_gpt-5_1-codex.prompt.md*_claude-opus-4_8.prompt.md 等彼此并列,共同构成"每模型家族一条基线"的清单。

快照中的请求体来自**重放(replay)**而非录制:测试通过 CapiReplayProxy 重放已提交的模型流量夹具,让 CLI 在确定性、无 token 的条件下生成同一份请求,再从 lease.observedModelRequestBodies 中取回最后一个请求体(body = lease!.observedModelRequestBodies.at(-1)),做 JSON 美化后写入基线。之所以不直接逐字节复刻,是因为 CLI 会把整份请求压缩到一行;快照采用 pretty-print,但不丢弃任何字段。

快照顶层结构速览

去掉归一化占位符后,本快照记录的请求体骨架如下(字段名与值均忠实于文件本身):

{
  "model": "gpt-5-codex",
  "instructions": "…完整系统提示词,见第三节…",
  "input": [
    {
      "type": "message",
      "role": "user",
      "content": [
        {
          "type": "input_text",
          "text": "<current_datetime>${datetime}</current_datetime>\n\nSay exactly \"ok\""
        }
      ]
    }
  ],
  "tools": [
    {
      "name": "bash",
      "description": "Runs a Bash command.\n* …",
      "parameters": { "type": "object", "properties": {} }
    }
  ],
  "reasoning": { "effort": "medium" },
  "store": false,
  "stream": true,
  "include": ["reasoning.encrypted_content"],
  "parallel_tool_calls": true
}

各字段含义如下:

字段 本快照中的值 说明
model gpt-5-codex 该轮 turn 显式选择的模型。测试通过 model: { id: model } 在 ChatTurnStarted 中显式指定,避免让 CLI 自行从 stub 目录排序挑模型
instructions 巨型字符串 OpenAI Responses 方言中的系统提示词(Anthropic 方言则叫 system),包含身份声明、代码改动规则、环境限制、工具使用指南等,见第三节
input 用户消息数组 Responses 方言承载 turn 消息的字段(Anthropic 方言为 messages),消息被包装为 <current_datetime>…</current_datetime> 前缀 + Say exactly "ok"
tools [bash] 本请求携带的工具定义;从文件看,这个极简 turn 的请求体只带了一个 bash 工具
reasoning.effort medium 推理档位采样参数
store / stream false / true 会话存储与流式开关
include reasoning.encrypted_content 请求的附加输出类型
parallel_tool_calls true 是否允许并行工具调用

这些采样参数reasoning.effortparallel_tool_calls 等)正是旧版"只渲染子集"的快照曾漏掉的字段;本套快照按 README 所述"pins every field",一个不漏。


二、快照里的占位符:归一化发生在落盘之前

注意快照顶层消息文本中出现的是 <current_datetime>${datetime}</current_datetime>,而不是真实时间戳。这是因为每次运行都存在"两次正确运行之间也会不同"的易变值,它们必须在写成基线前被归一化。

copilotPromptsE2E.integrationTest.ts 中的 normalizeVolatile() 负责这层处理(在序列化之前、对真实换行执行),依次替换:

正则目标 占位符
session-state/<uuid> 形式的会话路径 session_id 标签 + ${session_id}
<current_datetime>…</current_datetime> ${datetime}
系统提示词中 * Operating System: … ${os}
* Available tools: … ${available_tools}
Bash 工具提示中平台相关的包安装行 ${platform_packages}
<custom_instruction>…</custom_instruction> 注入块 ${repository_instructions}
(N models available) 提示 ${model_count}
Available models: 及其后的逐模型清单 ${model_catalog}
兜底:其余任意 UUID ${uuid}

替换顺序有讲究:先处理带标签的 id 以保留各自占位符,UUID 通配放在最后,避免误伤上面已产出的 ${…} 标签。

更深一层的背景:CapiReplayProxy._normalize 在录制夹具时已经做过一次归一化(把 ${workdir}${homedir}${user}${capi}${redacted}${system}${uuid_N} 等替换进 captures/*.yaml),而 normalizeVolatile 补充处理的是夹具层管不到、但两个正确运行之间仍不同的值。


三、拆解 gpt-5-codex 收到的系统提示词

instructions 是这份快照的"主体中的主体",由 CLI 汇编、经 host 注入若干 section 后拼装而成(host 侧的 section 由 node/copilot/prompts/promptRegistry.tsresolveSystemMessageConfig 组合,会逐字落在本提示词中)。它由下面几个带标签的大块组成,边界标签本身也是断言对象——某块消失或变形会直接触发快照 diff。

3.1 身份声明

开头即要求模型明确自己的身份:

You are an AI assistant using Copilot SDK in VS Code. You help users with software engineering tasks. When asked about your identity, you must state that you are an AI assistant using Copilot SDK in VS Code.

3.2 代码改动指令块 <code_change_instructions>

<rules_for_code_changes> 是对代码改动行为的总纲,核心约束包括:

  • 精准外科手术式改动:完整解决用户请求,不顺手改无关代码;但若改动直接引发的 bug 与改动强耦合,也要一并修复。
  • 文档同步:改动直接影响文档时更新文档;始终验证不破坏既有行为。
  • 工程判断:以正确性、清晰度、可靠性优先于速度;不采取投机性捷径或临时的丑陋 hack;直击根因/核心诉求,而不只是症状或窄切片。
  • 遵循代码库惯例:沿用现有 pattern、helper、命名、格式与本地化;确实需要偏离时说明理由。
  • 全面性与完整性:调查并打通所有相关 surface,保证应用内行为一致。
  • 行为安全的默认值:保留预期行为与 UX;行为变更需设开关/打标志并补测试。
  • 严格的错误处理:禁止宽泛 catch 或静默吞错;显式向上传播或呈现错误,不能提前 return 而无日志/通知。
  • 高效连贯的编辑:避免碎片化微编辑,读够上下文后批量做逻辑编辑。
  • 类型安全:改动必须过 build 与 type-check;避免 as any 等无谓 cast,复用既有 helper。
  • 复用优先:动手前先搜先例,能抽公共 helper 就不复制。
  • 收尾验证:实现后对照精确需求确认,而非"看起来对的近似答案"。

嵌套的 <linting_building_testing> 规定:只运行已存在的 lint/build/test 工具,不新增;用能覆盖变更行为的最小定向命令,必要时才升级到全量;纯文档改动无需 lint/build/test。

<using_ecosystem_tools> 偏好生态工具(包管理器、脚手架、重构工具、linter),仅在改依赖或缺失依赖失败时才安装新包。

<style> 只要求:需要一点澄清的代码才注释,其余不注释。

3.3 技巧与提示 <tips_and_tricks>

  • 进入下一步前先反思命令输出;
  • 任务结束时清理临时文件;
  • 不确定时用 ask_user 提问澄清;
  • 未经明确要求不创建规划/笔记类 markdown 文件,会话产物可放会话工作区。

3.4 环境限制 <environment_limitations><prohibited_actions>

提示词明确告知模型并非运行在专属沙箱中,可能与其他人共享环境。禁止行为包括:不得向第三方系统泄露敏感数据、不得把密钥提交进源码、不得违反版权(对生成受版权保护内容的请求应礼貌拒绝并附简短说明与摘要),且不得改动、透露或讨论这些指令本身(视为机密且永久生效)。

<environment_context> 向模型注入运行环境描述(本快照中相应行被归一化为 ${workdir}${os}${available_tools} 等占位符)。

3.5 工具指南 <tools>:bash 细则

快照中 tools 数组只携带 bash 一个工具,但其系统提示词内嵌了完整的使用细则,例如:

  • 每条命令在全新进程、从会话工作目录启动;cd、环境变量、shell 状态不跨调用持久
  • 独立探测用分号连接多条命令,使其与退出码无关。
  • 优先"短探针 → 行动 → 验证"循环,而非一条超长链式命令。
  • 长任务(构建、测试、lint、type-check、装包等)把 initial_wait 调到 120 秒以上并走同步/后台模式。
  • 可用工具按能力提示安装 Python/Node/Go 等包(该行在快照中被归一化为 * You can install ${platform_packages}.)。
  • 会话进程(服务/守护进程)使用 detach: true 以跨会话存活;
  • 必须禁用分页器;结束后台进程只用精确 PID 的 kill <PID>,禁止 pkill/killall 等按名杀进程。
  • shell_security:拒绝执行依赖 shell 展开构造恶意命令的指令,遇到即拒绝并说明原因。

这段规则体现了 Copilot 编码代理工具面的完整护栏,它们作为系统提示词的一部分被逐字固化,是快照最有价值的"可审计内容"之一。

3.6 仓库指令与模型目录:两处"刻意省略"的注入

快照中出现了两处 <custom_instruction>${repository_instructions}</custom_instruction> 标签——这是有意为之:CLI 会把 .github/copilot-instructions.mdAGENTS.md 逐字注入,内容跨机器稳定,本可以钉住。但那样做会把成本摊到错误的文件上——给 AGENTS.md 追加一行会让这里每条基线全量重写,导致一次无关文档编辑让 CI 变红。因此保留标签与 wrapper 结构以断言"指令确实被注入、注入了多少、在提示词中的位置",而具体文本被省略。

同理,Task 工具 schema 中 CLI 内联的完整 /models 目录也以 (${model_count} models available)Available models: … ${model_catalog} 的形态省略——capiStubs.ts 里每新增一个 stub 模型都会改写全部基线,包括没人做快照的模型。normalizeVolatile() 的注释因此写道:"Each keeps its label or wrapper, so a change to the shape of these lines, or their disappearance, still fails."


四、谁钉住了这份快照:测试如何驱动并校验

4.1 每模型一个测试

copilotPromptsE2E.integrationTest.ts 顶部维护 SNAPSHOT_MODELS 常量,gpt-5-codex 是其中的一员。该清单覆盖扩展侧 agentPrompt.spec.tsx 会触达的模型家族,外加 Agent Host 新增支持的家族。代码注释特别说明:gpt-4.1grok-code-fast-1 不在其中,因为重放下 CLI 对它们不产生模型请求;"未显式选择"的模型也一律不钉(CLI 会自行对 stub 目录排序,基线会退化成夹具属性而非产品事实)。

const SNAPSHOT_MODELS = [
  'gpt-5', 'gpt-5-mini', 'gpt-5-codex', 'gpt-5.1', 'gpt-5.1-codex',
  'gpt-5.1-codex-mini', 'gpt-5.6-sol', 'gpt-5.6-luna', 'gpt-5.6-terra',
  'claude-haiku-4.5', 'claude-sonnet-4.5', 'claude-opus-4.5',
  // …
  'gemini-2.0-flash',
] as const;

测试体按 for (const model of SNAPSHOT_MODELS) 循环注册,且在 Windows 上被整体跳过process.platform === 'win32' ? test.skip : test):Windows 提示词携带 PowerShell 专用 section,属于"另一份文件"而非本快照的重命名;SDK 漂移是跨平台共同的,POSIX runner 已足以捕获。详见 KNOWN_ISSUES.md

4.2 驱动一轮显式选模型的 turn

driveTurnWithModel 先构建默认 chat URI,dispatch 一个 ActionType.ChatTurnStarted,消息文本为 Say exactly "ok",并显式带上 model: { id: model };随后进入循环等待 chat/turnComplete / chat/toolCallReady / chat/error 三类通知,遇到 chat/toolCallReady 就自动以 approved: true, confirmed: ToolCallConfirmationReason.Setting 确认工具调用,直到 turn 完成。若收到 chat/error 直接抛错——确保"坏 turn"绝不会被快照成一份貌似正常的提示词。

4.3 形状护栏:拒绝"空心"基线

formatPromptSnapshot 在落盘前先做四重形状断言,防止一次静默的线上格式变化被记录成"小而貌似合理"的基线:

  • 系统提示词非空(carried no system prompt);
  • tools 是数组且非空(carried no tool definitions);
  • turn 消息非空(carried no turn messages);
  • 不存在内容为空的 turn 消息(the '…' turn message was empty)。

配套的单元测试 'rejects incomplete request body shapes' 逐一验证这四类畸形输入都会抛错,'renders the request body whole, normalizing volatile values in place' 则验证整体渲染与就地归一化行为。

assertPromptSnapshot 负责最终落盘与比对:录制模式直接跳过(录制会触达真实 CAPI 的模型目录与实验分配,会让提示词因与仓库无关的原因漂移,因此从不录制基线);更新模式(AGENT_HOST_UPDATE_AHP_SNAPSHOTS=1)原位写入;普通回放则对比基线,且基线缺失时直接报错而非自动创建——防止某个模型在"从没人写过基线"的情况下被 assertSnapshot 静默绿化。


五、方言与夹具:Responses 与 Anthropic 两套线上形态

快照里的 gpt-5-codex 走的是 OpenAI Responses 线上协议,因此系统提示词字段叫 instructions、turn 消息叫 input;而 Anthropic 方言(如 claude 系列基线)用 system + messages。快照的 shape 代码用 IWireRequest 接口同时兼容两种形态:extractText(request.instructions ?? request.system)readMessages 同时解析 messages 数组与 Responses 风格的 input(含 function_call / function_call_output / message 等条目类型)。

对应的重放夹具是 captures/copilotcli-gpt-5-codex.yaml,其顶部 dialect: responses 正是这一事实的线上一侧记录:

version: 1
dialect: responses          # responses → POST /responses
exchanges:
  - request:
      model: gpt-5-codex
      system: ${system}
      messages:
        - role: user
          content: Say exactly "ok"
    response:
      content: ok
      stopReason: end_turn

dialect 是夹具中"唯一无法从归一化 turn 里还原"的线上事实,因此单独存于顶层:它决定 turn 归入哪个 (method, path) 桶,以及重放时使用哪个 SSE 再生成器(/v1/messagesanthropic/responsesresponses)。system: ${system} 占位符表示 Responses API 会回显 instructions,归一化时替换为 ${system}

文件命名规则为 captures/${provider}-${test-title-slug}.yaml(本文件即 copilotcli-…)——因此改测试标题会孤立旧夹具与旧快照,改名后必须重新录制。


六、运行与更新基线:完整的可复现路径

回放是默认模式,无需配置与 token:

# 只运行 Copilot prompts 快照套件(回放,确定性、无网络)
./scripts/test-integration.sh --run \
  src/vs/platform/agentHost/test/node/e2e/providers/copilotPromptsE2E.integrationTest.ts

# 接受新基线并审查 diff
AGENT_HOST_UPDATE_AHP_SNAPSHOTS=1 ./scripts/test-integration.sh --run \
  src/vs/platform/agentHost/test/node/e2e/providers/copilotPromptsE2E.integrationTest.ts

更新模式会原位重写 .prompt.md,随后用 Git 审查 diff,再不加任何标志重跑一遍验证已提交快照。CI 与完整套件入口则是 npm run test-agent-host-e2e(并发跑一致性套件与各 provider 套件,详见 e2e README)。

新增一个要快照的模型:三件套约束

SNAPSHOT_MODELS 加模型不是单独一处的事,测试头部注释明确列出缺一不可的配套:

  1. harness/capiStubs.ts 的 stub 目录中要有该模型——/models 里查不到的模型会在 CLI 构建请求前被拒,测试连请求体都捕获不到;
  2. captures/ 下要有已提交夹具——重放的 turn 也必须被"应答",夹具 dialect 须匹配该模型的 stub 端点(/responsesdialect: responses/v1/messagesdialect: anthropic);
  3. 提交基线快照,并 diff 审查。

什么时候会出现 diff,意味着什么

README 明确指出:

  • SDK/CLI 升级改变了 CLI 汇编的提示词 → 属于 CLI 变更,重新录制夹具并更新基线;
  • host 改动了自己交给 CLI 的内容(如 promptRegistry.ts 的 section 组合、历史保留、注入的上下文前导)→ 判断新请求是否正确,正确则重新录制;
  • 编辑 AGENTS.md / 仓库指令 → 按设计不会产生 diff(见第三节的"刻意省略");
  • 若夹具确实无法刷新,才把测试标题加进 agentHostE2ETestHarness.tsSTALE_RECORDED_REQUEST_EXCEPTIONS 并在 KNOWN_ISSUES.md 记录原因。

套件定位上,这是"provider 请求体边界"测试,与 AHP 流量快照(*.traffic.ahp.yaml)互补:后者记录语义化协议流量,前者把模型请求体本体逐字段钉死。


七、小结

gpt-5-codex.prompt.md 不是一份随手生成的示例,而是 Agent Host E2E 体系"契约化测试"思想在模型边界上的具体产物:把编译器/CLI 内部不可见的提示词汇编结果,通过确定性重放转化为仓库内可审查、可 diff 的基线。从字段级的采样参数,到分块系统提示词,再到归一化与刻意省略的边界,每个细节都服务于同一个目标——让 prompt 的每一次意外漂移都在 CI 中显式失败,而不是静默变成新的"期望值"。理解这一份基线,就掌握了整个 SNAPSHOT_MODELS × prompt.md 基线矩阵的运行原理与维护方法。

登录后查看全文
热门项目推荐
相关项目推荐