代码语言

知识点思维导图

21 个知识节点

Prompt Engineering(16) - 多轮对话与上下文管理

读完后,你应能完成以下任务:

  • 绘制“Prompt Engineering(16) - 多轮对话与上下文管理 / history 是你手动维护的列表”的关键对象与数据流,解释“先明确:所谓「对话历史」不是模型存的,是你的后端存的。”,并用源码位置、日志或 Trace 标注证据。
  • 为“Prompt Engineering(16) - 多轮对话与上下文管理 / 两个控制手段:截断和摘要”设计正常与异常输入,验证“代价是:被扔掉的早期信息模型彻底失忆。”,输出首个偏差位置与回归测试结果。
  • 实现“Prompt Engineering(16) - 多轮对话与上下文管理 / 摘要:把旧历史压成一句话”的最小代码或配置,检验“更聪明的做法:旧历史不整段扔,而是压缩成一句摘要塞进 system,关键信息用少量 token 保住。”,输出命令、结果与 Diff,并说明不适用边界。

一、多轮对话与上下文管理的真实应用场景

用户和你的客服助手聊了 20 轮。第 21 轮他问「那这个能报销吗?」——「这个」指的是第 3 轮提到的出差住宿。

模型要答对,就得知道前面聊了啥。但 大模型基础(07) - 大模型 API 基础 已经说明:模型没有持久会话记忆,每轮都得由应用重新发送需要的历史。于是问题来了:20 轮历史全发,token 爆了,而且越发越贵越慢;只发最近 2 轮,模型又忘了「这个」是啥。

上下文管理就是在这两难之间找平衡:发得够多让对话连贯,发得够少不撑爆预算。这跟前端管理 store 很像——状态要有边界、能清理、不能无限膨胀。

二、history 是你手动维护的列表

先明确:所谓「对话历史」不是模型存的,是你的后端存的。每轮你都要干这三件事:

1. 把用户这句 append 进 history(role: user)
2. 把 system + history 一起发给模型
3. 把模型回复 append 进 history(role: assistant)

只要不管理,history 就会无限长。第 N 轮发给模型的 token ≈ 前 N-1 轮所有内容之和。这是个累加曲线,长会话必然撑爆。

三、两个控制手段:截断和摘要

3.1 截断:只保留最近 N 轮

最简单的策略:超过预算,就把最早的几轮直接扔掉,只留最近 N 条。

代价是:被扔掉的早期信息模型彻底失忆。如果用户突然回头引用很早的内容,就接不上了。所以纯截断适合「话题不怎么回溯」的场景。

3.2 摘要:把旧历史压成一句话

更聪明的做法:旧历史不整段扔,而是压缩成一句摘要塞进 system,关键信息用少量 token 保住。

发给模型的 = system + 摘要(旧历史) + 最近 N 轮原文

比如前 10 轮压成「用户先后咨询了报销流程、所需材料、电子发票、审批时长」,只占十几个 token,但模型还知道聊过这些。摘要本身一般是再调一次模型生成的(「请把以下对话概括成一句话」)。

策略 优点 代价 适用
截断 简单、零额外成本 早期信息全丢 短会话、话题不回溯
摘要 保住关键信息、省 token 多一次模型调用、摘要可能丢细节 长会话、需要回溯

实战常组合用:最近几轮保原文(细节不丢),更早的压成摘要(省 token),system 永远保留(规则不能丢)。

四、工程上真正会踩的坑

  • 会话 id 不稳定 / 不隔离。多用户共用一个 history,张三的对话串到李四那里,既是 bug 也是隐私事故。每个会话独立存,按 sessionId 隔离。
  • system 也被当历史截断掉。截断时只能裁 user/assistant,system 规则必须永远保留,否则模型连「自己是谁」都忘了。
  • 摘要丢了关键事实。摘要是有损压缩,可能把「用户说他是 VIP」这种关键信息丢掉。重要事实(用户身份、确认过的金额)应单独存成结构化字段,别只靠摘要兜。
  • 敏感信息长期留在 history。手机号、身份证这类不该长期存。落库前脱敏,或对长期记忆设过期和删除能力。

五、一句话面试答法

多轮对话的上下文怎么管理? 模型本身无记忆,历史是后端维护的列表,每轮把 system 加历史重发。不能无限发,会爆 token 上限也费钱,所以要做上下文管理:最近几轮保留原文保证细节,更早的历史调模型压成摘要塞进 system 省 token,system 规则永远保留。会话按 sessionId 严格隔离,敏感信息落库前脱敏。本质和前端管理 store 一样——状态要有边界、可清理、可复现。

