系统提示词、上下文与提醒

每次模型请求前的那段“前缀”怎样拼出:十几个段落按静态、动态分进三条 system 消息,AGENTS.md 与记忆索引走 meta user 块;二十多种 system reminder 何时产生、落在请求哪里、是否落库;中途 system 消息投影,以及上下文用量拆解给谁看。

作者 David更新于 11 篇(共 47 篇)

模型每次请求看到的消息序列,开头都是运行时拼出来的一段“前缀”:三条 system 消息,外加一两条包在 <system-reminder> 里的 user 消息;对话中间还散落着各种带来源标签的提醒。这一篇讲前缀由哪些段落组成、按什么顺序排、什么时候重建,提醒在什么条件下产生、最后落在请求的哪个位置。回合循环在哪一步调用这些逻辑,见回合循环与 TurnMachine;对话本体怎样被摘要替换,留给下一篇。

以下目录都在 apps/zcode-cli/packages 下:

位置职责
core/src/context/ContextBuilder 与各段落构造器,纯函数,不碰文件系统
core/src/system-reminder/提醒来源登记表、包装与转义、附件与中途消息的正文格式
core/src/runtime/methods/context*.ts首次初始化、每回合重建前缀、上下文用量快照
core/src/runtime/helpers/runtime-reminders.tsPlan 模式、Todo、日期等运行时提醒的正文与节奏
core/src/runtime/helpers/provider-*.ts把历史投影成 provider 请求:提醒排位、中途 system 消息、缓存标记
adapters/src/context/NodeContextSourceAdapter:探测环境与 git、查找 AGENTS.md

前缀怎样拼出来

第一次模型请求前,运行时走一次 ensureContextInitializedapps/zcode-cli/packages/core/src/runtime/methods/context.ts:31):向 ContextSourcePort 要一份上下文源快照,顺带启动 MCP、发现技能、定位项目记忆目录并读入 MEMORY.md,然后用快照构造 ContextBuilder,把 build() 的产物装进 MessageHistory 的开头(context.ts:273)。

图表加载中…

