Java RAG 实战(第 3 篇):从交互式聊天到多轮上下文

系列导航

本篇先把写死的问题改为终端输入,再在 Java 中保存 user 与 assistant 消息,让连续提问从"多次独立请求"升级为真正的多轮对话。

完成进度

  • 1. 从终端连续读取用户问题
  • 2. 用 Java 对象安全生成请求 JSON
  • 3. 使用 exit 正常结束程序
  • 4. 在 Java List 中保存完整对话历史
  • 5. 验证第二轮能够理解第一轮
  • 6. 使用 clear 清空当前上下文

为什么要学

可交互和有上下文是聊天程序的两个连续层次:终端循环解决"怎样反复提问",消息列表解决"怎样让下一轮知道上一轮"。放在同一篇更容易看出两版请求中 messages 的差异。

第一阶段:实现交互式聊天

上一版的问题写在源代码中,每换一个问题都需要修改和重新编译。交互式程序把"程序逻辑"和"用户输入"分开,是从接口演示走向可用应用的第一步。

本篇目标

  • 从终端读取问题。
  • 连续调用 Ollama。
  • 输入空行时继续等待。
  • 输入 exit 时正常退出。
  • 使用 Java 对象生成 JSON,避免输入中的引号破坏请求格式。

为什么使用 Java 对象生成 JSON

如果直接把用户输入拼进 JSON 字符串,下面的问题可能造成格式错误:

text 复制代码
请解释 "RAG"

这一版定义 ChatRequestChatMessage,再由 Jackson 的 writeValueAsString 负责正确转义。

对应源码:

text 复制代码
01-ollama-basics/src/main/java/com/example/ai/step03/InteractiveChat.java

终端循环的关键代码如下:

java 复制代码
try (Scanner scanner = new Scanner(System.in, StandardCharsets.UTF_8)) {
    while (true) {
        System.out.print("你:");
        if (!scanner.hasNextLine()) {
            break;
        }

        String question = scanner.nextLine().trim();
        if (question.equalsIgnoreCase("exit")) {
            break;
        }
        if (question.isEmpty()) {
            continue;
        }

        String answer = askOllama(client, objectMapper, question);
        System.out.println("AI:" + answer + "\n");
    }
}

每一轮只产生一个当前问题,再交给 askOllama()。请求对象也只包含这一条 user 消息:

java 复制代码
ChatRequest chatRequest = new ChatRequest(
        MODEL,
        List.of(new ChatMessage("user", question)),
        false,
        false
);
String requestBody = objectMapper.writeValueAsString(chatRequest);

这解释了两个现象:程序可以反复提问,是因为 while 循环一直读取输入;模型不能理解上一轮,是因为每次 messages 中只有当前问题。

streamthink

请求中暂时设置:

text 复制代码
stream = false
think = false
  • stream: false:等待完整回答后一次显示。
  • think: false:关闭 Qwen3 的深度思考输出,提高基础聊天速度。

后面的迭代会单独学习流式输出和模型参数。

运行

bash 复制代码
mvn -f 01-ollama-basics/pom.xml compile exec:java \
  -Dexec.mainClass=com.example.ai.step03.InteractiveChat

输入一个问题,例如:

text 复制代码
请用一句话解释 Embedding

结束程序:

text 复制代码
exit

成功标准

程序可以连续接受问题,每次都显示模型回答,并在输入 exit 后输出"对话已结束"。

当前限制

每次请求只发送当前问题。模型不会自动知道上一轮问了什么,下一篇将通过保存消息列表解决这个问题。


第二阶段:加入多轮对话上下文

聊天接口通常是无状态的:模型只知道本次请求中携带的消息,不会自动记住 Java 程序上一次发送的内容。

如果第一轮问"Java 17 有什么特点",第二轮只问"它和 21 有什么区别",模型必须同时收到第一轮消息才能知道"它"指的是 Java 17。

消息角色

对话历史由按顺序排列的消息组成:

text 复制代码
user: Java 17 有什么特点?
assistant: Java 17 是......
user: 它和 21 有什么区别?
  • user 表示用户输入。
  • assistant 表示模型之前的回答。
  • 后面还会学习用于规定模型身份和行为的 system

Java 如何保存上下文

程序创建一个消息列表:

java 复制代码
List<ChatMessage> conversation = new ArrayList<>();

每轮执行以下流程:

text 复制代码
把用户问题加入 conversation
  -> 把完整 conversation 发给 Ollama
  -> 得到模型回答
  -> 把模型回答加入 conversation

如果请求失败,程序会移除刚加入的用户问题,避免留下没有对应回答的不完整历史。

对应源码:

text 复制代码
01-ollama-basics/src/main/java/com/example/ai/step04/ContextChat.java

