JDK1.8也可以對(duì)接DeepSeek-R1,你知道嗎?
什么是ai4j
首先,我們先了解一下什么是ai4j。
AI4J 是一款 Java SDK,用于快速接入 AI 大模型應(yīng)用。它能整合多平臺(tái)大模型,如 OpenAI、Ollama、智譜 Zhipu(ChatGLM)、深度求索 DeepSeek、月之暗面 Moonshot(Kimi)、騰訊混元 Hunyuan、零一萬(wàn)物(01)等,為用戶(hù)提供快速整合 AI 的能力。
其特點(diǎn)包括提供統(tǒng)一的輸入輸出(對(duì)齊 OpenAI)以消除差異化,優(yōu)化函數(shù)調(diào)用(Tool Call)和 RAG 調(diào)用,支持向量數(shù)據(jù)庫(kù)(如 Pinecone),并且支持 JDK1.8,能滿(mǎn)足很多仍在使用 JDK8 版本的應(yīng)用需求。
敲重點(diǎn):JDK1.8
看過(guò)上一篇使用SpringAI的都知道,SpringAI對(duì)JDK的要求非常高,那次了不起使用了JDK 17,但是Java發(fā)展了這么多年,很多項(xiàng)目都是基于JDK1.8來(lái)構(gòu)建的,你讓他們現(xiàn)在去升級(jí)JDK,可能AI還沒(méi)接入,項(xiàng)目就先起不來(lái)了。
也因此誕生了ai4j,他支持 JDK1.8,能滿(mǎn)足很多仍在使用 JDK8 版本的應(yīng)用需求,并且向量數(shù)據(jù)庫(kù)還能幫助很多項(xiàng)目做知識(shí)庫(kù)搜索。
進(jìn)入正題
我們使用目前最新版本的ai4j。
<dependency>
<groupId>io.github.lnyo-cly</groupId>
<artifactId>ai4j</artifactId>
<version>0.8.1</version>
</dependency>
現(xiàn)在網(wǎng)上很多版本的ai4j都不支持ollama調(diào)用,所以直接使用最新版本的話,就沒(méi)有問(wèn)題了。
我們依舊是寫(xiě)兩個(gè)接口,一個(gè)直接返回,一個(gè)流式返回。
IChatService chatService = aiService.getChatService(PlatformType.OLLAMA);
通過(guò)getChatService的方式,選擇是用本地ollama還是其他平臺(tái)。
它一共支持以下平臺(tái)。
@AllArgsConstructor
@Getter
public enum PlatformType {
OPENAI("openai"),
ZHIPU("zhipu"),
DEEPSEEK("deepseek"),
MOONSHOT("moonshot"),
HUNYUAN("hunyuan"),
LINGYI("lingyi"),
OLLAMA("ollama"),
MINIMAX("minimax"),
BAICHUAN("baichuan"),
;
....
}
由于我修改過(guò)ollama的端口,所以我沒(méi)辦法使用默認(rèn)的端口,需要單獨(dú)設(shè)置調(diào)用的url。
spring.application.name=demo
server.port=8080
ai.ollama.api-host=http://localhost:8000
創(chuàng)建請(qǐng)求體:
// 創(chuàng)建請(qǐng)求參數(shù)
ChatCompletion chatCompletion = ChatCompletion.builder()
.model("deepseek-r1:7b")
.message(ChatMessage.withUser(question))
.build();
直接返回就調(diào)用chatCompletion方法:
// 發(fā)送chat請(qǐng)求
ChatCompletionResponse chatCompletionResponse = chatService.chatCompletion(chatCompletion);
流式放回就調(diào)用chatCompletionStream方法:
// 發(fā)送chat請(qǐng)求
chatService.chatCompletionStream(chatCompletion, sseListener);
流式的話他是以SSE端點(diǎn)的形式去獲取數(shù)據(jù),所以需要你實(shí)現(xiàn)一個(gè)SSE監(jiān)聽(tīng)器去打印和發(fā)送數(shù)據(jù)給前端。
以下是完整的后端接口:
@RestController
@CrossOrigin
public class OllamChatController {
// 注入Ai服務(wù)
@Autowired
private AiService aiService;
@GetMapping("/chat")
public String getChatMessage(@RequestParam String question) throws Exception {
// 獲取OLLAMA的聊天服務(wù)
IChatService chatService = aiService.getChatService(PlatformType.OLLAMA);
// 創(chuàng)建請(qǐng)求參數(shù)
ChatCompletion chatCompletion = ChatCompletion.builder()
.model("deepseek-r1:7b")
.message(ChatMessage.withUser(question))
.build();
System.out.println(chatCompletion);
// 發(fā)送chat請(qǐng)求
ChatCompletionResponse chatCompletionResponse = chatService.chatCompletion(chatCompletion);
// 獲取聊天內(nèi)容和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("開(kāi)始進(jìn)行Chat對(duì)話: {}", question);
// 獲取chat服務(wù)實(shí)例
IChatService chatService = aiService.getChatService(PlatformType.OLLAMA);
logger.info("成功創(chuàng)建服務(wù)實(shí)例");
// 構(gòu)造請(qǐng)求參數(shù)
ChatCompletion chatCompletion = ChatCompletion.builder()
.model("deepseek-r1:7b")
.messages(Arrays.asList(ChatMessage.withUser(question)))
.functions()
.build();
logger.info("成功構(gòu)建流式請(qǐng)求體");
// 構(gòu)造監(jiān)聽(tīng)器
SseListener sseListener = new SseListener() {
@Override
protected void send() {
try {
// 將消息發(fā)送到前端
String data = this.getCurrStr();
if (data != null && !data.isEmpty()) {
emitter.next(ServerSentEvent.<String>builder()
.data(data)
.build());
}
} catch (Exception e) {
logger.error("SSE端點(diǎn)報(bào)錯(cuò)", e);
emitter.error(e);
}
}
};
// 顯示函數(shù)參數(shù),默認(rèn)不顯示
sseListener.setShowToolArgs(true);
// 發(fā)送SSE請(qǐng)求
chatService.chatCompletionStream(chatCompletion, sseListener);
logger.info("成功請(qǐng)求SSE端點(diǎn)");
} catch (Exception e) {
logger.error("流式輸出報(bào)錯(cuò)", e);
emitter.error(e);
}
});
}
}
流式的話,我們?cè)賹?xiě)個(gè)前端來(lái)看看測(cè)試效果。
<!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="請(qǐng)輸入問(wèn)題">
<button id="sendButton">發(fā)送</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('請(qǐng)輸入問(wèn)題');
return;
}
// 創(chuàng)建 EventSource 實(shí)例,連接到后端的 SSE 接口
const eventSource = new EventSource(`http://localhost:8080/chat-stream?question=${encodeURIComponent(question)}`);
// 監(jiān)聽(tīng) message 事件,當(dāng)接收到服務(wù)器發(fā)送的消息時(shí)觸發(fā)
eventSource.onmessage = (event) => {
const data = event.data;
// 將接收到的數(shù)據(jù)追加到響應(yīng)容器中
responseContainer.textContent += data;
};
// 監(jiān)聽(tīng) error 事件,當(dāng)連接出現(xiàn)錯(cuò)誤時(shí)觸發(fā)
eventSource.onerror = (error) => {
console.error('EventSource failed:', error);
// 關(guān)閉連接
eventSource.close();
};
});
</script>
</body>
</html>
運(yùn)行服務(wù),打開(kāi)html,在輸入框輸入一個(gè)問(wèn)題,點(diǎn)擊按鈕發(fā)送,在F12的接口請(qǐng)求里,你會(huì)在Response里看到服務(wù)不斷的推送文字給你。
圖片