Grok Build 是如何工作的(二):模型调用
上一章说明了三层循环。本章说明 L3 内的一次模型调用:请求如何组装、如何发 HTTP 并处理流式响应、结果如何写回、默认使用哪一类 API,以及 Session 与模型调用层的边界。
Copiloted by Grok Build.
一次模型调用的流程
1. 确定本轮请求附带内容
Turn 在调用前确定:
- 本 turn 可用的客户端 tool 定义(含 MCP;初始化策略为阻塞时,可能先等待 MCP 就绪)
- 可选的输出 JSON Schema
- 可选的服务端托管 tool(由推理服务在响应过程中执行;是否启用取决于模型与配置)
- 模型调用配置:model、temperature、reasoning effort、context window 等
tool 定义放在请求的 tools 字段中,不写入历史正文。plan mode、fork、结构化输出会改变 tools 内容。
2. 由对话状态生成请求
对话状态保存内部条目列表(system、user、assistant、tool result、reasoning、服务端已执行的 tool 记录等)。列表格式与实际发往模型服务的 JSON 无关。
每次调用模型前,按固定顺序从该列表生成一份 ConversationRequest。
-
复制
复制一份将要发给模型的条目列表。后续步骤可以只改这份副本。
-
修复结构
修副本里与 API 不兼容的部分,例如未闭合的
tool_call、重复的tool_result。 -
按需写入 memory 类 reminder
若需要,把 memory 类 reminder 写入 system 段。
写入目标分两种调用方式:只写进本次请求副本,或持久写进对话状态中的 system 条目(由调用参数决定)。 -
按需去掉较早的 inline 图片
若序列化后的请求体接近体积上限,从副本中移除较早的 inline 图片。
-
按需裁剪旧 tool result
若 token 占用超过上下文窗口的约一半,在副本上按 tool result 所属 turn 的新旧裁剪。龄期从对话尾向前数 User 条数估算。
| 龄期 | 行为 | 具体做法 |
|---|---|---|
| 最近 N 个 turn(默认 N=3) | 不裁剪 | 原文完整保留在本次请求里 |
| 中等龄:超过 N,且未到 hard-clear 龄期(默认 hard-clear 龄期 = 10 个 turn) | soft-trim | 仅当该条正文超过字符阈值(默认 4000)时改写:保留文首 M 字 + 固定分隔标记 + 文尾 K 字(默认 M=K=1500),中间删除;分隔标记形如 […trimmed…]。未超阈值则原样保留 |
| 更早:龄期 ≥ hard-clear 龄期 | hard-clear | 不论原文字多长,整段正文换成固定占位句(形如 [Tool result omitted — too old]),不保留原文任何片段 |
-
组装 ConversationRequest
填入 items、tools、model 参数、追踪用 header 字段、可选
json_schema等。
第 4、5 步与“历史真值”
第 4、5 步只改“本次发给模型的副本”,不改对话状态中的历史真值。
另有一条与第 5 步 hard-clear 同龄期、但作用对象不同的路径:在用户消息写入对话状态时,可对内存中的历史真值做 hard-clear(不写 soft-trim),目的是限制长 session 内存占用;磁盘上的 updates.jsonl 仍可保留原文供 replay。这两条路径不要和 compact(见后文)混为一谈。
token 估计:上次服务端返回的 total(或等价字段),加上此后新增内容的字节估算。用于 compact 阈值、界面 context 显示,以及是否进入第 4、5 步。
3. 提交到模型调用层
Session 把 ConversationRequest 交给模型调用层,由后者发 HTTP、处理流式/非流式响应,并回传统一的流式事件与最终结果(或错误)。
4. 流式过程
流式事件顺序(建立流 → delta → 完成/失败)以及正文与 reasoning 分 channel,与常见流式 chat 一致,不展开。
需要单独说明的是 Responses 路径上的 context 长度:若服务端在一次 HTTP 响应内执行托管 tool 循环,usage 里的累计 token 可能大于“当前上下文窗口占用”。流中若带有 context_details,客户端用它计算当前上下文长度,写入供 compact 阈值与 context 显示使用的 total 类字段;计费相关累计字段仍可按服务端累计值处理。
5. 写回对话状态
调用成功后,Session 将 ConversationResponse 写入 ChatState。
ConversationResponse 的 items 为有序列表,形态接近 Responses API 的 output:
- 零条或多条 Reasoning
- 零条或多条服务端已执行 tool 的记录(客户端不再执行,保留用于后续回放)
- 末尾通常为一条 Assistant(正文与客户端 function call)
写回之后,L3 按上一章规则分支:有客户端 tool call 则执行;无则尝试结束本段 L3。
API backend 与中间表示
模型调用层支持三种 backend:
| Backend | 路径 | 使用场景 |
|---|---|---|
| Chat Completions | /v1/chat/completions |
兼容;枚举默认值 |
| Responses | /v1/responses |
默认模型 grok-4.5 的配置 |
| Messages | Anthropic /v1/messages |
Anthropic 兼容后端 |
业务逻辑使用中间类型 ConversationRequest、ConversationResponse、ConversationItem。发送时按当前模型的 api_backend 映射为实际发往模型服务的 JSON。切换模型即切换配置(含 backend),不改变 L1/L2/L3 结构。
默认路径
内置模型表中,grok-4.5 的 api_backend 为 responses,context window 为 500000 量级,并配置 reasoning effort 选项。默认安装下的主路径为 Responses API,通常使用流式请求。
Backend 能力差异
| 能力 | Chat Completions | Responses | Messages |
|---|---|---|---|
| 原生 JSON Schema 与 tools 同时使用 | 支持 | 支持 | 不支持(Messages 上 schema 与 tools 冲突时改用 StructuredOutput 伪 tool) |
| 将 prompt_cache_key 写入请求 | 否 | 是 | 否 |
| 回放历史中的 reasoning / thinking 时的额外限制 | 无(按映射能力) | 无(按映射能力) | 在缺少 thinking 配置时可能需剥离 thinking 块 |
| 服务端托管 tool 的记录与回放 | 弱或无 | 支持 | 形态不同 |
是否使用原生 schema,由 backend 是否支持“schema 与 tools 同时使用”决定。
中间类型
业务层用 ConversationItem / ConversationRequest / ConversationResponse,由模型调用层映射为各 backend 的 JSON。system、user、assistant、tool result 及 request/response 常规字段不展开。
常规角色以外的 item:
| 类型 | 行为 |
|---|---|
| Reasoning | 独立条目,顺序与响应一致(可与 tool 记录交错)。回放保持该前缀顺序,供 prefix / KV cache。 |
| 服务端已执行的 tool 记录 | 响应内由推理服务执行;客户端只落盘并回放,不本地执行。 |
| 客户端 function call | 模型返回 call → 本地执行 → 写 tool result;L3 工具循环只处理此类。 |
模型调用层:请求、流、重试、取消
同一次提交里的多次 HTTP
Session / L3 向模型调用层提交一份 ConversationRequest,并等待一次交付结果(ConversationResponse 或错误)。
在这份请求尚未向 L3 交付之前,模型调用层内部可以多次发起 HTTP。对 L3 而言仍是同一次模型调用;ChatState 不会因这些内部 attempt 多写几轮 assistant。
内部再发 HTTP 只有两类原因,计数预算分开:
| 类型 | 原因 | 默认次数量级 | 退避 |
|---|---|---|---|
| 网络 / 瞬时失败重试 | 请求未正常完成,或结果瞬时无效 | 约 15(可配置) | 指数退避,有上限 |
| Doom-loop 重发 | HTTP 往往已成功,但生成内容被判定为空转 | 默认 2(可配置,0–5) | 接近 0 |
网络 / 瞬时失败重试
同一份请求再发一次 HTTP。触发条件如下。
会重试:
- 5xx、连接失败、流中断
- 空响应(含仅有 reasoning、无可见正文且无 tool call)
- 429(单独使用更低次数上限)
会重试,且可能先改请求再发:
- 413 或请求体过大:去掉 inline 图片后再试;连接被 reset 时也可能先去图再试
- 首次传输失败:可强制改用 HTTP/1.1 并重建客户端后再试
不重试(错误直接返回上层):
- 多数 4xx(400、401、403、404 等)
- 空闲超时(默认约 300 秒无新 chunk)
- 响应反序列化失败
- max tokens 截断(按既有策略结束,不当作可重试传输失败)
- 配置或凭证错误
服务端可返回“是否应重试”类 header;明确指示不重试时,客户端停止网络 / 瞬时失败重试。
Doom-loop 重发
Doom-loop:模型在同一次生成过程中进入无意义循环(反复产出同一段尾巴,或极低信息量输出)。由推理服务端在生成过程中检测,经 Responses 流报告客户端。名称来自实现与协议中的标签。
作用对象是单次 HTTP 响应内的生成文本(是否在通道内重复/退化),与 L3 动作停滞(连续相同 tool 名 + 参数)无关。
如何开启
- 主要路径:
/v1/responses流式请求。 - 客户端在请求上带 opt-in header:
x-grok-doom-loop-check。未配置恢复策略时不带该 header,服务端不按此协议报告,客户端也不做 doom-loop 重发。 - 配置来源:环境变量 / 本地配置 / 远端设置等解析为
DoomLoopRecoveryPolicy;解析结果为“关闭”时整条能力关闭。
服务端如何报告
触发标签字符串放在:
- 流中途的非标准 SSE 事件:
response.doom_loop_check(triggers为累计集合) - 结束时响应对象上的字段:
doom_loop_check.triggers
常见标签形态:
| 形态 | 含义 |
|---|---|
tail_repetition:{n}@{channel} |
在通道 channel 上检测到尾部重复;n 为检测阈值,数值越小表示重复越紧、证据越强 |
low_logprob@{channel} |
该通道生成熵很低(退化输出) |
| 其它 | 客户端记为未知类型,保留原文;不因解析失败打断整条流 |
channel 例如 thinking(思考)、response(可见输出)。示例:tail_repetition:4@thinking。
客户端何时真的丢弃并重发
并非所有触发都会重发。默认策略(可配置阈值)下:
| 信号 | 客户端行为 |
|---|---|
thinking 通道上的 tail_repetition,且 n ≤ 配置上限(默认 8) |
置信:整段本次响应丢弃,同一份 ConversationRequest 再发 HTTP(doom-loop 重发计数 +1) |
仅在 response 通道上的重复、low_logprob、更松的阈值、未知标签 |
仅记录 / 警告,不因该项做 doom-loop 重发 |
默认仅对 thinking 通道上满足阈值的 tail_repetition 自动重发;response 通道及其它标签默认不触发 doom-loop 重发。
doom-loop 重发次数用尽后:不再因 doom-loop 重发;接受当前响应,或按错误路径返回(与信号出现在流中途或结束时有关)。
与网络重试、与 L3 的边界
| 机制 | 层 | 条件 | 对 L3 的可见结果 |
|---|---|---|---|
| 网络 / 瞬时失败重试 | 模型调用层 | 传输失败或瞬时无效响应 | 成功则仍交付一次 Completed;耗尽则交付错误 |
| Doom-loop 重发 | 模型调用层 | 服务端报告且达到置信条件 | 中间 attempt 的生成不写进 ChatState;最终交付一次结果或错误 |
| 动作停滞 8 / 16 | L3 | 连续相同 tool 名 + 参数 | 注入 reminder 或结束 L3 |
| max_turns | L3 | 工具轮次达到上限 | 结束 L3 |
取消
每个模型调用请求关联取消 token。用户取消、Turn 被抢占、等待完成的 future 被 drop 时,模型调用任务停止当前 attempt。
结果如何交回
模型调用层对同一请求可同时使用两条交付路径:
-
流式事件
经共享 channel 按request_id推送,TUI / ACP 用这条路径做实时显示。 -
整次结果
提交时可附带一次性完成通知。在内部网络重试与 doom-loop 重发全部结束后,调用方收到完整的ConversationResponse或错误,不必自己从事件流里拼装。
主 turn 路径同时使用两条:TUI / ACP 消费流式事件;L3 在收到整次结果后写入 ChatState。
compact、会话摘要、/btw 等顺序逻辑使用“提交并等待整次结果”的接口;流式事件仍会推送到共享 channel。
Session 在调用失败后的处理
下列失败由 Session 处理。模型调用层返回错误,不修改 ChatState 历史策略。
上下文超限
错误表明超过 context window 时,Session 可执行 compact(改写内存对话历史),再让 L3 重新生成请求并调用模型。触发、与请求副本 tool 裁剪 / 内存 hard-clear / 请求体去图的差别、two-pass、compaction mode 见文末「compact 与会话减负」。
Compact
调用前 auto-compact、失败后超窗恢复 compact、用户 /compact,共用摘要替换内核。细节见文末「compact 与会话减负」。
401
模型调用层将 401 标为鉴权错误且不做网络 / 瞬时失败重试;是否刷新凭证、是否允许把当前 token 发往当前 base URL,以及 tool 侧 401 是否刷新后重试,由 Session / 鉴权模块决定。
encrypted_content 无法解密
Responses 路径会把加密 reasoning 写入历史并在后续请求回传;换模型族或服务端无法解密时返回 400,Session 视为会话与当前模型不兼容并提示新建 session,不重试。
调用前提前执行的步骤
提交模型调用前,Turn 还可能刷新将过期的凭证,以及按阈值触发 auto-compact(含可选的 two-pass Pass 1)。
结构化输出
调用方可为单次 Prompt 指定 JSON Schema(例如 ACP meta、headless 参数)。普通 TUI 对话通常不指定。
处理方式:
存在合法 schema
├─ backend 支持 schema 与 tools 同时使用
│ → 使用原生 structured output 字段
└─ 否则
→ 向 tools 增加 StructuredOutput 伪 tool
要求模型在结束时调用该 tool 提交结果
校验失败时可有限次重试
json_schema 属于单次请求。原生路径下,模型仍可先调用普通 tool,再给出符合 schema 的最终输出(具体语义依赖 backend)。伪 tool 路径将结构化提交放进 L3 的 tool 循环。
实际发往模型服务的 JSON 形态(概要)
Responses(grok-4.5 默认)
这里的“扁平”指:请求和响应的内容是一维有序列表,每一项是带类型的 item;reasoning、function_call、message 等并列排在同一列表里。不是 message 套 content / tool_calls 再套一层的树形结构。
Chat Completions 常见形态(嵌套):
messages: [
{ role: "assistant", content: "...", tool_calls: [ {...}, {...} ] },
{ role: "tool", tool_call_id: "...", content: "..." },
]
一层是 message;tool call 挂在 assistant 下面;tool 结果再另成 role=tool 的 message。
Responses(字段名以实际 API 为准):
input: [
{ type: "message", role: "user", content: [...] },
{ type: "function_call", ... },
{ type: "function_call_output", ... },
{ type: "reasoning", ... },
...
]
顶层就是 item 数组。output 同理:reasoning、message、function_call、服务端 tool 记录等,也是同一条列表里的多个 item。
“扁平”不表示没有类型、全是纯字符串,也不表示没有多轮历史。只表示历史和输出在协议上展成 item 序列。
其余要点:
- 可带原生 JSON Schema 相关字段
- 可带 prompt_cache_key
- 可声明托管 tool
- 流式事件中可能包含非标准 doom-loop 事件;客户端在 typed 反序列化前拦截
Chat Completions
messages+tools(结构如上节嵌套示例)- 流式 delta 合并为 assistant
- 映射时将内部 reasoning、服务端 tool 记录转换为该 API 可接受的形式
Messages
- system 与 messages 分离;含 thinking 与 signature 等
- 流开始时可提供 message id 与输入 token 分桶
- 原生 schema 与 tools 不能按 Chat/Responses 方式并用时,结构化输出走伪 tool
写回内部历史时保留顺序与可回放字段。映射丢失的是目标 API 无法表达的信息。
模型调用小结
- 对话状态从历史生成请求。模型调用层发 HTTP、收 stream 流、按规则重试。Session 把结果写回历史,并在失败或需要压缩时决定是否再提交。
- 默认模型 grok-4.5 使用 Responses API。Chat Completions 与 Messages 共用同一套中间类型,只是映射到不同协议。
- 内部历史是有序条目列表。reasoning 与服务端已执行的 tool 记录单独成条,用于回放,也用于和服务端缓存对齐。
- 网络失败、空响应、限流、请求体过大,由模型调用层分类处理。生成被判定为空转时,走 doom-loop,单独计数,可能整段重发。鉴权失败、上下文超限后的 compact、换模型后 encrypted_content 无法解密,由 Session 处理。
- 一次模型调用结束,只表示这一次 I/O 做完。本 turn 是否继续,由上一章的 L3 与 L2 规则决定。
作者的邮箱:tokamak9000@163.com。如有问题,欢迎讨论

浙公网安备 33010602011771号