六、动手实践:14 多轮对话与上下文管理

模拟一段 6 轮对话,演示 history 怎么一直累积,以及超过 token 预算后怎么把旧历史压成摘要、只保留最近几轮原文,让对话连贯又不撑爆上下文。

6.1 在线运行

零依赖,纯标准库。

6.2 预期输出

--- 第 1 轮 ---
用户:公司的报销流程是什么?
完整历史条数:2,本轮实际发给模型:2 条 / 约 20 token
模型:报销需在 30 天内提交。

--- 第 2 轮 ---
用户:需要哪些材料?
完整历史条数:4,本轮实际发给模型:4 条 / 约 37 token
模型:好的,请继续说。

--- 第 3 轮 ---
用户:电子发票可以吗?
完整历史条数:6,本轮实际发给模型:6 条 / 约 52 token
模型:好的,请继续说。

--- 第 4 轮 ---
用户:审批一般要多久?
完整历史条数:8,本轮实际发给模型:8 条 / 约 67 token
模型:刚说过了,是 30 天内。

--- 第 5 轮 ---
用户:出差住宿能报销吗?
完整历史条数:10,本轮实际发给模型:5 条 / 约 42 token
已摘要旧历史:(早前对话摘要)用户先后问过:公司的报销流程是什么?;需要哪些材料?;电子发票可以吗?
模型:报销需在 30 天内提交。

--- 第 6 轮 ---
用户:报销要几天内提交?
完整历史条数:12,本轮实际发给模型:5 条 / 约 46 token
已摘要旧历史:(早前对话摘要)用户先后问过:公司的报销流程是什么?;需要哪些材料?;电子发票可以吗?;审批一般要多久?
模型:报销需在 30 天内提交。

结论:历史一直累积,但发给模型的上下文被控制住了——旧的压成摘要,近的保原文。

盯着第 4 轮和第 5 轮的对比:完整历史从 8 条涨到 10 条,但「实际发给模型」从 8 条(67 token)反而降到 5 条(42 token)。因为第 5 轮超了 80token 预算,旧历史被压成了一句摘要。

6.3 代码↔概念对应

概念 在 main.py 哪里
完整历史累积 Conversation.history + add
token 预算估算 estimate_tokens
超预算时裁剪:保最近 N 条 build_messageskeep_recent
旧历史压缩成摘要 _summarize + summary
每轮实际发给模型的上下文 build_messages 返回值

6.4 动手改

  • max_tokens 从 80 调大到 200,会发现 6 轮都不触发摘要——预算够就全量发。
  • keep_recent 从 4 调成 2,摘要会更早触发、保留的原文更少,体会「记得多少」和「省多少 token」的取舍。
  • _summarize 现在是规则抽关键词,真实项目换成调模型「把这段对话概括成一句话」,摘要质量会高很多。

6.5 可运行源码:多轮对话与上下文管理

main.py

七、总结

  • history 是你手动维护的列表:先明确:所谓「对话历史」不是模型存的,是你的后端存的。
  • 两个控制手段:截断和摘要:代价是:被扔掉的早期信息模型彻底失忆。
  • 工程上真正会踩的坑:多用户共用一个 history,张三的对话串到李四那里,既是 bug 也是隐私事故。
  • 一句话面试答法:模型本身无记忆,历史是后端维护的列表,每轮把 system 加历史重发。
  • 摘要:把旧历史压成一句话:更聪明的做法:旧历史不整段扔,而是压缩成一句摘要塞进 system,关键信息用少量 token 保住。

学完自测

选择所有正确答案;提交后逐项核对判断依据。

1在“多轮对话与上下文管理”中,需要同时满足“多轮对话与上下文管理的真实应用场景”与“history 是你手动维护的列表”。给定正文约束“模型没有持久会话记忆,每轮都得由应用重新发送需要的历史。”,哪些判断保持了原有处理机制?多选
2“多轮对话与上下文管理”出现偏差:“在“多轮对话与上下文管理 / 截断:只保留最近 N 轮”中,即使不满足“所以纯截断适合「话题不怎么回溯」的场景”,结果与副作用仍会保持不变。”已成为实际行为。围绕“截断:只保留最近 N 轮”与“摘要:把旧历史压成一句话”,哪些判断能定位被改变的职责或边界?多选
3评审“多轮对话与上下文管理”方案时,验收条件包含“多轮对话的上下文怎么管理? 模型本身无记忆,历史是后端维护的列表,每轮把 system 加历史重发。”。关于“一句话面试答法”与“动手实践:14 多轮对话与上下文管理”的哪些决策符合正文机制?多选