JAVA通过 http api与ollama本地大模进行流式多轮对话

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); } 的简易裁剪逻辑。

相关推荐
坤坤子吖2 小时前
C++继承:友元、静态成员、多继承与组合
开发语言·c++·笔记·学习·1024程序员节
破芽4 小时前
learn-claude-code-s08-s09-学习笔记
笔记·学习
星恒随风4 小时前
Linux网络编程基础(二):彻底理解局域网通信、MAC与IP地址、数据封装、端口号、网络字节序及Socket接口
linux·网络·笔记·学习·tcp/ip·1024程序员节
嘻嘻嘻3216 小时前
ICLR2025|MIM‑Refiner:利用预训练掩码图像建模中间表征提升对比学习
学习
Young Doro6 小时前
从 KSG 到 InfoAtlas:互信息估计完整学习册
学习
Be for thing6 小时前
PyCharm安装与PyTorch项目配置|Jupyter Notebook与PyCharm使用对比
pytorch·学习·1024程序员节
java_nnnn7 小时前
Linux 学习第二篇:从零认识 Linux 文件系统、目录结构与基本命令
linux·运维·学习·1024程序员节
東隅已逝,桑榆非晚8 小时前
搜索二叉树
c++·笔记·学习·1024程序员节