Realtime Conversation¶
Realtime conversation 是 Codex 把语音、文本和后台 agent loop 接到同一个 live 会话里的设计。它的核心不是“多一种模型调用”,而是一个会话编排器:前端不断送入音频或文本,Realtime API 不断返回转写、音频、handoff 请求和函数调用结果,Codex 同时还要把后台 agent 的执行进度再写回 realtime 会话。
协议入口¶
协议定义在 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。
关键策略:
- transport 默认为 websocket;WebRTC 走 sideband websocket。
- WebRTC 当前要求 realtime v1 且 session type 为 conversational。
- websocket 需要 API key auth;优先 provider api key,其次 experimental bearer token,再到 auth cached api key,OpenAI provider 可临时 fallback 到
OPENAI_API_KEY。 - realtime session id 默认复用 thread id,便于把实时会话和 Codex thread 对齐。
- v1 不支持 text output modality,text output 只能走 v2。
Prompt 与 startup context¶
build_realtime_session_config 会合成最终 instructions:
experimental_realtime_ws_backend_prompt配置优先。- 请求里的
prompt次之;Some(None)或空字符串表示显式不要 backend prompt。 - 默认使用
codex_prompts::BACKEND_PROMPT,并把{{ user_first_name }}替换为本机用户名字。 - 如果
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 |
有界本地目录扫描,过滤 .git、node_modules、target 等 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_handoff 和 last_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。ResponseDone 或 ResponseCancelled 到达后再发送下一次 create。
音频截断¶
v2 中,用户开始讲话时,如果 assistant 音频还在播放,Codex 会根据 OutputAudioState 发送 conversation.item.truncate。audio_duration_ms 会优先用 frame 自带的 samples_per_channel,否则 base64 解码 PCM 字节估算采样数。
这个细节让实时对话更像真实打断:前端不只是停止播放,后端 conversation item 也被截断到用户打断时刻。
可学习的设计点¶
- realtime 不是旁路功能,而是复用 session、thread、event protocol 和 agent loop。
- 使用 bounded channel 明确背压策略,音频输入和文本/agent 输出采用不同可靠性。
- v1/v2 兼容逻辑集中在
RealtimeSessionKind和 event parser 分支,避免散落在 UI 层。 - handoff 采用 XML-like envelope 给后台 agent,保留 transcript delta,便于 agent 理解“用户刚刚说了什么”和“上下文变化是什么”。
- 对 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 |