仿DeepSeek AI问答系统完整版(带RAG本地知识库+联网搜索+深度思考) +springboot+vue3

今天教大家如何设计一个企业级的 deepseek问答 一样的系统 , 基于目前主流的技术:前端vue3,后端springboot。同时还带来的项目的部署教程

系统的核心功能

1. 支持本地上传文档知识库,RAG技术。 支持的文档有txt,doc,docx,pdf 。

2. 支持联网搜索。

3. 支持深度思考。

4. 支持历史上下文消息。

5.支持websocket流式。

6. 支持用户登录,注册。

7. 支持会话管理。

系统需要的组件

ElasticSearch8 : 存储知识库文档向量。

redis: 存储系统用的消息缓存。

mysql8: 存储关系型表。

技术栈

JDK11 + SpringBoot + VUE3

视频演示

仿DeepSeek AI问答系统完整版

图片演示

系统实现

RAG技术实现

RAG是一种结合 信息检索(Retrieval) 和 文本生成(Generation) 的 AI 技术,主要用于提升大语言模型(LLM)生成内容的准确性和时效性。

下面来介绍下RAG实现的核心步骤。

文本提取分块

用户上传文档时, 首选需要将文档解析成很多文本块, 系统通过 DocmentChunkParser 接口的 textChunks 方法 将传入 的文档(txt,docx,pdf)解析成文本块,代码:

public interface DocmentChunkParser {// 每个文本块的最大字符数public static final int CHUNK_SIZE = 1000;// 文本块之间的重叠字符数public static final int CHUNK_OVERLAP = 200;List<String> textChunks();
}

对应的TXT实现:

 对应的WORD实现:

  对应的PDF实现:

embeddings阶段,文本转向量

拿到文本块后, 需要将文本转换成向量, 也就是 embeddings阶段。系统采用的是 “阿里云百炼” 平台的 向量模型:

用户需要自己申请api的key。 到阿里百炼平台申请就行了。

向量数据库存储+检索

将文本转成向量后,需要存储到向量数据库里面,这里我选择的是elasticsearch8。

为什么选择elasticsearch , 支持数据量大,能水平分片, 支持 dense_vector 字段,直接支持向量存储和相似性搜索,无需插件。支持 cosine(余弦相似度)、dot_product(点积)、l2_norm(欧式距离)等计算方式。

支持 近似最近邻搜索(ANN)。如果企业用也可以选择。

整个向量数据操作都是在 DocumentChunkRepository 接口中实现:

public interface DocumentChunkRepository {/*** 通过文档id查询文本块* @param documentId* @return*/List<DocumentChunk> findByDocumentId(String documentId);/*** 删除文档* @param documentId*/void deleteByDocumentId(String documentId);/*** 存储文本向量* @param documentChunk* @return*/DocumentChunk save(DocumentChunk documentChunk);/*** 向量关键词搜索,基于KNN算法* @param documentId* @param queryVector* @param k* @return*/List<DocumentChunk> findTopKSimilarChunks(String documentId, List<Float> queryVector, int k);
}

联网搜索实现

我们直到deepseek模型是不能搜索到今天的天气,新闻等信息的。如果有这样的需求,就需要开启联网搜索功能。

首先 需要寻找一个联网搜索的插件或者api接口。目前有很多这样的接口,比如:

searchapi (国外), duckduckgo(国外),必应搜索API 等。

国内的我随便找了一个叫 “博查搜索” ,提供了api搜索。

下面是对接博查搜索的代码:

