SpringBoot+本地部署大模型實現知識庫功能

SpringBoot+本地部署大模型實現RAG知識庫功能

  • 1、Linux系統部署本地大模型
    • 1.1 安裝ollama
    • 1.2 啟動ollama
    • 1.3 下載deepseek模型
  • 2、Springboot代碼調用本地模型實現基礎問答功能
  • 3、集成向量數據庫
  • 4、知識庫數據喂取
  • 5、最終實現RAG知識庫功能

1、Linux系統部署本地大模型

1.1 安裝ollama

# wget https://ollama.com/download/ollama-linux-amd64.tgz
# tar -C /usr/local -zxvf ollama-linux-amd64.tgz

1.2 啟動ollama

# ollama serve  
// 這里注意如果要允許其他客戶端遠程調用本模型的話需要執行以下啟動命令
OLLAMA_HOST=0.0.0.0 OLLAMA_ORIGINS=* ollama serve
或者
OLLAMA_DEBUG=1 OLLAMA_HOST=0.0.0.0 OLLAMA_ORIGINS=* ollama serve > ollama.log 2>&1

這里我啟動的時候遇到了報錯(主要問題是服務器上的libstdc++ 版本低)

ollama: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.25‘ not found (required by ollama)

解決辦法如下:(鏈接放這里)

https://blog.csdn.net/u011250186/article/details/147144845
這里需要注意的是這里的第三步驟配置并編譯的時間會比較長

1.3 下載deepseek模型

在這里插入圖片描述

ollama run deepseek-r1:1.5b

可以直接通過服務器去先模型發起提問:
在這里插入圖片描述

2、Springboot代碼調用本地模型實現基礎問答功能

這里前端我主要做的是知識庫基本問答功能的一個界面可以調用后臺接口然后后臺接口根據http請求去調用本地模型的問題大接口生成回復。

    @GetMapping(value = "/getArtificialIntelligence")public ResponseEntity<String> getFaultsByTaskId(@RequestParam(name = "message") String message) throws PromptException {return ResponseEntity.ok(aiService.getArtificialIntelligence(message));}
