Java快速接入DeepSeek实现流式、联网、知识库以及多轮问答
本文将详细的说明,如何使用Java、JDK8快速接入deepseek的聊天服务,包含官方的API服务,以及本地Ollama的服务。并搭建一个简单的前端界面,用于流式输出、多轮问答、联网、知识库问答的效果展示。
1. 创建spring boot应用
![图片[1]-Java快速接入DeepSeek实现流式、联网、知识库以及多轮问答 - 拾光赋-拾光赋](https://i0.wp.com/img-blog.csdnimg.cn/img_convert/199b1d510ba42be1aaaad73d2a30401f.png)

2. 引入pom
引入ai4j库的依赖。
AI4J是一款JavaSDK用于快速接入AI大模型应用,整合多平台大模型,如OpenAi、Ollama、智谱Zhipu(ChatGLM)、深度求索DeepSeek、月之暗面Moonshot(Kimi)、腾讯混元Hunyuan、零一万物(01)等等,提供统一的输入输出(对齐OpenAi)消除差异化,优化函数调用(Tool Call),优化RAG调用、支持向量数据库(Pinecone),并且支持JDK1.8,为用户提供快速整合AI的能力。
AI4J-GitHub
<dependency>
<groupId>io.github.lnyo-cly</groupId>
<artifactId>ai4j-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
3. 配置application.yml
在deepseek官网,创建API-KEY,然后在application.yml中配置
ai:
deepseek:
api-key: "sk-123456789"
4. 创建聊天服务Controller
接下来实现流式输出:
@RestController
public class OpenAiController {
// 注入Ai服务
@Autowired
private AiService aiService;
@GetMapping("/chatStream")
public SseEmitter getChatMessageStream(@RequestParam String question) {
SseEmitter emitter = new SseEmitter();
// 获取DEEPSEEK的聊天服务
IChatService chatService = aiService.getChatService(PlatformType.DEEPSEEK);
// 创建请求参数
ChatCompletion chatCompletion = ChatCompletion.builder()
.model("deepseek-chat")
.message(ChatMessage.withUser(question))
.build();
Executors.newSingleThreadExecutor().submit(() -> {
try {
SseListener sseListener = new SseListener() {
@Override
protected void send() {
try {
emitter.send(this.getCurrData());
System.out.println(this.getCurrData()); // 打印当前发送的内容
} catch (IOException e) {
emitter.completeWithError(e);
}
}
};
emitter.onCompletion(() -> {
System.out.println("完成");
sseListener.getEventSource().cancel();
});
// 发送流式数据
chatService.chatCompletionStream(chatCompletion, sseListener);
// 完成后关闭连接
emitter.complete();
} catch (Exception e) {
emitter.completeWithError(e);
}
});
return emitter;
}
}
或者
@GetMapping("/chatStream")
public SseEmitter getChatMessageStream(@RequestParam String question) throws Exception {
SseEmitter emitter = new SseEmitter();
// 获取OLLAMA的聊天服务
IChatService chatService = aiService.getChatService(PlatformType.DEEPSEEK);
// 创建请求参数
ChatCompletion chatCompletion = ChatCompletion.builder()
.model("deepseek-chat")
.message(ChatMessage.withUser(question))
.build();
SseListener sseListener = new SseListener() {
@Override
protected void send() {
try {
emitter.send(this.getCurrData());
System.out.println(this.getCurrData()); // 打印当前发送的内容
if ("[DONE]".equals(this.getCurrData())) {
emitter.complete();
}
} catch (IOException e) {
emitter.completeWithError(e);
}
}
};
emitter.onCompletion(() -> {
System.out.println("完成");
sseListener.getEventSource().cancel();
});
// 发送流式数据
sseListener.getCountDownLatch().countDown(); // 取消同步阻塞
chatService.chatCompletionStream(chatCompletion, sseListener);
return emitter;
}
测试流式接口如下:

5. 修改为ollama调用
@GetMapping("/chatStream")
public SseEmitter getChatMessageStream(@RequestParam String question) {
SseEmitter emitter = new SseEmitter(-1L);
// 获取OLLAMA的聊天服务
IChatService chatService = aiService.getChatService(PlatformType.OLLAMA);
// 创建请求参数
ChatCompletion chatCompletion = ChatCompletion.builder()
.model("deepseek-r1:1.5b")
.message(ChatMessage.withUser(question))
.build();
Executors.newSingleThreadExecutor().submit(() -> {
try {
SseListener sseListener = new SseListener() {
@Override
protected void send() {
try {
emitter.send(this.getCurrData());
System.out.println(this.getCurrData()); // 打印当前发送的内容
} catch (IOException e) {
emitter.completeWithError(e);
}
}
};
emitter.onCompletion(() -> {
System.out.println("完成");
sseListener.getEventSource().cancel();
});
// 发送流式数据
chatService.chatCompletionStream(chatCompletion, sseListener);
// 完成后关闭连接
emitter.complete();
} catch (Exception e) {
emitter.completeWithError(e);
}
});
return emitter;
}
修改两处即可:
- 修改
PlatformType为OLLAMA - 修改
model为deepseek-r1:1.5b

6. 搭建前端界面
注意:此前端界面由AI生成,并未经过严格测试,仅供参考。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI聊天助手</title>
<!-- Font Awesome CDN -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style> :root { --primary-color: #6366f1; --primary-light: #818cf8; --primary-dark: #4f46e5; --text-light: #ffffff; --text-dark: #1e293b; --bg-light: #f8fafc; --bg-dark: #0f172a; --message-user: #6366f1; --message-bot: #f1f5f9; --border-color: #e2e8f0; } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Inter', 'Arial', sans-serif; } body { background-color: var(--bg-light); color: var(--text-dark); display: flex; justify-content: center; align-items: center; min-height: 100vh; padding: 20px; } .chat-container { width: 100%; max-width: 900px; background: white; border-radius: 16px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05); overflow: hidden; display: flex; flex-direction: column; height: 85vh; position: relative; border: 1px solid var(--border-color); } .chat-header { background: var(--primary-color); color: var(--text-light); padding: 18px 24px; display: flex; align-items: center; gap: 12px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); z-index: 10; } .chat-header i { font-size: 1.5rem; } .chat-header h1 { font-size: 1.3rem; font-weight: 600; } .chat-messages { flex: 1; overflow-y: auto; padding: 24px; display: flex; flex-direction: column; gap: 20px; scroll-behavior: smooth; } .message-container { display: flex; gap: 12px; max-width: 85%; } .user-container { align-self: flex-end; flex-direction: row-reverse; } .bot-container { align-self: flex-start; } .avatar { width: 38px; height: 38px; border-radius: 50%; display: flex; justify-content: center; align-items: center; font-size: 1.2rem; flex-shrink: 0; } .user-avatar { background: var(--primary-light); color: var(--text-light); } .bot-avatar { background: var(--primary-dark); color: var(--text-light); } .message { padding: 14px 20px; border-radius: 18px; font-size: 1rem; line-height: 1.6; position: relative; max-width: 100%; } .user-message { background: var(--message-user); color: var(--text-light); border-top-right-radius: 4px; } .bot-message { background: var(--message-bot); color: var(--text-dark); border-top-left-radius: 4px; } .message-time { font-size: 0.7rem; opacity: 0.7; margin-top: 6px; text-align: right; } .user-message .message-time { color: rgba(255, 255, 255, 0.9); } .bot-message .message-time { color: rgba(0, 0, 0, 0.6); } .chat-input-container { padding: 16px 24px; background: white; border-top: 1px solid var(--border-color); display: flex; align-items: center; gap: 14px; z-index: 10; } .chat-input { flex: 1; padding: 14px 20px; border: 1px solid var(--border-color); border-radius: 30px; font-size: 1rem; outline: none; transition: all 0.3s; background: var(--bg-light); } .chat-input:focus { border-color: var(--primary-color); box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2); } .send-button { width: 50px; height: 50px; border: none; background: var(--primary-color); color: var(--text-light); border-radius: 50%; cursor: pointer; transition: all 0.3s; display: flex; justify-content: center; align-items: center; box-shadow: 0 2px 10px rgba(99, 102, 241, 0.3); } .send-button:hover { background: var(--primary-dark); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4); } .send-button:active { transform: translateY(0); } .send-button i { font-size: 1.2rem; } /* 滚动条样式 */ .chat-messages::-webkit-scrollbar { width: 6px; } .chat-messages::-webkit-scrollbar-track { background: transparent; } .chat-messages::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 10px; } .chat-messages::-webkit-scrollbar-thumb:hover { background: #9ca3af; } /* 打字机效果 */ .typing { display: flex; align-items: center; gap: 4px; padding: 8px 12px; border-radius: 18px; background: var(--message-bot); width: fit-content; } .typing-dot { width: 8px; height: 8px; background: var(--primary-color); border-radius: 50%; animation: typing-animation 1.4s infinite both; } .typing-dot:nth-child(2) { animation-delay: 0.2s; } .typing-dot:nth-child(3) { animation-delay: 0.4s; } @keyframes typing-animation { 0%, 100% { opacity: 0.3; transform: scale(0.8); } 50% { opacity: 1; transform: scale(1); } } /* 消息进入动画 */ @keyframes message-in { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .message-container { animation: message-in 0.3s ease-out forwards; } /* 响应式调整 */ @media (max-width: 768px) { .chat-container { height: 90vh; border-radius: 12px; } .message-container { max-width: 90%; } .chat-header h1 { font-size: 1.1rem; } } @media (max-width: 480px) { .chat-container { height: 92vh; border-radius: 8px; } .message { padding: 12px 16px; } .avatar { width: 32px; height: 32px; font-size: 1rem; } .chat-input { padding: 12px 16px; } .send-button { width: 45px; height: 45px; } .chat-header { padding: 14px 20px; } .chat-messages { padding: 20px; } } </style>
</head>
<body>
<div class="chat-container">
<div class="chat-header">
<i class="fas fa-robot"></i>
<h1>AI聊天助手</h1>
</div>
<div class="chat-messages" id="chat-messages">
<div class="message-container bot-container">
<div class="avatar bot-avatar">
<i class="fas fa-robot"></i>
</div>
<div class="message-content">
<div class="message bot-message">
您好!我是AI助手,很高兴为您服务。请问有什么我可以帮助您的吗?
</div>
<div class="message-time">
刚刚
</div>
</div>
</div>
</div>
<div class="chat-input-container">
<input type="text" class="chat-input" id="user-input" placeholder="输入您的问题..." autofocus>
<button class="send-button" id="send-button">
<i class="fas fa-paper-plane"></i>
</button>
</div>
</div>
<script> document.addEventListener('DOMContentLoaded', function() { const chatMessages = document.getElementById('chat-messages'); const userInput = document.getElementById('user-input'); const sendButton = document.getElementById('send-button'); let eventSource = null; // 获取当前时间 function getCurrentTime() { const now = new Date(); let hours = now.getHours(); let minutes = now.getMinutes(); // 确保分钟为两位数 minutes = minutes < 10 ? '0' + minutes : minutes; return `${ hours}:${ minutes}`; } // 添加用户消息 function addUserMessage(message, time) { const messageContainer = document.createElement('div'); messageContainer.className = 'message-container user-container'; const avatarDiv = document.createElement('div'); avatarDiv.className = 'avatar user-avatar'; avatarDiv.innerHTML = '<i class="fas fa-user"></i>'; const messageContentDiv = document.createElement('div'); messageContentDiv.className = 'message-content'; const messageDiv = document.createElement('div'); messageDiv.className = 'message user-message'; messageDiv.textContent = message; const timeDiv = document.createElement('div'); timeDiv.className = 'message-time'; timeDiv.textContent = time; messageContentDiv.appendChild(messageDiv); messageContentDiv.appendChild(timeDiv); messageContainer.appendChild(avatarDiv); messageContainer.appendChild(messageContentDiv); chatMessages.appendChild(messageContainer); chatMessages.scrollTop = chatMessages.scrollHeight; } // 发送消息函数 function sendMessage() { const message = userInput.value.trim(); if (!message) return; // 添加用户消息到聊天区域 const time = getCurrentTime(); addUserMessage(message, time); // 清空输入框 userInput.value = ''; // 添加机器人正在输入的指示 const typingContainer = document.createElement('div'); typingContainer.className = 'message-container bot-container'; typingContainer.id = 'bot-typing'; const avatarDiv = document.createElement('div'); avatarDiv.className = 'avatar bot-avatar'; avatarDiv.innerHTML = '<i class="fas fa-robot"></i>'; const typingDiv = document.createElement('div'); typingDiv.className = 'typing'; typingDiv.innerHTML = '<span class="typing-dot"></span><span class="typing-dot"></span><span class="typing-dot"></span>'; typingContainer.appendChild(avatarDiv); typingContainer.appendChild(typingDiv); chatMessages.appendChild(typingContainer); chatMessages.scrollTop = chatMessages.scrollHeight; // 更改按钮为暂停 sendButton.innerHTML = '<i class="fas fa-pause"></i>'; sendButton.onclick = stopStream; // 创建EventSource连接 const url = `http://127.0.0.1:8080/chatStream?question=${ encodeURIComponent(message)}`; eventSource = new EventSource(url); let botResponse = ''; let responseContainer = null; eventSource.onmessage = function(event) { // 如果这是第一条消息,创建回复容器 if (!responseContainer) { // 移除打字指示器 const typingIndicator = document.getElementById('bot-typing'); if (typingIndicator) { typingIndicator.remove(); } // 创建回复容器 responseContainer = document.createElement('div'); responseContainer.className = 'message-container bot-container'; responseContainer.id = 'current-bot-response'; const avatarDiv = document.createElement('div'); avatarDiv.className = 'avatar bot-avatar'; avatarDiv.innerHTML = '<i class="fas fa-robot"></i>'; const messageContentDiv = document.createElement('div'); messageContentDiv.className = 'message-content'; const messageDiv = document.createElement('div'); messageDiv.className = 'message bot-message'; messageDiv.id = 'current-bot-message'; const timeDiv = document.createElement('div'); timeDiv.className = 'message-time'; timeDiv.textContent = getCurrentTime(); messageContentDiv.appendChild(messageDiv); messageContentDiv.appendChild(timeDiv); responseContainer.appendChild(avatarDiv); responseContainer.appendChild(messageContentDiv); chatMessages.appendChild(responseContainer); } // 更新回复内容 botResponse += event.data; const messageDiv = document.getElementById('current-bot-message'); if (messageDiv) { messageDiv.textContent = botResponse; } // 自动滚动到底部 chatMessages.scrollTop = chatMessages.scrollHeight; }; eventSource.onerror = function() { // 处理完成或错误时 completeResponse(); }; } // 停止流式响应 function stopStream() { if (eventSource) { eventSource.close(); completeResponse(); } } // 完成响应处理 function completeResponse() { // 关闭连接 if (eventSource) { eventSource.close(); eventSource = null; } // 移除打字指示器 const typingIndicator = document.getElementById('bot-typing'); if (typingIndicator) { typingIndicator.remove(); } // 恢复发送按钮 sendButton.innerHTML = '<i class="fas fa-paper-plane"></i>'; sendButton.onclick = sendMessage; // 移除id,以便下次使用 const currentBotResponse = document.getElementById('current-bot-response'); if (currentBotResponse) { currentBotResponse.removeAttribute('id'); } const currentBotMessage = document.getElementById('current-bot-message'); if (currentBotMessage) { currentBotMessage.removeAttribute('id'); } } // 设置事件监听器 sendButton.addEventListener('click', sendMessage); userInput.addEventListener('keydown', function(e) { if (e.key === 'Enter') { sendMessage(); } }); // 自动聚焦到输入框 userInput.focus(); }); </script>
</body>
</html>

7. 多轮对话
只需要简单修改,即可携带历史上下文进行对话:
private List<ChatMessage> history = new ArrayList<>(); // 1. 创建历史消息列表
@GetMapping("/chatStream")
public SseEmitter getChatMessageStream(@RequestParam String question) {
// ......
history.add(ChatMessage.withUser(question)); // 2. 向历史中添加用户输入
// 创建请求参数
ChatCompletion chatCompletion = ChatCompletion.builder()
.model("deepseek-chat")
.messages(history) // 3. 添加完整历史消息
.build();
// ......
Executors.newSingleThreadExecutor().submit(() -> {
try {
// ......
emitter.onCompletion(() -> {
System.out.println("完成");
history.add(ChatMessage.withAssistant(sseListener.getOutput().toString())); // 4. 向历史中添加AI回复
sseListener.getEventSource().cancel();
});
// ......
} catch (Exception e) {
emitter.completeWithError(e);
}
});
// ......
}

8. 联网对话
- 配置
application.yml中的searxng,将其中的url替换为你已经部署的searxng服务的地址。
ai:
websearch:
searxng:
url: "http://127.0.0.1:29080/search"
nums: 10
- 修改代码,添加联网对话的功能:
@GetMapping("/chatStream")
public SseEmitter getChatMessageStream(@RequestParam String question) {
// ......
// 获取DEEPSEEK的聊天服务
IChatService chatService = aiService.webSearchEnhance(aiService.getChatService(PlatformType.DEEPSEEK)); // 1. 使用webSearchEnhance对原本chat服务增加联网功能,该联网服务使用的为searxng
// ......
// ......
}

9. 搭建知识库
本文使用的向量数据库为Pinecone
9.1 创建Pinecone
大家可以进入Pinecone官网进行注册和登录,至于注册账号,这里不在演示,相信大家都会。
选择Database->Indexes->Create Index来创建索引

在这里可以输入你的维度,或者点击Setup by model,根据模型来选择向量维度。这里我以text-embedding-3-large模型为例子
![图片[2]-Java快速接入DeepSeek实现流式、联网、知识库以及多轮问答 - 拾光赋-拾光赋](https://i0.wp.com/i-blog.csdnimg.cn/img_convert/42aa37d41f510adc9dcdae68a54adda2.png)

创建完成后,记录自己的Host,我们后面要用到

创建自己的API Key

9.2 配置application.yml
请将上文得到的Host和API Key填入application.yml中
ai:
vector:
pinecone:
host: "https://XXXXXXX-XXXXXXXXX.io"
key: "XXXXXX"
9.3 构建RAG知识库文档
既然要建立RAG应用,那肯定少不了知识库。
本文搭建的是一个简单的法律AI助手,所以我们需要一个法律知识库。
接下来我以刑法知识库为例为大家讲解
可以将所需要的知识库,存入一个文本文档当中:

注意:如果有现成的文档,你也可以忽略这一步,例如你已经有了txt、word、pdf等文件的知识库文档。
9.4 存储至Pinecone向量数据库中
@SpringBootTest
public class RagTest {
// 1. 注入Pinecone服务
@Autowired
private PineconeService pineconeService;
// 2. 注入AI服务
@Autowired
private AiService aiService;
@Test
public void test_rag_store() throws Exception {
// 3. 获取Embedding服务
IEmbeddingService embeddingService = aiService.getEmbeddingService(PlatformType.OPENAI);
// 4. Tika读取file文件内容
String fileContent = TikaUtil.parseFile(new File("D:\\data\\test.txt"));
System.out.println(fileContent);
// 5. 分割文本内容
RecursiveCharacterTextSplitter recursiveCharacterTextSplitter = new RecursiveCharacterTextSplitter(1000, 200);
List<String> contentList = recursiveCharacterTextSplitter.splitText(fileContent);
System.out.println(contentList.size());
// 6. 转为向量
Embedding build = Embedding.builder()
.input(contentList)
.model("text-embedding-3-large")
.build();
EmbeddingResponse embedding = embeddingService.embedding(build);
List<List<Float>> vectors = embedding.getData().stream().map(EmbeddingObject::getEmbedding).collect(Collectors.toList());
VertorDataEntity vertorDataEntity = new VertorDataEntity();
vertorDataEntity.setVector(vectors);
vertorDataEntity.setContent(contentList);
System.out.println(vertorDataEntity);
// 7. 向量存储至pinecone
Integer count = pineconeService.insert(vertorDataEntity, "abc-123-abc");
System.out.println(count > 0 ? "存储成功" : "存储失败");
}
}
下图是插入成功的数据

9.5 知识库查询
下面代码只多了对embedding的处理,chat部分基本不变。
@Autowired
private AiService aiService;
@Autowired
private PineconeService pineconeService;
private List<ChatMessage> history = new ArrayList<>();
@GetMapping("/chatStream")
public SseEmitter getChatMessageStream(@RequestParam String question) throws Exception {
SseEmitter emitter = new SseEmitter(-1L);
// 获取Embedding服务
IEmbeddingService embeddingService = aiService.getEmbeddingService(PlatformType.OPENAI);
// 构建要查询的问题,转为向量
Embedding build = Embedding.builder()
.input(question)
.model("text-embedding-3-large")
.build();
EmbeddingResponse embedding = embeddingService.embedding(build);
List<Float> questionEmbedding = embedding.getData().get(0).getEmbedding();
// 构建向量数据库的查询对象
PineconeQuery pineconeQueryReq = PineconeQuery.builder()
.namespace("abc-123-abc")
.topK(5)
.vector(questionEmbedding)
.build();
// 查询
// PineconeQueryResponse queryResponse = pineconeService.query(pineconeQueryReq);
// delimiter为想用什么字符拼接查询出来的内容
String retrievalContent = pineconeService.query(pineconeQueryReq, " ");
String contentFormat = "你是一个善于回答中华人民共和国刑法相关问题的助手。请使用以下提供的检索内容和自身知识来回答问题。如果你不知道答案,请直接说不知道,不要杜撰答案。请用三句话以内回答,保持简洁。\n" +
"\n" +
"问题:%s\n" +
"\n" +
"检索内容:%s";
String content = String.format(contentFormat, question, retrievalContent);
// 获取DEEPSEEK的聊天服务
IChatService chatService = aiService.getChatService(PlatformType.DEEPSEEK);
history.add(ChatMessage.withUser(content)); // 向历史中添加用户输入
// 创建请求参数
ChatCompletion chatCompletion = ChatCompletion.builder()
.model("deepseek-chat")
.messages(history) // 添加完整历史消息
.build();
Executors.newSingleThreadExecutor().submit(() -> {
try {
SseListener sseListener = new SseListener() {
@Override
protected void send() {
try {
emitter.send(this.getCurrStr());
System.out.println(this.getCurrData()); // 打印当前发送的内容
} catch (IOException e) {
emitter.completeWithError(e);
}
}
};
emitter.onCompletion(() -> {
System.out.println("完成");
history.add(ChatMessage.withAssistant(sseListener.getOutput().toString())); // 向历史中添加AI回复
sseListener.getEventSource().cancel();
});
// 发送流式数据
chatService.chatCompletionStream(chatCompletion, sseListener);
// 完成后关闭连接
emitter.complete();
} catch (Exception e) {
emitter.completeWithError(e);
}
});
return emitter;
}



![表情[baoquan]-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/smilies/baoquan.gif)


暂无评论内容