/*** 博查AI搜索服务实现* 基于博查AI开放平台的Web Search API* 支持实时网页搜索,适用于AI应用*/
@Slf4j
@Service
public class BochaWebSearchService implements WebSearchService {// 博查API配置private static final String BOCHA_API_URL = "https://api.bochaai.com/v1/web-search";private static final String BACKUP_API_URL = "https://api.bochaai.com/v1/search";@Value("${bocha.api.key}")private String apiKey;private final HttpClient httpClient;private final ObjectMapper objectMapper;private final Random random = new Random();// 用户代理池private static final String[] USER_AGENTS = {"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36","Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0"};public BochaWebSearchService() {this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();this.objectMapper = new ObjectMapper();}@Overridepublic List<String> search(String query, int maxResults) {if (query == null || query.trim().isEmpty()) {log.warn("搜索查询为空");return Collections.emptyList();}try {// 首先尝试主APIList<String> results = performSearch(BOCHA_API_URL, query, maxResults);if (!results.isEmpty()) {return results;}// 主API失败,尝试备用APIlog.warn("主API返回空结果,尝试备用API");results = performSearch(BACKUP_API_URL, query, maxResults);if (!results.isEmpty()) {return results;}log.warn("所有API都返回空结果,返回模拟结果");return getMockResults(query, maxResults);} catch (Exception e) {log.error("博查搜索服务异常: {}", e.getMessage(), e);return getMockResults(query, maxResults);}}/*** 执行搜索请求*/private List<String> performSearch(String apiUrl, String query, int maxResults) throws Exception {String requestBody = buildRequestBody(query, maxResults);HttpRequest request = HttpRequest.newBuilder().uri(URI.create(apiUrl)).header("Authorization", "Bearer " + apiKey).header("Content-Type", "application/json").header("Accept", "application/json").header("User-Agent", getRandomUserAgent()).POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8)).timeout(Duration.ofSeconds(30)).build();log.info("发送博查搜索请求: {}", query);CompletableFuture<HttpResponse<String>> future = httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));HttpResponse<String> response = future.get(30, TimeUnit.SECONDS);if (response.statusCode() == 200) {return parseSearchResults(response.body(), maxResults);} else {log.warn("博查API请求失败,状态码: {}, 响应: {}", response.statusCode(), response.body());throw new RuntimeException("API请求失败: " + response.statusCode());}}/*** 构建请求体*/private String buildRequestBody(String query, int maxResults) {try {Map<String, Object> requestData = new HashMap<>();requestData.put("query", query);requestData.put("count", Math.min(maxResults, 20)); // 博查API最大支持20个结果requestData.put("freshness", "oneYear"); // 搜索一年内的内容requestData.put("summary", false); // 不需要摘要requestData.put("safeSearch", "moderate"); // 中等安全搜索return objectMapper.writeValueAsString(requestData);} catch (Exception e) {log.error("构建请求体失败: {}", e.getMessage());throw new RuntimeException("构建请求体失败", e);}}/*** 解析搜索结果*/private List<String> parseSearchResults(String responseBody, int maxResults) {try {JsonNode root = objectMapper.readTree(responseBody);List<String> results = new ArrayList<>();// 解析博查API响应格式JsonNode data = root.path("data") ;JsonNode webPages = data.path("webPages");JsonNode valueArray = webPages.path("value");if (valueArray.isArray()) {for (JsonNode item : valueArray) {if (results.size() >= maxResults) {break;}String title = getJsonValue(item, "name");String url = getJsonValue(item, "url");String snippet = getJsonValue(item, "snippet");String siteName = getJsonValue(item, "siteName");if (!title.isEmpty() && !url.isEmpty()) {StringBuilder result = new StringBuilder();result.append("标题: ").append(title);if (!siteName.isEmpty()) {result.append(" (来源: ").append(siteName).append(")");}result.append("\n链接: ").append(url);if (!snippet.isEmpty()) {result.append("\n摘要: ").append(snippet);}results.add(result.toString());}}}log.info("博查搜索成功,返回{}个结果", results.size());return results;} catch (Exception e) {log.error("解析博查搜索结果失败: {}", e.getMessage(), e);return Collections.emptyList();}}/*** 安全获取JSON值*/private String getJsonValue(JsonNode node, String fieldName) {JsonNode field = node.path(fieldName);return field.isMissingNode() ? "" : field.asText("").trim();}/*** 获取随机User-Agent*/private String getRandomUserAgent() {return USER_AGENTS[random.nextInt(USER_AGENTS.length)];}/*** 获取模拟搜索结果(当API不可用时)*/private List<String> getMockResults(String query, int maxResults) {List<String> mockResults = new ArrayList<>();int count = Math.min(maxResults, 3);for (int i = 1; i <= count; i++) {mockResults.add(String.format("标题: 关于'%s'的搜索结果 %d (模拟数据)\n" +"链接: https://example.com/search-result-%d\n" +"摘要: 这是关于'%s'的模拟搜索结果,实际使用时请配置博查API Key。",query, i, i, query));}log.info("返回{}个模拟搜索结果", mockResults.size());return mockResults;}
}

将联网搜索的结果转变成一个List<String>的字符串集合,然后传到deepseek ,deepseek就会按照联网的结果进行总结输出。

深度思考实现

这个其实很简单,我们打开deepseek官网 , 找到推理模型, deepseek-reasoner。

当设置为 deepseek-reasoner 模型,然后问答时,  就会先输出推理的内容, 然后才输出有用结果。

部署教程

前端部署

安装node , 版本:v22.15.0 , 安装完成后。

 进入到项目 chatgpt-web 目录下,这个项目是vue前端, 右键,运行cmd,运行下面命令:

npm run dev

由于我已经跟你npm install好了,所以你无需执行,直接run就可以了!!

运行项目

执行sql

