Realtime Conversation

Realtime conversation 是 Codex 把语音、文本和后台 agent loop 接到同一个 live 会话里的设计。它的核心不是“多一种模型调用”,而是一个会话编排器:前端不断送入音频或文本,Realtime API 不断返回转写、音频、handoff 请求和函数调用结果,Codex 同时还要把后台 agent 的执行进度再写回 realtime 会话。

Realtime Conversation Runtime

协议入口

协议定义在 codex-rs/protocol/src/protocol.rs

op 作用
RealtimeConversationStart 启动 websocket 或 WebRTC realtime 会话
RealtimeConversationAudio 追加用户音频帧
RealtimeConversationText 追加文本输入
RealtimeConversationSpeech 把本地 speech 文本注入 conversation
RealtimeConversationClose 关闭实时会话
RealtimeConversationListVoices 查询可用 voice

ConversationStartParams 把多个关键策略显式暴露出来:是否由 client 管理 handoff、Codex 响应是否作为 conversation item 写回、输出模态、是否注入 startup context、prompt override、transport、协议版本和 voice。

会话管理器

codex-rs/core/src/realtime_conversation.rs 中的 RealtimeConversationManager 只持有一个 ConversationState。新会话启动时会先取出旧 state 并停止旧任务,因此同一个 session 内 realtime 是单实例的。

ConversationState 保存四类通道和两个后台任务:

字段 设计意图
audio_tx 用户音频输入队列,容量 256。满队列时丢弃帧而不是阻塞主流程
text_tx 用户文本输入队列,容量 64
handoff.output_tx 后台 agent 输出回灌队列,容量 64
events_rx/events_tx realtime server 事件输出队列,容量 256
input_task 负责把本地输入、handoff 输出和 server event 三路 select 到 realtime writer
fanout_task 负责把 realtime server event 转成 Codex event,并在 handoff 时路由到后台 agent

这个设计把实时 I/O 的背压边界放在 channel 上:音频可以丢,文本和 handoff 需要可靠发送,server event 统一 fanout 到 Codex protocol。

启动流程

handle_start 先调用 prepare_realtime_start 构造启动配置,再由 handle_start_inner 创建 realtime 连接并注册 fanout task。

关键策略:

  1. transport 默认为 websocket;WebRTC 走 sideband websocket。
  2. WebRTC 当前要求 realtime v1 且 session type 为 conversational。
  3. websocket 需要 API key auth;优先 provider api key,其次 experimental bearer token,再到 auth cached api key,OpenAI provider 可临时 fallback 到 OPENAI_API_KEY
  4. realtime session id 默认复用 thread id,便于把实时会话和 Codex thread 对齐。
  5. v1 不支持 text output modality,text output 只能走 v2。

Prompt 与 startup context

build_realtime_session_config 会合成最终 instructions:

  1. experimental_realtime_ws_backend_prompt 配置优先。
  2. 请求里的 prompt 次之;Some(None) 或空字符串表示显式不要 backend prompt。
  3. 默认使用 codex_prompts::BACKEND_PROMPT,并把 {{ user_first_name }} 替换为本机用户名字。
  4. 如果 include_startup_context 为 true,会追加 startup context。

startup context 由 codex-rs/core/src/realtime_context.rs 构造,预算是 5_300 tokens。它分成四块:

section token budget 内容
Current Thread 1_200 当前 thread 最近用户/assistant turn,单 turn 最多 300 tokens
Recent Work 2_200 thread store 中最近工作,按 git root/cwd 分组
Machine / Workspace Map 1_600 有界本地目录扫描,过滤 .gitnode_modulestarget 等 noisy 目录
Notes 300 告知该 context 可能不完整或过期

Realtime context 明确排除了 AGENTS、项目 docs prompt blend 和 memory summary。这样做降低启动延迟,也避免实时会话启动时重新执行完整 CLI 上下文装配。

Handoff 编排

Realtime API 可能发出 HandoffRequested。Codex 的 fanout task 会把它转换成:

<realtime_delegation>
  <input>...</input>
  <transcript_delta>...</transcript_delta>
</realtime_delegation>

然后调用 Session::route_realtime_text_input 进入后台 agent loop。后台 agent 的输出再通过 RealtimeConversationManager::handoff_out 写回 realtime 会话。

v1 和 v2 的写回语义不同:

场景 v1 v2
standalone output conversation.handoff.append 到固定 id codex 创建 user item 后 request response.create
active handoff progress function call output 或 handoff append 创建 user item;如果 handoff id 过期则丢弃
handoff complete 多数情况下不额外处理 function call output 写入完成确认,并 request response.create
steering 无特殊路径 如果已有 active handoff,新 handoff 作为 steering ack 处理

active_handofflast_output_text 是 v2 的关键状态。它们让 Codex 能区分“继续当前后台任务”和“用户在同一实时会话里又发起了新意图”。

响应创建队列

Realtime v2 有一个 race:当已有 active response 时直接发 response.create 可能失败。RealtimeResponseCreateQueue 用两个布尔状态解决:

状态 含义
active_default_response 当前默认 response 正在进行
pending_create 有一次 create 被推迟,等待当前 response done/cancelled 后补发

如果 API 返回 "Conversation already has an active response in progress:",Codex 不把它当致命错误,而是标记为 pending。ResponseDoneResponseCancelled 到达后再发送下一次 create。

音频截断

v2 中,用户开始讲话时,如果 assistant 音频还在播放,Codex 会根据 OutputAudioState 发送 conversation.item.truncateaudio_duration_ms 会优先用 frame 自带的 samples_per_channel,否则 base64 解码 PCM 字节估算采样数。

这个细节让实时对话更像真实打断:前端不只是停止播放,后端 conversation item 也被截断到用户打断时刻。

可学习的设计点

  1. realtime 不是旁路功能,而是复用 session、thread、event protocol 和 agent loop。
  2. 使用 bounded channel 明确背压策略,音频输入和文本/agent 输出采用不同可靠性。
  3. v1/v2 兼容逻辑集中在 RealtimeSessionKind 和 event parser 分支,避免散落在 UI 层。
  4. handoff 采用 XML-like envelope 给后台 agent,保留 transcript delta,便于 agent 理解“用户刚刚说了什么”和“上下文变化是什么”。
  5. 对 active response race 做显式 queue,而不是依赖 sleep/retry。

关键源码

主题 文件
protocol op/event codex-rs/protocol/src/protocol.rs
conversation manager codex-rs/core/src/realtime_conversation.rs
startup context codex-rs/core/src/realtime_context.rs
backend prompt codex-rs/core/src/realtime_prompt.rs
op dispatch codex-rs/core/src/session/handlers.rs