什么是ai4j
首先,我们先了解一下什么是ai4j
AI4J 是一款 Java SDK,用于快速接入 AI 大模型应用。它能整合多平台大模型,如 OpenAI、Ollama、智谱 Zhipu(ChatGLM)、深度求索 DeepSeek、月之暗面 Moonshot(Kimi)、腾讯混元 Hunyuan、零一万物(01)等,为用户提供快速整合 AI 的能力。
其特点包括提供统一的输入输出(对齐 OpenAI)以消除差异化,优化函数调用(Tool Call)和 RAG 调用,支持向量数据库(如 Pinecone),并且支持 JDK1.8,能满足很多仍在使用 JDK8 版本的应用需求。
敲重点:JDK1.8
看过上一篇使用SpringAI的都知道,SpringAI对JDK的要求非常高,那次了不起使用了JDK 17,但是Java发展了这么多年,很多项目都是基于JDK1.8来构建的,你让他们现在去升级JDK,可能AI还没接入,项目就先起不来了。
也因此诞生了ai4j,他支持 JDK1.8,能满足很多仍在使用 JDK8 版本的应用需求,并且向量数据库还能帮助很多项目做知识库搜索。
进入正题
我们使用目前最新版本的ai4j
<dependency> <groupId>io.github.lnyo-cly</groupId> <artifactId>ai4j</artifactId> <version>0.8.1</version> </dependency>
现在网上很多版本的ai4j都不支持ollama调用,所以直接使用最新版本的话,就没有问题了。
我们依旧是写两个接口,一个直接返回,一个流式返回。
IChatService chatService = aiService.getChatService(PlatformType.OLLAMA);
通过getChatService的方式,选择是用本地ollama还是其他平台。
PS:如果你还不知ollama怎么弄,看这篇《使用SpringAI对接大模型DeepSeek-r1》
它一共支持以下平台。
@AllArgsConstructor @Getter public enum PlatformType { OPENAI("openai"), ZHIPU("zhipu"), DEEPSEEK("deepseek"), MOONSHOT("moonshot"), HUNYUAN("hunyuan"), LINGYI("lingyi"), OLLAMA("ollama"), MINIMAX("minimax"), BAICHUAN("baichuan"), ; .... }
由于我修改过ollama的端口,所以我没办法使用默认的端口,需要单独设置调用的url
spring.application.name=demo server.port=8080 ai.ollama.api-host=http://localhost:8000
创建请求体:
// 创建请求参数 ChatCompletion chatCompletion = ChatCompletion.builder() .model("deepseek-r1:7b") .message(ChatMessage.withUser(question)) .build();
直接返回就调用chatCompletion方法:
// 发送chat请求 ChatCompletionResponse chatCompletionResponse = chatService.chatCompletion(chatCompletion);
流式放回就调用chatCompletionStream方法:
// 发送chat请求 chatService.chatCompletionStream(chatCompletion, sseListener);
流式的话他是以SSE端点的形式去获取数据,所以需要你实现一个SSE监听器去打印和发送数据给前端。
以下是完整的后端接口:
@RestController @CrossOrigin public class OllamChatController { // 注入Ai服务 @Autowired private AiService aiService; @GetMapping("/chat") public String getChatMessage(@RequestParam String question) throws Exception { // 获取OLLAMA的聊天服务 IChatService chatService = aiService.getChatService(PlatformType.OLLAMA); // 创建请求参数 ChatCompletion chatCompletion = ChatCompletion.builder() .model("deepseek-r1:7b") .message(ChatMessage.withUser(question)) .build(); System.out.println(chatCompletion); // 发送chat请求 ChatCompletionResponse chatCompletionResponse = chatService.chatCompletion(chatCompletion); // 获取聊天内容和token消耗 String content = chatCompletionResponse.getChoices().get(0).getMessage().getContent(); long totalTokens = chatCompletionResponse.getUsage().getTotalTokens(); System.out.println("总token消耗: " + totalTokens); return content; } @GetMapping(path = "/chat-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<ServerSentEvent<String>> chatStream(@RequestParam String question) { Logger logger = LoggerFactory.getLogger(getClass()); return Flux.create(emitter -> { try { logger.info("开始进行Chat对话: {}", question); // 获取chat服务实例 IChatService chatService = aiService.getChatService(PlatformType.OLLAMA); logger.info("成功创建服务实例"); // 构造请求参数 ChatCompletion chatCompletion = ChatCompletion.builder() .model("deepseek-r1:7b") .messages(Arrays.asList(ChatMessage.withUser(question))) .functions() .build(); logger.info("成功构建流式请求体"); // 构造监听器 SseListener sseListener = new SseListener() { @Override protected void send() { try { // 将消息发送到前端 String data = this.getCurrStr(); if (data != null && !data.isEmpty()) { emitter.next(ServerSentEvent.<String>builder() .data(data) .build()); } } catch (Exception e) { logger.error("SSE端点报错", e); emitter.error(e); } } }; // 显示函数参数,默认不显示 sseListener.setShowToolArgs(true); // 发送SSE请求 chatService.chatCompletionStream(chatCompletion, sseListener); logger.info("成功请求SSE端点"); } catch (Exception e) { logger.error("流式输出报错", e); emitter.error(e); } }); } }
流式的话,我们再写个前端来看看测试效果
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Chat Stream Frontend</title> </head> <body> <input type="text" id="questionInput" placeholder="请输入问题"> <button id="sendButton">发送</button> <div id="responseContainer"></div> <script> const questionInput = document.getElementById('questionInput'); const sendButton = document.getElementById('sendButton'); const responseContainer = document.getElementById('responseContainer'); sendButton.addEventListener('click', () => { const question = questionInput.value; if (question.trim() === '') { alert('请输入问题'); return; } // 创建 EventSource 实例,连接到后端的 SSE 接口 const eventSource = new EventSource(`http://localhost:8080/chat-stream?question=${encodeURIComponent(question)}`); // 监听 message 事件,当接收到服务器发送的消息时触发 eventSource.onmessage = (event) => { const data = event.data; // 将接收到的数据追加到响应容器中 responseContainer.textContent += data; }; // 监听 error 事件,当连接出现错误时触发 eventSource.onerror = (error) => { console.error('EventSource failed:', error); // 关闭连接 eventSource.close(); }; }); </script> </body> </html>
运行服务,打开html,在输入框输入一个问题,点击按钮发送,在F12的接口请求里,你会在Response里看到服务不断的推送文字给你。
图片