本次落地基于 Java 生态的主流技术栈(Spring Boot 3.x),遵循 "接口验证→知识库向量化→多轮问答核心逻辑→前端集成→测试上线" 的思路,聚焦 "精准问答 + 多轮上下文保留 + 低成本适配现有官网",以下是可直接落地的实操步骤:
一、前期准备(1-2 天完成)
1. 资源与环境准备
表格
| 类别 | 具体内容 |
|---|---|
| API 密钥 | 登录DeepSeek 开发者平台,创建应用,获取API Key(注意开通 "DeepSeek-R1" 和 "Embedding" 接口权限) |
| 技术栈选型 | 后端:Java 17+、Spring Boot 3.2.x(轻量易集成)、Maven(依赖管理)、OkHttp(HTTP 请求)、Chroma Java 客户端(向量数据库);前端:原生 JS(适配任意官网技术栈);向量数据库:Chroma(轻量级,支持 Java 客户端) |
| 数据准备 | 整理官网知识库(产品 FAQ、售后政策等),拆分为 200-500 字的语义完整短片段,清洗冗余内容 |
| 环境依赖 | Maven 核心依赖(添加到pom.xml):```xml |
XML
<dependencies><!-- Spring Boot Web核心 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- OkHttp HTTP客户端 --><dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>4.12.0</version></dependency><!-- Jackson JSON解析 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency><!-- Chroma Java客户端 --><dependency><groupId>io.github.chroma-ai</groupId><artifactId>chroma-java</artifactId><version>1.4.0</version></dependency><!-- 环境变量配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency></dependencies>
2. 核心需求确认
- 多轮问答:保留对话历史,支持追问(如 "产品 A 保修期多久?"→"保修期内维修收费吗?");
- 回答边界:仅基于企业知识库回答,无相关内容时返回预设兜底话术;
- 性能要求:单轮响应时间 < 1.5s,支持 10-20 并发请求。
二、核心落地步骤(3-5 天完成)
步骤 1:DeepSeek-R1 API 对接验证(Java 版)
先验证 API 连通性,确保 Java 端能正常调用 DeepSeek 接口,避免后续开发踩坑。
1.1 配置文件管理(避免硬编码 API Key)
在src/main/resources/application.yml中添加配置:
html
deepseek:
api-key: 你的DeepSeek API Key
chat-url: https://api.deepseek.com/v1/chat/completions
embedding-url: https://api.deepseek.com/v1/embeddings
chroma:
collection-name: official_website_kb
persist-path: ./chroma_db # 向量库本地存储路径
1.2 API 调用工具类(通用 HTTP 请求)
创建src/main/java/com/company/chatbot/util/DeepSeekApiUtil.java:
java
package com.company.chatbot.util;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Map;
@Component
public class DeepSeekApiUtil {
@Value("${deepseek.api-key}")
private String apiKey;
private final OkHttpClient okHttpClient = new OkHttpClient();
private final ObjectMapper objectMapper = new ObjectMapper();
/**
* 通用调用DeepSeek API的方法
*/
public JsonNode callApi(String url, Map<String, Object> requestBody) throws IOException {
// 构建请求头
RequestHeaders headers = new RequestHeaders.Builder()
.add("Content-Type", "application/json")
.add("Authorization", "Bearer " + apiKey)
.build();
// 转换请求体为JSON
String jsonBody = objectMapper.writeValueAsString(requestBody);
RequestBody body = RequestBody.create(jsonBody, MediaType.parse("application/json"));
// 发送请求
Request request = new Request.Builder()
.url(url)
.headers(headers)
.post(body)
.build();
try (Response response = okHttpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("API调用失败:" + response);
}
// 解析响应
return objectMapper.readTree(response.body().string());
}
}
}
1.3 对话接口测试(验证连通性)
创建测试类src/test/java/com/company/chatbot/DeepSeekApiTest.java:
java
package com.company.chatbot;
import com.company.chatbot.util.DeepSeekApiUtil;
import com.fasterxml.jackson.databind.JsonNode;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.HashMap;
import java.util.Map;
@SpringBootTest
public class DeepSeekApiTest {
@Autowired
private DeepSeekApiUtil deepSeekApiUtil;
@Value("${deepseek.chat-url}")
private String chatUrl;
@Test
public void testChatApi() throws Exception {
// 构建请求参数
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("model", "deepseek-r1");
requestBody.put("temperature", 0.1);
// 对话消息(单轮测试)
Map<String, String> message = new HashMap<>();
message.put("role", "user");
message.put("content", "你们公司的核心产品是什么?");
requestBody.put("messages", new Object[]{message});
// 调用API
JsonNode response = deepSeekApiUtil.callApi(chatUrl, requestBody);
// 输出回答
String answer = response.get("choices").get(0).get("message").get("content").asText();
System.out.println("AI回答:" + answer);
}
}
预期结果:控制台输出 DeepSeek-R1 的回答,验证接口调用成功。
步骤 2:知识库构建与向量化对接(核心)
通过 DeepSeek Embedding 接口将知识库文本转为向量,存入 Chroma 向量库,实现 "相似问题检索"(多轮问答精准性的核心)。
2.1 向量数据库初始化工具类
创建src/main/java/com/company/chatbot/util/ChromaUtil.java:
java
package com.company.chatbot.util;
import io.github.chroma-ai.ChromaClient;
import io.github.chroma-ai.Collection;
import io.github.chroma-ai.beans.Embedding;
import io.github.chroma-ai.utils.CollectionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class ChromaUtil {
@Value("${chroma.collection-name}")
private String collectionName;
@Value("${chroma.persist-path}")
private String persistPath;
private ChromaClient chromaClient;
private Collection collection;
// 初始化Chroma客户端和集合
public void init() {
this.chromaClient = new ChromaClient.Builder()
.persist(persistPath) // 本地持久化
.build();
// 获取/创建集合
this.collection = chromaClient.getOrCreateCollection(collectionName);
}
// 导入知识库文本到向量库
public void importKnowledgeBase(List<String> texts, List<String> ids, List<List<Float>> embeddings) {
init();
collection.add(ids, texts, embeddings, null);
}
// 检索相似文本
public List<String> retrieveSimilarTexts(List<Float> queryEmbedding, int topK) {
init();
// 向量检索
Embedding embedding = CollectionUtils.embedding(queryEmbedding);
List<Embedding> queryEmbeddings = List.of(embedding);
// 获取最相似的topK条
return collection.query(queryEmbeddings, null, topK).getDocuments().get(0);
}
}
2.2 Embedding 接口调用(文本转向量)
在DeepSeekApiUtil.java中新增方法:
java
/**
* 调用Embedding接口,将文本转为向量
*/
public List<Float> getEmbedding(String text) throws IOException {
String url = "https://api.deepseek.com/v1/embeddings";
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("model", "text-embedding-ada-002");
requestBody.put("input", text);
JsonNode response = callApi(url, requestBody);
// 提取向量
JsonNode embeddingNode = response.get("data").get(0).get("embedding");
return objectMapper.convertValue(embeddingNode, List.class);
}
2.3 知识库导入服务(首次执行)
创建src/main/java/com/company/chatbot/service/KnowledgeBaseService.java:
java
package com.company.chatbot.service;
import com.company.chatbot.util.ChromaUtil;
import com.company.chatbot.util.DeepSeekApiUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Service
public class KnowledgeBaseService {
@Autowired
private DeepSeekApiUtil deepSeekApiUtil;
@Autowired
private ChromaUtil chromaUtil;
/**
* 导入知识库到向量库(仅首次执行,更新知识库时重新调用)
*/
public void importKB() throws IOException {
// 示例知识库(替换为企业实际内容)
List<String> texts = List.of(
"产品A的系统要求:支持Windows 10及以上系统。",
"产品A的保修政策:保修期2年,支持全国联保。",
"产品A的售后联系方式:售后电话400-XXX-XXXX。"
);
List<String> ids = List.of("kb_001", "kb_002", "kb_003");
List<List<Float>> embeddings = new ArrayList<>();
// 批量转换为向量
for (String text : texts) {
embeddings.add(deepSeekApiUtil.getEmbedding(text));
}
// 导入向量库
chromaUtil.importKnowledgeBase(texts, ids, embeddings);
System.out.println("知识库导入成功,共" + texts.size() + "条");
}
}
✅ 执行方式:可通过 Test 类调用importKB()方法,完成知识库初始化。
步骤 3:多轮问答核心逻辑开发(Spring Boot 后端)
核心流程:用户提问 → 检索相似知识库 → 拼接上下文 → 调用 DeepSeek-R1 → 保留对话历史 → 返回回答。
3.1 实体类定义(请求 / 响应)
5.2 上线部署
三、上线后运维与优化
总结
这套方案完全适配有基础 Java 开发团队的企业,代码可直接复用,仅需替换知识库内容和 API Key 即可落地,中小企业月度 API 调用成本通常低于 1000 元,且能无缝集成到现有官网系统。
-
对话消息实体:
src/main/java/com/company/chatbot/entity/ChatMessage.javajavapackage com.company.chatbot.entity; import lombok.Data; @Data public class ChatMessage { // 角色:user/assistant private String role; // 消息内容 private String content; }问答请求实体:
src/main/java/com/company/chatbot/entity/ChatRequest.javajavapackage com.company.chatbot.entity; import lombok.Data; import java.util.List; @Data public class ChatRequest { // 用户当前问题 private String question; // 对话历史(多轮问答核心) private List<ChatMessage> history; }java问答响应实体:src/main/java/com/company/chatbot/entity/ChatResponse.javajavapackage com.company.chatbot.entity; import lombok.Data; import java.util.List; @Data public class ChatResponse { private int code; private String msg; private DataDTO data; @Data public static class DataDTO { // AI回答 private String answer; // 更新后的对话历史 private List<ChatMessage> history; } }java3.2 多轮问答核心服务 创建src/main/java/com/company/chatbot/service/ChatBotService.java:javapackage com.company.chatbot.service; import com.company.chatbot.entity.ChatMessage; import com.company.chatbot.util.ChromaUtil; import com.company.chatbot.util.DeepSeekApiUtil; import com.fasterxml.jackson.databind.JsonNode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Service public class ChatBotService { @Autowired private DeepSeekApiUtil deepSeekApiUtil; @Autowired private ChromaUtil chromaUtil; @Value("${deepseek.chat-url}") private String chatUrl; // 兜底话术 private static final String DEFAULT_ANSWER = "暂无相关答案,可联系人工客服:400-XXX-XXXX"; /** * 多轮问答核心方法 */ public ChatResponse.DataDTO chat(String question, List<ChatMessage> history) throws IOException { // 1. 检索相似知识库片段(topK=3,平衡精准性和效率) List<Float> queryEmbedding = deepSeekApiUtil.getEmbedding(question); List<String> similarTexts = chromaUtil.retrieveSimilarTexts(queryEmbedding, 3); // 2. 拼接上下文(限定AI仅基于知识库回答) String context = similarTexts.isEmpty() ? "" : String.join("\n", similarTexts); String prompt = String.format("请基于以下企业官网知识库内容回答用户问题,仅使用知识库中的信息,不要编造内容。\n知识库内容:\n%s\n\n用户问题:%s", context, question); // 3. 构建多轮对话消息(历史+当前prompt) List<ChatMessage> messages = history == null ? new ArrayList<>() : new ArrayList<>(history); ChatMessage currentMessage = new ChatMessage(); currentMessage.setRole("user"); currentMessage.setContent(prompt); messages.add(currentMessage); // 4. 调用DeepSeek-R1接口 Map<String, Object> requestBody = new HashMap<>(); requestBody.put("model", "deepseek-r1"); requestBody.put("temperature", 0.1); // 低温度保证回答精准 requestBody.put("max_tokens", 500); // 限制回答长度 requestBody.put("messages", messages); JsonNode response = deepSeekApiUtil.callApi(chatUrl, requestBody); String answer = response.get("choices").get(0).get("message").get("content").asText(); // 5. 兜底逻辑:无知识库内容时返回预设话术 if (context.isEmpty()) { answer = DEFAULT_ANSWER; } // 6. 更新对话历史(添加AI回答) ChatMessage aiMessage = new ChatMessage(); aiMessage.setRole("assistant"); aiMessage.setContent(answer); messages.add(aiMessage); // 7. 封装返回数据 ChatResponse.DataDTO dataDTO = new ChatResponse.DataDTO(); dataDTO.setAnswer(answer); dataDTO.setHistory(messages); return dataDTO; } }java3.3 暴露 HTTP 接口(供前端调用) 创建src/main/java/com/company/chatbot/controller/ChatBotController.java:javapackage com.company.chatbot.controller; import com.company.chatbot.entity.ChatRequest; import com.company.chatbot.entity.ChatResponse; import com.company.chatbot.service.ChatBotService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; @RestController @RequestMapping("/api/chat") public class ChatBotController { @Autowired private ChatBotService chatBotService; @PostMapping public ChatResponse chat(@RequestBody ChatRequest request) { ChatResponse response = new ChatResponse(); try { ChatResponse.DataDTO data = chatBotService.chat(request.getQuestion(), request.getHistory()); response.setCode(200); response.setMsg("success"); response.setData(data); } catch (IOException e) { response.setCode(500); response.setMsg("系统异常:" + e.getMessage()); ChatResponse.DataDTO errorData = new ChatResponse.DataDTO(); errorData.setAnswer("系统暂时无法回答,请稍后再试"); errorData.setHistory(request.getHistory()); response.setData(errorData); } return response; } }java3.4 跨域配置(解决前端跨域问题) 创建src/main/java/com/company/chatbot/config/CorsConfig.java:javapackage com.company.chatbot.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; @Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); // 允许所有域名(生产环境需指定企业官网域名) config.addAllowedOriginPattern("*"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }步骤 4:前端集成(原生 JS,支持多轮问答)
开发轻量级问答组件,嵌入官网侧边栏 / 弹窗,核心是保留对话历史并传递给后端:
html<!-- 官网多轮问答组件(可嵌入官网任意位置) --> <div id="chatbot-container" style="position: fixed; bottom: 20px; right: 20px; width: 380px; border: 1px solid #eee; border-radius: 8px; background: #fff; box-shadow: 0 2px 10px rgba(0,0,0,0.1);"> <div style="padding: 12px; background: #007bff; color: #fff; border-radius: 8px 8px 0 0; font-size: 16px;">官网智能问答</div> <!-- 对话历史展示区 --> <div id="chat-history" style="height: 320px; padding: 10px; overflow-y: auto; border-bottom: 1px solid #eee;"></div> <!-- 输入区 --> <div style="padding: 10px; display: flex; gap: 8px;"> <input type="text" id="question-input" placeholder="请输入您的问题(支持追问)..." style="flex: 1; padding: 8px; border: 1px solid #eee; border-radius: 4px; outline: none;"> <button id="send-btn" style="padding: 8px 18px; background: #007bff; color: #fff; border: none; border-radius: 4px; cursor: pointer;">发送</button> </div> </div> <script> // 全局变量:存储多轮对话历史(核心) let chatHistory = []; // 后端API地址(生产环境替换为企业服务器域名) const API_URL = "http://localhost:8080/api/chat"; /** * 发送消息并处理多轮对话 */ async function sendMessage() { const inputEl = document.getElementById("question-input"); const question = inputEl.value.trim(); if (!question) return; // 1. 显示用户问题到聊天窗口 const historyEl = document.getElementById("chat-history"); historyEl.innerHTML += `<div style="margin: 8px 0;"><strong>您:</strong>${question}</div>`; inputEl.value = ""; try { // 2. 调用后端API(传递问题+对话历史) const response = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ question: question, history: chatHistory }) }); const result = await response.json(); if (result.code === 200) { // 3. 显示AI回答 historyEl.innerHTML += `<div style="margin: 8px 0;"><strong>智能客服:</strong>${result.data.answer}</div>`; // 4. 更新全局对话历史(用于下一轮追问) chatHistory = result.data.history; // 5. 滚动到聊天窗口底部 historyEl.scrollTop = historyEl.scrollHeight; } else { historyEl.innerHTML += `<div style="margin: 8px 0; color: red;"><strong>智能客服:</strong>${result.msg}</div>`; } } catch (e) { historyEl.innerHTML += `<div style="margin: 8px 0; color: red;"><strong>智能客服:</strong>网络异常,请稍后再试</div>`; } } // 绑定发送按钮点击事件 document.getElementById("send-btn").addEventListener("click", sendMessage); // 绑定回车发送 document.getElementById("question-input").addEventListener("keypress", (e) => { if (e.key === "Enter") sendMessage(); }); </script>步骤 5:测试与上线
5.1 测试要点(聚焦多轮问答)
-
功能测试:测试多轮追问(如 "产品 A 保修期多久?"→"保修期内维修收费吗?"),验证历史上下文是否保留;
-
精准性测试:覆盖所有知识库问题,验证回答仅来自知识库,无编造内容;
-
边界测试:测试无相关内容的问题、超长问题、特殊字符问题;
-
性能测试:用 Postman 模拟 20 并发请求,确保响应时间 < 1.5s。
-
后端部署:将 Spring Boot 项目打包为 JAR 包(
mvn clean package),部署到企业服务器 / 云服务器(如阿里云 ECS),配置 JDK 17 环境,启动命令:java -jar chatbot-0.0.1-SNAPSHOT.jar; -
前端集成:将问答组件代码嵌入官网公共模板(如 header/footer),替换 API_URL 为服务器域名;
-
监控配置:在后端添加日志(记录用户问题、API 调用次数、错误信息),便于后续优化。
-
知识库迭代:更新知识库时,重新调用
KnowledgeBaseService.importKB()方法,覆盖 / 新增向量库内容; -
成本控制:在 DeepSeek 开发者平台监控 API 调用量,设置月度调用阈值,避免超额;
-
多轮体验优化:分析用户高频追问场景,优化知识库文本拆分(提升检索准确率),调整
temperature参数(0.1-0.3 为宜); -
性能优化:生产环境可将 Chroma 向量库部署为独立服务,而非本地存储,提升检索效率。
-
核心技术栈:后端基于 Spring Boot 3.x+OkHttp 实现 DeepSeek API 调用,Chroma Java 客户端完成向量检索,前端通过原生 JS 保留多轮对话历史;
-
多轮问答关键:后端接收并传递
history(对话历史)参数,前端全局存储历史数据,确保追问上下文连贯; -
落地核心:知识库的 "短文本拆分 + 向量检索" 是保证回答精准的关键,需按语义拆分文本(200-500 字 / 段),并调试
topK值(建议 3-5)。