@Value("${ollama.url}")private String OLLAMA_API_URL;@Value("${ollama.model}")private String OLLAMA_MODEL;@Overridepublic String getArtificialIntelligence(String message) throws PromptException {try {// 1. 構建請求體OllamaRequest request = new OllamaRequest(OLLAMA_MODEL, message,true);// 2. 發送請求HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);HttpEntity<OllamaRequest> entity = new HttpEntity<>(request, headers);ResponseEntity<OllamaResponse> response = restTemplate.exchange(OLLAMA_API_URL,HttpMethod.POST,entity,OllamaResponse.class);System.out.println(response);System.out.println(response.getBody().getResponse());// 3. 解析響應// generate接口if (response.getStatusCode().is2xxSuccessful() && response.hasBody()) {return response.getBody().getResponse();}throw new PromptException("API響應異常:" + response.getStatusCode());} catch (HttpStatusCodeException e) {// 精準處理HTTP狀態碼switch (e.getStatusCode().value()) {case 400:throw new PromptException("請求參數錯誤:" + e.getResponseBodyAsString());case 404:throw new PromptException("模型未找到,請檢查配置");case 500:throw new PromptException("模型服務內部錯誤");default:throw new PromptException("API請求失敗:" + e.getStatusCode());}} catch (Exception e) {throw new PromptException("系統異常:" + e.getMessage());}}

3、集成向量數據庫

這里我選擇使用的是PostgreSQL的vector向量拓展
官網地址:

https://pgxn.org/dist/vector/0.7.4/README.html#Windows

首先必須確保已安裝Visual Studio 中的 C++ 支持,然后運行:

& "D:\SoftWare\visual Studio\VC\Auxiliary\Build\vcvars64.bat"cd D:\SoftWare\pgvector\pgvector-master
set "PGROOT=D:\SoftWare\PgSQL"
nmake /F Makefile.win
nmake /F Makefile.win install# 設置 Visual Studio 環境變量
& "D:\SoftWare\Visual Studio\VC\Auxiliary\Build\vcvars64.bat"# 進入源碼目錄
cd D:\SoftWare\pgvector\pgvector-master# 執行編譯
& "D:\SoftWare\Visual Studio\VC\Tools\MSVC\14.43.34808\bin\Hostx64\x64\nmake.exe" /F Makefile.win PGROOT="D:\SoftWare\PgSQL" PG_CONFIG="D:\SoftWare\PgSQL\bin\pg_config.exe"

然后就可以根據pgsql的拓展給的提示去建數據庫和表了
在這里插入圖片描述

4、知識庫數據喂取

我是通過前端界面實現一個文件上傳功能然后將文件解析成向量然后存入向量數據庫。
在這里插入圖片描述
這里使用到了另外一個deepseek模型 nomic-embed-text 需要通過此模型將文件中的數據轉成向量然后存儲到數據庫中。
在這里插入圖片描述
后端代碼如下:

    @PostMapping("/upload")public ResponseEntity<String> uploadFile(@RequestParam("multipartFiles") MultipartFile file) {try {documentService.processUploadedFile(file);return ResponseEntity.ok("文件已成功解析并存入數據庫");} catch (Exception e) {return ResponseEntity.status(500).body("文件處理失敗:" + e.getMessage());}}
    private final Tika tika = new Tika();@Resourceprivate  EmbeddingService embeddingService;@Resourceprivate  DocumentChunkRepository documentChunkRepository;/*** 處理上傳的文件* @param file*/@Overridepublic void processUploadedFile(MultipartFile file) throws IOException, TikaException {String fileName = file.getOriginalFilename();if (fileName == null || fileName.isEmpty()) throw new IllegalArgumentException("文件名為空");// 使用Tika解析文件為文本String textContent = tika.parseToString(file.getInputStream());// 對文本進行分塊 - 改進分塊策略
//        List<String> chunks = splitTextIntoChunks(textContent, 512);List<String> chunks = splitTextIntoChunks(textContent, 512, 100);// 批量保存DocumentChunk對象List<DocumentChunk> documentChunks = new ArrayList<>();for (String chunk : chunks) {try {float[] embedding = embeddingService.getEmbedding(chunk);DocumentChunk chunkEntity = new DocumentChunk();chunkEntity.setFilename(fileName);chunkEntity.setContent(chunk);chunkEntity.setEmbedding(embedding);documentChunks.add(chunkEntity);} catch (Exception e) {logger.error("處理文本塊時出錯: {}", e.getMessage(), e);}}// 批量保存到數據庫if (!documentChunks.isEmpty()) {documentChunkRepository.saveAll(documentChunks);}}/*** 將文本分割成適當大小的塊,同時嘗試保留句子或段落的完整性。** @param text 文本內容* @param maxChunkSize 每個塊的最大字符數* @return 分割后的文本塊列表*/private List<String> splitTextIntoChunks(String text, int maxChunkSize, int overlap) {List<String> chunks = new ArrayList<>();StringBuilder currentChunk = new StringBuilder(maxChunkSize);String[] sentences = text.split("。|?|!|\\n"); // 按句號/換行切分句子for (String sentence : sentences) {if (sentence.trim().isEmpty()) continue;if (currentChunk.length() + sentence.length() > maxChunkSize) {chunks.add(currentChunk.toString());// 添加 overlap 部分if (overlap > 0 && !chunks.isEmpty()) {String lastPart = getLastNChars(chunks.get(chunks.size() - 1), overlap);currentChunk = new StringBuilder(lastPart).append(sentence);} else {currentChunk = new StringBuilder(sentence);}} else {currentChunk.append(sentence).append(" ");}}if (currentChunk.length() > 0) {chunks.add(currentChunk.toString());}return chunks;
}// 輔助函數:取字符串末尾 n 字符
private String getLastNChars(String str, int n) {return str.length() > n ? str.substring(str.length() - n) : str;
}
@Service
public class EmbeddingServiceImpl implements EmbeddingService {private final RestTemplate restTemplate = new RestTemplate();private final ObjectMapper objectMapper = new ObjectMapper();@Overridepublic float[] getEmbedding(String text) {String url = "http://192.168.2.45:11434/api/embeddings";ObjectMapper objectMapper = new ObjectMapper();try {Map<String, Object> requestBody = new HashMap<>();requestBody.put("model", "nomic-embed-text");requestBody.put("prompt", text);String requestBodyJson = objectMapper.writeValueAsString(requestBody);// 發送POST請求并接收響應ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, requestBodyJson, String.class);if (responseEntity.getStatusCode() != HttpStatus.OK) {throw new RuntimeException("HTTP 錯誤狀態碼: " + responseEntity.getStatusCodeValue());}Map<String, Object> map = objectMapper.readValue(responseEntity.getBody(), Map.class);Object embeddingObj = map.get("embedding");if (embeddingObj instanceof float[]) {return (float[]) embeddingObj;} else if (embeddingObj instanceof List<?>) {@SuppressWarnings("unchecked")List<Double> list = (List<Double>) embeddingObj;float[] arr = new float[list.size()];for (int i = 0; i < arr.length; i++) {arr[i] = list.get(i).floatValue();}return arr;} else {throw new RuntimeException("Unexpected type for embedding: " + (embeddingObj != null ? embeddingObj.getClass().getName() : "null"));}} catch (Exception e) {throw new RuntimeException("Failed to get embedding", e);}}

5、最終實現RAG知識庫功能

最后就是當用戶發起提問的時候首先得去數據庫中檢索是否有相似的內容片段 如果有的話需要將向量匹配到的內容的原文拿到之后再給模型發起提問的時候帶上這些上下文的 Prompt然后讓模型根據這部分內容去增強生成 這既是檢索增強生成 RAG。下面是代碼示例如下:

這里調用的模型的chat接口返回的數據格式是流式回復所以我這里月使用Flux格式返給前端 讓前端生成回復的時候不需要等待太久用戶體驗更好一點。

    /*** 流式回復* @param message* @return*/@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<StreamResponse> streamResponse(@RequestParam String message) {return aiService.getStreamResponse(message).timeout(Duration.ofMinutes(10)).doFinally((SignalType signal) -> {if (signal == SignalType.CANCEL) {logger.info("客戶端中斷了流式連接");}});}
 public Flux<StreamResponse> getStreamResponse(String message) {return Flux.defer(() -> {try {// 1. 檢索相關上下文List<DocumentChunk> chunks = retrievalService.retrieveRelevantChunks(message, 3);StringBuilder contextBuilder = new StringBuilder();for (DocumentChunk chunk : chunks) {contextBuilder.append(chunk.getContent()).append("\n\n");}// 2. 構建帶上下文的 PromptString prompt = String.format("請基于以下上下文回答問題:\n\n%s\n\n問題:%s", contextBuilder.toString(), message);// 3. 構建請求體Map<String, Object> requestBody = new HashMap<>();requestBody.put("model", OLLAMA_MODEL);requestBody.put("messages", Collections.singletonList(new Message("user", prompt)));requestBody.put("stream", true);requestBody.put("options", new Options());// 4. 發送流式請求return webClient.post().uri(OLLAMA_API_URL).contentType(MediaType.APPLICATION_JSON).bodyValue(requestBody).retrieve().bodyToFlux(String.class).map(this::parseChunk).doOnSubscribe(sub -> log.debug("建立連接成功")).doOnNext(response -> log.trace("收到分塊數據:{}", response)).doOnError(e -> log.error("流式處理異常:", e)).onErrorResume(e -> {log.error("流式請求失敗", e);return Flux.error(new PromptException("AI服務暫時不可用"));});} catch (Exception e) {return Flux.error(new PromptException("文檔檢索失敗: " + e.getMessage()));}})
}// 處理每部分分塊數據private StreamResponse parseChunk(String chunk) {try {JsonNode node = new ObjectMapper().readTree(chunk);StreamResponse response = new StreamResponse();response.setResponse( node.path("message").path("content").asText());response.setDone(node.path("done").asBoolean());return response;} catch (Exception e) {return new StreamResponse("解析錯誤", true);}}
@Service
public class RetrievalServiceImpl implements RetrievalService {@Resourceprivate EmbeddingService embeddingService;@Resourceprivate DocumentChunkRepository documentChunkRepository;/*** 檢索最相關的文檔片段* @param query 檢索內容* @param topK 返回最相似的數量* @return*/@Overridepublic List<DocumentChunk> retrieveRelevantChunks(String query, int topK) {// 1. 獲取嵌入向量float[] queryVector = embeddingService.getEmbedding(query);// 2. 將 float[] 轉換為 PostgreSQL 可識別的 vector 字符串格式 "[v1,v2,v3]"StringBuilder sb = new StringBuilder("[");for (int i = 0; i < queryVector.length; i++) {sb.append(queryVector[i]);if (i < queryVector.length - 1) {sb.append(",");}}sb.append("]");String vectorAsString = sb.toString();// 3. 使用字符串形式傳參,避免 Hibernate 自動轉成 byteareturn documentChunkRepository.findSimilarChunks(vectorAsString, topK);}
}

這個demo測試已經寫了很久了 有很多細節在文章的時候有點想不起來了 后期還會繼續優化補充!!!!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/87370.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/87370.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/87370.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

嵌入式原理與應用篇---ARM

ARM 架構的 STM32 系列微控制器廣泛應用于嵌入式系統開發&#xff0c;理解其匯編語言指令對于優化性能、訪問硬件底層非常重要。下面詳細解釋常見的 ARM 匯編指令及其使用實例。 數據處理指令 1. MOV&#xff08;移動&#xff09; 功能&#xff1a;將立即數或寄存器值復制到…

【RHCSA-Linux考試題目筆記(自用)】servera的題目

一、開始 1、啟動rhcsa環境 2、點擊題目&#xff0c;看題 3、通過控制器來啟動所有虛擬機 控制器 打開后點start&#xff0c;然后ok 之后進入一個有classroom、servera、serverb&#xff08;考試不一定叫這些名&#xff0c;但大差不差&#xff09;什么之類的界面&#xff0c;…

SpringBoot項目使用arthas-tunnel-server

參考官網Arthas Spring Boot Starter | arthas Spring Boot系列之使用Arthas Tunnel Server 進行遠程調試實踐-騰訊云開發者社區-騰訊云 springBoot項目, 增加maven依賴 <dependency><groupId>com.taobao.arthas</groupId><artifactId>arthas-sprin…

Modbus TCP 進階:基于以太網的遠程設備控制(二)

基于 Modbus TCP 的遠程設備控制實戰 &#xff08;一&#xff09;硬件與網絡搭建實操 1. 設備選型與連接 在工業現場&#xff0c;根據遠程控制需求進行設備選型至關重要 。對于傳感器&#xff0c;若要監測溫度&#xff0c;可選擇高精度的熱電偶傳感器&#xff0c;如 K 型熱電…

分庫分表之實戰-sharding-JDBC

大家好&#xff0c;我是工藤學編程 &#x1f989;一個正在努力學習的小博主&#xff0c;期待你的關注實戰代碼系列最新文章&#x1f609;C實現圖書管理系統&#xff08;Qt C GUI界面版&#xff09;SpringBoot實戰系列&#x1f437;【SpringBoot實戰系列】Sharding-Jdbc實現分庫…

httpcore-nio引起的線程、fd泄露問題

依賴來源&#xff1a;httpasyncclient-4.1.4.jar 現象 程序報錯too many open files 線程數飆升、句柄數飆升 thread dump顯示大量 "I/O dispatcher 7215" #9102 prio5 os_prio0 tid0x00002b7ba036a800 nid0x6f24 runnable [0x00002b7d98d41000]java.lang.Thread.…

多線程生產者消費者模型實戰案例

多線程生產者消費者模型實戰案例 前言業務場景術前準備無鎖無事務有事務 synchronized事務在鎖外事務在鎖內 數據庫行鎖什么是數據庫行鎖有事務沒有事務 樂觀鎖ReentrantLock分布式鎖 前言 曾經一直有一個疑惑&#xff0c;就是關于多線程生產者消費者模型的學習過程中&#xf…

青少年編程與數學 02-022 專業應用軟件簡介 03 三維建模及動畫軟件:Autodesk Maya

青少年編程與數學 02-022 專業應用軟件簡介 03 三維建模及動畫軟件&#xff1a;Autodesk Maya 一、什么是三維建模二、什么是計算機動畫三、三維建模及動畫設計軟件的發展歷程&#xff08;一&#xff09;早期探索階段&#xff08;20世紀60年代 - 80年代&#xff09;&#xff08…

獲得 OCM 大師證書學習歷練

當我站在山城重慶的洪崖洞前&#xff0c;看著璀璨的夜景倒映在嘉陵江上&#xff0c;手中緊握著 OCM 大師證書&#xff0c;那一刻&#xff0c;備考時的艱辛與考試時的緊張都化作了滿滿的成就感。這段在重慶獲得 OCM 大師證書的經歷&#xff0c;就像一場充滿挑戰與驚喜的冒險&…

srs-gb28181 與 SRS 5.0 對 GB28181 國標支持

srs-gb28181 是基于 SRS 4.0/5.0 的國標&#xff08;GB28181&#xff09;擴展分支&#xff0c;而 SRS 5.0 官方版本也逐步增強了對 GB28181 的支持。以下是兩者的主要區別&#xff1a; 1. 功能支持對比 功能srs-gb28181&#xff08;擴展分支&#xff09;SRS 5.0&#xff08;官…

算法第18天|繼續二叉樹:修剪二叉搜索樹、將有序數組轉化為二叉搜索樹、把二叉搜索樹轉換為累加樹

今日總結&#xff1a; 1、修剪二叉搜索樹&#xff08;重點思考如何修剪&#xff09; &#xff08;1&#xff09;遞歸的返回值是什么&#xff1f;&#xff08;與插入、刪除一樣&#xff09; &#xff08;2&#xff09;遞歸的單層邏輯一定要縷清&#xff08;3中情況討論&#xff…

C# 多線程(三)線程池

目錄 1.通過TPL使用線程池 2.不使用TPL進入線程池的辦法 異步委托 3.線程池優化技術 最小線程數的工作原理 每當啟動一個新線程時&#xff0c;系統都需要花費數百微秒來分配資源&#xff0c;例如創建獨立的局部變量棧空間。默認情況下&#xff0c;每個線程還會占用約1…

學習筆記(29):訓練集與測試集劃分詳解:train_test_split 函數深度解析

學習筆記(29):訓練集與測試集劃分詳解&#xff1a;train_test_split 函數深度解析 一、為什么需要劃分訓練集和測試集&#xff1f; 在機器學習中&#xff0c;模型需要經歷兩個核心階段&#xff1a; 訓練階段&#xff1a;用訓練集數據學習特征與目標值的映射關系&#xff08;…

【全網唯一】自動化編輯器 Windows版純本地離線文字識別插件

目的 自動化編輯器超輕量級RPA工具&#xff0c;零代碼制作RPA自動化任務&#xff0c;解放雙手&#xff0c;釋放雙眼&#xff0c;輕松玩游戲&#xff0c;刷任務。本篇文章主要講解下自動化編輯器的TomatoOCR純本地離線文字識別Windows版插件如何使用和集成。 準備工作 1、下載自…

GitHub 2FA綁定

GitHub 2FA綁定 作為全球最大的代碼托管平臺&#xff0c;GitHub對賬號安全的重視程度不斷提升——自2023年3月起&#xff0c;GitHub已要求所有在GitHub.com上貢獻代碼的用戶必須啟用雙因素身份驗證&#xff08;2FA&#xff09;。如果你是符合條件的用戶&#xff0c;會收到一封…

pytest fixture基礎大全詳解

一、介紹 作用 fixture主要有兩個作用&#xff1a; 復用測試數據和環境&#xff0c;可以減少重復的代碼&#xff1b;可以在測試用例運行前和運行后設置和清理資源&#xff0c;避免對測試結果產生影響&#xff0c;同時也可以提高測試用例的運行效率。 優勢 pytest框架的fix…

Unity知識點-Renderer常用材質變量

本篇總結了Unity中renderer的3種常用的材質相關的變量&#xff1a;renderer.material,renderer.sharedMaterial,renderer.MaterialPropertyBlock。以及三者對SRPBatcher的影響。 一.介紹及對比 1.概念介紹 1.material 定義&#xff1a;material 是Render組件&#xff08;如…

【算法】??如何判斷時間復雜度?

文章目錄 1. 什么是時間復雜度&#xff1f;為什么需要時間復雜度&#xff1f; 2. 常見時間復雜度對比3. 如何分析時間復雜度&#xff1f;&#xff08;Java版&#xff09;&#x1f539; 步驟1&#xff1a;找出基本操作&#x1f539; 步驟2&#xff1a;分析循環結構&#xff08;1…

MySQL使用C語言連接

文章目錄 版本查看以及編譯mysql接口介紹初始化鏈接數據庫下發mysql命令mysql_query獲取執行結果mysql_store_result獲取結果行數mysql_num_rows獲取結果列數mysql_num_fields獲取列名mysql_fetch_fields獲取結果內容mysql_fetch_row關閉mysql鏈接mysql_closeC語言操作mysql查看…

堅持每日Codeforces三題挑戰:Day 7 - 題目詳解(2025-06-11,難度:1200,1300,1500)

每天堅持寫三道題第七天&#xff1a; Problem - A - Codeforces 1200 Problem - B - Codeforces 1300 Problem - A - Codeforces 1500 目錄 題目一: 題目大意: 解題思路: 代碼(C): 題目二: 題目大意: 解題思路: 代碼(C): 題目三: 題目大意: 解題思路: 代碼(C): …