关键逻辑是:

java 复制代码
List<ChatMessage> conversation = new ArrayList<>();

conversation.add(new ChatMessage("user", question));

String answer;
try {
    // 每轮发送的是完整 conversation,不是只发当前问题。
    answer = askOllama(client, objectMapper, conversation);
} catch (Exception exception) {
    // 失败时撤回本轮 user 消息,避免留下残缺历史。
    conversation.remove(conversation.size() - 1);
    throw exception;
}

// 成功后才保存 assistant 回答。
conversation.add(new ChatMessage("assistant", answer));

这段代码就是"多轮记忆"的真相:记忆在 Java 的 List 中,Ollama 每次只处理本次请求收到的消息。

清空上下文

在终端输入:

text 复制代码
clear

Java 会执行 conversation.clear()。这只清空当前程序内存中的聊天记录,不会删除模型或停止 Ollama。

运行和验证

bash 复制代码
mvn -f 01-ollama-basics/pom.xml compile exec:java \
  -Dexec.mainClass=com.example.ai.step04.ContextChat

依次输入:

text 复制代码
请记住:今天的测试词是蓝色。
我刚才让你记住的测试词是什么?

如果第二轮回答"蓝色",就说明上下文已经生效。

再输入:

text 复制代码
clear

程序应提示"上下文已清空"。输入 exit 结束。

上下文不是永久记忆

当前消息只保存在 Java 进程的内存中:

  • 退出程序后记录消失。
  • 历史越长,请求携带的 Token 越多。
  • 超过模型上下文窗口后,需要截断、摘要或持久化管理。

后面构建完整应用时会逐步处理这些问题。

本篇自测

  1. 为什么不应把终端输入直接拼进 JSON 字符串?
  2. 交互式程序为什么能连续提问,却仍然不理解上一轮?
  3. 多轮聊天为什么必须同时保存 user 和 assistant 消息?
  4. 请求失败时为什么要撤回刚加入的用户消息?
  5. 退出程序后上下文为什么会消失?

参考答案:输入中的引号和换行可能破坏 JSON;每次只发送当前问题;完整问答共同构成后续语境;避免留下没有对应回答的残缺历史;消息只保存在当前 Java 进程内存中。


本篇小结

  1. Scanner 和循环让同一个 Java 程序可以反复接受问题。
  2. Java 对象配合 Jackson 可以安全生成包含用户输入的 JSON。
  3. 模型不会自动记住历史,多轮上下文依靠 Java 维护并重复发送完整消息列表。
  4. clear 只清除当前进程内存中的对话,不会删除模型或 Ollama 数据。

下一篇

👉 本专栏下一篇:《第4篇:System Prompt 与模型参数》

完整代码都在 GitHub(欢迎 Star ⭐)

本专栏的全部示例代码都已开源,包含 5 个可独立运行的 Maven 模块、自动化测试和完整分篇教程。建议 Fork / Clone 下来,边读边跑:

🔗 https://github.com/bysbsh/ai-rag-learning-guide

  • 代码与教程同步更新,对照每一篇动手实践效果最好。
  • 如果这份教程帮到了你,点个 Star 就是对我最大的支持,也方便你之后找回最新版本。
  • 遇到问题或发现错漏,欢迎在仓库提 Issue / PR。项目采用 MIT 协议,可自由学习与二次创作。
相关推荐
雪隐2 小时前
个人电脑玩AI-16让5060 Ti给你打工——MiniMax H3 提速实录:从"泡杯茶等视频"到"视频等你",一张 5060 Ti 的自我修养
前端·人工智能·后端
深兰科技2 小时前
深兰科技亮相天津医疗器械创新生态大会,DeepAgent智能体赋能医疗医美并签约天津OPC项目
人工智能·科技·r语言·symfony·视觉大模型·深兰科技·deepagent智能体
cxr8282 小时前
从“对象本体”到“关系本体”
人工智能·架构
shxjnpl2 小时前
Whisper + CAM++ + 本地大模型:一套完全离线的AI会议处理链路
人工智能·机器学习·whisper
JaydenAI2 小时前
[基于OpenEvals的自动化评估-12]Agent执行轨迹评估[无LLM参与]
ai·langchain·agent·evaluation·openevals
952362 小时前
Sentinel
java·后端·spring·sentinel·springcloud
小润nature2 小时前
AI 与嵌入式三时简报|早报
人工智能
今天AI了吗2 小时前
MCP 协议打通 AI 与国产数据库,SQL 调优全流程一站式闭环
数据库·人工智能·sql
阿pin2 小时前
Java随笔-JDK7 HashMap头插法为何能导致死循环?
java·开发语言·hashmap