用 Java 接入大模型(五):多轮对话的上下文怎么管

简介: 本文深入剖析多轮对话的核心误区:大模型本身无记忆,所谓“遗忘”实为上下文管理失当。从messages结构化封装、token精准截断策略,到Redis会话存储与三步排错法,系统讲解如何可靠构建长程对话能力。

客户端封装好了,接下来要往里面装业务。多轮对话是最典型的场景,也是最容易做错的一个。很多人做出第一版之后会遇到同一个现象:聊到十几轮,模型开始"忘了前面说过什么"。这时候第一反应是模型不行,但原因通常在自己这边——上下文没管好。这一篇把这个场景从头拆开。


一、先接受一件事:模型不记得你

所有多轮对话的困惑,都源于一个没被讲清楚的前提:大模型接口是无状态的。

不是"记性不好",是根本不记。你发过去的请求,对模型来说就是一次性的:给它一段文本,它接着往下写,写完就忘。下一次请求,除非你把之前的内容再发一遍,否则它完全不知道你们聊过什么。

这个前提听起来很基础,但它解释了一连串现象:

  • 为什么每次请求都要把历史全带上?
  • 为什么聊得越久,每次请求越慢、越贵?
  • 为什么"它明明刚才说过"这种抱怨,本质上是个伪命题?

所谓"多轮对话",其实是客户端每次把完整的历史重新发一遍,让模型看起来像是有记忆。记忆不在模型那边,在你这边。

所以多轮对话的本质工作,不是"让模型记住",而是"决定每次带多少历史过去"。这一篇剩下的内容都是围绕这一句展开的。


二、别提字符串,用 messages 数组

看两种典型写法。

第一种,自己拼字符串:

String prompt = "用户:" + q1 + "\n助手:" + a1 + "\n用户:" + q2;

第二种,用结构化的 messages:

List<Map<String, Object>> messages = List.of(
    Map.of("role", "system",    "content", "你是一个技术支持助手"),
    Map.of("role", "user",      "content", q1),
    Map.of("role", "assistant", "content", a1),
    Map.of("role", "user",      "content", q2)
);

第一种能跑,但问题会陆续冒出来:

  • 用户在问题里打一句"助手:好的",历史就被伪造了。模型分不清哪句是它自己说的。这是那种平时遇不到、一旦被恶意用户发现就很难看的漏洞。
  • 你要做截断、要按消息数限制、要插入工具调用结果,都得在字符串上做正则,很快就没法维护。
  • 一些新能力(工具调用、多模态)根本没法用字符串表达。

所以没有理由用字符串拼接。用 messages 数组,这是接口本身规定的形式,不是风格偏好。

三个角色的分工要记牢:

  • system:设定身份、约束和规则,放最前面,每次请求都带;
  • user:用户说的话;
  • assistant:模型之前的回答,从上一轮响应里拿到的原文。

assistant 的内容要用模型返回的原文,不要自己改写或截断。改写会污染后续上下文,而且改出来的内容会让模型对自己之前说过的话产生误判。


三、上下文窗口:一个共享的额度

每次请求,输入和输出共享一个 token 上限,这就是上下文窗口。

这一点很关键,因为它意味着你带的输入越多,能留给输出的空间就越少。极端情况是:历史塞得太满,模型只剩下几十个 token 的输出空间,回答还没说完就被截断——表现就是"它突然不说话了"或者"话说到一半停了"。

所以带多少历史,不能只看"能塞多少",要给它留出输出空间:

可用输入额度 ≈ 上下文窗口 − max_tokens − 安全余量

安全余量必须留。原因有两个:一是 token 的估算很难精确(中文一个字往往不是一个 token,代码和标点更不规律),二是有些模型实现会在内部加一些额外的结构 token,你的估算里没算进去。留出 10%~15% 的余量,比事后排查"为什么偶尔报超长"要省事得多。

