第 5 章:模型抽象与 Sampler
Agent 不应该知道某个供应商的 SSE 字段名,也不应该自己判断 429 是否重试。Grok Build 把“对话语义”“后端协议”“流式生命周期”拆成三个层次。本章解释这一边界如何让 SessionActor 保持稳定。
5.1 三层而不是一个 HTTP Client
xai-grok-sampler/src/lib.rs 的模块说明把采样分为三层:
SamplingClient:向具体后端发送请求,产生原始流;- stream transform:把原始流归一为
SamplingEvent; SamplerHandle/actor:并发请求、重试、取消和生命周期管理。
另一个 crate xai-grok-sampling-types 则只承载纯数据,不执行 I/O。
5.2 规范 Conversation 模型
conversation.rs 中的 ConversationItem 不等同于某一家 API 的 message:
| Item | 含义 |
|---|---|
System | 系统级指令 |
User | 用户或 synthetic user content |
Assistant | 助手输出 |
ToolResult | 本地工具执行结果 |
BackendToolCall | 后端托管工具事件 |
Reasoning | 可独立表达的推理内容 |
这种建模避免把所有东西压成 role + content string。特别是 reasoning、托管搜索和本地 tool result,在不同供应商协议中的位置并不相同。
SyntheticReason
用户角色内容有时由系统注入,而非真人输入。SyntheticReason 显式记录来源,例如:
CompactionMeta、SystemReminder、ProjectInstructions;AutoContinue、AutoRecovery、Interjection;TaskCompleted、SubagentCompleted、NotificationDrain;GoalSummary、GoalClassifierNudge、SchedulerFired;StopHookFeedback、WorkingDirectorySwitch。
这比在字符串中约定前缀更可靠。后续可以针对 synthetic content 做过滤、统计、供应商转换或 UI 隐藏,而不会误判真人消息。
5.3 ConversationRequest 包含什么
ConversationRequest 定义在 conversation.rs 约 551 行。它大致包含:
- 有序
items; - 本地
tools与后端hosted_tools; tool_choice;- model、temperature、max tokens、top_p;
- effort、response schema;
- trace、cache key、Grok 相关会话标识。
它是“本次模型调用想表达什么”,而不是“某个 HTTP endpoint 要什么 JSON”。
5.4 三种后端形状
client.rs 中的后端形状至少包括:
| 后端 | 典型 endpoint | 主要差异 |
|---|---|---|
| Chat Completions | /chat/completions | message/tool_calls 传统结构 |
| Responses | /responses | item/event 化、托管工具与 reasoning 更自然 |
| Anthropic Messages | /messages | content block 与 tool use/result 规则不同 |
转换器需要处理的不只是字段重命名:
- system 指令放在哪里;
- reasoning 是否独立 item;
- tool call id 如何映射;
- image/content block 如何表达;
- usage 与 cache token 如何归一;
- stop reason 如何统一;
- hosted tool 是远端执行还是本地执行。
把这些差异锁在 sampler/client 层,可以使 SessionActor 只处理一种 conversation 语义。
5.5 流式事件协议
SamplingEvent 定义在 xai-grok-sampler/src/events.rs,核心事件包括:
| 事件 | 语义 |
|---|---|
StreamStarted | 一次可观测采样流已建立 |
FirstToken | 首 token 延迟可被计量 |
ChannelToken | Text 或 Reasoning 增量 |
ToolCallDelta | 工具名/参数的增量构造 |
ModelMetadata | 上下文窗口等模型信息 |
BackendToolCallStarted/Completed | 供应商托管工具 |
Retrying | 当前 attempt 失败,系统将重试 |
Completed | 得到规范化终态 |
Failed | 不再重试的失败 |
这个事件协议是模型网络流与会话业务之间的防火墙。
5.6 为什么工具调用要增量组装
模型可能这样流式输出参数:
delta 1: name="read_file", arguments="{"
delta 2: arguments="\"path\":"
delta 3: arguments="\"src/main.rs\"}"2
3
UI 可以早期显示工具名,但工具不能在 JSON 完整前执行。Sampler/Session 的 accumulator 需要按 call id/index 聚合 delta,终态时再生成规范 tool call。
由此产生一个不变量:
增量事件用于展示与累积,只有完成后的规范调用才进入 Tool Runtime。
5.7 ConversationResponse 为什么是有序 item 列表
响应结构在 conversation.rs 约 732 行。它保留一个扁平、有序的 item 序列,并附带:
StopReason:Stop、Length、ToolCalls、ContentFilter;- 归一化 usage;
- cost ticks、chunk count;
- doom-loop signals;
- stop message 等元信息。
有序列表比“text 字段 + tool_calls 字段”更能表达 reasoning、文本、多个工具调用交错出现的后端。SessionActor 可以再根据业务需要提取展示文本和工具调用。
5.8 一次采样 attempt 的控制流
actor/request_task.rs 的 run_one_attempt(约 473 行)选择后端并建立流;drive_l2(约 590 行)消费归一事件,直到终态。
每个 request 在独立 task 中运行,Sampler actor 保存活跃请求与取消句柄。这样一个慢流不会阻塞 actor 接收其他 request/cancel。
5.9 重试分类
retry.rs 不把所有错误一视同仁。分类大致要区分:
- 临时网络/服务端错误;
- 限流与带 retry hint 的错误;
- 认证/额度错误;
- context length;
- 内容过滤;
- 请求本身无效;
- 流已开始后的中途失败;
- doom loop 或空响应。
指数退避加入 jitter,避免多个 session 同时重试形成同步风暴。doom-loop 还有单独 backoff。
重要的是:context overflow 不只是 sampler 的网络错误。它携带语义返回 SessionActor,由会话层决定是否压缩上下文并重新请求。
5.10 取消是一级事件
取消可能发生在:
- 连接尚未建立;
- 正在读取流;
- backoff sleep;
- 后端工具运行;
- terminal event 已产生但 consumer 还未排空。
request_task 在等待和 retry 路径中都检查 cancellation token,并统一发送终止结果。若只在 HTTP client 上 drop stream,SessionActor 可能永远等不到明确的 terminal event。
通用原则:
可取消异步任务必须同时定义“停止工作”和“向观察者报告已停止”。
5.11 空响应与尾流
HTTP 200 不等于有效模型响应。Sampler 还检查:
- 是否产生任何有效内容;
- tool call 是否完整;
- stop reason 是否合理;
- 流是否突然断开;
- max token/content filter 是否需要特殊提示;
- terminal 是否恰好发送一次。
Session 层随后再等待 stream-drained 屏障,保证最后的增量已经被处理。
5.12 图片过大恢复
规范 conversation 支持图片。当后端因 payload 过大(例如 413)拒绝时,恢复路径可把图片替换为占位信息再尝试,而不是把整个会话丢弃。
这体现了上下文恢复的一个层级:
- sampler 知道请求形状和 HTTP 错误;
- conversation types 知道怎样安全地 strip image;
- SessionActor 决定是否值得重试;
- ChatState 继续保存原始会话语义。
5.13 可观测性
Sampler 事件天然支持以下指标:
- time to stream start;
- time to first token;
- total latency;
- chunk count;
- retry count/reason;
- usage 与 cost;
- cancel/failed/completed 分布。
因为 metrics 建在规范事件上,不需要每个后端复制一套业务监控。
5.14 源码阅读卡
| 目的 | 文件 |
|---|---|
| 规范对话类型 | sampling-types/src/conversation.rs |
| SamplingEvent | sampler/src/events.rs |
| 后端 client 与 endpoint | sampler/src/client.rs |
| 单请求 task | sampler/src/actor/request_task.rs |
| 错误分类与退避 | sampler/src/retry.rs |
| Session 收集采样 | shell/session/acp_session_impl/sampler_turn.rs |
5.15 本章结论
模型抽象的关键不是定义一个 ModelProvider trait,而是把三种变化率分开:
对话语义变化最慢,供应商 wire protocol 独立变化,单次网络流变化最快。
sampling-types、backend client 和 SamplerActor 分别承接这三层。SessionActor 因而能用统一的 request/response/event 工作。
下一章进入工具系统,观察模型产出的动态 JSON 如何穿过类型、安全与并发边界。