> 技术文档 > Java快速接入DeepSeek实现流式、联网、知识库以及多轮问答

Java快速接入DeepSeek实现流式、联网、知识库以及多轮问答


Java快速接入DeepSeek实现流式、联网、知识库以及多轮问答

本文将详细的说明,如何使用Java、JDK8快速接入deepseek的聊天服务,包含官方的API服务,以及本地Ollama的服务。并搭建一个简单的前端界面,用于流式输出、多轮问答、联网、知识库问答的效果展示。

1. 创建spring boot应用

Java快速接入DeepSeek实现流式、联网、知识库以及多轮问答
Java快速接入DeepSeek实现流式、联网、知识库以及多轮问答

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中配置

DeepSeek-APIKEY

ai: deepseek: api-key: \"sk-123456789\"

4. 创建聊天服务Controller

接下来实现流式输出:

@RestControllerpublic 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; }

测试流式接口如下:

Java快速接入DeepSeek实现流式、联网、知识库以及多轮问答

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; }

修改两处即可:

  • 修改PlatformTypeOLLAMA
  • 修改modeldeepseek-r1:1.5b

Java快速接入DeepSeek实现流式、联网、知识库以及多轮问答

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>  <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 = \'\'; 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 = \'\'; const typingDiv = document.createElement(\'div\'); typingDiv.className = \'typing\'; typingDiv.innerHTML = \'\'; typingContainer.appendChild(avatarDiv); typingContainer.appendChild(typingDiv); chatMessages.appendChild(typingContainer); chatMessages.scrollTop = chatMessages.scrollHeight; // 更改按钮为暂停 sendButton.innerHTML = \'\'; 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 = \'\';  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 = \'\'; 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>

Java快速接入DeepSeek实现流式、联网、知识库以及多轮问答

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); } }); // ...... }

Java快速接入DeepSeek实现流式、联网、知识库以及多轮问答

8. 联网对话

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

Java快速接入DeepSeek实现流式、联网、知识库以及多轮问答

9. 搭建知识库

本文使用的向量数据库为Pinecone

9.1 创建Pinecone

大家可以进入Pinecone官网进行注册和登录,至于注册账号,这里不在演示,相信大家都会。

选择Database->Indexes->Create Index来创建索引
Java快速接入DeepSeek实现流式、联网、知识库以及多轮问答

在这里可以输入你的维度,或者点击Setup by model,根据模型来选择向量维度。这里我以text-embedding-3-large模型为例子
Java快速接入DeepSeek实现流式、联网、知识库以及多轮问答
Java快速接入DeepSeek实现流式、联网、知识库以及多轮问答

创建完成后,记录自己的Host,我们后面要用到
Java快速接入DeepSeek实现流式、联网、知识库以及多轮问答

创建自己的API Key
Java快速接入DeepSeek实现流式、联网、知识库以及多轮问答

9.2 配置application.yml

请将上文得到的HostAPI Key填入application.yml

ai: vector: pinecone: host: \"https://XXXXXXX-XXXXXXXXX.io\" key: \"XXXXXX\"

9.3 构建RAG知识库文档

既然要建立RAG应用,那肯定少不了知识库。

本文搭建的是一个简单的法律AI助手,所以我们需要一个法律知识库。

接下来我以刑法知识库为例为大家讲解

可以将所需要的知识库,存入一个文本文档当中:
Java快速接入DeepSeek实现流式、联网、知识库以及多轮问答

注意:如果有现成的文档,你也可以忽略这一步,例如你已经有了txt、word、pdf等文件的知识库文档。

9.4 存储至Pinecone向量数据库中

@SpringBootTestpublic 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 ? \"存储成功\" : \"存储失败\"); }}

下图是插入成功的数据
Java快速接入DeepSeek实现流式、联网、知识库以及多轮问答

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; }

Java快速接入DeepSeek实现流式、联网、知识库以及多轮问答