第六部分:模型请求与流式网络层
主流程
text
Session 历史 + 当前 Turn 配置
↓
build_prompt()
↓
ModelClientSession::stream()
↓
选择 WebSocket 或 HTTP Responses API
↓
SSE / WebSocket 原始事件
↓
ResponseEvent
↓
Core 的 run_sampling_request()
↓
写入 Session + 发布 EventMsg + 触发工具

请求传入的内容
run_sampling_request 将这些内容交给 client_session.stream():
- 已编译的
Prompt:系统指令、历史消息、工具定义和当前输入。 ModelInfo:模型标识、输入模态、Responses Lite 等模型能力。- 推理强度、推理摘要配置、服务等级等采样参数。
CodexResponsesMetadata:Session、Thread 等请求标识。InferenceTraceContext和会话遥测信息。
因此网络层接收的是一个已经完成上下文编译的请求,不负责重新决定 Session 历史或工具权限。
Prompt 如何变成 ResponsesApiRequest
Prompt 进入 ModelClientSession::stream() 后,由 build_responses_request() 编译成供应商请求:
rust
let mut input = prompt
.get_formatted_input_for_request(
model_info.use_responses_lite,
);
// input 是已经规范化的历史:用户消息、模型输出、tool-call、tool-result
let request = ResponsesApiRequest {
model: model_info.slug.clone(),
instructions,
input,
tools,
tool_choice: "auto".to_string(),
parallel_tool_calls:
prompt.parallel_tool_calls
&& !model_info.use_responses_lite,
reasoning: Some(reasoning),
store: false,
stream: true,
include,
service_tier,
prompt_cache_key,
text,
client_metadata: Some(
responses_metadata.client_metadata(),
),
access_programs: None,
};
普通 Responses API 中,字段关系是:
text
instructions = 系统指令
input = 会话历史和工具结果
tools = 本轮模型可见的工具 JSON Schema
Responses Lite 则把工具定义和系统指令插入 input 前缀:
text
input = [AdditionalTools] + [系统指令] + [会话历史]
这里做的是协议形态适配,不是重新编译 Session;请求数据仍然来自上一层的 Prompt。
ModelClientSession::stream() 做什么
client.rs:2016 的 stream() 根据 Provider 的 WireApi 选择传输方式:
- Responses API 且 WebSocket 可用:尝试复用或建立 WebSocket。
- WebSocket 未启用,或返回明确的
FallbackToHttp:走 HTTP Responses API;并非所有 WebSocket 错误都立即回退,其他错误会向上传递,由上层重试逻辑处理。 - HTTP 路径建立请求、认证、兼容性 Header 和遥测信息,再发送流式请求。
对应代码:
client.rs:1563:构建 Responses API 请求、Header、Session/Thread 标识和遥测信息。client.rs:1720:WebSocket 流路径。client.rs:2016:传输选择与 HTTP fallback。
这层的职责是统一传输和 Provider 适配,不负责 Agent Loop 的继续/结束判断。
HTTP 路径:JSON 请求进入 SSE 流
HTTP Responses 路径会把 ResponsesApiRequest 编码成 JSON,并设置 SSE 接受类型:
rust
let body = EncodedJsonBody::encode(&request)?;
req.headers.insert(
http::header::ACCEPT,
HeaderValue::from_static(
"text/event-stream",
),
);
let stream_response = self
.session
.stream_encoded_json_with(
Method::POST,
self.endpoint.path(),
extra_headers,
Some(body),
...,
)
.await?;
源码:responses.rs
请求头还会携带 Session/Thread 标识、认证信息、路由提示和压缩配置。响应建立后进入 spawn_response_stream()。
SSE 不是最终协议对象
HTTP 返回的是 SSE 数据帧。Codex 的 SSE 解析器负责:
text
HTTP byte stream
→ SSE data frame
→ 字符串数据
→ ResponseEvent
解析器先从字节流读取 SSE 帧,再把 event.data 反序列化为 Responses 事件,最后按事件类型转换:
rust
match event.kind.as_str() {
"response.output_text.delta" => {
Ok(Some(ResponseEvent::OutputTextDelta(
event.delta.unwrap_or_default(),
)))
}
"response.output_item.done" => {
let item = serde_json::from_value::<ResponseItem>(
event.item.unwrap(),
)?;
Ok(Some(ResponseEvent::OutputItemDone(item)))
}
"response.custom_tool_call_input.delta" => {
Ok(Some(ResponseEvent::ToolCallInputDelta {
item_id,
call_id,
delta,
}))
}
"response.completed" => {
Ok(Some(ResponseEvent::Completed {
response_id,
token_usage,
usage_metadata,
end_turn,
}))
}
}
对应源码:process_responses_event()。网络层输出的不是原始 JSON,而是统一的 ResponseStream<Item = Result<ResponseEvent, ApiError>>。WebSocket 路径也返回相同的 ResponseStream,上层不需要区分底层传输方式。
空闲超时、连接错误、流提前关闭都会作为流错误返回,而不是伪装成正常完成。
ResponseEvent 回到 Core 后做什么
session/turn.rs:2295 开始逐条读取 ResponseEvent,主要类型包括:
| 事件 | Core 的处理 |
|---|---|
OutputTextDelta |
追加模型文本,发布文本增量事件 |
ReasoningSummaryDelta |
更新推理摘要流 |
ToolCallInputDelta |
累积工具参数,形成工具调用 |
OutputItemDone |
收口一个完整输出项,写入当前 Turn |
Completed |
记录 token 使用量,判断是否需要 follow-up |
模型的原始流式事件不会直接交给 UI。Core 将事件归属到当前输出项或工具调用:文本增量可以直接转为 EventMsg 发给 App Server,完成项与工具结果另行写入 Session 历史。写历史与发事件不具有统一的先后顺序。
核心边界
text
模型网络层:构造请求、选择传输、解析流、统一事件
Core 编排层:根据事件更新状态、执行工具、判断下一步
App Server:把 Core 事件转换给 TUI / IDE / Desktop
所以"模型返回工具调用"并不意味着网络层直接执行工具。网络层只返回 ResponseEvent::ToolCallInputDelta 或完整工具调用,真正的工具分派仍由 Core 的 Agent Loop 完成。
这一段的完整数据流
text
Prompt
↓ build_responses_request()
ResponsesApiRequest
↓ JSON 编码 + Header
HTTP POST /responses 或 WebSocket
↓
SSE / WebSocket 原始事件
↓ JSON 解析
ResponsesStreamEvent
↓ 事件类型映射
ResponseEvent
↓
run_sampling_request()
├─ 文本增量 → 当前 assistant message
├─ tool-call → 工具 Future 入队
├─ reasoning → 推理状态
└─ completed → 返回 Loop 做继续/停止判断
源码基线:OpenAI Codex
d58d0e5841e0de08e251673db2d5af8cf3a1ad51。文中的流程图用于标出本篇所处的运行阶段。