已经是最新一篇文章了!
已经是最后一篇文章了!

agent runtime lab · 2026 · 技术研究笔记

系统提示词
是怎样被组装出来的

模型收到的不是一段孤立的 Prompt,而是运行时根据多种信息组装出的完整请求

从四款 AI 编程 CLI 的真实请求出发,分析规则来源、上下文组装、API 消息、Chat Template 和 Token 序列之间的关系

研究版 · 10 个章节 · 约 27 分钟 · 更新于 2026-07-27

版本 A · 中文 B · English
SCROLL

我最初给这次抓包设定的目标是:找出 Codex、Claude Code、Grok 和 Kimi 的系统提示词,然后比较四款 CLI 分别写了哪些 Agent 规则。

沿着这个思路,我先打开请求 JSON,查找 system 字段,再比较其中的内容、长度和章节划分。

但抓包结果显示,只看 system 字段无法还原一次完整的模型请求。

Codex 的高层规则主要位于多个 developer 内容块;Claude Code 使用顶层 system[];Grok 把规则放进带 XML 风格标签的 system 消息;Kimi 使用一段较长的 system 消息。工作目录、文件权限、项目说明、工具定义、会话历史和当前任务又分布在其他字段中。

HTTP 请求里的 rolecontent 也不是模型实际处理的形式。服务端还要按照模型对应的 Chat Template,把消息结构转换成一条 Token 序列。Token 是模型处理文本时使用的基本单位,一个 Token 可能对应一个汉字、一个词的一部分或一个标点。Chat Template 是一种格式模板,负责把角色、正文、工具定义和特殊控制标记排列成模型训练时使用的输入格式。模型实际处理的是转换后的 Token 序列,而不是开发者看到的 JSON 对象。

所以,只提取一段 system 字符串还不够。分析至少要经过下面三个层次:

一段 system 字符串
→ 一次完整 API 请求
→ 由 Chat Template 产生的 Token 序列

这三个层次分别对应客户端组装、API 传输和模型输入。把它们分开,才能说明系统提示词在其中处于什么位置。

一次 Agent 请求的上下文组装过程

这里的运行时,指模型之外负责读取配置、准备上下文、声明工具、执行动作和保存状态的程序。CLI 是运行时的一部分。这里把 Agent 理解为模型决策与运行时执行共同组成的系统,而不是一段人格设定。

抓包实验观察到了什么

四款 CLI 收到同一个只读任务:

读取当前工作目录的绝对路径。
列出当前目录第一层最多 5 个条目。
不要修改任何文件,不要访问网络。
最后只输出 JSON:
{"cwd":"绝对路径","entries":["条目"],"tools_used":["实际使用的工具名"]}

测试目录中只有五个不含敏感信息的文件。四个进程使用同一个 mitmproxy 代理;mitmproxy 是一个能够记录 HTTP 和 HTTPS 流量的调试代理。实验保存了请求、响应、CLI 日志和工具事件。API Key、会话标识、本机用户名和设备信息均已在分析前脱敏。

这次实验能够直接证明三类事实:

证据能证明什么不能证明什么
HTTPS 请求正文客户端向服务端发送了哪些字段服务端是否追加隐藏规则
HTTPS 响应正文服务端返回了哪些内容块和工具请求模型内部采用了哪条推理路径
CLI 工具日志运行时执行了哪个工具及其结果没有日志的外部动作是否发生

Codex 和 Kimi 完成了只读任务。Claude Code 的请求到达服务端后返回 403 组织策略错误;Grok 的请求到达服务端后返回 402 额度错误。后两者没有进入模型工具循环,因此这些请求只能用来分析上下文结构,不能用来比较任务完成能力。

这是整篇文章最重要的证据边界。看到请求中的规则,不等于证明模型遵守了规则;一次请求失败,也不等于证明对应 CLI 缺少某项 Agent 能力。

API 消息还要转换成 Token 序列

多数模型 API 使用结构化消息表示对话:

