Grok 异步请求和延迟补全有什么不同:两种模式怎么选
数据截至 2026-08,价格与限额以各官网为准。本文只讲计费与接入机制,不列具体价格数字。
一句话说清:Grok 的「异步请求」是客户端层面的并发技巧,你打的还是普通实时补全接口,只是同时打了很多个;「延迟补全」是服务端层面的两段式接口,提交时拿一个 request_id,结果稍后去另一个端点取。前者受你账号的实时限流直接约束,官方文档明确写了并发数不能超过 API 控制台里显示的速率上限;后者官方文档写明限流与普通 chat completions 相同,但结果只能被成功取回一次,过期即丢弃。如果你的场景既不要求实时、量又很大,官方文档在异步那一页专门给了指路:去用 Batch API,那是第三条路,和前两条不是一回事。
先把三个容易混的词分开
xAI 官方文档把这几件事分在了不同页面,混着看很容易串味:
/developers/advanced-api-usage/async,标题是 Asynchronous Requests,讲的是你自己的代码怎么并发发请求。/developers/advanced-api-usage/deferred-chat-completions,标题是 Deferred Chat Completions,讲的是一个真正的两段式接口。/developers/advanced-api-usage/batch-api,讲的是排队式的批处理。
它们解决的问题不同。异步解决的是「顺序发请求太慢」,官方文档开篇就说,当你要处理成百上千个请求时,一个个顺序发会非常耗时。延迟补全解决的是「一次调用等太久、连接挂不住」,你把请求丢过去就断开,晚点再来取。Batch 解决的是「量特别大、还想省钱」。
我见过不少人把 deferred 理解成「异步的官方版本」,这理解会带来实际的架构错误——因为这两条路对连接、对重试、对结果保存期限的要求完全不一样。
异步:并发的是你的客户端,不是接口
官方文档给的做法很直白:用 xai_sdk 里的 AsyncClient,或者用 openai 库里的 AsyncOpenAI,把多个请求并发发出去。
两份官方示例的骨架是一致的:建一个 asyncio.Semaphore 信号量限制在途请求数,把每个请求包成一个协程,最后用 asyncio.gather 一起收。示例里用来控制并发上限的那个参数叫 max_concurrent,官方文档专门有一小节讲它,标题就是 Rate Limits——调这个参数就是在调最大并行请求数。
这里有一句非常关键、但很多人跳过去了的话:官方文档明写,你无法把并发跑到超过 API 控制台里显示的速率上限之外。也就是说 max_concurrent 调大不会让你突破限流,只会让你更快撞上限流。这条决定了异步模式的天花板在哪儿——它是把你已有的配额用满,不是给你更多配额。
另一个细节值得抄走:官方两份示例在创建客户端时都显式覆盖了默认超时,并在注释里说明是为了适配推理模型(推理模型要跑更久)。这是个很实在的提醒——你用异步跑推理模型时,卡住的往往不是模型,是你客户端库自带的那个默认超时。至于默认值具体是多少、当前版本改没改,以官方文档当前版本为准。
关于并发数该怎么定,可以配合站内的并发规划与配额匹配一起看,思路是跨厂商通用的。
延迟补全:提交和取回是两个端点
延迟补全的形态和异步完全不同。按官方文档:
- 往
https://api.x.ai/v1/chat/completions发 POST,payload 里加一个字段deferred,置为 true。 - 这次响应的 body 不是补全结果,而是
{"request_id": "..."}。 - 拿这个 request_id 去 GET
https://api.x.ai/v1/chat/deferred-completion/{request_id}。
也就是说,提交走的还是熟悉的 chat completions 端点,只是多了一个开关;取结果走的是另一个独立路径。这个设计有点反直觉——你可能以为会有一个 /v1/deferred 之类的提交端点,实际没有。
官方文档在这一页顶部有一条限制说明必须看清楚:延迟补全目前只能通过 REST 请求或 xAI SDK 使用。换句话说,如果你整套代码是架在 OpenAI 兼容 SDK 上的,异步那条路可以直接复用(官方示例就是用 AsyncOpenAI 写的),但延迟补全这条路走不通,得单独写 REST 调用或者换 SDK。这是选型时的硬约束,不是风格偏好。
xAI SDK 里有更省事的封装:chat.defer(),可以传 timeout 和 interval 两个参数,由 SDK 自己按间隔轮询到结果为止。用 requests 裸写的话,官方示例是配 tenacity 的指数退避重试来轮询的。
202 和 200:轮询时怎么判断「还没好」
这是延迟补全最该记住的一条机制:**结果没准备好时,GET 请求返回 202 Accepted,并且响应体是空的。**准备好了才是 200,带完整 body。
所以轮询逻辑要写成三分支,官方示例就是这么写的:
- 200 → 解析 JSON 返回
- 202 → 抛出「还没好」,交给退避重试继续等
- 其他 → 当成真错误抛出,带上状态码和响应文本
很多人的通用 HTTP 封装会把 2xx 一律当成功,然后去 response.json() 解析一个空 body,直接抛 JSON 解析异常——排查半天以为是接口坏了。202 在这里不是异常,是「稍等」。这类状态码语义踩坑的通用处理思路,可以参考限流与重试的通用处理那篇,退避策略是同一套。
还有一条限制别忽略:官方文档写明,结果只能被成功取回一次,并且只在一段有效期内可取,超期就会被丢弃,具体时长以官方文档为准。这意味着取回那一步不能像普通接口那样随手重试——你的重试逻辑必须区分「HTTP 层没拿到」和「已经拿到但业务处理失败」。后者再去 GET 一次很可能什么都拿不到。稳妥做法是拿到 200 之后立刻落库原始 JSON,再去做后续解析。
返回体和用量字段:延迟补全没有特殊待遇
官方文档明确说,延迟补全的响应体和非延迟补全是一样的。示例 JSON 里能看到常规的 choices、finish_reason、usage 结构,usage 里包含 prompt_tokens_details(其中有 cached_tokens)和 completion_tokens_details(其中有 reasoning_tokens),另外还有 num_sources_used。
这一点在做成本核算时很有用:你不需要为延迟补全单独写一套用量解析,缓存命中和推理 token 的统计口径与实时调用一致,原有的账单归集逻辑可以直接复用。
官方文档另有一条提示:可以通过 message.reasoning_content 拿到模型的原始思考轨迹。这是文档里明写的字段,不是推断。
限流与计费上,两者的位置不同
- 异步:你打的就是普通实时接口,配额、限流、计价全部按标准实时调用走,没有任何折扣通道。它省的是墙上时间,不是钱。
- 延迟补全:官方文档在提示框里写得很清楚,延迟补全的速率限制与你的 chat completions 速率限制相同,具体数值要去 xAI 控制台看。所以它也不是省配额的手段,它省的是长连接。
- Batch API:这才是官方明说会按低于标准价计费的通道(具体比例见官方定价页),而且官方文档写明批量请求不计入速率限制。代价是完成时间是尽力而为、不做保证,且并非所有模型都支持——官方文档专门提醒,不支持的模型会直接拒绝批量请求,要去各自的模型页看 Details。
把这三行并排看,选择就清楚了。要交互体验、要尽快跑完一批评测,用异步;单次任务很重、不想让连接一直挂着、又要求今天就要结果,用延迟补全;离线批量、能等、想控成本,用 Batch。缓存和批量这两条省钱路径怎么搭配,站内有缓存与批量的取舍一篇专门讲。
最容易栽的三个坑
**第一,把 max_concurrent 当油门踩。**它约束的是你自己在途的请求数,撞上账号限流之后再调大只会制造更多失败请求。真正该做的是先去控制台确认限流档位,再反推并发数。
**第二,用 OpenAI 兼容 SDK 去调延迟补全。**官方文档写明这个能力只走 REST 或 xAI SDK,兼容层这条路没有。如果你的接入层是围绕 OpenAI 兼容端点抽象的,接延迟补全就得开一个专门分支——这件事最好在架构评审阶段就定下来,别等到写完才发现。Grok 侧的基础接入形态可以先看Grok API 接入那篇。
**第三,把取回当成幂等操作。**结果只能成功取回一次这件事,会在你加了一层通用重试中间件之后悄悄咬人:中间件在下游解析失败时自动重放整个流程,第二次 GET 拿不到东西,日志里只剩一条语焉不详的失败记录。落库先行,解析在后,这个顺序在延迟补全里是硬要求。
最后提醒一句:以上机制描述全部来自 xAI 官方文档对应页面的原文,我们没有跑过这些接口。字段名、端点路径和状态码语义可能随版本调整,接入前请以官方文档当前版本为准,限流档位以你自己控制台里显示的为准。