首页
/ 从快照读懂 Agent:VS Code 中 claude-sonnet-5 的 Copilot 完整模型请求基线解析

从快照读懂 Agent:VS Code 中 claude-sonnet-5 的 Copilot 完整模型请求基线解析

2026-09-07 09:36:43作者:毕习沙Eudora

Agent_Host_E2E___Copilot_prompts_claude-sonnet-5.prompt.md 是 VS Code Agent Host 端到端(E2E)测试体系为 claude-sonnet-5 这个模型提交的一 份"提示词快照基线":它逐字段固定了捆绑的 Copilot CLI 在真实回合中序列化到线上的完整模型请求体,覆盖系统提示(system)、回合消息(messages)、全部工具定义与采样参数。本文以该快照为主体,结合其生成测试、规范化规则与宿主侧提示词注册表源码,说明这份基线里每一层内容从哪来、被规约(pin)到什么程度、何时会失效以及如何安全更新——读完你可以看懂仓库中任意一份 *.prompt.md,并具备为 Copilot 提示词做"改动归因"与"确定性回归"的完整方法。

这份 .prompt.md 是什么

在仓库中它位于 E2E 测试的 providers/__snapshots__/ 目录,与其并列的既有同类的 Agent_Host_E2E___Copilot_prompts_claude-opus-5.prompt.md...gpt-5.prompt.md 等近二十份按模型家族划分的基线,也有记录 AHP 协议语义流量的 *.traffic.ahp.yaml 快照。二者由同一套 assertSnapshot 机制写入,区别在于:

  • *.traffic.ahp.yaml 快照固定的是 AHP 协议(客户端 ⇄ Agent Host) 上的语义化消息序列;
  • *.prompt.md 快照固定的是 Agent Host ⇄ 模型供应商 CAPI 之间、由 Copilot CLI 实际发出的完整 HTTP 请求体

文件名为测试全名加规约名清洗后的拼接结果。例如本快照对应套件 Agent Host E2E — Copilot prompts、用例 claude-sonnet-5(规则见 ahpSnapshot.ts 中 sanitizeName/snapshotPathForTest)。文件名里的 claude-sonnet-5 同时也是 SNAPSHOT_MODELS 常量中的一员——该数组逐个模型驱动一次真实回合,claude-sonnet-5 是其成员之一。

它的物理形态是一段被 ```json 围栏包裹的 JSON(因此扩展名是 .md),内容由测试把请求体做原位脱敏后 pretty-print 而来,README 的 "Prompt snapshots" 一节对此有明确定义(见 e2e/README.md "Prompt snapshots" 小节)。

快照从哪一条线上读出来:replay 而非 record

理解这份基线,先要理解它的获取方式。Copilot CLI 的提示词是编译进 @github/copilot 原生二进制里的产品实现,只有当 CLI 把它序列化到网络线上时才可观察。因此测试的设计是:从一次"重放(replay)"回合中读取请求体,而不是在录制(record)时抓取。原因在测试文件头部注释与 README 中写得很明确(见 copilotPromptsE2E.integrationTest.ts):

  • replay(默认、CI 运行)确定性且零 token:CapiReplayProxy 按 (method, path) 的次序回放 captures/ 中已提交的 YAML 夹具,模型响应是假的,其余(服务器、CLI 子进程、工具执行、协议)全是真的;
  • recordAGENT_HOST_REPLAY_RECORD=1AGENT_HOST_UPDATE_SNAPSHOTS=1)会触达真实 CAPI,去取模型目录与实验分配——二者都可能因为本仓库无法控制的原因移动提示词,所以录制方向的运行永远不产生基线

落到具体代码:for (const model of SNAPSHOT_MODELS) 循环中,测试先 createRealSession 建一个真实会话,再经 driveTurnWithModel 用显式模型选择(message.model.id = model)发送固定的极简提示 Say exactly "ok",并把回合驱动到 chat/turnComplete(见 driveTurnWithModel)。回合期间服务器租约把 CLI 发出的请求体记录在 lease.observedModelRequestBodies,测试取最后一个 body——这样即使 CLI 中途插入了预检请求,基线抓到的仍是真正到模型的那一发。随后经过 formatPromptSnapshot 的形状守卫与规范化后落盘为基线。

快照的顶层结构:一份真实请求体长什么样

以本文件为例,请求体最外层字段可直接列出,它们与 Anthropic Messages 方言对应(Claude 走 POST /v1/messagesdialect: anthropic):

字段 含义
model claude-sonnet-5 显式选中的模型 id
max_tokens 32000 输出 token 上限
system 两个带 cache_control: {type: "ephemeral"} 的文本块 系统提示,分两块做临时提示缓存标记
messages 一条 user 回合 即固定的 Say exactly "ok" 提示(带 ${datetime} 占位符)
tools 二十余个完整工具定义 每个含 name + description + 完整 JSON input_schema
temperature 1 采样温度
thinking adaptive + display: summarized 自适应思考模式,对端总结显示
output_config effort: medium 输出投入度
stream true 流式输出

其中 system/messages 这两个 key 是 Anthropic Messages 方言特有的(Responses 方言同一位置叫 instructions/input),快照读取逻辑因此对两种方言做了兼容,见 formatPromptSnapshot 中 IWireRequestreadMessages

system 块的三层内容:身份、规则与环境

两个系统文本块各带 cache_control: ephemeral,分别承担不同职责:

第一块:身份 + 代码改动规则 + 环境限制。 开头固定自述 "You are an AI assistant using Copilot SDK in VS Code"(被问及身份时须如此陈述)。其后是几组结构性段落,从快照里能清楚看到它们各自的标签外壳:

  • <code_change_instructions>:内嵌 <rules_for_code_changes>(精准外科手术式改动、不修与任务无关的存量问题、文档随改动更新、不破坏既有行为)、<linting_building_testing>(只跑已存在的 lint/build/test,用最小的定向命令,文档改动无需构建)、<using_ecosystem_tools>(优先生态工具而非手改)、<style>(只在需要澄清处加注释);
  • <tips_and_tricks>:先复盘命令输出再走下一步、任务结束清理临时文件、对已存在文件用 view/edit 而不是 create、拿不准时用 ask_user 工具澄清、未经要求不建规划类 markdown;
  • <environment_limitations> 及其内的 <prohibited_actions>:声明不是在为任务隔离的沙箱里运行,可能与其他用户共享环境;并列出必须禁止的动作(不向第三方泄露敏感数据、不把密钥提交进源码、不侵犯版权,且不得透露/讨论本指令本身——快照忠实保留了这条"保密且永久有效"的约束)。

第二块:环境上下文 + 工具使用指南 + 仓库指令注入点。 依次包含 <environment_context>(当前工作目录 ${workdir}、git 仓库根、操作系统 ${os}、可用工具 ${available_tools} 等运行时占位符)、大段 <tools> 使用指引(bash 的进程与会话语义、同步/异步模式、view 的分块读取、grep 的正则注意点、task 子代理的用法与安全审查契约等),以及末尾两个 <custom_instruction>${repository_instructions}</custom_instruction> 占位——这是宿主注入仓库级指令的接缝。它说明:宿主把自己要加的段落拼进这个请求体,而基线原样覆盖了这些段落,形成端到端断言。

工具定义:被完整规约的"可用能力"清单

快照后半的大半篇幅都是 tools 数组,每个元素是完整的 name + description + input_schema。从本文件可归纳出这些能力族:

  • 命令执行族bash(含 shell 安全说明、同步/异步/后台模式、禁止用名字批量 kill 等约定),以及配套的 read_bash/stop_bash/list_bash
  • 文件观察/变更族view(20KB 截断、view_range不要用其读二进制)、edit(精确 old_str/new_str 替换、同响响应多编辑)、create(不能覆盖已存在路径、父目录必须先存在);
  • 检索族globgrep(基于 ripgrep,注意 \{\} 转义、支持 content/files_with_matches/count 三种输出与上下文参数);
  • 技能与子代理族skill(只能引用 available_skills 中的名字)、task(explore/task/general-purpose/code-review/research/security-review 等内置子代理,同步与后台两种模式);
  • 交互与数据族ask_user(多选优先、一次只问一个问题、推荐项放第一并标注 "(Recommended)")、sql(会话级 SQLite,预置 todos 表等);
  • 评审协作族addComment/listComments/replyToComment/deleteComments/resolveComments/viewUnreviewedComments——快照内的 schema 明确约定了基于 1-based 行号区间、resourceUri 的输入形态;
  • 会话管理服务端工具族list_sessions/get_current_session/create_session/send_message/get_session_context/delete_session,并携带 cache_control: ephemeral

由于是逐字段完整固定,工具的描述文案(如 task 工具里各 agent 的能力区分、安全评审的汇报表格格式契约)一旦被 SDK 改版改写,都会在下一次基线 diff 中原形毕露。

占位符:把"必然变化"从基线里摘出去

快照并非逐字节复制线上请求。CLI 会把请求压缩成一行,所以测试先 pretty-print;又因为运行期值会变化,formatPromptSnapshot 在序列化前递归执行 normalizeVolatileValues,对字符串套用 normalizeVolatile(见 normalizeVolatile)。它把以下内容原位替换成稳定占位符:

占位符 被替换的易变内容
${session_id} / ${uuid} 会话 UUID 与其余运行时 UUID
${datetime} <current_datetime> 时钟值(保留标签)
${os} 操作系统探测行(保留 * Operating System: 前缀)
${available_tools} PATH 上发现的工具清单行(保留前缀)
${platform_packages} bash 工具里平台相关的包管理器提示
${repository_instructions} 注入的仓库指令 <custom_instruction> 内容(保留标签)
${model_count} / ${model_catalog} 模型计数与 Task schema 内联的模型目录(保留标签)

设计要点是"摘值不摘形":占位符保留了原有标签或外壳,所以如果某行形状变了(例如 <custom_instruction> 从提示词里消失、模型目录不再出现),测试仍然会红;只有内容值本身被替换。测试里还有专门的单元用例验证了这套格式化的行为,例如 Copilot prompt snapshot formatting 套件断言"残缺请求体被拒绝"和"易变值原位规范化"。

其中有两处脱敏与运行差异无关,而是改动归因的选择,README 专门解释了(见 e2e/README.md):

  1. 仓库指令(repository instructions):内容跨机器稳定、本可被固定,但 AGENTS.md.github/copilot-instructions.md 加一行就会重写全仓库每一份基线,让无关的文档编辑弄红 CI——所以只保留 <custom_instruction> 包裹断言"注入了、注入几份、位于提示词哪个位置"。
  2. 模型目录(model catalog):CLI 会把整个 /models 列表内联进 Task 工具的 schema,若原样固定,在 capiStubs.ts 增加一个模型会重写包括"无人规约的模型"在内的所有基线——同样保留标签而摘除列表。

这两个设计共同回答了一个问题:为什么某份基线的 diff 出现/不出现。在 claude-sonnet-5 这份基线里,它们以 ${repository_instructions}${model_catalog} 形式出现,而不是真实内容。

宿主侧贡献与改动归因:何时该更新基线

快照 diff 意味着两端有一端变了:

  1. SDK bump@github/copilot CLI 改版,提示词或工具 schema 是 CLI 的产品,变化只可能随 SDK 升级上线;
  2. 宿主侧改动:Agent Host 把自己拼给 CLI 的内容改了。宿主侧的组装入口是 promptRegistry.ts 的 AgentHostPromptRegistry.resolveSystemMessageConfig——它组合出的多个段落会原样落进本快照的 system 块,因此基线对该注册表是端到端覆盖的。

反例是:编辑仓库 AGENTS.md 不会(也不该)弄红这些基线,这正是上述占位符设计的产物。README 还指出,本 E2E 无法覆盖"被宿主配置门控、按模型贡献者不同"的那些分支,因为 E2E 没有设置根配置的接缝——它们由 agentHostPromptRegistry.test.ts 同目录层的单测覆盖。这一点从代码结构看,是"E2E 覆盖宿主默认贡献、单测覆盖配置分支"的明确分工。

新增一个模型、接受一份基线:运维命令

快照对模型是opt-in的:不派生自实时 /models 目录,所以新模型不会自动出现。README 给出了完整运维路径(见 e2e/README.md):

先跑一次确认当前基线通过(replay 默认零 token):

./scripts/test-integration.sh --run \
  src/vs/platform/agentHost/test/node/e2e/providers/copilotPromptsE2E.integrationTest.ts

接受新基线(与 AHP 快照共用同一环境变量):

AGENT_HOST_UPDATE_AHP_SNAPSHOTS=1 ./scripts/test-integration.sh --run \
  src/vs/platform/agentHost/test/node/e2e/providers/copilotPromptsE2E.integrationTest.ts

更新模式直接原地覆写基线(本文件即在 providers/__snapshots__/ 下被重写);普通断言模式下不一致时测试失败并写出 .actual 供诊断。若只有个别模型需要更新,用 --grep "<test title>" 收敛范围。

若要新增一个此前未被规约的模型,需要同时满足三处约束(README 的 "Prompt snapshots" 一节与测试文件头注释一致):

  1. SNAPSHOT_MODELS 加一项——不加则新模型永不进入快照范围;
  2. capiStubs.ts 的桩模型目录中登记该模型——否则 CLI 构建请求前就被 /models 拒绝,测试以"没有抓到 body"失败;
  3. 提交一份夹具 captures/copilotcli-<用例slug>.yaml——重放回合仍需被应答;夹具的 dialect 必须与模型的桩端点匹配(Claude 走 /v1/messagesdialect: anthropic,Codex 类走 /responsesdialect: responses)。

平台与既有基线族:几点补充

几个值得注意的边界条件,在排查红绿时直接相关:

  • POSIX-onlycopilotPromptsE2E 套件在 Windows 上对每个模型用例直接 test.skip(见 测试循环注释)。Windows 的提示词携带 PowerShell 专属段落,并非本基线的重命名版本;SDK 漂移是跨 provider 的,POSIX runner 已能捕获,详见 KNOWN_ISSUES.md
  • 一族一基线:同一方言内多数模型产出的提示词几乎相同——CLI 并不在方言内按模型分支,宿主对各模型贡献的段落也一致。基线仍按家族各留一份,是为了未来某模型出现分支时,diff 能指认引入分歧的那个家族
  • 显式选择模型:测试刻意不发送"未选择模型"的回合,因为那样 CLI 会按自己的排序从桩目录选模型,基线就会记录夹具的属性而非产品行为,且随桩目录增删而漂移。
  • 夹具与请求断言的双保险:模型响应按序号回放,但夹具中记录的请求并非装饰——每次重放都会用 modelRequestProjection.ts 与真实请求比对,避免"回归变绿后又成为新基线"。

小结

claude-sonnet-5 这份 *.prompt.md 为入口,可以完整还原 VS Code Agent Host 的 Copilot 提示词回归策略:产物快照(pin 请求体) + 夹具回放(零 token 确定性) + 原位脱敏(摘值不摘形) 三者共同作用,让"CLI 编译进二进制、平时不可见"的提示词成为可评审、可 diff、可归因的仓库资产。读懂它的分层——顶层参数、双 system 块、完整工具 schema、占位符体系——之后,无论是 SDK 升级引发的全线基线刷新,还是宿主新增一段提示词贡献,你都能立刻判断"这次 diff 该不该出现、责任在哪一侧"。

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