每个段落是一个 ContextSection,除正文外还带两个标记:注入位置 injectionTargetsystemmeta_user)与缓存提示 cacheHintstabledynamic)(apps/zcode-cli/packages/core/src/context/types.ts:52)。build() 收齐段落后,先按“system 稳定、system 动态、meta user 稳定、meta user 动态”重排(apps/zcode-cli/packages/core/src/context/builder.ts:310),再拼成三条 system 消息,每条都带 ephemeral 缓存标记(builder.ts:230):

  private assembleSystemMessages(sections: ContextSection[]): ModelInputMessage[] {
    const messages: ModelInputMessage[] = [];

    const cliPrefixContent = buildSectionContent(
      sections.filter(
        (section) => section.injectionTarget === "system" && section.source === "cli_prefix",
      ),
    );
    if (cliPrefixContent) {
      messages.push({
        role: "system",
        content: cliPrefixContent,
        cacheControl: EPHEMERAL_CACHE_CONTROL,
      });
    }
  // ...
    const dynamicSystemContent = buildSectionContent(
      sections.filter(
        (section) => section.injectionTarget === "system" && section.cacheHint === "dynamic",
      ),
    );
    if (dynamicSystemContent) {
      messages.push({
        role: "system",
        // ZCode by design:Main Agent 的 dynamic system block 自带左边界,所有 provider 保持一致。
        content: `\n\n${dynamicSystemContent}`,
        cacheControl: EPHEMERAL_CACHE_CONTROL,
      });
    }

省略的中间一条收的是除 CLI 前缀以外的全部稳定段(builder.ts:246)。meta user 段拼成两条附件:技能清单单独一条,来源 skills_listing;其余段落合成一条,来源 context_prefix,首尾各加一句固定的话(builder.ts:327),开头一句是:

As you answer the user's questions, you can use the following context:

附件在历史里以 kind: "attachment" 存放(apps/zcode-cli/packages/core/src/runtime/methods/context-history-entries.ts:7),发请求时才包进 <system-reminder> 标签。历史开头哪些条目算“前缀”,由 countContextPrefixMessages 判定:连续的 system 消息,加上来源为 context_prefixskills_listing 的附件(apps/zcode-cli/packages/core/src/agent/message-history.ts:269)。重建前缀、压缩、回退都靠它把“前缀”和“对话”切开。

git 快照那一段还有一层约束:代码注释说 git status 只按 2000 字符截断、不按条目数截断,是为了“与上游 CLI 保持一致”,避免 provider 可见的提示词形状漂移(apps/zcode-cli/packages/adapters/src/context/git-snapshot.ts:168)。至少这部分提示词的形状是有意对齐某个上游 CLI 的,代码没有点名是哪一个。

段落清单

build() 的构造顺序(重排后次序不变):

次序段落(source)注入与缓存出现条件内容要点
1CLI Prefix(cli_prefixsystem,稳定,独占第一条不是工作流子代理(builder.ts:103一句身份
2Agent Identity(identitysystem,稳定默认(builder.ts:121身份句、安全声明、# Harness
3ZCode Desktop Context(desktop_contextsystem,稳定桌面端会话(builder.ts:131链接写法、::code-comment 指令
4Dynamic Behavior(dynamic_behaviorsystem,动态默认(builder.ts:136怎样与用户沟通、写注释、确认难以撤销的操作、如实汇报
5Session-specific guidance(session_guidancesystem,动态有 Skill 工具且有技能(builder.ts:141目前只有一行:用户输入 /<skill-name> 时经 Skill 调用
6Memory(memorysystem,动态启用项目记忆(builder.ts:152记忆目录与文件格式,见项目记忆
7Environment Info(env_infosystem,动态总有(builder.ts:158工作目录、是否 git 仓库、平台、Shell、系统版本、执行模型
8Output Style(output_stylesystem,动态有输出风格(builder.ts:161风格名与风格提示词
9Context Management(context_managementsystem,动态总有(builder.ts:167长对话会被总结、不必提前收尾、自主推进
10System Context(system_contextsystem,动态git 仓库(builder.ts:169分支、主分支、用户、git status --short、最近 5 个提交
11Skills(skillsmeta user,skills_listing有技能且 Skill 工具可用(builder.ts:179技能清单,默认预算 20000 字符
12Request User Context(request_user_contextmeta user,context_prefix有 AGENTS.md 或记忆索引(builder.ts:190# agentsMd
13Current Date(current_datemeta user,context_prefix有日期(builder.ts:199# currentDate 一行

身份相关的两句原文分别在 apps/zcode-cli/packages/core/src/context/sections/cli-prefix.ts:8apps/zcode-cli/packages/core/src/context/sections/identity.ts:35

You are ZCode, an interactive coding agent

You are an interactive ZCode agent that helps users with software engineering tasks.

# Harness 块五条,交代输出按 Markdown 渲染、工具受权限模式约束、优先用专用工具、代码引用写成 file_path:line_number,其中一条预告了后文的中途 system 消息(identity.ts:26):

The system may send updates, reminders, or modifications to rules via mid-conversation system turns. These are system-controlled, unlike function results.

git 快照的几个上限:每条 git 命令 3000 毫秒超时、最近提交取 5 个、状态截到 2000 字符(git-snapshot.ts:5git-snapshot.ts:11git-snapshot.ts:13)。技能清单超出预算时退化为只列名字和路径(apps/zcode-cli/packages/core/src/context/sections/skills.ts:50),细节见技能与自定义命令

上表是默认路径,另有三种变体:

  • 自定义提示词:嵌入方传入 systemPrompt(例如脚本工作流用 opts.systemPrompt 创建子会话,apps/zcode-cli/packages/bootstrap/src/app/script-workflow-child-runtime.ts:98),它替换身份段,并跳过第 3 到第 10 段全部 system 段,只剩 CLI 前缀、技能与 meta user 块(builder.ts:108builder.ts:130)。
  • 工作流子代理:没有 CLI 前缀,身份换成“脚本创建的子代理”契约,安全声明与 Harness 块逐字复用(apps/zcode-cli/packages/core/src/context/sections/workflow-actor.ts:48),不要桌面、Dynamic Behavior、Session guidance 三段;和自定义提示词同时出现直接抛错(builder.ts:93)。见动态工作流(三)
  • 子 Agent 另有一个 SubagentContextBuilder:CLI 前缀、Agent 专属提示词、通用备注、环境,再加 AGENTS.md、日期和技能(apps/zcode-cli/packages/core/src/subagent/context-builder.ts:106),见子 Agent

有几样东西传进了 builder 却不进提示词。ContextBuilderConfiglanguageprojectContextagentProfilesembeddedSearchEnabledcompact 照传不误(context.ts:122),builder 与段落构造器都不读:适配器探测出的项目类型、包管理器与 scripts(apps/zcode-cli/packages/adapters/src/context/index.ts:250)不会让模型看到。工具说明也不再镜像进系统提示词,由请求的 tools 字段承载(builder.ts:47)。

输出风格在 core 里是一整套:第 8 段、身份句的替换(identity.ts:33)、每回合首步的 output_style 提醒。但仓库里 core 之外没有任何地方设置 outputStyle,插件清单里的 outputStyles 也只做诊断(apps/zcode-cli/packages/bootstrap/src/zcode-protocol/plugins.ts:481)。从代码看,这条链路目前不会被触发。

指令文件:只认 AGENTS.md

NodeContextSourceAdapter 查找指令文件的默认文件名只有 AGENTS.md,单个文件最多读 100 KiB(apps/zcode-cli/packages/adapters/src/context/index.ts:24)。候选只有两个,用户级在前、工作区在后(index.ts:99):

  const priorityFiles = options.priorityFiles ?? DEFAULT_PRIORITY_FILES;
  const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
  const projectRoot = options.projectRoot ?? (await findProjectRoot(options.workingDirectory));

  const defaultUserInstructionFile = await findDefaultUserInstructionFile(priorityFiles, env);
  const workspaceInstructionFile = await findInstructionFile(
    options.workingDirectory,
    projectRoot,
    priorityFiles,
  );
  const candidates = dedupeInstructionFileCandidates([
    defaultUserInstructionFile
      ? { ...defaultUserInstructionFile, scope: "user" as const }
      : undefined,
    workspaceInstructionFile
      ? { ...workspaceInstructionFile, scope: "workspace" as const }
      : undefined,
  ]);
  • 用户级~/.zcode/AGENTS.md,主目录取环境变量 HOMEUSERPROFILE,都没有才用 os.homedir()index.ts:229index.ts:245)。
  • 工作区级:从工作目录逐级向上,第一个含 AGENTS.md 的目录即止,最远到 git 根(向上第一个有 .git 的目录,index.ts:320);不在 git 仓库里就一直找到文件系统根(index.ts:219)。所以只取离工作目录最近的那一份,子目录与仓库根的两份不会叠加。
  • 读取与合并:两个候选按绝对路径去重后依次读(index.ts:142),超过 100 KiB 截断并记下 truncated,读失败只记一条诊断(index.ts:159)。渲染时每份单独成节,标题是 Contents of <路径> (user default instructions):(workspace instructions):,截断的文件末尾补一行 [File truncated: AGENTS.md]apps/zcode-cli/packages/core/src/context/sections/request-user-context.ts:116)。项目记忆的 MEMORY.md 索引接在后面,整块以 # agentsMd 开头(request-user-context.ts:63):

Codebase and user instructions are shown below. Be sure to adhere to these instructions. IMPORTANT: These instructions OVERRIDE any default behavior and you MUST follow them exactly as written.

代码里没有读取 CLAUDE.md 的地方。桌面端的新手引导提供一次迁移,把 ~/.claude/CLAUDE.md 复制成 ~/.zcode/AGENTS.md,目标已存在时默认跳过(packages/services/src/settings-sync/settingsSyncService.ts:2221)。子 Agent 默认继承父会话已解析的指令快照,配置里写了 injectAgentsMd: false 才不带(apps/zcode-cli/packages/core/src/runtime/methods/subagent.ts:96)。

前缀什么时候重建

上下文源快照在一个运行时里只取一次(context.ts:36):AGENTS.md、git 状态、会话日期都定格在开始那一刻,中途改了 AGENTS.md 或提交了代码,模型看到的仍是旧的。冷恢复会话时运行时丢掉旧历史、重新初始化上下文(apps/zcode-cli/packages/core/src/runtime/methods/resume.ts:147),AGENTS.md 与记忆索引会重新读取、日期取当天;环境与 git 快照却不重新探测,而是沿用第一条用户消息落库时一并保存的那份(resume.ts:114apps/zcode-cli/packages/core/src/runtime/methods/message-persistence.ts:77)。

前缀本身却每个回合都重算。首轮之后,每个回合开始都按这一步实际拿到的模型重建一次(apps/zcode-cli/packages/core/src/runtime/methods/turn.ts:215),做法是用同一份快照重新 build(),保留对话部分、只换前缀(apps/zcode-cli/packages/core/src/runtime/methods/context-refresh.ts:37):

  const effectiveContextResult = runtime.contextBuilder.build();
  const contextEntries = buildContextHistoryEntries(effectiveContextResult);
  const canonicalEntries = runtime.messageHistory.borrowReadOnlyRuntimeEntries();
  const canonicalConversationEntries = canonicalEntries.slice(
    countContextPrefixMessages(canonicalEntries),
  );

  runtime.latestContextBuildResult = effectiveContextResult;
  runtime.messageHistory.replaceMessages([...contextEntries, ...canonicalConversationEntries]);

  const turnEntries = options.turnRequestEntries;
  if (!turnEntries) return runtime.messageHistory.borrowReadOnlyRuntimeEntries();
  return [...contextEntries, ...turnEntries.slice(countContextPrefixMessages(turnEntries))];

快照不变,那每回合重建改的是什么?主要是模型:# Environment 最后一行写的是本步骤的执行模型(apps/zcode-cli/packages/core/src/context/sections/env-info.ts:77),Session guidance 依据的也是这个模型可见的工具表(context.ts:141),二者都在第三条动态 system 消息里,前两条不受影响。这就是“中途变更系统提示词”的第一种方式:直接替换历史开头的前缀。另外几处也会触发重建:空闲时改语言或输出风格(apps/zcode-cli/packages/core/src/runtime/methods/config.ts:59)、回合中途经引导换模型(apps/zcode-cli/packages/core/src/runtime/methods/turn-guide-drain.ts:45)、回退消息之后(apps/zcode-cli/packages/core/src/runtime/methods/rewind-message.ts:706)、首发前刷新 Shell(apps/zcode-cli/packages/core/src/runtime/methods/session-shell-environment.ts:225)。

日期是个例外。# currentDate 用的是快照里的日期,跨过午夜不会变;运行时改在每个回合开头比对本地日期,变了就追加一条 date_change 提醒,并要求模型不要特意向用户提起(turn.ts:848apps/zcode-cli/packages/core/src/runtime/helpers/runtime-reminders.ts:91)。

提醒:带来源标签的附件

提醒在历史里是带 source 的附件条目(message-history.ts:59)。全部来源登记在 apps/zcode-cli/packages/core/src/system-reminder/source.ts,分三组:前缀两种(source.ts:20);可以随会话落库、冷恢复时按原来源重建的 15 种(source.ts:22);只活在进程内存历史里的 10 种(source.ts:40)。每个来源有一张描述符,记投递通道与生命周期(source.ts:88)。包装时,正文里出现的 <system-reminder</system-reminder 会把起始尖括号转义成 &lt;,防止提醒正文伪造标签边界(source.ts:233)。

回合循环每一步请求前依次检查几种运行时提醒,写进本回合的请求状态(apps/zcode-cli/packages/core/src/runtime/methods/turn-loop.ts:120):

    if (!outputTokenRecoveryActive && this.needsPlanModeExitReminder) {
      this.needsPlanModeExitReminder = false;
      commitTurnRequestEntries(this, state.turnRequestState, [
        systemReminderAttachmentEntry("plan_mode_exit", buildPlanModeExitReminderBody()),
      ]);
    }
    const runtimeModeReminderBody = outputTokenRecoveryActive
      ? null
      : buildRuntimeModeReminderBody(
          state.turnRequestState.entries,
          this.getMode(),
          this.getPlanEnabled(),
        );
    if (runtimeModeReminderBody) {
      commitTurnRequestEntries(this, state.turnRequestState, [
        systemReminderAttachmentEntry("runtime_mode", runtimeModeReminderBody),
      ]);
    }

随后是 Todo 提醒与输出风格提醒(turn-loop.ts:138turn-loop.ts:157)。这些检查排在 microcompact 与自动压缩之后(turn-loop.ts:69turn-loop.ts:78),所以提醒总是加在压缩过的历史上;输出 token 截断后的续写步骤一律跳过。全部来源一览:

来源什么时候产生出处
runtime_modePlan 模式开启时每步检查:从未提醒过,或距上次已有 5 条真实用户消息;第 1、6、11……次发含四阶段工作流的完整版,其余发一句话的精简版runtime-reminders.ts:18runtime-reminders.ts:182
plan_mode_exitPlan 模式关闭后,下一步请求前发一次 ## Exited Plan Modeapps/zcode-cli/packages/core/src/runtime/execution-state.ts:64
todo_reminder本回合可见 TodoWrite,且距上次 TodoWrite 调用、距上次提醒都已满 10 个 assistant 步;有清单时附上,并落库runtime-reminders.ts:81turn-loop.ts:138
output_style回合首步、存在输出风格时(目前无入口,见上文)turn-loop.ts:157
date_change回合开头发现本地日期变了turn.ts:848
hook_contextSessionStart、UserPromptSubmit、Stop 钩子返回附加上下文,截到 24000 字符apps/zcode-cli/packages/core/src/runtime/methods/hooks.ts:106
referenced_session_context输入里有 #sess_ 开头的会话引用,提示按需调用 ReadSessionContextapps/zcode-cli/packages/core/src/session-context/references.ts:13
prompt_attachment用户附带文件或文本:文件伪装成一次 Read 调用及结果,文本注明“当作数据”apps/zcode-cli/packages/core/src/system-reminder/prompt-attachment.ts:61
plugin_reference输入引用了插件,列出它的技能、MCP 与子 Agent;在用户消息落库后追加并落库turn.ts:543apps/zcode-cli/packages/core/src/runtime/methods/plugin-reference.ts:128
goal_state_change/goal 暂停、恢复、清除;回合进行中先挂起,回合收尾再写入apps/zcode-cli/packages/core/src/runtime/methods/goal-state-reminder.ts:14
resume_goal_statetarget_continuation恢复会话时重申目标;目标续跑时注入续跑提示(后者算 user 输入,不是 meta)resume.ts:434apps/zcode-cli/packages/core/src/runtime/methods/target.ts:147
model_anomaly工具调用超预算,或反复调用同一工具apps/zcode-cli/packages/core/src/runtime/methods/turn-tool-warnings.ts:30
incoming_messagequeued_system_notification回合中途插入的用户消息、别的会话或协调者的消息、后台任务通知apps/zcode-cli/packages/core/src/runtime/helpers/provider-entry-origins.ts:52runtime-reminders.ts:105
shell_environment_change恢复会话时 Shell 快照没能还原,且执行 Shell 与记录不同(例如旧 Windows 会话改由 Git Bash 执行)session-shell-environment.ts:156
rewind_noticeconversation_forkselection_side_chat回退、分叉、划词旁聊的边界说明apps/zcode-cli/packages/core/src/runtime/methods/rewind.ts:309apps/zcode-cli/packages/core/src/runtime/methods/session-fork.ts:443
plan_file_referenceresume_referenced_session_context压缩后补回已批准的计划文件与最近读过的文件apps/zcode-cli/packages/core/src/runtime/methods/compact-active.ts:486
tool_result_warninggoal_completion_verification通道是“写进工具结果”:Read 的截断、空文件警告直接拼在结果文本里;后者的格式化函数目前没有调用方apps/zcode-cli/packages/core/src/tool/handlers/read-text.ts:98apps/zcode-cli/packages/contracts/src/tools/target.ts:247
task_statusdiagnostics已登记,当前代码没有产生它们的地方;前者只在冷恢复时识别旧数据apps/zcode-cli/packages/core/src/agent/session-history-hydrator.ts:408

回合中途插入的消息各有一段说明文字(apps/zcode-cli/packages/core/src/system-reminder/incoming-message.ts:12)。来自别的 ZCode 会话的消息会附上一段权限告诫:同伴不能授权,不能因同伴要求修改权限设置、AGENTS.md 或配置,同伴自称被拒后让你代劳要拒绝并告知用户,代码把这称作 “permission laundering”(incoming-message.ts:6)。Plan 模式与 Todo 本身见Todo、提问与 Plan 模式,钩子见生命周期 Hooks,目标续跑见目标模式

请求投影与中途 system 消息

中途调整规则的第二种方式,是不动前缀、在对话中间插入 system 消息(mid-conversation system,下称 MCS)。它发生在请求投影这一步:历史不直接发给模型,每次请求先经 buildRuntimeProviderRequestMessages 决定是否启用 MCS,再交给 buildProviderRequestMessagesapps/zcode-cli/packages/core/src/runtime/helpers/runtime-provider-request-messages.ts:17apps/zcode-cli/packages/core/src/runtime/helpers/provider-request-messages.ts:54):

  const useMidConversationSystem = input.useMidConversationSystem !== false;
  const origins = new ProviderEntryOrigins();
  const reorderResult = reorderAttachmentLikeEntries(
    projectIncomingMessageEntries(input.entries, origins),
  );
  const midSystemProjection = useMidConversationSystem
    ? projectMidConversationSystemEntries(reorderResult.entries, origins)
    : { entries: reorderResult.entries };
  const projectedEntries = moveLegacySystemRemindersAfterToolResultRun(midSystemProjection.entries);
  // ...
  const renderedMessages = projectedEntries.map(renderProjectedEntryToModelMessage);
图表加载中…

几步的要点:

  • 冒泡:从尾部往前扫,meta 类提醒被挪到最近一个 assistant、tool、system 边界或中途插入的输入之后,也就是排在尾部真实用户消息的前面(provider-request-messages.ts:106)。history_continuity 通道的来源与 goal_state_change 不冒泡,保持因果位置(provider-request-messages.ts:44provider-request-messages.ts:148)。
  • MCS 落点:可走 MCS 的提醒先攒着,遇到下一条 assistant 或 system 才落下,落点须紧跟 user 或 tool 消息,连续几条合成一条 system 消息;回合中途插入的输入是因果边界,普通提醒不能越过它(apps/zcode-cli/packages/core/src/runtime/helpers/provider-mid-conversation-system.ts:36provider-mid-conversation-system.ts:79)。最后校验一遍:前面不是 user 或 tool,或者后面跟的既不是 assistant 也不是请求结尾,这样的 system 消息降级为 <system-reminder> user 文本(provider-mid-conversation-system.ts:128)。
  • 结果:从代码看,围绕一条新用户消息产生的提醒(日期、钩子、插件引用等,不论写在用户消息之前还是之后),开启 MCS 时落成“用户消息,接一条 system 消息”,与 turn.ts:544 注释所说的 “user → system” 一致;关闭时是排在用户消息之前的几条 <system-reminder> user 消息。
  • 缓存:渲染后先清掉所有非 system 消息上的缓存标记,只给最后一条非 system 消息打 ephemeralprovider-request-messages.ts:292)。加上前缀三条 system 消息,默认路径下一次请求共四个缓存标记;它们怎样变成各家协议的缓存参数见模型适配层。相邻 user 消息的合并开关是关着的,注释说这是 Anthropic 序列化层的职责(provider-request-messages.ts:39)。

有 8 种来源永远不走 MCS:context_prefix、两类压缩后提醒、分叉与旁聊边界、目标续跑、两种工具结果内联提醒(source.ts:76)。注释解释了分叉边界的理由:走 MCS 会被挪到新问题之后,改变上下文顺序(source.ts:79)。技能清单 skills_listing 不在其列,从代码看,开启 MCS 时它会离开前缀,以一条 system 消息落在第一条用户消息之后(provider-mid-conversation-system.ts:84)。

MCS 是否启用取决于模型属性 supportsMidConversationSystem,或运行时配置 midConversationSystem.modeforceruntime-provider-request-messages.ts:17)。内置 Provider 配置里这个属性默认关(config/provider/zcode-builtin.json:885),对 Anthropic Messages 形态的 api.z.aiopen.bigmodel.cn、ZCode Coding Plan 与闲时端点、DeepSeek 的 Anthropic 端点打开(zcode-builtin.json:4090zcode-builtin.json:4067),另对部分 Claude 模型打开(zcode-builtin.json:2486)。桌面端的模型设置里对应“对话中系统消息”开关(packages/ui/src/i18n/locales/zh-CN.ts:2867);命令行的 --force-mcs 强制开启,只能与 --prompt--targettui 一起用(apps/zcode-cli/packages/cli/src/run.ts:43)。规则怎样匹配到模型见Provider 规则与模型目录

上下文用量拆解

每次模型请求前,runModelTextRequest 都算一份上下文用量快照(apps/zcode-cli/packages/core/src/runtime/methods/model.ts:137),分七类:System prompt、Meta user context、Skills、Tool prompt、System tool schemas、MCP tool schemas、Messages(apps/zcode-cli/packages/core/src/runtime/methods/context-usage.ts:158)。

  • 前四类直接用 build() 时每个段落记下的字符数与估算 token(context-usage.ts:123)。估算把中文字符按两个字符计,再除以 3(apps/zcode-cli/packages/core/src/context/utils.ts:12,除数见 packages/shared/src/usage-stats.ts:9)。
  • 工具 schema 把名称、描述、输入输出 schema、权限等字段序列化后估算,MCP 工具按 mcp__ 前缀区分(context-usage.ts:310)。
  • Messages 按角色汇总,排除 system 消息与以 <system-reminder> 开头的 user 消息(后台任务通知除外)(context-usage.ts:147)。快照自带两条警告:token 是本地估算,Messages 不重复计 system 与 meta 内容(context-usage.ts:248)。从代码看,对话中途的提醒无论渲染成 system 还是 <system-reminder> 文本,两边都没算进去;Tool prompt 一类也恒为 0,因为已经没有 tools 来源的段落。

这份快照有两个去处。一是 debug 日志 context_usage_snapshot:每类下按段落、工具、技能或消息角色列出贡献者(apps/zcode-cli/packages/core/src/runtime/helpers/context-usage-breakdown.ts:39),写日志时每类只留前 5 个(apps/zcode-cli/packages/core/src/runtime/methods/context-usage-log-compact.ts:1);初始化时的 context.built 日志更是带着每段全文(context.ts:282),调试工具据此画时间线(apps/zcode-cli/packages/debug/server/analyzer.ts:1385),见遥测、调试与提示词轨迹。二是界面,给的只是按类汇总的字符数(context-usage.ts:255),而且只挂在主回合的 ModelComplete 事件上(apps/zcode-cli/packages/core/src/runtime/methods/turn-model-step.ts:610),经协议投影进 usage.contextWindow.breakdownapps/zcode-cli/packages/bootstrap/src/zcode-protocol-v4/product-projection.ts:4525)。桌面与 Web 端输入栏的上下文用量浮层据此画分段进度条,只显示各类占比,注释说分项 token 数会和顶部总量的口径混淆(packages/ui/src/chat-input-toolbar/contextUsage.tsx:961)。TUI 的状态栏与侧边栏只显示已用 token 与占窗口的比例(apps/zcode-cli/packages/tui/src/app-input-status.tsx:177),不展示拆解。

下一篇:上下文压缩——上下文快满时,microcompact、自动压缩与手动 /compact 各自怎样把对话本体换成摘要,又保留了什么。

本页目录