解析Codex代理循环
摘要
# 解析Codex代理循环 来源:[https://openai.com/index/unrolling-the-codex-agent-loop/](https://openai.com/index/unrolling-the-codex-agent-loop/) [Codex CLI\\(在新窗口中打开\\)](https://developers.openai.com/codex/cli)是我们的跨平台本地软件代理,旨在在你的机器上安全高效地运行,生成高质量、可靠的软件更改。自从我们首次推出以来,我们已经学到了大量关于如何构建世界一流软件代理的知识[自从我们首次启动
深入技术解析Codex代理循环,解释Codex CLI如何使用Responses API协调模型、工具、提示和性能。
查看缓存全文
缓存时间:
2026/04/20 14:52
# 解析 Codex 智能体循环
来源:https://openai.com/index/unrolling-the-codex-agent-loop/
Codex CLI\(opens in a new window\) (https://developers.openai.com/codex/cli) 是我们跨平台的本地软件智能体,旨在高效、安全地在你的机器上运行,并生成高质量、可靠的软件更改。自从我们在 4 月份首次推出 CLI 以来 (https://openai.com/index/introducing-o3-and-o4-mini/),我们学到了大量关于如何构建世界一流软件智能体的知识。为了深入剖析这些见解,本系列的第一篇文章将探讨 Codex 的工作原理以及我们获得的来之不易的经验。(若想更细致地了解 Codex CLI 的构建方式,请查看我们的开源仓库:https://github.com/openai/codex。我们设计决策中的许多细节都记录在 GitHub issue 和 pull request 中,欢迎进一步了解。)
首先,我们将重点关注 **智能体循环 (agent loop)**,这是 Codex CLI 中的核心逻辑,负责协调用户、模型以及模型为执行有意义的软件工作而调用的工具之间的交互。希望本文能让你清楚地了解我们的智能体(或称“框架”)在利用 LLM 方面所扮演的角色。
在深入探讨之前,先简要说明一下术语:在 OpenAI,“Codex”包含一套软件智能体产品,包括 Codex CLI、Codex Cloud 和 Codex VS Code 扩展。本文重点介绍的是 Codex 的 **框架 (harness)**,它提供了支撑所有 Codex 体验并通过 Codex CLI 展现的核心智能体循环和执行逻辑。为方便起见,本文中我们将互换使用“Codex”和“Codex CLI”。
每个 AI 智能体的核心都有一个被称为“智能体循环”的东西。智能体循环的简化示意图如下所示:
开始时,智能体从用户那里获取 **输入**,并将其包含在它准备的一大套用于模型的文本指令中,这套指令被称为 **提示 (prompt)**。下一步是查询模型:将我们的指令发送给模型,并让它生成一个响应,这一过程称为 **推理 (inference)**。在推理过程中,文本提示首先被转换为一系列输入 **标记 (tokens)**\(opens in a new window\) (https://platform.openai.com/docs/concepts#tokens)——这些是索引到模型词汇表中的整数。然后这些标记被用于对模型进行采样,产生新的输出标记序列。输出标记再被转换回文本,成为模型的响应。由于标记是增量生成的,这种转换可以在模型运行时进行,这就是为什么许多基于 LLM 的应用会显示流式输出。实际上,推理通常被封装在一个对文本进行操作的 API 背后,隐藏了分词的具体细节。
作为推理步骤的结果,模型要么 (1) 对用户的原始输入产生最终响应,要么 (2) 请求执行一个 **工具调用**,要求智能体去执行(例如,“运行 `ls` 并报告输出”)。在情况 (2) 下,智能体执行工具调用,并将其输出附加到原始提示中。这个输出用于生成新的输入,并再次用于查询模型;这样智能体就能考虑这个新信息并再次尝试。这个过程重复进行,直到模型停止发出工具调用,而是为用户生成一条消息(在 OpenAI 模型中称为 **助手消息**)。在许多情况下,这条消息直接回答了用户的原始请求,但也可能是向用户提出的后续问题。
由于智能体可以执行修改本地环境的工具调用,因此它的“输出”并不限于助手消息。在许多情况下,软件智能体的主要输出是它在你的机器上编写或编辑的代码。然而,每一轮(turn)总是以一条助手消息结束——例如“我已添加你要求的 `architecture.md`”——这标志着智能体循环的终止状态。从智能体的角度来看,它的工作已完成,控制权交还给用户。
图中从 **用户输入** 到 **智能体响应** 的旅程被称为一次对话的 **一轮**(在 Codex 中称为一个 **线程 (thread)**)。尽管这次 **对话轮次** 可能包含 **模型推理** 和 **工具调用** 之间的多次迭代。
每次你向现有对话发送新消息时,对话历史都会作为新轮次提示的一部分包含在内,其中包括之前轮次的消息和工具调用:
这意味着随着对话的增长,用于采样模型的提示长度也会增加。这个长度很重要,因为每个模型都有一个 **上下文窗口**,即一次推理调用最多能使用的标记数量。注意这个窗口既包括输入标记也包括输出标记。可以想象,智能体可能在单轮中决定进行数百次工具调用,可能会耗尽上下文窗口。因此,**上下文窗口管理** 是智能体的众多职责之一。
现在,让我们深入看看 Codex 是如何运行智能体循环的。
Codex CLI 发送 HTTP 请求到 Responses API\(opens in a new window\) (https://platform.openai.com/docs/api-reference/responses) 来运行模型推理。我们将研究信息如何在 Codex 中流动,Codex 利用 Responses API 来驱动智能体循环。
- 当在 Codex CLI 中使用 ChatGPT 登录\(opens in a new window\) (https://github.com/openai/codex/blob/d886a8646cb8d3671c3029d08ae8f13fa6536899/codex-rs/core/src/model_provider_info.rs#L141) 时,它使用 `https://chatgpt.com/backend-api/codex/responses` 作为端点
- 当使用 API 密钥认证\(opens in a new window\) (https://github.com/openai/codex/blob/d886a8646cb8d3671c3029d08ae8f13fa6536899/codex-rs/core/src/model_provider_info.rs#L143) 并使用 OpenAI 托管模型时,它使用 `https://api.openai.com/v1/responses` 作为端点
- 当使用 `--oss` 运行 Codex CLI 以结合 gpt-oss (https://openai.com/index/introducing-gpt-oss/) 和 ollama 0.13.4+\(opens in a new window\) (https://github.com/openai/codex/pull/8798) 或 LM Studio 0.3.39+\(opens in a new window\) (https://lmstudio.ai/blog/openresponses) 时,它默认使用本地运行的 `http://localhost:11434/v1/responses`
- Codex CLI 也可与云提供商(如 Azure)托管的 Responses API 一起使用
让我们探讨 Codex 如何为对话中的第一次推理调用创建提示。
作为最终用户,你查询 Responses API 时,并不是逐字指定用于采样模型的提示。相反,你需要在查询中指定各种输入类型,然后 Responses API 服务器会决定如何将这些信息构建成模型可以消费的提示。你可以把提示想象成一个“项目列表”;本节将解释你的查询如何转换成该列表。
在初始提示中,列表中的每个项目都与一个角色 (role) 相关联。`role` 表示相关内容应该具有多大权重,是以下值之一(按优先级递减):`system`、`developer`、`user`、`assistant`。
- `instructions`\(opens in a new window\) (https://platform.openai.com/docs/api-reference/responses/create#responses_create-instructions):系统(或开发者)消息,插入到模型的上下文中
- `tools`\(opens in a new window\) (https://platform.openai.com/docs/api-reference/responses/create#responses_create-tools):模型在生成响应时可能调用的工具列表
- `input`\(opens in a new window\) (https://platform.openai.com/docs/api-reference/responses/create#responses_create-input):模型的文本、图像或文件输入列表
`tools` 字段是一个工具定义列表,符合 Responses API 定义的 schema。对于 Codex,这包括由 Codex CLI 提供的工具、由 Responses API 提供并应可供 Codex 使用的工具,以及由用户提供的工具(通常通过 MCP 服务器):
1. 一条 `role=developer` 的消息,描述仅适用于 Codex 提供的 `shell` 工具的沙箱,该工具在 `tools` 部分定义。也就是说,其他工具(例如来自 MCP 服务器的工具)不受 Codex 沙箱保护,并负责实施自己的护栏。
2. (可选)一条 `role=developer` 的消息,其内容是用户 `config.toml` 文件中读取的 `developer_instructions` 值。
3. (可选)一条 `role=user` 的消息,其内容是“用户指令”,这些指令并非来自单个文件,而是跨多个来源\(opens in a new window\) (https://github.com/openai/codex/blob/99f47d6e9a3546c14c43af99c7a58fa6bd130548/codex-rs/core/src/project_doc.rs#L37-L42) 聚合而成。通常,更具体的指令出现在后面:
- `$CODEX_HOME` 中 `AGENTS.override.md` 和 `AGENTS.md` 的内容
- 受限于限制(默认 32 KiB),从 Git/项目根目录(如果存在)向上到 `cwd` 本身,在每个文件夹中查找:添加任何 `AGENTS.override.md`、`AGENTS.md` 或 `config.toml` 中 `project_doc_fallback_filenames` 指定的任何文件名的内容
- 如果任何技能\(opens in a new window\) (https://developers.openai.com/codex/skills/) 已配置:
一旦 Codex 完成上述所有计算来初始化 `input`,它就会追加用户消息以开始对话。
前面的示例侧重于每条消息的内容,但请注意,`input` 的每个元素都是一个 JSON 对象,包含 `type`、`role`\(opens in a new window\) (https://www.reddit.com/r/OpenAI/comments/1hgxcgi/what_is_the_purpose_of_the_new_developer_role_in/) 和 `content`,如下所示:
一旦 Codex 构建了要发送给 Responses API 的完整 JSON 负载,它就会发出 HTTP POST 请求,并携带 `Authorization` 头(取决于 `~/.codex/config.toml` 中如何配置 Responses API 端点,如果指定了额外的 HTTP 头和查询参数,也会一并添加)。
当 OpenAI Responses API 服务器收到请求时,它会使用 JSON 为模型派生出提示,如下所示(当然,Responses API 的自定义实现可能会做出不同的选择):
如你所见,提示中前三个项目的顺序由服务器决定,而不是客户端。尽管如此,在这三个项目中,只有 **系统消息** 的内容也由服务器控制,因为 `tools` 和 `instructions` 由客户端决定。这些之后是来自 JSON 负载的 `input`,从而完成提示。
现在我们有了提示,就可以对模型进行采样了。这个到 Responses API 的 HTTP 请求启动了 Codex 中对话的第一个“轮次”。服务器以服务器推送事件 (SSE\(opens in a new window\) (https://en.wikipedia.org/wiki/Server-sent_events)) 流的形式进行回复。每个事件的 `data` 是一个 JSON 负载,带有以 `"response"` 开头的 `"type"`,可能如下所示(完整的事件列表可在我们的 API 文档\(opens in a new window\) (https://platform.openai.com/docs/api-reference/responses-streaming) 中找到):
Codex 消费事件流\(opens in a new window\) (https://github.com/openai/codex/blob/2a68b74b9bf16b64e285495c1b149d7d6ac8bdf4/codex-rs/codex-api/src/sse/responses.rs#L334-L342),并将其重新发布为可供客户端使用的内部事件对象。像 `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` 字段中表示出来,以便我们在使用工具调用的响应再次查询模型时使用:
用于后续查询中采样模型的提示如下所示:
特别注意,旧提示是新提示的 **精确前缀**。这是有意为之,因为这样可以使后续请求更加高效,让我们能够利用 **提示缓存**(我们将在下一部分关于性能的讨论中介绍这一点)。
回顾我们第一个智能体循环示意图,我们会发现推理和工具调用之间可能存在多次迭代。提示可能会持续增长,直到我们最终收到一条助手消息,标志着该轮次的结束:
在 Codex CLI 中,我们将助手消息呈现给用户,并聚焦作曲器,向用户表明现在轮到他们继续对话。如果用户做出响应,则上一轮次的助手消息以及用户的新消息都必须附加到 `input` 中,以启动新轮次的 Responses API 请求:
同样,由于我们是在继续对话,我们发送给 Responses API 的 `input` 长度会不断增加:
让我们来看看这个不断增长的提示对性能意味着什么。你可能会问:“等等,从对话过程中发送给 Responses API 的 JSON 量来看,智能体循环不是 **二次方** 增长的吗?” 你说得对。虽然 Responses API 确实支持可选的 `previous_response_id`\(opens in a new window\) (https://platform.openai.com/docs/api-reference/responses/create#responses_create-previous_response_id) 参数来缓解这个问题,但 Codex 目前没有使用它,主要是为了保持请求完全无状态并支持零数据保留 (ZDR) 配置。避免使用 `previous_response_id` 简化了 Responses API 提供者的工作,因为它确保每个请求都是 **无状态的**。这也使得支持选择零数据保留 (ZDR)\(opens in a new window\) (https://platform.openai.com/docs/guides/migrate-to-responses#4-decide-when-to-use-statefulness) 的客户变得简单明了,因为存储支持 `previous_response_id` 所需的数据会与 ZDR 冲突。请注意,ZDR 客户并不会失去从先前轮次的专有推理消息中获益的能力,因为相关的 `encrypted_content` 可以在服务器端解密。(OpenAI 会保留 ZDR 客户的解密密钥,但不会保留他们的数据。)相关支持 ZDR 的 Codex 更改,请参阅 PR #642\(opens in a new window\) (https://github.com/openai/codex/pull/642) 和 #1641\(opens in a new window\) (https://github.com/openai/codex/pull/1641)。
通常,模型采样的成本主导了网络流量的成本,因此采样是我们效率工作的主要目标。这就是为什么提示缓存如此重要,它使我们能够重用上一次推理调用的计算。当获得缓存命中时,模型采样是 **线性的,而不是二次方** 的。我们的提示缓存\(opens in a new window\) (https://platform.openai.com/docs/guides/prompt-caching#structuring-prompts) 文档对此有更详细的解释:
*缓存命中仅可能出现在提示中的精确前缀匹配。要获得缓存的好处,请将静态内容(如指令和示例)放在提示的开头,并将可变内容(如用户特定信息)放在末尾。这也适用于图像和工具,它们在请求之间必须完全相同。*
考虑到这一点,让我们思考哪些类型的操作可能会在 Codex 中导致“缓存未命中”:
- 在对话中途更改模型可用的 `tools`。
- 更改 Responses API 请求的目标 `model`(实际上,这会更改原始提示中的第三项,因为它包含特定于模型的指令)。
- 更改沙箱配置、审批模式或当前工作目录。
Codex 团队在引入 Codex CLI 的新功能时必须保持警惕,因为这些功能可能会破坏提示缓存。
相似文章
OpenAI Blog
本文是 OpenAI Codex 的入门指南,介绍了线程、项目和设置等核心界面元素。文章重点介绍了实时任务引导以及同时运行多个操作等功能。
YouTube AI Channels
OpenAI的Codex已从编程工具演变为通用AI代理,现被知识工作者用于研究、协调和数据分析,将数小时的工作缩短至几分钟。
OpenAI Blog
OpenAI描述了一项内部实验,使用Codex智能体构建了一个零手动编写代码的生产软件产品,在五个月内由AI编写了150万行代码,开发速度提升了约10倍。团队认识到,有效的智能体驱动开发要求工程师专注于系统设计、脚手架和反馈循环,而不是直接编写代码。
Hacker News Top
Jason Liu 分享了他如何使用 OpenAI 的 Codex 进行编码之外的知识工作,利用持久化线程、语音输入和引导将编码代理整合到他更广泛的工作流程中。
OpenAI Blog
OpenAI 推出 Codex,这是一款独立的 AI 智能体,旨在跨文件和工具执行各类任务。它与 ChatGPT 的对话能力形成互补,专注于处理文件更新、流程自动化等工作流。