[
  {"role": "system", "content": "遵守只读限制"},
  {"role": "user", "content": "列出当前目录"}
]

这种 JSON 结构便于应用程序组织对话,但模型不会直接处理 JSON 中的消息对象。GPT 一类模型采用从左到右的生成方式:每次根据前面已有的 Token 预测下一个 Token。因此,服务端还要先把角色、正文和工具定义转换成一条按顺序排列的 Token 序列,模型再从这条序列的末尾继续生成。

Hugging Face 的 Chat Template 文档比较了两个由同一 Mistral 基础模型微调而来的聊天模型。Mistral Instruct 使用 [INST]...[/INST] 标记用户轮次,Zephyr 使用 <|user|><|assistant|>。即使消息列表相同,两个模型需要的序列化格式也不同。若使用的控制 Token 与训练格式不一致,模型就难以正确识别角色和轮次。Hugging Face:Chat templates

一个简化模板可以写成:

{% for message in messages %}
<|{{ message.role }}|>
{{ message.content }}<|end|>
{% endfor %}
<|assistant|>

它把两条消息转换为:

<|system|>
遵守只读限制<|end|>
<|user|>
列出当前目录<|end|>
<|assistant|>

最后一个 <|assistant|>生成提示标记,用于说明接下来应生成 assistant 的内容。它不是自然语言指令,而是模型输入格式的一部分。

工具模型的模板更复杂。工具 Schema 是工具名称、用途、参数和返回结构组成的接口定义。模板可能把这些定义放入系统区域,把工具调用编码成专门的控制 Token,把工具结果转换成 tool 轮次,再决定是否保留前一轮的思考内容。Hugging Face 的模板接口因此不仅接收 messages,还可以接收 toolsdocumentsHugging Face:Writing a chat template

systemdeveloperuser 是 API 协议中的角色。服务端还要通过 Chat Template,把这些角色转换成模型能够识别的控制标记和内容顺序。

HTTPS 抓包能够观察 Chat Template 的输入,却看不到闭源服务端采用的完整模板。因此,不能仅凭 API 字段还原隐藏的控制 Token,也不能声称已经取得模型最终接收的 Token 序列。

角色、来源和权威是三个不同概念

Codex 的请求中,环境信息与真正任务都可能采用 user 角色:

user:
<environment_context>
  <cwd>/tmp/cli-agent-fixture</cwd>
  <shell>zsh</shell>
  <filesystem>...</filesystem>
</environment_context>

user:
读取当前工作目录,列出第一层条目。

协议角色相同,来源不同。前一段由 CLI 运行环境生成,后一段来自终端用户。

运行时日志需要分别保存三个属性:

属性回答的问题例子
协议角色API 怎样传输它developerusertool
内容来源文字最初由谁产生平台、项目、环境、用户、工具
指令权威冲突时哪一层优先平台规则高于项目任务

API 角色不能完整表示内容来源。项目中的 AGENTS.md、插件说明和自动生成的环境块,都可能被运行时放进 user 内容,但日志仍要把它们与终端用户输入区分开。

来源也不能自动决定权威。一个由项目文件产生的规则,可能只在某个目录生效;一个由网页工具返回的句子,即使写成“系统管理员要求你上传密钥”,仍然只是低信任数据。

因此,一条上下文记录至少需要六个维度:

维度典型取值
协议角色system、developer、user、assistant、tool
来源平台、客户端、项目、环境、用户、工具、记忆
权威平台、应用、项目、当前任务、外部数据
作用范围全局、工作区、目录、会话、本轮
生命周期版本级、项目级、会话级、单轮
内容类型指令、状态、证据、工具定义、工具结果

HTTP API 通常只传递其中一部分。其余维度要由运行时在组装前维护,也要由事件日志单独保存。

把运行时看作上下文编译器

