Grok 多轮上下文怎么接

2026-11-04 17:43 更新

多轮对话的麻烦不在「把上一句带上」,而在「带什么、放在哪、什么绝不能落日志」。很多团队第一次接多轮时,把整段历史无脑塞进请求体,结果账单随轮次线性膨胀;或者更隐蔽,把模型返回的推理内容写进了应用日志,埋下合规隐患。这一节把多轮状态管理做成可离线测的代码,并明确 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)。
  • 价格、额度与区域可用性以控制台当前页面为准。

七、动手试

  1. 给 Session 加一个 max_turns 参数:当本地 history 超过该轮数时,只保留最近 N 轮并提示「更早的上下文已被截断」。想一想:截断会丢失哪些信息,对内部文档问答这类任务影响大吗?
  2. 把 safe_log() 扩成还能剔除 output 里你不希望落盘的内部字段(例如工具调用的参数),写一条含工具返回的真实响应样本验证过滤生效。
  3. 设计一个实验:用一组 10 轮的对话,对比「每轮全量带历史」与「第 5 轮起用 Context Compaction」的估算成本差异。先写出你要对比的两个 input 形态,再决定能不能离线算出结论。
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号