VS Code Agent Host E2E“Prompt 快照”解析:读懂 claude-haiku-4.5 的完整模型请求体基线
本仓库(Visual Studio Code)内置了一套针对 Agent Host 的确定性端到端测试,其中 copilotPromptsE2E.integrationTest.ts 会把捆绑的 Copilot CLI(@github/copilot)在每次模型回合中真正写到网络上的完整请求体逐模型固化成一份快照基线。本文以 claude-haiku-4.5 的快照 Agent_Host_E2E___Copilot_prompts_claude-haiku-4_5.prompt.md 为中心,讲解这类“Prompt 快照”的产生原理、请求体的每一层结构、易变值的稳定化替换规则,以及如何运行、更新和新增一个模型的基线。
这份快照到底是什么
Agent_Host_E2E___Copilot_prompts_claude-haiku-4_5.prompt.md 是一份被提交进仓库的测试基线(committed baseline),文件名来自测试套件标题 Agent Host E2E — Copilot prompts 与测试名 claude-haiku-4.5(sanitizeName 将非字母数字字符替换为 _,见 ahpSnapshot.ts 的 snapshotPathForTest)。
它的内容不是人写的说明文档,而是一个被 pretty-print 成 Markdown 围栏(```json)的完整 JSON——也就是 Copilot CLI 发送给 claude-haiku-4.5 的那次模型请求体。测试文件的头部注释说明了它的职责:
Pins every field of the model request body the bundled Copilot CLI sends per model.
即:固定捆绑 CLI 针对每个模型发送的请求体中的每一个字段。覆盖范围包括:组装完成的 system prompt、工具定义、携带 CLI 注入上下文的回合消息(如 <current_datetime>、<system_reminder>),以及采样参数(thinking / text.verbosity / max_tokens / parallel_tool_calls 等),这些内容此前用“渲染后的子集”方式校验时一直是未被固定的部分。
为什么要用“重放回合”来读取 Prompt
Prompt 是 CLI 的产品而不是宿主的产物——它被编译进 @github/copilot 原生二进制,只有在 CLI 把它序列化到网络线上时才可被观测。因此这套测试从重放(replayed)回合中读取请求体,而不是在录制(recording)时快照:
- 重放是确定性的、免 token 的:请求体从已提交的 YAML fixture 回放中获得;
- 录制则天然不确定:它会访问真实 CAPI 来获取模型目录(
/models)与实验分配(experiment assignment),二者都可能出于本仓库无法控制的原因移动 Prompt 内容,所以“录制运行永不产生基线”。
快照对应的重放 fixture 位于 captures/copilotcli-claude-haiku-4-5.yaml,内容极简且可人工审查:
version: 1
dialect: anthropic # anthropic → POST /v1/messages
exchanges:
- request: # 规范化后的请求摘要(review 用投影)
model: claude-haiku-4.5
system: ${system}
messages:
- role: user
content: Say exactly "ok"
response: # 捕获的模型回复,以 SSE 重放
content: ok
stopReason: end_turn
dialect: anthropic 说明该模型走的是 Anthropic Messages 协议(POST /v1/messages),这与下文请求体使用 system/messages 字段而不是 Responses API 的 instructions/input 字段完全对应。
快照的生成流程
测试的核心循环位于 copilotPromptsE2E.integrationTest.ts,对 SNAPSHOT_MODELS 中的每个模型执行同一套流程:
- 用
mkdtemp建一个临时工作区,createRealSession创建真实会话; driveTurnWithModel派发一次ChatTurnStarted回合,消息文本固定为Say exactly "ok",并在消息中显式携带model: { id: model };- 循环等待通知直到
turnComplete,对toolCallReady自动确认放行; - 取
lease.observedModelRequestBodies的最后一个请求体(取最后一个是有意为之:若 CLI 插入一次 preflight 请求,取末尾仍能拿到真正驱动回合的那一次); - 交给
formatPromptSnapshot做形状守卫与易变值规范化,然后调用assertPromptSnapshot写入或比对快照。
运行与更新方式(同样来自测试头注释与 e2e README 的 “Prompt snapshots” 一节):
# 普通运行:确定性重放,比对已提交基线
./scripts/test-integration.sh --run \
src/vs/platform/agentHost/test/node/e2e/providers/copilotPromptsE2E.integrationTest.ts
# 接受新基线(就地重写快照文件),然后审阅 git diff
AGENT_HOST_UPDATE_AHP_SNAPSHOTS=1 ./scripts/test-integration.sh --run \
src/vs/platform/agentHost/test/node/e2e/providers/copilotPromptsE2E.integrationTest.ts
值得注意的守卫逻辑:assertSnapshot 在文件缺失时会自动创建并让测试通过,这会把“没人写过基线”的模型变成绿色,因此测试显式先检查 existsSync(snapshotPath),缺失直接抛错并要求用 AGENT_HOST_UPDATE_AHP_SNAPSHOTS=1 生成基线后提交。
逐层解剖 claude-haiku-4.5 的请求体
以下是对快照内容的逐段解读。
顶层字段:采样参数与流控
{
"model": "claude-haiku-4.5",
"max_tokens": 8192,
"temperature": 1,
"thinking": {
"type": "enabled",
"budget_tokens": 1024,
"display": "summarized"
},
"stream": true
}
model:本次显式选定的模型 id,请求不依赖 CLI 对 stub 目录自行排序;max_tokens/temperature:输出上限与采样温度;thinking:启用了 thinking,budget_tokens: 1024,display: summarized;stream:SSE 流式输出。
这些采样参数正是 README 所说的“rendered subset 过去没有固定的那部分”,现在每一处都被精确钉住——哪怕 CLI 未来新增一个参数,也会在下一次基线 diff 中自行现身。
system:双 text 块结构
快照中 system 是一个数组,包含两个 type: text 的块,且每个块都带 cache_control: { type: "ephemeral" }。
第一块是该 Agent 的主身份与行为指令,以 “You are an AI assistant using Copilot SDK in VS Code. You help users with software engineering tasks.” 开头,包含:不得修改无关代码、文档改动需同步等 rules_for_code_changes;只运行已存在的 lint/build/test 的 linting_building_testing;优先使用生态工具、缺失依赖失败时才安装的 using_ecosystem_tools;最小化注释的 style 要求;以及运行环境限制与安全边界(不把敏感数据交给第三方、不提交密钥、拒绝生成侵权内容、不透露/篡改以上指令本身,即“confidential and permanent”条款)。
第二块是运行环境上下文与工具使用指导,结构大致为:
<environment_context>:当前工作目录(${workdir})、Git 仓库状态、操作系统(${os})、可用工具(${available_tools})等占位符化信息;- 对各类工具的使用准则,例如 bash 每次命令运行于全新进程、
cd与环境变量不跨调用保留;view 多文件并行读取、20KB 截断;edit 支持同批多次顺序替换等; - 会话相关说明与 git 提交 trailer 约定;
- 结尾以两次
<custom_instruction>${repository_instructions}</custom_instruction>占位符标注注入的仓库级自定义指令位置。
tools:随请求内联的工具目录
快照中的 tools 数组完整内联了本次请求携带的工具定义及其 JSON Schema。从文件可以读到的工具名包括(按出现顺序):
bash、read_bash、stop_bash、list_bash、view、create、edit、web_fetch、skill、ask_user、sql、read_agent、list_agents、write_agent、grep、glob、task、list_sessions、get_current_session、create_session、send_message、get_session_context、delete_session。
README 特别指出:CLI 会把整个 /models 目录内联进 Task 工具的 schema(包括模型数量与逐模型清单)。正因如此,快照中该处的模型目录被规范化为 (${model_count} models available) 与 Available models:${model_catalog} 之类的占位符,否则在 capiStubs.ts 中新增一个模型条目就会连带重写所有模型的基线(详见下文“稳定化”)。
messages:单个用户回合
快照的回合消息只有一个 role: user、type: text 的条目,同样带有 cache_control: ephemeral:
<current_datetime>${datetime}</current_datetime>
Say exactly "ok"
<current_datetime> 是 CLI 注入的时间上下文,${datetime} 是规范化后的占位符;“Say exactly 'ok'” 是与 fixture 中记录的请求摘要一致的最小化、确定性提示词——提示词越简单,重放回合越稳定,模型回复(ok)也越可靠。
快照中的“稳定化”:易变值如何被占位符替换
formatPromptSnapshot 在序列化前会递归地对每个字符串执行 normalizeVolatile(定义于 copilotPromptsE2E.integrationTest.ts),把两次正确运行之间必然不同的值,或属于“另一个文件的变更预算”的稳定值替换为带标签的占位符:
| 原始内容 | 占位符 |
|---|---|
session-state/<uuid> |
session-state/${session_id} |
<current_datetime>…</current_datetime> |
<current_datetime>${datetime}</current_datetime> |
* Operating System: <实际值> |
* Operating System: ${os} |
* Available tools: <实际值> |
* Available tools: ${available_tools} |
| 平台包管理提示行 | * You can install ${platform_packages}. |
<custom_instruction>…</custom_instruction> |
<custom_instruction>${repository_instructions}</custom_instruction> |
(N models available) |
(${model_count} models available) |
Available models: 逐模型清单 |
${model_catalog} |
| 其余任意 UUID | ${uuid} |
| CRLF | LF |
其中两个替换不是针对“运行间波动”,而是刻意把变更成本隔离到正确文件:
- 仓库指令:CLI 会把
.github/copilot-instructions.md和AGENTS.md原样注入,若不加占位符,给AGENTS.md追加一行就会重写所有模型基线、让一次无关文档编辑弄红 CI。<custom_instruction>包裹结构仍在,因此仍能断言“指令被注入、注入了几条、位于 Prompt 何处”; - 模型目录:若原样保留,
capiStubs.ts中新增一个模型就会重写包括“无人快照的模型”在内的全部基线。
核心设计哲学是:占位符只是值变了,标签与包裹结构保留——某一行消失或形状变化,测试依然会失败,不会被静默吞掉。
模型族如何加入基线
SNAPSHOT_MODELS(copilotPromptsE2E.integrationTest.ts)共含 19 个模型族,claude-haiku-4.5 与 claude-sonnet-4.5/4.6/5、claude-opus-4.5/4.6/4.7/4.8/5、各 gpt-5*、gemini-2.0-flash 并列。覆盖范围 = Copilot 扩展 agentPrompt.spec.tsx 中在重放下确实产生模型请求的模型族 + Agent Host 支持的更新模型族。若干模型族共享近乎相同的 Prompt(CLI 在同一个方言内不按模型分支、宿主给每个模型的贡献段落也相同),但仍逐族保留独立基线,以便未来某一族发生分叉时能直接定位是哪个模型引入的。
README 明确给出了新增模型的三个硬性前置:
- 必须在 capiStubs.ts 的 stub 目录中。该文件第 57 行即包含
{ id: 'claude-haiku-4.5', vendor: 'Anthropic', supportedEndpoints: ['/v1/messages', '/chat/completions'], maxContextWindowTokens: 200000, maxOutputTokens: 32000, maxPromptTokens: 168000, vision: true }。缺失于/models的模型会在 CLI 构建请求前被拒绝; - 需要一个已提交的 fixture:
captures/copilotcli-<slugified-test-title>.yaml,重放回合仍需要有回复可回放;fixture 方言须与模型的 stub 端点匹配(/v1/messages→anthropic,/responses→responses); - 需要一条已提交的 prompt 基线,即
__snapshots__/下的*.prompt.md文件。
注意 gpt-4.1 与 grok-code-fast-1 缺席:两者在重放下不会产生模型请求。同时所有模型都必须显式选择——不传 model 选择不会被固定,因为那会让 CLI 自行对 stub 目录排序,基线将变成“这套 fixture 的属性”而非产品行为。
护栏、边界与已知限制
快照有一套“形状守卫”,防止空壳捕获被写成貌似合理的基线(formatPromptSnapshot 内 4 个 assert.ok):system prompt 非空、tools 是非空数组、至少一条回合消息、且不能存在文本为空的回合消息。另有配套的单元测试覆盖这些守卫与规范化行为(同文件的 “Copilot prompt snapshot formatting” suite)。
平台边界方面,该套件是 POSIX-only:process.platform === 'win32' ? test.skip : test。原因记录在 KNOWN_ISSUES.md:Windows 的 Prompt 不是 POSIX Prompt 的重命名——除了 shell 工具名不同,CLI 运行时还携带 PowerShell-only 段落(如单引号 here-string 的 “no-heredoc” 指导、if ($?) { … } 显式检查、.bat 的 PATH/LIB/INCLUDE 变更不可用的提示),其中一段还以探测主机得到的 supportsPowerShell7Syntax 为门控,因此 Windows 基线只在 PowerShell 能力一致的 runner 间稳定。由于 SDK 漂移是全 provider 共性问题,Linux/macOS runner 已能发现它,故这被判定为“刻意的缺口”而非待补项。
另一个边界是宿主自身的 Prompt 贡献:resolveSystemMessageConfig(promptRegistry.ts)把若干段落原样拼进这里的 Prompt,因此快照对它们做了端到端覆盖;但被宿主配置门控的逐模型 contributor 不在覆盖内——E2E harness 没有设置根配置的缝,这些门控由单元测试 agentHostPromptRegistry.test.ts 负责。
如何阅读一次基线 diff
基线变化意味着两类事件:CLI 变了(一次 SDK bump),或宿主交给 CLI 的内容变了(历史保留策略、注入的前置内容、attachment 序列化等)。修改仓库指令文档则按设计不会改写基线——这正是占位符替换的回报。
复现与验收路径始终是:先跑普通重放确认失败点,再以 AGENT_HOST_UPDATE_AHP_SNAPSHOTS=1 接受新基线并审阅 diff(若行为同时改变了 LLM 请求/响应序列,则改用 AGENT_HOST_UPDATE_SNAPSHOTS=1 一并更新),最后不带更新标志重跑一遍验证已提交快照可复现。整个流程与 AHP 语义快照(*.traffic.ahp.yaml)、LLM fixture(captures/*.yaml)共同构成 Agent Host E2E“只有模型响应被伪造、其余全部真实”的确定性测试资产(架构详见 e2e README)。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00