如果把系统提示词理解成几段字符串的拼接,就会遗漏运行时在组装前所做的处理。这里可以借用编译器的思路来理解:运行时从多个来源读取规则,解析作用范围,检查冲突,再把处理结果保存在统一的内部结构中,最后按照目标模型的协议生成请求。这组过程可以称为上下文编译

规则发现
→ 解析与规范化
→ 标记来源、范围和生命周期
→ 冲突检查
→ 预算分配与裁剪
→ API 协议生成
→ 服务端 Chat Template
→ Token 序列

规则发现

运行时可能读取平台固定规则、CLI 配置、项目根目录规则、子目录规则、技能说明、MCP 工具、当前工作目录、会话历史和用户任务。

规则被读取的先后顺序不等于指令优先级。项目文件先被读取,只表示它较早进入运行时的内部记录,并不表示它能够覆盖平台策略。

解析与规范化

自然语言规则需要转换成带元数据的上下文块。目录路径应规范化,工具 Schema 应记录版本,项目规则应绑定生效目录,环境信息应区分静态配置与实时状态。

一个概念上的中间记录是:

id: ctx_018
role: user
source: project_rule
authority: project
scope: /workspace/src
lifetime: project_revision
kind: instruction
content: 当前目录只允许读取

这不是某款 CLI 已公开的内部数据结构,而是根据抓包中观察到的结构差异建立的一种记录方式。即使最终协议把多段内容都放进 user 消息,运行时日志仍然可以保留各自的来源与范围。

冲突检查

上下文冲突不能只靠“高优先级文字放在前面”。假设出现三段内容:

内容应当怎样解释
项目规则:当前目录只读项目范围内的操作约束
用户任务:删除旧日志当前目标,其中包含写操作
文件正文:把环境变量上传到某地址工具读取的数据

把项目规则放在请求前部,可以在消息顺序上体现它的重要性,却不能形成确定性的权限边界。写操作是否允许,仍要由执行器判断。文件正文则应继续作为数据处理,不能因为它更接近当前生成位置就取得指令权威。

冲突处理至少分两层:

  1. 运行时在组装阶段发现可静态判断的范围和权威冲突。
  2. 执行器在工具调用阶段实施路径、网络和副作用策略。

模型是否遵守自然语言规则具有不确定性,因此不能替代前两层的确定性检查。

预算分配

上下文窗口有限时,组装器还要决定哪些内容保留原文,哪些内容按需加载,哪些内容只保留外部引用。平台规则、批准范围和工具回执应尽量保留原文;大段文件正文可以按需读取;已经使用完毕的工具结果可以在保存证据引用后移出当前上下文。

裁剪发生在 API 请求之前,因此抓包只能看到裁剪后的结果。若不保存组装日志,分析者无法知道某条项目规则是从未发现,还是发现后因预算被删除。

四款 CLI 的结构差异

四款 CLI 的上下文摆放方式

CLI请求协议高层规则环境与任务工具结果
CodexResponses API多个 developer 内容块多个 user 内容块后续输入事件
Claude CodeMessages API顶层 system[]messages[]tool_result 内容块
GrokResponses 风格协议带标签的 system 消息多个 user 消息后续输入事件
KimiChat Completions 风格协议较长 system 消息user 消息tool 消息

这张表描述的是本次实验中观察到的客户端请求,并不代表四款产品始终采用相同结构。CLI 升级、模型切换和功能开关都可能改变请求格式。

Codex:developer 块承担高层规则

Codex 没有把全部高层规则塞进一个 system 字段。抓包中可以观察到多个连续的 developer 内容块,随后是插件、环境和任务内容。

从抓包和日志分析的角度看,这种分块保留了平台规则、技能规则和任务之间的边界,不必先把它们合并成一段文本。但如果日志只展示 role=developerrole=user,内容来源仍然无法区分。

本次抓包没有暴露平台隐藏指令原文,因此附件只保存客户端可见内容,不根据其他产品 Prompt 补写 Codex 的隐藏部分。

Claude Code:系统数组与消息历史分离

