引言
本周主要分享之前的一个小研究:如何实现一个 Coding Agent?
Coding Agent 无疑是目前 AI 应用里实际价值最大的一类,比如正当红的 Claude Code、 Codex 等。
不知道你是否使用过,以及想过他们到底是如何实现的?
如果没有,你现在可以思考下。下面我们借着 Codex 这个开源项目一起学习下,再看看和你的设想是否一样。
定义场景
我们以一个最简单的前端需求为例:
把导航栏
/AppHeader.tsx链接之间的间距加大一点。
来拆解它是如何工作的。
这里只看当前场景下的核心逻辑和流程,不会完全展开全部功能和实现细节。
基本保证准确,能力有限错误难免。
核心逻辑
1. 处理用户输入
阶段目标:把用户输入处理成可被 Agent 消费的内容。
核心代码:codex-rs/tui/src/chatwidget.rs
主要逻辑:
- Codex 获取到用户输入后,会先解析成结构化的
UserInput,包括:文本、图像、Skills。 - 再把
UserInput结合很多环境配置信息打包成UserTurn,这里的环境配置信息主要包括:工作目录、选择的模型、命令审批和沙盒策略等。 - 最后把
UserTurn提交给 Codex 核心线程。
2. 构建 TurnContext
阶段目标:基于 UserTurn 构建更完整的 Context 并启动任务。
核心代码:codex-rs/core/src/codex.rs
主要逻辑:
- 结合
UserTurn构建TurnContext,注入更多信息,包括:developer_instructions(从配置文件config.toml中获取)、compact_prompt(如果有压缩的话会用到)、user_instructions(项目文档AGENTS.md+ Skills 拼接)、toolsConfig等。 - 启动
RegularTask开始运行。
3. 运行 Turn
阶段目标:通过 Agent Loop,完成具体工作,比如修改代码。
核心代码:codex-rs/core/src/codex.rs
主要逻辑:
- 构造最终发出的 initial prompt。
- 简单说就是 3 个关键参数,对应 OpenAI
/v1/responses的入参:instructions:系统提示词,针对不同模型做了定义,比如gpt-5.2-codex-prompttools:获取可用工具input:基于之前的TurnContext和UserTurn得到
tools具体包括 Codex 定义的工具和 MCP 工具等,比如:shell、计划工具、获取文件工具、编辑代码工具等。- 发出推理请求,也就是调用
/v1/responsesAPI,进入 Agent Loop。 - 是否继续循环的核心逻辑,是看模型的响应是否包含 tool call:
- 如果包含,就执行调用工具并继续请求
- 若没有,则结束本轮 turn 请求并返回 assistant 消息
- 调用工具方法时,大致分为:
- 解析:把模型响应转化成
ToolCall - 调度:控制工具的并发与取消
- 执行:根据不同的工具找到对应的执行逻辑
- 回写:把工具执行完的结果回写,触发下一轮推理请求
- 解析:把模型响应转化成
- 开始下一轮请求时,除了第一轮请求的内容,新一轮请求会增加:
- 上一轮模型返回的推理内容
- 上一轮模型返回的工具调用
- 调用工具得到的结果
4. 前端展示结果
阶段目标:把 Agent Loop 的过程展示给用户。
核心代码:codex-rs/tui/src/chatwidget.rs
主要逻辑:把推理接口响应的内容解析后,流式输出给用户,直到循环结束展示 assistant 消息为止。
拆解真实请求
下面我们打印实际日志,看一个完整 Agent Loop 的过程。
第一个回合
请求
Agent 基于我的输入「把 /AppHeader.tsx 链接之间的间距加大一点。」构建了 initial prompt 并发出请求。
响应
核心就是模型要求调用工具,执行 ls 命令。
response.output_item.added
item.type="function_call"
arguments="{\"command\":\"ls\",\"workdir\":\"/Users/...\"}"
第二个回合
请求
Agent 完成了 ls 的执行,带上执行结果再次请求。
响应
模型要求调用 sed 查看指定文件的前 200 行内容。
response.output_item.added
item.type = "function_call"
name = "shell_command"
arguments = "{\"command\":\"sed -n '1,200p' apps/web-backend/src/components/layout/AppHeader.tsx\", \"workdir\":\"/Users/...\"}"
第三个回合
请求
Agent 完成了 sed 命令,带上执行结果再次请求。
响应
模型要求调用 apply_patch 修改指定代码。apply_patch 不是 OpenAI 标准的 function_call,所以是 custom_tool_call。
response.output_item.added
item.type = "custom_tool_call"
name = "apply_patch"
input = "*** Begin Patch
*** Update File: apps/web-backend/src/components/layout/AppHeader.tsx
@@
- <nav className=\"flex items-center justify-center gap-4 text-sm font-medium leading-none text-muted-foreground sm:h-10 sm:justify-self-center\">\n
+ <nav className=\"flex items-center justify-center gap-3 text-sm font-medium leading-none text-muted-foreground sm:h-10 sm:justify-self-center\">\n
*** End Patch"
第四个回合
请求
Agent 完成了工具调用修改了代码,带上执行结果再次请求。
Exit code: 0
Wall time: 0 seconds
Output:
Success. Updated the following files:
M apps/web-backend/src/components/layout/AppHeader.tsx
响应
这次终于没有 tool_call 了,返回了 assistant 消息,最终展示给用户。
"output": [
{
"id": "msg_...",
"type": "message",
"status": "completed",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "已把导航链接间距从 ..."
}
]
}
]
我的感受
看完了以上的过程,不知道和你的想象是否一样呢?
说实话,在知道 Codex 是如何实现之后,我其实是很震惊的。
从产品层面来说,真的是大道至简。我试图去理解下 Codex 的产品在做什么:
- 构建上下文
- 定义工具
- 确保一些交互体验
- 剩下的交给模型
你应该能感受到模型到底有多重要。而作为一个古典产品出身的人,感受到了一丝「绝望」。
可能 Coding Agent 是一个比较极端的场景,恰好是模型最擅长的领域。但是我感受到了 AI 原生产品的设计思路和非 AI 产品的巨大不同。
一个感受是:传统产品依然是在定义和创造,然后交付给用户; 但是 AI 产品只是在帮助连接用户和模型,从一个创造者变成了传声筒。
还有一个想法是:没有垂直 Agent,只有通用 Agent。
Codex 其实只是给了 Agent 一个运行环境,然后提供各种 tools 和需要的上下文。从某种层面来看,和编程没有什么关系,最近火爆的 OpenClaw 算是某种证明?
同时我对于 Skills 有了新的理解。它其实是 Agent 的拓展,拓展了 instructions 和 tools。夸张地说,凡是通过计算设备交付的工作,Agent 理论上都可以完成。
虽然类比很不准确,还是禁不住想:如果 LLM 是大脑,那它需要的就是一个身体(运行环境)和一台电脑(工具)。