三种 API 流 · 一套词汇统一三家后端
grok 同时支持 OpenAI Chat Completions、Anthropic Messages、OpenAI Responses 三种 API 格式。这一章看它怎么把三种迥异的流,归一成同一套 SamplingEvent 与中立会话模型——这正是「换个模型就能跑」的底层原因。
三种 API 流 · 一套词汇统一三家后端
grok 不绑死某一家模型。你可以用它连 xAI 自己的模型,也可以 自定义模型接 Ollama、OpenAI 兼容端点、Anthropic。能做到这点,靠的是采样器把三种 API 格式统一成了一套内部词汇。
三种后端
ApiBackend 枚举(sampling-types/types.rs)三选一:
| 后端 | 对应 | 特点 |
|---|---|---|
ChatCompletions | OpenAI Chat Completions(默认) | 最通用,多数兼容端点走这条 |
Messages | Anthropic Messages | 块结构(Text/ToolUse/Thinking 分块),缓存字段独立 |
Responses | OpenAI Responses / xAI | 最丰富:reasoning、后端托管工具、context 详情 |
统一的关键:中立模型 + 同一套事件
不管哪种后端,采样器都把它们收敛到同一个中立模型(sampling-types/conversation.rs):
- 请求侧
ConversationRequest:items + tools + hosted_tools + 各种x-grok-*头(外加结构化输出的 JSON schema、prompt_cache_key等字段)。 - 响应侧
ConversationResponse:items + stop_reason + usage + cost,外加服务端报来的 doom-loop 信号。 - 历史单元
ConversationItem:System / User / Assistant / ToolResult / BackendToolCall / Reasoning的扁平有序列表。
中立模型到各家线上格式的请求构造,拆在同目录 conversation/ 下的 chat_completions.rs、messages.rs、responses.rs 里。
每种后端有一个流变换(stream/{chat_completions,messages,responses}.rs),都是 async_stream::stream! 生成器,把该后端五花八门的 chunk 归一成同一套 SamplingEvent,并保证恰好一个终止事件(Completed 或 Failed)。所以循环上层根本不关心底下是哪家 API。
魔鬼在 token 账里
三家把「用了多少 token」报得都不一样,采样器得替它们对齐(这直接关系到压缩何时触发、成本怎么算):
- Anthropic 把缓存分开报:
input_tokens(未缓存)+cache_read_input_tokens+cache_creation_input_tokens,采样器把三者相加才是完整 prompt token。 - Responses 的
total_tokens被 SSE 解码器改写成「当前上下文实际长度」(context_details),而计费字段保持累计——因为 async-openai 的类型建模不了 xAI 这些扩展字段,grok 用原始 JSON 注入 + 宽容解析把它们接回来。 - 成本:xAI 在
usage.cost_in_usd_ticks里报费用(1 美元 = 10¹⁰ tick)。async-openai 的类型化 Response 事件没有这个字段,解码器就从原始 JSON 读出,塞进response.metadata的自定义 key(xai.cost_usd_ticks)捎给流变换。
两个值得学的设计
- 字节稳定的历史顺序:
ConversationItem刻意保留[Reasoning, tool_call, …, message]的交错顺序,且跨轮字节稳定——这样服务端的 KV 前缀缓存才能命中,省钱省延迟。 - prompt 缓存:走 Anthropic 时,会话→Messages 的构造(
conversation/messages.rs的apply_cache_breakpoints)会打三个cache_control: ephemeral断点——最后一个 system 块、最新一条消息的末尾块、上一次请求结束的位置。只标 system prompt 的话,整段对话前缀都进不了缓存;第四个断点位则刻意留给开了自动缓存的网关(API 最多只收四个)。走 Responses 时改用prompt_cache_key做粘性路由。两者都对应 llm-to-agent 的 prompt-caching。
和《从 LLM 到 Coding Agent》对照
那本的 raw-llm-api 讲了「一种」API 的请求响应。grok 的增量是:真实产品要同时伺候三家,于是多了一层「中立模型 + 每后端一个变换」的翻译层。这层翻译,就是「一份 agent 逻辑,换任意模型都能跑」的代价与红利。
上一篇:采样器 · actor 模型 · 下一篇:流式解析与事件