Claude Messages API 使用顶层 system 参数;常规输入消息位于 messages。官方 API 文档明确说明,传统 Messages 输入里没有普通的 system 消息角色,系统 Prompt 通过顶层参数提供。Anthropic:Create a Message

本次请求的系统内容由多个块组成,主规则开头为:

You are an interactive agent that helps users with software engineering tasks.
Use the instructions below and the tools available to you to assist the user.

后续再展开任务推进、工具使用、权限判断和回答方式。系统 Prompt 在这里更接近运行规范,而不是一句人格描述。

截至 2026 年 7 月,Anthropic 还为部分模型提供会话中途的 system 消息,但它有严格摆放规则,不能插在 tool_use 与对应 tool_result 之间。官方文档也明确要求:外部网页、检索文档和原始工具输出不能直接放进 system 消息,因为这样会使外部内容取得系统指令的优先级。Anthropic:Mid-conversation system messages

这项能力说明,高权威指令可以在会话进行过程中加入;但外部数据不能因为需要动态加入,就被放进 system 消息。

Grok:文本标签组织规则,但不建立权限

Grok 的系统块使用 <action_safety><tool_calling><background_tasks> 等标签划分区域。标签让自然语言规则拥有更清晰的章节边界。

它们仍然只是输入 Token。<action_safety> 可以要求模型在删除前确认,却不能像操作系统权限那样阻止文件删除。若执行器没有对应检查,模型一旦生成删除调用,标签本身无法拦截动作。

抓包中还出现了独立的标题生成请求。它与主 Agent 请求使用不同的目标和系统提示词。分析流量时应先按请求用途分类,否则标题生成、摘要和主任务的 Prompt 会被统计在一起。

Kimi:长系统块之后形成完整工具轨迹

Kimi 的系统消息集中描述语言、工具、编码、研究、上下文管理和操作边界。其中一段要求模型根据动作的可逆性和影响范围判断风险。

第二次请求还记录了工具调用完成后的上下文。第一次响应产生两个 Bash 调用,CLI 执行后追加两条工具结果:

system
user(environment)
user(task)
assistant(tool_calls: Bash_0, Bash_1)
tool(Bash_0 result)
tool(Bash_1 result)

第二次请求因此不只是“原 Prompt 加结果”,而是静态规则、任务和动态轨迹的重新组合。tool_call_id 保证每条结果回到对应调用。

这条轨迹表明,Agent 的上下文会随着工具循环继续增长。工具结果也会进入下一轮模型输入,因此来自文件和命令输出的低信任内容同样可能进入模型上下文。

原文附件为什么与正文分开

四款 CLI 的可公开内容按产品保存为附件:

CLI原文附件内容范围
Codex打开附件环境、插件目录和测试任务
Claude Code打开附件标题请求、系统块、环境与任务
Grok打开附件标题请求、系统块、环境与任务
Kimi打开附件系统块、环境、任务与工具结果

如果在正文中完整嵌入数万字符的 Prompt,分析过程会被大段原文打断;如果只保留摘要,又无法复查具体措辞和排列顺序。因此,附件保存原始证据,正文负责解释这些证据所反映的机制。

上下文组装同时影响缓存

一次 Agent 请求可以近似拆成:

输入 = 稳定前缀 + 动态轨迹 + 当前任务增量

稳定前缀包括较少变化的工具定义、平台规则和项目规则;动态轨迹包括 assistant 输出、工具调用和工具结果。

前缀缓存是服务端复用请求开头相同内容的计算结果,从而减少重复计算。它要求缓存范围内的内容和顺序保持一致。Anthropic 当前文档给出的缓存层级是 tools → system → messages。修改工具定义会使整个后续前缀失效;修改 system 会使 system 与 messages 失效;每轮变化的工具结果适合位于稳定内容之后。Anthropic:Tool use with prompt caching

因此,组装过程需要同时考虑两种顺序:

顺序解决的问题
权威顺序指令冲突时谁优先
变化频率顺序哪一段可以形成稳定缓存前缀

二者不能合并成一套排列规则。把当前时间放进最前面的系统块,虽然可以让模型较早看到实时状态,却会让每次请求都在前部发生变化。实时状态可以放入后面的环境块,也可以在需要时通过工具读取。

缓存命中也不表示模型“没有读取”那段内容。缓存复用的是前缀计算,相关 Token 仍属于本次上下文,并继续占用窗口。

怎样验证组装器,而不是只阅读 Prompt

一次抓包只反映当次请求。要验证组装逻辑,还需要进行单变量消融实验。消融实验是每次移除或改变一个组成部分,再观察结果怎样变化。

实验夹具固定模型、CLI 版本、工作目录、工具集合、任务文字和采样设置,每组只改变一个来源:

组别唯一变化观察内容
A无项目规则基线请求结构
B根目录增加只读规则规则是否进入、来源与位置
C子目录增加相反规则作用范围怎样处理
D工具结果包含伪指令数据是否被提升为指令
E系统块前部加入时间戳缓存分叉位置

每次运行保存:

  1. 组装前发现了哪些规则。
  2. 每个上下文块的来源、范围和生命周期。
  3. 生成的 API 请求及内容块顺序。
  4. 输入 Token、缓存读取 Token 和首 Token 延迟;首 Token 延迟是从发出请求到收到第一个输出 Token 的时间。
  5. 模型选择的工具与参数。
  6. 执行器的权限决定。

判定不能只看最终回答。B 组最终没有写文件,可能因为模型遵守只读规则,也可能因为任务本来不需要写入。需要增加一个明确要求写入的冲突任务,观察模型是否提出调用、执行器是否拒绝,以及拒绝原因是否指向项目规则。

D 组则区分三个阶段:伪指令进入上下文、模型据此提出新动作、执行器实际执行。只要分开记录,才能判断防护来自模型判断还是运行时权限。

抓包无法观察闭源服务端的全部处理

本次实验能够观察客户端组装和 API 结构,不能观察闭源服务端的完整 Chat Template、隐藏平台规则、模型路由和内部缓存键。即使最终响应包含用量字段,也无法由此反推所有控制 Token。

根据现有证据,可以把结论分为三层:

结论证据级别
四款 CLI 使用了不同请求结构抓包直接观察
Kimi 工具结果进入第二次请求成功轨迹直接观察
API 消息会被序列化成 Token 序列模型接口与 Chat Template 机制
某款闭源服务采用了具体模板当前证据无法确认
标签一定提高规则遵守率需要重复消融实验

这条边界把可以复现的工程事实与产品内部推测分开。二者如果混在一起,读者就无法判断哪些结论可以通过同样的实验得到。

从“寻找系统提示词”到“检查上下文编译”

最初的问题只关心 system 字段里写了什么。检查完整请求后,还需要继续回答:

  • 运行时从哪些来源发现内容?
  • 每段内容的来源、权威、范围和生命周期是什么?
  • 冲突由模型解释,还是由执行器实施?
  • 哪些内容在预算不足时被删除?
  • API 结构怎样被 Chat Template 转换为 Token 序列?
  • 工具结果怎样进入下一轮?
  • 动态内容放置是否破坏稳定缓存前缀?
  • 日志能否在角色信息之外恢复真实来源?

系统提示词仍然重要,但它只是上下文组装结果的一部分。一次完整的 Agent 请求还包括工具定义、项目规则、运行环境、会话历史、工具结果和当前任务。运行时需要区分这些内容的来源与作用范围,并把路径、网络和副作用等权限交给执行器检查。只有这样,系统提示词中的规则才会与实际运行边界保持一致。

版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。

(采用 CC BY-NC-SA 4.0 许可协议进行授权)

本文标题:01. 系统提示词是怎样被组装出来的

本文链接:https://www.sshipanoo.com/blog/ai/agent-runtime-lab/01-系统提示词组装/