Codex 源码导读:第六部分——模型请求与流式网络层

第六部分:模型请求与流式网络层

主流程

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 选择传输方式:

  1. Responses API 且 WebSocket 可用:尝试复用或建立 WebSocket。
  2. WebSocket 未启用,或返回明确的 FallbackToHttp:走 HTTP Responses API;并非所有 WebSocket 错误都立即回退,其他错误会向上传递,由上层重试逻辑处理。
  3. 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。文中的流程图用于标出本篇所处的运行阶段。

相关推荐
Jlzn88883 小时前
华东电芯分选通道数怎么配才不堵线
java·人工智能·架构·自动化·制造
CCblandy4 小时前
雪花 ID 四个边界实测:时钟回拨重号 500 个,前端 JSON.parse 直接丢精度
后端·python·算法·性能优化·架构
阡陌数智4 小时前
Gemini 4 Argon 百万 Token 输出能力解析:原生长输出模型的工程落地与并发成本治理
人工智能·架构
海宇数据7 小时前
零信任架构实战:基于海宇入职背调报告构建自动化即时配送与网约车众包运力准入鉴权网关
运维·人工智能·架构·自动化
天远API8 小时前
零信任架构实战:基于天远公安二要素政务版构建自动化不动产交易与契税核算实名核实网关
人工智能·架构·自动化·php
预知同行8 小时前
深入解析 MoE 混合专家模型:从 Router 到 All-to-All 的架构全景与生产实践
后端·架构
Linye9 小时前
换个会话就忘了方案?我把项目约定写成了一个文件
架构
KylinLab9 小时前
四条主张五条禁令:人不在环内的组织级契约
架构
奔跑的架构师9 小时前
[A-51]ARMv9/v8-SCMI系统架构
linux·arm开发·架构·系统架构·arm
糖果豆豆9 小时前
让 AI 自查三遍,它只会把错的说得更像对的——检查的人和被检查的,是同一个
算法·架构