顺带一个现实提醒:不同模型判断超长的行为不一样。 有的直接报错返回,有的悄悄从中间截掉一段(这种情况最坑,你完全不知道历史缺了一块),有的会拒绝。所以别依赖报错来发现问题,要在自己这边主动控制长度。


四、超长了丢哪部分

这是多轮对话里最需要想清楚的一个决策。历史必然有一天会超出额度,那时候丢掉什么、保留什么,直接决定对话质量。

绝对不能丢的

system 消息。 它是整段对话的规则来源,丢了之后模型的行为会突然变得不受约束,出现完全不遵守指令的情况。截断逻辑里必须把 system 排除在外。

最近几轮对话。 用户当前的问题往往依赖前面的语境,"那它呢?"这种问题离开上一轮就没法回答。所以尾部要完整保留。

三种常见策略

策略一:滑窗(保留最近 N 轮)

最简单,把最老的轮次整对删掉,直到长度够。

// 保留 system + 最近 N 轮(一轮 = 一问一答)
List<Map<String, Object>> trimmed = new ArrayList<>();
trimmed.add(systemMessage);
int from = Math.max(1, messages.size() - N * 2);
trimmed.addAll(messages.subList(from, messages.size()));

优点是实现简单、可预测。缺点是丢历史是硬丢失——用户在前面提过的关键信息,一旦滑出窗口就彻底没了,模型会再问一遍"请问您说的是哪个订单"。长对话里这个体验很糟。

策略二:摘要压缩

当历史超过阈值时,把最早的一段交给模型,让它压缩成一段摘要,然后用这段摘要替换原来的多轮内容。

好处是信息密度高,几十轮能压成几百字,还保住了早期提到的关键事实。代价是多了一次调用(要花钱、要花时间),而且摘要会丢细节,压缩过程中的信息损失不可控。

实践上比较稳的用法是混合:保留 system + 摘要 + 最近 N 轮。这样既有远期记忆,又有精确的近期语境。

策略三:按重要性挑

把历史里的关键信息(用户给的订单号、报错信息、时间范围)单独抽出来,作为一条附加说明保留。

这个做法效果好,但需要针对业务定制,通用性差。适合对话内容比较固定的场景,比如客服。

一个必须处理的边界

真正动手写截断时,有个地方特别容易出问题:别把成对的消息拆散。

具体说三件事:

  • user 和 assistant 要成对。切在中间会出现连续两条 user,或者历史以 assistant 开头。有些模型对这种结构会直接报错,有些则不报错但回答质量明显下降——后者更难排查。
  • 工具调用和它的返回必须在一起。如果你用了 Function Calling,一条 assistant 消息里带着 tool_calls,紧接着必须是对应的 tool 结果消息。从中间截断会让结构不合法,接口直接拒绝。
  • 要用 token 数判断,不要按消息条数判断。消息条数相同,内容长度可能差十倍。按条数截断必然出现"有时候没超,有时候超了"的不稳定现象。

判断超长时,token 数尽量用模型厂商提供的计数接口或 SDK 来算,不要自己按字符数乘个系数估——中文、代码、标点、emoji 的差异很大,估出来的值会系统性偏移。


五、会话状态存在哪

上面讲的都是"怎么拼上下文",但还有一个前置问题:这些历史存在哪里?

内存

用 Map<String, List<Map<String,Object>>> 存,会话 ID 做 key。

适合单机演示。上生产会同时踩两个坑:

  • 重启就丢。用户发完一条消息,你这边重启了一下,历史全没了,模型立刻"失忆"。
  • 多实例不共享。部署了两个实例,用户的下一条请求被负载均衡打到另一台,历史是空的。表现就是"聊着聊着突然忘了",而且是随机发生的,很难复现。

如果你的服务是多实例部署(绝大多数生产环境都是),内存方案直接排除。

Redis

把 messages 序列化后存 Redis,用会话 ID 做 key,设置过期时间(比如 30 分钟无活动就回收)。