自己安装好数据库,注意,必须是mysql8 ,否则代码运行会出错。新建一个 wxhadluo-deepseek 数据库, 然后执行  “wxhadluo-deepseek.sql”

Redis安装

项目需要安装redis,直接下载一个windows版本的redis即可,没有的联系我。

ElasticSearch8安装

es的安装可以自行百度

几个api key的申请

1. deepseek官网的api key申请,这个自己申请就好。

2. 博查搜索的api key 申请, 这个自己到官网申请就好。

3. 向量模型的api key 申请,这个到阿里的百炼大模型平台申请就好。前面讲解原理我也提到过。

启动后端项目

然后部署后端 , 打开idea, 导入maven工程 。

打开resources目录, 修改 application.yml 配置文件,主要修改下面几个信息:

 邮件服务器是用来注册 用户的, 因为是通过邮箱注册的。

然后就是几个api key:

然后启动  main 启动类 :DeepSeekApiApplication.class

访问项目

必须登录成功后,才可以使用。

前端:

http://localhost:3010/

账号可以自己注册用户。需要配置好邮箱服务器, 如果没有的,直接往数据库表 user 插入数据也行, 密码直接是明文的就可以。using_doc_id不用填。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/diannao/84802.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

27、请求处理-【源码分析】-怎么改变默认的_method

27、请求处理-【源码分析】-怎么改变默认的_method 要改变 Spring Boot 中默认的 _method 参数&#xff0c;可以通过以下步骤实现&#xff1a; #### 原理分析 Spring Boot 中默认的 HiddenHttpMethodFilter 用于将表单中的 _method 参数值映射为实际的 HTTP 方法&#xff08;如…

欧拉角转为旋转矩阵

外旋是固定坐标系&#xff0c;内旋是动态坐标系。外旋和内旋具有等价性。 固定坐标系依次绕xyz轴旋转&#xff0c;旋转矩阵 动态坐标系依次绕zyx轴旋转&#xff0c;旋转矩阵 numpy和scipy计算对比 import numpy as np from numpy import sin, cos, pi # 抑制科学计数法&#…

【AI学习笔记】Coze平台实现生成小红书热门多图笔记

背景前摇&原视频教程&#xff1a; 最近总是在小红书上刷到多图组成的养生小妙招、效率提升小tips、退休奶奶疗愈语录等等这样的图文笔记&#xff0c;而且人物图像一眼就是AI画的。 当时我以为这个排版和文字是人工的&#xff0c;就让AI保持角色一致性画了下图&#xff0c;…

如何选择自动化编程平台

从事自动化行业的工作者都知道&#xff0c;做PLC编程需要PLC编程软件&#xff0c;做HMI可视化需要HMI编程软件&#xff0c;做SCADA需要SCADA编程软件&#xff0c;做DCS需要DCS软件&#xff0c;做仿真调试需要仿真软件。这些软件有国外的、国内的&#xff0c;有传统自动化厂商开…

Bug 背后的隐藏剧情

Bug 背后的隐藏剧情 flyfish 1. 「bug」&#xff1a;70多年前那只被拍进史书的飞蛾 故事原型&#xff1a;1947年哈佛实验室的「昆虫命案」 1947年的计算机长啥样&#xff1f;像一间教室那么大&#xff0c;塞满了几万根继电器&#xff08;类似老式开关&#xff09;&#xff…

如何将通话记录从Android传输到Android

“如何将通话记录从 Android 转移到 Android&#xff1f;我换了一部新的 Android 手机&#xff0c;想要将通话记录复制到其中。”您需要将通话记录从 Android 传输到 Android 是一种常见的情况&#xff0c;因为通话记录是手机上最重要的数据之一。幸运的是&#xff0c;如果您从…

Android 云手机横屏模式下真机键盘遮挡输入框问题处理

一、背景 打开横屏应用,点击云机EditText输入框,输入框被键盘遮挡,如下图&#xff1a; 未打开键盘状态: 点击第二个输入框,键盘遮挡了输入框&#xff1a; 二、解决方案&#xff08;推荐第三中方案,博主采用的也是第三种方案&#xff09; 博主这里整理了三种方案&#xff1a;…

进程IO之 进程

一、进程相关概念 1.什么是进程 程序&#xff1a;静态的&#xff0c;编译好的可执行文件&#xff0c;存放在磁盘中的指令和数据的集合 进程&#xff1a;动态的&#xff0c;是程序的一次执行过程&#xff0c;是独立的可调度的任务 2.进程的特点 &#xff08;1&#xff09;对…

Condition源码解读(二)

