深入解析 Codex 智能体循环
🤑深入解析 Codex 智能体循环
技术分享|2026-2-18|最后更新: 2026-3-1
type
Post
status
Published
date
Feb 18, 2026
slug
summary
tags
llm
category
技术分享
icon
password
Status

深入解析 Codex 智能体循环

智能体循环

对任何一个 AI 智能体来说,其核心都是一套称为"智能体循环"的运行机制。其简化示意如下图所示:
notion image
首先,智能体接收用户输入,并将其整合为一系列为可供模型使用的文本指令,这一系列指令称为提示
接着,智能体将指令发送给模型,并要求模型生成响应,这个过程称为推理。在推理过程中,文本提示首先被转换为一串输入 Token -- 这些 Token 是用于索引模型词汇表的整数值。随后,这些 Token 被用于对模型进行采样,从而生成一串新的输出 Token。
输出 Token 会转换回文本作为模型响应。由于 Token 是逐次生成的,转换过程可以与模型运行同步进行,因此许多大语言模型应用能实现流式输出。实际应用中,推理过程通常封装在一个处理文本的 API 内部,从而对使用者隐藏了 Token 化的细节。
推理步骤完成后,模型可能产生两种结果:
  1. 生成对用户原始输入的最终响应
  1. 请求智能体执行一次工具调用(例如,"运行 ls 命令并返回结果")。
若是情况 (2),智能体则执行所请求的工具调用,并将工具执行的结果追加到原始提示中。工具的输出结果将用于生成新的输入,并再次提交给模型;智能体基于这个更新后的上下文,开始新一轮推理。
此过程循环往复,直至模型不再发起工具调用,转而生成一条面向用户的消息(在 OpenAI 模型中称为助手消息)。多数情况下,此消息会直接回应用户的初始请求,但也可能是一个向用户提出的追问。
由于智能体能够执行会修改本地环境的工具调用,因此其"输出"并不局限于助手消息本身。在许多场景下,软件智能体最主要的"输出",其实是它在您计算机上直接编写或修改的代码。然而,每一轮交互最终都会由一条助手消息来结束,例如"您要求的 architecture.md 文件已添加",这标志着智能体循环达到终止状态。从智能体的视角来看,其任务已告完成,控制权也随之交还给用户。

多轮对话

图中所示的从用户输入到智能体响应的完整过程,被称为一次对话轮次(在 Codex 中也称为一个对话线程)。需要注意的是,一次对话轮次可能包含模型推理与工具调用之间的多次循环迭代。每当您向既有对话发送新消息时,包括之前所有轮次的消息与工具调用在内的完整对话历史,都会被纳入新轮次的提示中:
notion image
这意味着,随着对话的推进,用于模型采样的提示也会越来越长。提示长度至关重要,因为每个模型都有一个上下文窗口,即其单次推理调用所能处理的最大 Token 数。需要注意,此窗口的容量同时涵盖了输入与输出 Token。可以想象,智能体在单轮交互中可能发起数百次工具调用,因此存在耗尽上下文窗口的风险。正因如此,上下文窗口管理便成了智能体的核心职责之一。
现在,让我们深入 Codex 内部,看其如何运行智能体循环。

模型推理

Codex CLI 通过向 Responses API 发送 HTTP 请求来执行模型推理。接下来,我们将详细分析信息如何流经 Codex,以及它如何利用 Responses API 来驱动智能体循环。
Codex CLI 所使用的 Responses API 端点是可配置的,这意味着它可以与任何实现 Responses API 的端点一同工作,例如:
  • 通过 ChatGPT 登录使用 Codex CLI 时,会使用 https://chatgpt.com/backend-api/codex/responses 作为端点
  • 使用 API 密钥对 OpenAI 托管模型进行身份验证时,会使用 https://api.openai.com/v1/responses 作为端点
  • 使用 -oss 参数运行 Codex CLI 以配合 gpt-oss(并与 ollama 0.13.4+LM Studio 0.3.39+ 一同使用)时,默认端点为本地运行的 http://localhost:11434/v1/responses
  • Codex CLI 也可与 Azure 等云服务商托管的 Responses API 协同运行
接下来,我们探讨 Codex 如何为对话中的第一次推理调用创建提示。

构建初始提示

作为最终用户,您在查询 Responses API 时,并不需要逐字输入用于对模型进行采样的提示。取而代之的是,您只需在查询中指定各类输入,Responses API 服务器会负责将这些信息组织成模型可接受的提示结构。您可以将提示理解为一个"项目列表";本节将阐述您的查询如何被转换为此列表。
在初始提示中,列表内的每个项目都关联一个角色。角色决定了其关联内容的权重优先级,取值如下(按优先级降序排列):systemdeveloperuserassistant
Responses API 接收一个包含多个参数的 JSON 负载。我们重点关注以下三个参数:
  • instructions:插入到模型上下文中的系统(或开发者)消息
  • tools:模型在生成响应时可能调用的工具列表
  • input:提供给模型的文本、图像或文件输入列表