这是多数场景的合理选择:多个实例共享、天然支持过期、读写快。要注意的几点:

  • 必须设过期时间。否则废弃的会话会一直堆着,内存慢慢被吃掉。
  • 加锁防止并发写坏历史。同一个用户快速连发两条消息,两个请求会同时读到同一份历史、各自追加、各自写回,后写的把先写的覆盖掉——用户会发现自己说的一句话"没被听见"。用分布式锁或者按会话串行化来避免。
  • 控制存储大小。一个会话的历史可能占几十 KB,量大了要评估内存成本,必要时压缩后存。

数据库

要把对话留档(审计、数据分析、客服质检)就落库。但别把库当上下文存储用——每次都从库里查全量历史再拼,数据量上来之后读写都会成为瓶颈。

更合理的分工是:库负责留档,Redis 负责上下文。每次对话结束后异步落库,上下文只从 Redis 读写。两边职责分开,谁都不为难。


六、"忘了"的问题,先查这三处

聊到十几轮之后出问题,按这个顺序排查基本能定位:

第一处:历史根本没传全。 打印一次实际发出的 messages 数组,看到底带了几轮。很多时候是拼接逻辑有问题——比如每次都只带了最后一条,或者历史被某个地方覆盖了。这个方法最土,但命中率最高。

第二处:被截断截掉了。 看截断阈值和实际历史长度。如果 token 计数用的是自己估的系数,很可能估算偏低,导致实际请求超长,然后被模型那边悄悄截掉中间一段——你这边完全无感。

第三处:会话串了。 多实例部署 + 内存存储 + 会话 ID 复用(比如用了用户 ID 而不是会话 ID),就会出现两个用户的对话混到一起。这种问题看起来像"模型疯了",实际是数据串了。检查和会话 ID 的生成规则,以及是否存在多实例共享问题。

有一类现象要特别区分开:模型回答变得敷衍、反复问同样的问题、忽略 system 里的约束——这三种往往是上下文过长导致的退化,不是模型能力问题。上下文塞得越满,模型对中间部分的注意力越弱,这是已知的普遍现象。看到这种症状,先怀疑上下文太长,而不是先换模型。


七、几个容易忽略的实践细节

  • 历史里的消息要按时间顺序,别交错。并发写坏的典型症状就是顺序乱了。
  • 空的 assistant 响应不要进历史。如果上一轮因为超时或过滤返回了空内容,把它塞进历史会让模型困惑。宁可跳过这一轮。
  • 用户输入要先做长度限制。一个用户粘贴了十万字的文档进来,一次就把额度占满。在入口处就拦掉或分段,别等到底层报错。
  • 上下文长度会影响成本,不是只影响效果。第二轮请求带一轮历史,第十轮带九轮历史——每次请求的输入 token 是递增的。长对话里输入成本会明显超过输出成本。做成本估算时别只算输出。
  • 把"最近 N 轮"做成可配置项。不同业务差别很大,客服可能要 10 轮,翻译工具 1 轮就够。写死了后面一定要改。

八、小结

多轮对话的核心可以收成一句话:模型不记得你,记得的人是你。

所以这件工作的实质是每次请求前做一次取舍——带多少历史过去、丢掉哪些、怎么保证带的内容结构合法、存在哪里、并发下怎么不被写坏。这些决策都有明确的取舍代价,没有"标准答案",但都有明确的错误做法。

一个有用的自检方式:把每次实际发出的 messages 数组完整打出来看一眼。 很多"模型不行"的抱怨,在看到那串真实数据的那一刻就解释清楚了。


下一篇

阶段一到这里就结束了:能连上、知道该不该流式、参数会调、客户端能复用、上下文管得住。接下来的问题是,模型稳定返回的格式能不能被程序可靠地解析——也就是结构化输出。这件事看着简单,但"偶尔不是合法 JSON"这种情况,会让下游的解析全部失守。下一篇讲怎么让它稳定。


