oSgGWeFA6 发表于 2025-2-14 10:47:23

JDK1.8也可以对接DeepSeek-R1,你知道吗?

什么是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
      io.github.lnyo-cly      ai4j      0.8.1

[*]1.
[*]2.
[*]3.
[*]4.
[*]5.





现在网上很多版本的ai4j都不支持ollama调用,所以直接使用最新版本的话,就没有问题了。
我们依旧是写两个接口,一个直接返回,一个流式返回。
IChatService chatService = aiService.getChatService(PlatformType.OLLAMA);

[*]1.





通过getChatService的方式,选择是用本地ollama还是其他平台。
PS:如果你还不知ollama怎么弄,看这篇《使用SpringAI对接大模型DeepSeek-r1》
它一共支持以下平台。
@AllArgsConstructor@Getterpublic enum PlatformType {    OPENAI("openai"),    ZHIPU("zhipu"),    DEEPSEEK("deepseek"),    MOONSHOT("moonshot"),    HUNYUAN("hunyuan"),    LINGYI("lingyi"),    OLLAMA("ollama"),    MINIMAX("minimax"),    BAICHUAN("baichuan"),    ;   ....}

[*]1.
[*]2.
[*]3.
[*]4.
[*]5.
[*]6.
[*]7.
[*]8.
[*]9.
[*]10.
[*]11.
[*]12.
[*]13.
[*]14.
[*]15.





由于我修改过ollama的端口,所以我没办法使用默认的端口,需要单独设置调用的url
spring.application.name=demoserver.port=8080ai.ollama.api-host=http://localhost:8000

[*]1.
[*]2.
[*]3.





创建请求体:
// 创建请求参数ChatCompletion chatCompletion = ChatCompletion.builder()         .model("deepseek-r1:7b")         .message(ChatMessage.withUser(question))         .build();

[*]1.
[*]2.
[*]3.
[*]4.
[*]5.





直接返回就调用chatCompletion方法:
// 发送chat请求ChatCompletionResponse chatCompletionResponse = chatService.chatCompletion(chatCompletion);

[*]1.
[*]2.





流式放回就调用chatCompletionStream方法:
// 发送chat请求chatService.chatCompletionStream(chatCompletion, sseListener);

[*]1.
[*]2.





流式的话他是以SSE端点的形式去获取数据,所以需要你实现一个SSE监听器去打印和发送数据给前端。
以下是完整的后端接口:
@RestController@CrossOriginpublic 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 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.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);            }      });    }}

[*]1.
[*]2.
[*]3.
[*]4.
[*]5.
[*]6.
[*]7.
[*]8.
[*]9.
[*]10.
[*]11.
[*]12.
[*]13.
[*]14.
[*]15.
[*]16.
[*]17.
[*]18.
[*]19.
[*]20.
[*]21.
[*]22.
[*]23.
[*]24.
[*]25.
[*]26.
[*]27.
[*]28.
[*]29.
[*]30.
[*]31.
[*]32.
[*]33.
[*]34.
[*]35.
[*]36.
[*]37.
[*]38.
[*]39.
[*]40.
[*]41.
[*]42.
[*]43.
[*]44.
[*]45.
[*]46.
[*]47.
[*]48.
[*]49.
[*]50.
[*]51.
[*]52.
[*]53.
[*]54.
[*]55.
[*]56.
[*]57.
[*]58.
[*]59.
[*]60.
[*]61.
[*]62.
[*]63.
[*]64.
[*]65.
[*]66.
[*]67.
[*]68.
[*]69.
[*]70.
[*]71.
[*]72.
[*]73.
[*]74.





流式的话,我们再写个前端来看看测试效果
            Chat Stream Frontend发送    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();      };    });

[*]1.
[*]2.
[*]3.
[*]4.
[*]5.
[*]6.
[*]7.
[*]8.
[*]9.
[*]10.
[*]11.
[*]12.
[*]13.
[*]14.
[*]15.
[*]16.
[*]17.
[*]18.
[*]19.
[*]20.
[*]21.
[*]22.
[*]23.
[*]24.
[*]25.
[*]26.
[*]27.
[*]28.
[*]29.
[*]30.
[*]31.
[*]32.
[*]33.
[*]34.
[*]35.
[*]36.
[*]37.
[*]38.
[*]39.
[*]40.
[*]41.
[*]42.
[*]43.
[*]44.
[*]45.
[*]46.
[*]47.





运行服务,打开html,在输入框输入一个问题,点击按钮发送,在F12的接口请求里,你会在Response里看到服务不断的推送文字给你。
图片
页: [1]
查看完整版本: JDK1.8也可以对接DeepSeek-R1,你知道吗?