Grok 多轮上下文怎么接
多轮对话的麻烦不在「把上一句带上」,而在「带什么、放在哪、什么绝不能落日志」。很多团队第一次接多轮时,把整段历史无脑塞进请求体,结果账单随轮次线性膨胀;或者更隐蔽,把模型返回的推理内容写进了应用日志,埋下合规隐患。这一节把多轮状态管理做成可离线测的代码,并明确 reasoning.encrypted_content 的处理边界。
官方在 grok-4.7 上有一个强约束:Responses API 总返回 reasoning.encrypted_content,即使 include 没有列出它(字段事实以官方 release-notes 为准;解析层配对心智见 2.1)。这条字段是加密的、不可读的,且明显不属于业务数据——但它就在响应里,处理不当就会进日志。
一、问题不是「历史放哪」,而是「状态归谁管」
多轮上下文有两条接法,区别在于历史由谁保存:
- stateless(无状态):平台不替你存历史,每一轮请求都要自己把前面所有轮次拼进
input。好处是状态完全可控,坏处是请求体随轮次变长。 - stateful(有状态):平台替你存会话状态,你每轮只发本轮输入。坏处是状态在平台侧,且某些数据保护模式下不可用(例如启用零数据留存 ZDR 后,stateful Responses 等能力会被禁用,核实见官方安全 FAQ)。
选哪条不靠直觉,靠你的合规与可观测性要求。下面用一个 mock 会话类把两条路线都跑通。
二、最小可运行示例:会话状态管理
下面的脚本不联网、不需要真实密钥。它同时演示 stateless 与 stateful 两种 input 构造,并保证加密推理内容绝不进日志。
"""多轮会话状态管理(mock):决定每轮发什么、什么不能落日志。"""
import json
REASONING_KEY = "reasoning.encrypted_content" # grok-4.7 在 Responses API 总返回
class Session:
def __init__(self, model: str = "grok-4.7", stateful: bool = False):
self.model = model
self.stateful = stateful
self.history = [] # stateless 模式需要本地保存
def ask(self, user_text: str) -> dict:
"""构造下一轮请求体。stateful 时交给平台存历史,本地只带本轮输入;
stateless 时把历史拼进 input。"""
if self.stateful:
# stateful 的会话衔接参数(如上一轮响应 ID)以官方页为准,本节不臆测字段名
body = {"model": self.model, "input": user_text}
else:
self.history.append({"role": "user", "content": user_text})
body = {"model": self.model, "input": list(self.history)}
return body
def safe_log(self, response: dict) -> str:
"""把响应里能落日志的部分抽出来;加密推理内容绝不写盘。"""
cleaned = {k: v for k, v in response.items() if k != REASONING_KEY}
return json.dumps(cleaned, ensure_ascii=False)
def offline_check() -> None:
# stateless:本地维护历史
s = Session(stateful=False)
b1 = s.ask("什么是长上下文?")
b2 = s.ask("那 200k 阈值又是什么?")
assert b1["input"] == [{"role": "user", "content": "什么是长上下文?"}]
assert len(b2["input"]) == 2, "第二轮应带上第一轮历史"
# 加密推理内容必须被安全日志过滤
resp = {"output_text": "…", REASONING_KEY: "opaque-blob"}
logged = s.safe_log(resp)
assert REASONING_KEY not in logged, "加密推理内容不能落日志"
assert "output_text" in logged
print("离线自检通过")
print("第二轮 input 长度 =", len(b2["input"]))
print("安全日志 =", logged)
if __name__ == "__main__":
offline_check()
真正上线时,ask() 里换成 SDK 调用即可;safe_log() 要作为日志前的强制关卡,任何响应在写盘前都过一遍。
三、两种接法怎么选
| 维度 | stateless(本地存历史) | stateful(平台存历史) |
|---|---|---|
| 历史维护 | 你自己拼 input |
平台维护,每轮只发本轮 |
| 请求体长度 | 随轮次增长 | 每轮稳定 |
| 可观测性 | 完全可控,能精确审计 | 状态在平台侧 |
| 数据保护限制 | 无额外限制 | 启用 ZDR 后 stateful 等能力不可用 |
| 长对话成本 | 主动调 Context Compaction,每 N 轮压一次 | 等报超限再救火,那时已经压不动 |
关于长对话成本,官方已有 Context Compaction,但它不是一个「自动生效的开关」,得你显式调用。把握三点就能用对:
- 调用形状:REST 是
POST /v1/responses/compact;OpenAI SDK 兼容层是client.responses.compact(...);xAI SDK 是chat.compact_context(...),另有chat.compact()直接原地替换会话里的消息。 - 返回值怎么用:响应里只有一个 compaction item(
type为compaction,正文是encrypted_content)。把它原样放进下一轮input的第一项,新用户轮追加在它后面。不要解析、不要改写、不要重排——它是不可读的加密封装,拆开就等于把上下文链断掉。 - 什么情况下用不了:会话本身必须已经装得下。压缩是把长对话变短,救不了已经超限的请求;一旦已经报
context_length_exceeded,得先裁剪或拆分再谈压缩。
还有两个容易漏掉的成本细节:压实这次调用本身也花 token(响应给出 usage.input_tokens / output_tokens,以及折叠了多少条消息的 dropped_message_count);一次请求只做一遍压实,但压过的会话之后可以再压。工程上常见的节奏是在 agent loop 里每 N 轮压一次,而不是等超限了才救火。
对 stateless 路线来说这是降本主力,但语义保真度要自己验:压完之后模型是否还记得你定的约束,只能靠回归用例回答,官方页不会替你保证。
四、预期输出与异常分支
运行离线会话类,预期输出:
离线自检通过
第二轮 input 长度 = 2
安全日志 = {"output_text": "…"}
换成真实调用后,返回结构里会多出 reasoning.encrypted_content。若出现异常,按下面顺序定位:
| 现象 | 优先怀疑 | 第一步动作 |
|---|---|---|
| 第二轮答非所问 | stateless 漏带历史 | 打印 input 长度,确认含前轮 |
| 日志里出现加密 blob | safe_log 没过 |
在写盘前加一道过滤关卡 |
| stateful 调用报错 | 数据保护模式禁用 | 核对是否启用 ZDR,改走 stateless |
| 长对话账单暴涨 | 历史无限累积 | 上 Context Compaction 或截断 |
最后一行尤其重要:reasoning.encrypted_content 不是业务字段,却最容易因为「整段 json.dumps(response) 写日志」而泄漏。把它在日志关卡里显式剔除,比事后审计便宜得多。
五、判据卡
| 判断项 | 该这么做 | 不该这么做 |
|---|---|---|
| 选接法 | 要审计/合规选 stateless | 为省事默认 stateful 却不管 ZDR |
| 历史维护 | stateless 本地拼 input |
漏带前轮导致答非所问 |
| 日志处理 | 写盘前剔除加密推理内容 | 整段响应 json.dumps 落盘 |
| 长对话成本 | stateless 上 Context Compaction | 让历史无限累积 |
| 出错排查 | 先打印 input 长度定位 |
先改模型或重发 |
这张卡的重点是先定状态归谁管。四类现象里「答非所问」和「账单暴涨」常常同源:stateless 路线下历史维护错了,表现一个是质量一个是成本,但修法都在 input 构造上。
六、来源与核验
- 官方 release-notes:
https://docs.x.ai/developers/release-notes(核验日期 2026-11-04)。grok-4.7在 Responses API 总返回reasoning.encrypted_content(F9),以及 Context Compaction API 已推出(F30),均以该页为准。 - 官方安全 FAQ:
https://docs.x.ai/developers/faq/security。零数据留存 ZDR 下 stateful Responses 等能力不可用的口径以该页为准(F24)。 - 官方 Context Compaction 页:
https://docs.x.ai/developers/advanced-api-usage/context-compaction(核验日期 2026-11-04)。端点、单条 compaction item 结构、encrypted_content必须原样回传、会话须先装得下、每次调用只压一遍、可重复压实,均以该页为准(F50–F53)。 - 价格、额度与区域可用性以控制台当前页面为准。
七、动手试
- 给
Session加一个max_turns参数:当本地history超过该轮数时,只保留最近 N 轮并提示「更早的上下文已被截断」。想一想:截断会丢失哪些信息,对内部文档问答这类任务影响大吗? - 把
safe_log()扩成还能剔除output里你不希望落盘的内部字段(例如工具调用的参数),写一条含工具返回的真实响应样本验证过滤生效。 - 设计一个实验:用一组 10 轮的对话,对比「每轮全量带历史」与「第 5 轮起用 Context Compaction」的估算成本差异。先写出你要对比的两个
input形态,再决定能不能离线算出结论。

TRAE-AI编程


更多建议: