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 小时前
Linux网络编程:TCP 粘包与半包问题,如何正确处理 TCP 流式数据?
linux·服务器·网络·笔记·网络协议·学习
LGL6030A6 小时前
Java学习历程31——初识Spring Boot
java·spring boot·学习
阿达和自己7 小时前
springboot学习-springboot引入MyBatis-Plus
spring boot·学习·mybatis
旖旎夜光8 小时前
【C++ 基础入门】C++11 新特性(中):右值引用、移动语义与完美转发
开发语言·c++·笔记·学习
§ꦿCFོ༉20 小时前
嵌入式八股文—C语言入门学习(四)
c语言·开发语言·学习
2603_969694851 天前
【JavaWeb学习记录】MyBatis核心知识点与实战笔记:从入门到动态SQL
笔记·sql·学习·mysql·mybatis·javaweb·学习方法
北京云帆互联科技1 天前
云帆培训考试系统更新说明:v9.3.0
java·学习
sxlishaobin1 天前
Maven Setting详解
java·学习·maven
li星野2 天前
相位补偿学习记录:把被颅骨“打乱“的声波重新对齐
学习
Z21-X2 天前
云原生运维学习笔记:ECShop全链路容器化部署与Docker底层资源隔离深度剖析
运维·笔记·学习·云原生