JAVA通过 http api与ollama本地大模进行流式多轮对话
1、核心原理:无状态与客户端记忆
大模型 API 本身是无状态的
它不会"记得"你上一句话说了什么。它之所以能和你连续聊天,是因为客户端(也就是我们的 Java 代码)在每次请求时,把之前的聊天记录(Messages 数组)全部带上,重新发给了模型。

pom.xml
bash
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
JDK 11 + 标准库 HttpClient + Jackson
bash
package org.example;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Scanner;
public class ChatWithQwen {
private static final String OLLAMA_URL = "http://localhost:11434/api/chat";
private static final String MODEL = "qwen2.5:7b"; // 改成 ollama list 里显示的名字,如 qwen3:8b
private static final ObjectMapper mapper = new ObjectMapper();
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
ArrayNode messages = mapper.createArrayNode(); // 对话历史,就是"记忆"
Scanner scanner = new Scanner(System.in);
System.out.println("已连接本地 Qwen,输入内容开始聊天(输入 /bye 退出):");
while (true) {
System.out.print("你: ");
String userInput = scanner.nextLine().trim();
if (userInput.equals("/bye")) break;
if (userInput.isEmpty()) continue;
// 1. 用户消息加入历史
ObjectNode userMsg = mapper.createObjectNode();
userMsg.put("role", "user");
userMsg.put("content", userInput);
messages.add(userMsg);
// 2. 构造请求体,带上全部历史
ObjectNode body = mapper.createObjectNode();
body.put("model", MODEL);
body.set("messages", messages);
body.put("stream", false);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(OLLAMA_URL))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(body)))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
System.err.println("请求失败: " + response.body());
continue;
}
// 3. 解析回复
JsonNode reply = mapper.readTree(response.body());
String assistantContent = reply.path("message").path("content").asText();
// 4. 模型回复也加入历史,下一轮才能"记得"
ObjectNode assistantMsg = mapper.createObjectNode();
assistantMsg.put("role", "assistant");
assistantMsg.put("content", assistantContent);
messages.add(assistantMsg);
System.out.println("Qwen: " + assistantContent);
}
}
}
2、每次请求输出messages观察一下
bash
System.out.println("目前的messages详情为:");
System.out.println(messages.toPrettyString());
可以看出来。
第二次请求=第一次请求的问题与答案+第二次请求的问题

进阶思考:上下文超长怎么办?
现在的代码有一个小隐患:messages 数组会无限增长。
如果电脑显存/内存有限,聊得时间太长后,会因为超出上下文窗口(Context Window)限制而报错。
在真实的生产环境中,需要加入"上下文裁剪"逻辑。例如:
只保留最近的 10 轮对话。或者当 messages 长度超过阈值时,调用模型对早期的对话生成一段"摘要",用摘要代替冗长的历史记录。
例如加上一个 if (messages.size() > 20) { messages.remove(0); } 的简易裁剪逻辑。