本章我们继续将Condition的最后一个方法signal方法&#xff0c;如果前面没有看过的可以点击LockSupport与Condition解析来看看Condition解读的前半部分。 signal方法&#xff1a; public final void signal() {if (!AbstractQueuedLongSynchronizer.this.isHeldExclusively())…

股票收益率的计算

首先&#xff0c;需要从 Tushare.pro 注册一个账号并调用其API获取股票日线数据&#xff08;具体操作请查看官网&#xff09;。 以通过调用tushare获取股票000001(平安银行)的股票数据为例&#xff0c;这里不设置日期&#xff0c;那么默认获取Tushare提供的所有历史数据。也可…

《算法笔记》13.2小节——专题扩展->树状数组(BIT) 问题 D: 数列-训练套题T10T3

数列(sequence.pas/c/cpp) - 问题描述 一个简单的数列问题&#xff1a;给定一个长度为n的数列&#xff0c;求这样的三个元素ai, aj, ak的个数&#xff0c;满足ai < aj > ak&#xff0c;且i < j < k。 - 输入数据 第一行是一个整数n(n < 50000)。 第二行n个整…

C# Windows Forms应用程序-001

目录 项目概述 主要组件及功能 类定义 控件声明 构造函数 Dispose 方法 InitializeComponents 方法 控件配置详解 Button 控件 (button1) TextBox 控件 (textBox1) GroupBox 控件 (groupBox1) Label 控件 (label1 至 label5) OpenFileDialog 控件 (openFileDialog1…

2025.5.28总结

今日工作&#xff1a;最近进入了项目的关键节点&#xff0c;要求每人每天提两单&#xff0c;今天周三&#xff0c;下班前只提了一个单。下午开了一场需求服务验收会&#xff0c;我演示了自己验收的那个需求&#xff0c;然后讲的不是很好。当初再构造数据时请教了一个人&#xf…

Transformer核心技术解析LCPO方法:精准控制推理长度的新突破

原创文章1FFN前馈网络与激活函数技术解析&#xff1a;Transformer模型中的关键模块2Transformer掩码技术全解析&#xff1a;分类、原理与应用场景3【大模型技术】Attention注意力机制详解一4Transformer模型中位置编码&#xff08;Positional Embedding&#xff09;技术全解析(…

在 WSL 中安装 JetBrains Toolbox:完整指南

JetBrains Toolbox 是一个非常实用的工具&#xff0c;它可以帮助开发者轻松管理 JetBrains 的各种开发工具&#xff0c;如 IntelliJ IDEA、PyCharm、WebStorm 等。通过它&#xff0c;你可以快速安装、更新和管理这些工具&#xff0c;极大地提高了开发效率。而在 WSL 环境中安装…

ZooKeeper 命令操作

文章目录 Zookeeper 数据模型Zookeeper 服务端常用命令Zookeeper 客户端常用命令 Zookeeper 数据模型 ZooKeeper 是一个树形目录服务,其数据模型和Unix的文件系统目录树很类似&#xff0c;拥有一个层次化结构。这里面的每一个节点都被称为&#xff1a; ZNode&#xff0c;每个节…

Turf.js:前端地理空间分析的瑞士军刀

在Web开发中,地理空间数据处理已成为许多应用的核心需求。从地图可视化到位置服务,再到复杂的数据分析,前端开发者需要强大的工具来处理这些任务。Turf.js 作为一款轻量级、模块化的地理空间分析库,凭借其丰富的功能和易用性,成为前端开发者的得力助手。本文将深入探讨 Tu…

大模型微调

使用 Ollama 微调大语言模型&#xff08;如 LLaMA、Mistral、Gemma 等&#xff09;主要是围绕 LoRA&#xff08;Low-Rank Adaptation&#xff09;或者 QLoRA 等轻量级微调技术进行的。Ollama 本身是一个部署和运行本地大语言模型的平台&#xff0c;但其微调能力有限&#xff0c…

《自动驾驶轨迹规划实战:Lattice Planner实现避障路径生成(附可运行Python代码)》—— 零基础实现基于离散优化的避障路径规划

《自动驾驶轨迹规划实战&#xff1a;Lattice Planner实现避障路径生成&#xff08;附可运行Python代码&#xff09;》 —— 零基础实现基于离散优化的避障路径规划 一、为什么Lattice Planner成为自动驾驶的核心算法&#xff1f; 在自动驾驶的路径规划领域&#xff0c;Lattice…

切换到旧提交,同时保证当前修改不丢失

在 Git 中&#xff0c;可以通过以下几种方式切换到之前的提交&#xff0c;同时保留当前的提交&#xff08;即不丢失工作进度&#xff09;&#xff1a; 1. 使用 git checkout 创建临时分离头指针&#xff08;推荐用于查看&#xff09; git checkout <commit-hash>这会让…