本文是「用 Java 接入大模型」系列第五篇。前四篇分别讲了流式输出、流式与非流式的取舍、参数调优、客户端封装,建议按顺序看。文中做法基于通义千问的 OpenAI 兼容接口,具体行为以你使用的模型文档为准。

目录
相关文章
|
8天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
7382 12
|
6天前
|
人工智能 测试技术 API
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
Jev是TypeSafe AI推出的“系统一模型”,不生成文本,专做毫秒级结构化决策:Choice(多选)、Score(打分)、Noul(是非概率)。响应快193倍、成本低444倍,适合工单路由、内容审核、测试定级等高频判断场景。
1545 4
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
|
7天前
|
人工智能 并行计算 PyTorch
秋叶 ComfyUI 2026 整合包 v3.2 完整部署教程:Python 3.13 + Torch 2.13 全栈升级
秋叶aaaki ComfyUI 2026年8月整合包v3.2正式发布!全面升级Python 3.13.11、PyTorch 2.13.0+cu130及ComfyUI v0.30.2,原生支持MiniMax H3、Wan 2.2、Qwen-Image-2.1等2026主流音视频/图像模型,解压即用,无需环境配置。
998 8
|
3天前
|
人工智能 JavaScript 芯片
DeepSeek 官方偷偷上传 Harness 桌面端安装包,我已经用上了。。附最新下载地址
DeepSeek Harness 官方的桌面端安装包被网友扒出来了,2 分钟讲明白如何使用,体验如何,适合作为 AI 编程工具么?附最新 Windows 和 Mac 双端的下载地址
1196 1
|
20天前
|
人工智能 自然语言处理 安全
阿里云千问办公 QwenWork详细介绍:产品核心能力、典型场景、价格及常见问题解答
千问办公是阿里云推出的一站式AI办公平台,主打"不止于对话,更注重交付",依托通义千问旗舰大模型,用户一句话即可完成数据分析、PPT生成、视频剪辑等复杂任务,直接输出可用成果。产品深度打通钉钉生态与企业OA,覆盖桌面端、网页端,提供企业标准版198元/人/月等多档订阅方案,新用户注册即赠2000积分,适配工程师、HR、财务等多职业办公场景,成为能动手干活的"全能AI同事"。
3579 10
|
15天前
|
缓存 IDE Java
【保姆级】Android Studio下载、安装和汉化教程(2026最新)
Android Studio 是 Google 官方推出的免费 Android 应用开发集成环境,基于 IntelliJ IDEA,内置模拟器、调试器、性能分析及 Compose 界面工具,功能全面,文档丰富,是安卓开发首选工具。(239字)
1611 1
|
4天前
|
编解码 缓存 PyTorch
16G 显卡能跑 Qwen-Image 2.1 吗?
9月20日,阿里Qwen开源Qwen-Image-2.1:7B DiT图像模型+8B文本编码器+VAE,单模型支持文生图与图像编辑,原生输出2K PNG(含Alpha通道),支持10张参考图。在自建Qwen-Image-Bench达60.28分(开源模型第一),GenAI Showdown文生图排名7/15。16G显存可跑1024×1024(需INT8量化+ComfyUI优化),但2K需24G以上。注意其Qwen Research License限非商业用途。
506 1
|
5天前
|
人工智能 编解码 并行计算
MiniMax-H3 一键整合包技术文档:8G 显存运行 AI 漫剧制作 —— 角色替换 / 动作迁移 / 文图生视频部署与调参指南
MiniMax H3 是 MiniMax 开源的全模态视频生成模型,支持文/图/音/视多条件输入,输出最高2K、15秒带双声道音频视频。本文档详述其Int8量化版在8GB显存下的本地一键部署、三段式工作流(EDIT/REPLACE/CONTINUE)、参数调优及常见问题排查。(239字)

热门文章

最新文章