三种 API 流 · 一套词汇统一三家后端

grok 同时支持 OpenAI Chat Completions、Anthropic Messages、OpenAI Responses 三种 API 格式。这一章看它怎么把三种迥异的流,归一成同一套 SamplingEvent 与中立会话模型——这正是「换个模型就能跑」的底层原因。

作者 David更新于 第 9 篇(共 60 篇)

三种 API 流 · 一套词汇统一三家后端

grok 不绑死某一家模型。你可以用它连 xAI 自己的模型,也可以 自定义模型接 Ollama、OpenAI 兼容端点、Anthropic。能做到这点,靠的是采样器把三种 API 格式统一成了一套内部词汇。

三种后端

ApiBackend 枚举(sampling-types/types.rs)三选一:

后端对应特点
ChatCompletionsOpenAI Chat Completions(默认)最通用,多数兼容端点走这条
MessagesAnthropic Messages块结构(Text/ToolUse/Thinking 分块),缓存字段独立
ResponsesOpenAI 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 模型 · 下一篇:流式解析与事件

本页目录