instructions 字段

在 Codex 中,instructions 字段的内容来源如下:若在 ~/.codex/config.toml 中指定了 model_instructions_file,则从中读取;否则,使用与模型绑定的 base_instructions。这些模型特定的指令存放在 Codex 代码仓库中,并随 CLI 打包发布(例如 gpt-5.2-codex_prompt.md)。

tools 字段

tools 字段是一个工具定义列表,需符合 Responses API 定义的 schema。对 Codex 而言,这一列表通常包含:Codex CLI 内置工具、通过 Responses API 向 Codex 开放的工具,以及用户(通常通过 MCP 服务器)提供的工具:

input 字段

最后,JSON 负载中的 input 字段本身也是一个项目列表。在添加用户消息之前,Codex 会先将以下项目插入 input:
1. 沙盒权限消息
一条 role=developer 的消息,用于描述沙盒权限。此沙盒仅适用于在 tools 部分定义的、由 Codex 提供的 shell 工具。也就是说,其他工具(例如来自 MCP 服务器的工具)不受 Codex 沙盒限制,需自行负责执行其防护措施。
该消息基于一个模板构建,其关键内容来自打包到 Codex CLI 中的 Markdown 代码片段,例如 workspace_write.mdon_request.md
2. 开发者指令(可选)
一条 role=developer 的消息,其内容是从用户 config.toml 文件中读取的 developer_instructions 值。
3. 用户指令(可选)
一条 role=user 的消息,其内容为"用户指令",这些指令并非源自单一文件,而是从多个来源聚合而来。通常,更具体的指令会出现在更靠后的位置:
  • $CODEX_HOME 目录中 AGENTS.override.mdAGENTS.md 的内容
  • 受大小限制(默认 32 KiB),从 cwd 的 Git/项目根目录(如果存在)开始,向上逐级检查每个目录,直至 cwd 本身:添加 AGENTS.override.mdAGENTS.md、或 config.toml 中由 project_doc_fallback_filenames 指定的任何文件的内容
  • 如果已配置任何技能
    • 一段关于技能的简短前言
    • 每个技能的技能元数据
    • 一个关于如何使用技能的章节
4. 环境上下文
一条 role=user 的消息,描述智能体当前运行的本地环境。该消息会指定当前工作目录和用户的 shell
当 Codex 根据前述计算对 input 进行初始化后,就会将用户消息追加进去,从而开始一轮新的对话。

消息的 JSON 结构

前面的示例侧重于每条消息的内容,但请注意,input 中的每个元素都是一个 JSON 对象,包含 typerolecontent 字段,如下所示:

发送请求

当 Codex 构建好要发送给 Responses API 的完整 JSON 负载后,它会根据 ~/.codex/config.toml 中 Responses API 端点的配置,发起一个带有 Authorization 标头的 HTTP POST 请求(如果指定了额外的 HTTP 头和查询参数,也会一并添加)。
当 OpenAI 的 Responses API 服务器收到请求时,它会使用 JSON 为模型推导出提示,如下所示(当然,Responses API 的自定义实现可能做出不同选择):
notion image
如您所见,提示中前三个项目的顺序由服务器决定,而非客户端。也就是说,在这三个项目中,只有 system message 的内容也由服务器控制,因为 toolsinstructions 由客户端决定。之后,来自 JSON 负载的 input 会紧随其后,从而完成整个提示的构建。
现在,我们已准备好提示,可以对模型进行采样了。

第一轮对话

这次对 Responses API 的 HTTP 请求开启了 Codex 中的第一轮对话。服务器回复一个服务器发送事件 (SSE) 流。每个事件的 data 是一个 JSON 负载,其 type 字段以 response 开头,可能类似以下示例(完整事件列表请参阅 API 文档):
Codex 会处理事件流,并将其重新发布为可供客户端使用的内部事件对象。像 response.output_text.delta 这样的事件用于支持 UI 中的流式输出,而像 response.output_item.added 这样的事件则被转换为对象,并追加到 input 中,供后续的 Responses API 调用使用。
假设对 Responses API 的第一次请求包含两个 response.output_item.done 事件:一个 type=reasoning,另一个 type=function_call。当我们带着工具调用的响应再次查询模型时,这些事件必须体现在 JSON 的 input 字段中:
那么,用于在后续查询中对模型进行采样的提示将如下所示:
notion image
需要特别注意的是,旧提示是新提示的精确前缀。这是有意为之的,因为这样可以显著提升后续请求的效率,让我们得以充分利用提示缓存(详见下一节"性能"部分)。
回顾智能体循环的第一张示意图,我们可以看到推理和工具调用之间可能发生多次迭代。提示可能会持续增长,直到我们最终收到一条助手消息,标志该轮对话结束:
在 Codex CLI 中,我们会向用户展示助手消息,并自动聚焦输入框,提示用户该轮到自己继续输入、推进对话了。如果用户做出回应,则上一轮的助手消息和用户的新消息都必须追加到 Responses API 请求的 input 中,以开始新一轮对话:
再次强调,随着对话不断推进,我们发送给 Responses API 的 input 长度会不断增加:
notion image
接下来,我们分析这个不断增长的提示会对性能产生什么样的影响。

