Gemini API 多轮对话怎么带历史

2026-11-06 16:17 更新

Gemini API 多轮有两种方式:Stateful 传 previous_interaction_id 由服务端管历史,Stateless 每轮回传全部轮次。(核验 2026-11-05,F74)

一、Gemini API 多轮对话怎么带历史:问题不是怎么拼,而是谁负责保管

第一轮问「我家有两只狗」,第二轮问「那我家有几只爪子」,模型答「四条腿,十六条」。这时候你才意识到:它根本没看到第一轮。

这个 bug 几乎每个接入 Gemini 的人都遇到过一次,原因也很简单——REST 请求本身是无状态的。你不把第一轮带上,服务端就当你从没说过。

官方给了两条路,措辞很明确(F74):

  • Stateful(官方标注 recommended):第二轮传上一轮的 previous_interaction_id,服务端替你管全部历史,还能优化跨轮缓存。
  • Stateless:客户端自己保管,每轮回传所有轮次——注意,官方特别写了要包含「中间模型思考与工具步骤」,不是只回传你看到的那两句话。

选择哪条,取决于你要不要「服务端记得」。要,就用 id;不要(比如多租户隔离、合规要求不落服务端状态),就自己带全量。

二、两种 Gemini API 多轮请求的差别:最小可运行示例

把同一段两轮对话用两种方式各拼一次请求,差别只有一行。

## EXPECT: 第 2 轮 stateful 请求=input+previous_interaction_id | stateless 请求=input+history | 差别只在谁保管历史
TURNS = ["我家有 2 只狗", "那我家有几只爪子"]

def build(mode, turn_index, prev_id=None):
    req = {"model": "gemini-3.8-flash", "input": TURNS[turn_index]}
    if mode == "stateful":
        req["previous_interaction_id"] = prev_id
    else:
        # 示意结构:这里用字符串列表表达「客户端带着前几轮」这件事,
        # 真实字段名与嵌套以官方 Stateless 示例为准,本节不臆造(工具步骤的完整结构见第 6 章)。
        req["history"] = TURNS[:turn_index]
    return req

s = build("stateful", 1, "int_1")
t = build("stateless", 1)
assert "previous_interaction_id" in s and "history" not in s
assert t["history"] == ["我家有 2 只狗"]
sk = "+".join(k for k in s if k != "model")
tk = "+".join(k for k in t if k != "model")
print(f"第 2 轮 stateful 请求={sk} | stateless 请求={tk} | 差别只在谁保管历史")

build 里的 mode 就是那个决策点。注意上面 req["history"] 里放的是示意结构——一串字符串,只表达「客户端把前几轮带上了」,不是可直接发送的真实字段结构。真实调用时,stateful 那条只要把 "int_1" 换成上一轮返回的 interaction.id;stateless 那条要把历史拼成官方要求的结构,不是简单塞一串字符串。本节不给那段结构,是因为官方示例里的字段嵌套要在工具调用那一轮才完整暴露,见第 6 章。

三、解释与边界:三件必须知道的事

第一件,服务端状态可以关。 默认是开着的(store=true),也可以显式关掉(store=false)(F62)。关掉之后行为就退回 stateless:历史得你自己带。所以「我用了 Interactions API,所以不用管历史」这个推论只在默认配置下成立。

第二件,stateless 不等于只传对话文本。 官方原文要求回传「all previous turns (including intermediate model thought and tool steps)」(F74)。工具调用那几轮,steps 里还有模型的思考记录,漏掉它们会让后续轮次对不上。这一点在第 6 章做函数调用时会再遇到。

第三件,两种模式不要混用。 同一段会话里,第一轮用 stateful、第二轮改 stateless,模型会看到两份不同来源的历史,行为不可预期。切换只能发生在会话边界。

边界。 本节不展开长会话的截断策略——上下文预算与历史压缩见 9.3;也不讨论服务端状态的保留期限,官方没有给出明确时长,属待核,正文不写死。

四、预期输出与异常分支

真跑输出:

第 2 轮 stateful 请求=input+previous_interaction_id | stateless 请求=input+history | 差别只在谁保管历史

四类分支。第二轮答非所问:九成是历史没带上,先打印请求体确认。带了历史但答得更差:检查是不是只带了文本、漏了 thought 与 tool steps。previous_interaction_id 传了过期 id:会报错,但错误信息不一定指向历史,注意看是不是 404(F69)。切了 store=false 却仍传 id:行为取决于服务端如何处理空历史,这在官方文档里没写清,属待核,正文不建议这么用。

五、判据卡:该用会话还是自己拼 history

该这么做 不该这么做
默认用 previous_interaction_id,让服务端管 每轮手工拼历史却说自己在用新接口
需要隔离或不落服务端状态时才用 stateless 关掉 store 后忘了自己带历史
stateless 回传含思考与工具步骤的完整轮次 只回传用户可见的问答文本
一段会话内只用一种模式 中途切换,产生两份历史

六、来源与核验

  • https://aistudio.google.com/docs/get-started.md(核验 2026-11-05,F74)
  • https://aistudio.google.com/docs/migrate-to-interactions.md(核验 2026-11-05,F62)
  • https://aistudio.google.com/docs/api-errors.md(核验 2026-11-05,F69)
  • https://aistudio.google.com/docs/get-started.md(核验 2026-11-05,F77,待核)

服务端状态的保留时长官方未说明,登记为待核(F77);正文因此只写「默认开启、可关闭」,不写「保留多久」,也不写「过期会自动续」。

七、动手试

  1. 把 build 的 mode 全改成 "stateless",重跑。预期两次请求都带 history;验收标准是你能说出「第 1 轮时 history 是什么」——提示:空列表。
  2. 给 build 加一个 store 参数,当它为 False 时强制走 stateless 并抛一句提示。验收标准:输出里能看到「已关闭服务端状态,改为客户端带历史」。
  3. 在你自己的代码里找到发请求的那一处,打印完整请求体。验收标准:能看到历史以哪种形式存在;看不到历史,就说明第二轮一定是断的。
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号