性能考量

不少读者这时可能会想到:"如果把整段对话都算上,发送到 Responses API 的 JSON 总量是不是呈二次增长?"这种直觉是正确的。尽管 Responses API 确实支持一个可选的 previous_response_id 参数来缓解此问题,但 Codex 目前并未使用它,主要是为了保持请求完全无状态,并支持零数据保留 (ZDR) 配置。
选择不使用 previous_response_id,可以让 Responses API 提供方的实现更简单,因为每个请求都将保持无状态。对选择启用零数据保留 (ZDR) 的客户而言,这也让支持流程更简单明晰,因为只要依赖 previous_response_id,就需要存储相应数据,而这与 ZDR 的原则相违背。请注意,ZDR 客户不会牺牲从先前轮次专有推理消息中获益的能力,因为相关的 encrypted_content 可以在服务器上解密。(OpenAI 会保存 ZDR 客户的解密密钥,但不会保存他们的数据。)有关支持 ZDR 的 Codex 相关更改,请参见 PR #642#1641

提示缓存

通常,模型采样的成本远高于网络流量成本,因此采样成为我们提升效率的主要目标。正因如此,提示缓存才至关重要,它使我们能够重用先前推理调用中的计算。一旦命中缓存,模型采样的开销就会从二次复杂度降为线性复杂度。我们的提示缓存文档有更详细的说明:
缓存命中仅在提示内部存在精确的前缀匹配时才可能发生。为实现缓存优势,请将指令和示例等静态内容置于提示开头,而将用户特定信息等可变内容置于提示末尾。这也适用于图像和工具,它们在各次请求间必须完全相同。
考虑到这一点,让我们看看哪些类型的操作可能导致 Codex 发生"缓存未命中":
  • 在对话中途更改模型可用的 tools
  • 更改作为 Responses API 请求目标的模型(实际上,这会更改原始提示中的第三项,因为该项包含模型特定的指令)
  • 更改沙盒配置、审批模式或当前工作目录
Codex 团队在 Codex CLI 中引入可能损害提示缓存的新功能时,必须非常谨慎。例如,我们最初对 MCP 工具的支持曾引入一个未能以固定顺序枚举工具的 bug,导致缓存未命中。请注意,MCP 工具可能尤其棘手,因为 MCP 服务器可以通过 notifications/tools/list_changed 通知动态更改其提供的工具列表。在长对话中途响应此通知,可能导致代价高昂的缓存未命中。

配置变更的处理策略

在可能的情况下,我们通过向 input 追加一条新消息(而非修改之前的消息)来处理对话中发生的配置更改:
  • 如果沙盒配置或审批模式发生变化,我们插入一条新的 role=developer 消息,其格式与原始的 <permissions instructions> 项目相同
  • 如果当前工作目录发生变化,我们插入一条新的 role=user 消息,其格式与原始的 <environment_context> 相同
我们竭尽全力确保缓存命中以提升性能。我们还需管理另一个关键资源:上下文窗口。

上下文压缩

为避免耗尽上下文窗口,我们采用的通用策略是:当 Token 总数超过设定阈值时,就对当前对话进行压缩。具体来说,我们用一个新的、更小的项目列表替换 input,该列表代表了对话内容,使智能体能够在理解已发生情况的基础上继续工作。
早期的压缩实现要求用户手动调用 /compact 命令,该命令会使用现有对话加上用于摘要的自定义指令来查询 Responses API。Codex 将包含摘要的助手消息结果用作新的 input,用于后续对话轮次。
在后续版本中,Responses API 引入了专门的 /responses/compact 端点,用于更高效地执行对话压缩。该端点会返回一个项目列表,可用来替代先前的 input 以继续对话,同时释放上下文窗口。此列表包含一个特殊的 type=compaction 项目,附带一个不透明的 encrypted_content 项目,用于保存模型对原始对话的潜在理解。现在,当超过 auto_compact_limit 时,Codex 会自动使用此端点压缩对话。
 
OpenAIOpenAI深入解析 Codex 智能体循环
人们到底给了 AI Agent 多少自主权?Anthropic 用数据回答了这个问题用数据证明直觉:CTR 特征工程的完整实战复盘
Loading...