| doc/20260608_知识库RAG向量检索功能前端联调文档.md | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/RuoYiApplication.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/ai/service/impl/KnowledgeRagServiceImpl.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/approve/controller/KnowledgeBaseController.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/framework/config/AsyncConfig.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
doc/20260608_֪ʶ¿âRAGÏòÁ¿¼ìË÷¹¦ÄÜǰ¶ËÁªµ÷Îĵµ.md
@@ -78,31 +78,24 @@ } ``` ### 3.2 ä¿åç¥è¯åºæä»¶å ³è ### 3.2 ä¿åç¥è¯åºæä»¶å ³èï¼è§¦ååéåï¼ ä¸ä¼ 宿åï¼è°ç¨éä»¶ä¿åæ¥å£å ³èæä»¶å°ç¥è¯åºã **éè¦**ï¼ä¸ä¼ 宿åï¼**å¿ é¡»**è°ç¨æ¤æ¥å£æ¥å ³èæä»¶å¹¶è§¦ååéåå¤çã **æ¥å£å°å**ï¼`POST /storageAttachment/add` **æ¥å£å°å**ï¼`POST /knowledgeBase/file/save` **请æ±åæ°**ï¼ ```json { "recordType": "knowledge_base", "recordId": 10, "application": "rag_file", "storageBlobDTOs": [ { "id": 123 }, { "id": 124 } ] "knowledgeBaseId": 10, "storageBlobIds": [123, 124] } ``` | åæ°å | ç±»å | å¿ å¡« | 说æ | |--------|------|------|------| | recordType | String | æ¯ | åºå®å¼ `knowledge_base` | | recordId | Long | æ¯ | ç¥è¯åºID | | application | String | æ¯ | åºå®å¼ `rag_file`ï¼æ è¯RAGæä»¶ | | storageBlobDTOs | List | æ¯ | ä¸ä¼ è¿åçæä»¶blobå表 | | knowledgeBaseId | Long | æ¯ | ç¥è¯åºID | | storageBlobIds | List<Long> | æ¯ | ä¸ä¼ è¿åçæä»¶blob IDå表 | **ååºç»æ**ï¼ ```json @@ -112,55 +105,16 @@ } ``` ### 3.3 ç¥è¯åºæä»¶å表 **è°ç¨æ¶æº**ï¼å¨ `/common/upload` ä¸ä¼ æååï¼ç«å³è°ç¨æ¤æ¥å£ã **æ¥å£å°å**ï¼`GET /storageAttachment/list` **注æ**ï¼æ¤æ¥å£ä¼ï¼ 1. ä¿åéä»¶å ³èå° `storage_attachment` 表 2. å建åéè®°å½å° `knowledge_base_vector` 表 3. **弿¥è§¦ååéåå¤ç**ï¼æä»¶åç â åéåµå ¥ â åå ¥Pineconeï¼ **请æ±åæ°**ï¼ | åæ°å | ç±»å | å¿ å¡« | 说æ | |--------|------|------|------| | recordType | String | æ¯ | åºå®å¼ `knowledge_base` | | recordId | Long | æ¯ | ç¥è¯åºID | | application | String | å¦ | åºå®å¼ `rag_file` | --- **ååºç»æ**ï¼ ```json { "code": 200, "data": [ { "id": 1, "storageBlobId": 123, "name": "æä½æå.docx", "url": "/profile/upload/20260608/xxx.docx", "previewURL": "/common/preview/xxx?token=yyy", "downloadURL": "/common/download/xxx?token=yyy", "createTime": "2026-06-08 10:00:00" } ] } ``` ### 3.4 ç¥è¯åºæä»¶å é¤ **æ¥å£å°å**ï¼`DELETE /storageAttachment/delete` **请æ±åæ°**ï¼ ```json { "ids": [1, 2, 3] } ``` **ååºç»æ**ï¼ ```json { "code": 200, "msg": "æä½æå" } ``` ### 3.5 æ¥è¯¢åéåç¶æï¼æ°å¢æ¥å£ï¼ ### 3.3 æ¥è¯¢åéåç¶æï¼æ¨è使ç¨ï¼ **æ¥å£å°å**ï¼`GET /knowledgeBase/vector/status/{knowledgeBaseId}` @@ -176,13 +130,35 @@ "fileType": "docx", "vectorStatus": 2, "chunkCount": 15, "namespace": "kb-10" "namespace": "kb-10", "vectorError": null } ] } ``` ### 3.6 éæ°åéåæä»¶ ### 3.4 å é¤ç¥è¯åºæä»¶ **æ¥å£å°å**ï¼`DELETE /knowledgeBase/file/delete` **请æ±åæ°**ï¼ ```json { "ids": [1, 2, 3] } ``` **注æ**ï¼æ¤æ¥å£ä¼åæ¶å é¤åéåºä¸çç¸å ³æ°æ®ã **ååºç»æ**ï¼ ```json { "code": 200, "msg": "æä½æå" } ``` ### 3.5 éæ°åéåæä»¶ **æ¥å£å°å**ï¼`POST /knowledgeBase/vector/reprocess/{vectorId}` @@ -194,7 +170,7 @@ } ``` ### 3.7 ç¥è¯åºé®çæ¥å£ ### 3.6 ç¥è¯åºé®çæ¥å£ **æ¥å£å°å**ï¼`POST /ai/knowledge/chat`ï¼æµå¼è¿åï¼ @@ -219,7 +195,7 @@ 1. ç»å½ç³»ç»åè¿å ¥å®¡æ¹ç®¡ç模å... ``` ### 3.8 ç¥è¯åºé®çä¼è¯åå² ### 3.7 ç¥è¯åºé®çä¼è¯åå² **æ¥å£å°å**ï¼`GET /ai/knowledge/history/{memoryId}` @@ -320,12 +296,12 @@ return true } // ä¸ä¼ æååä¿åéä»¶å ³è // ä¸ä¼ æååä¿åæä»¶å ³è并触ååéå const handleUploadSuccess = async (response, file) => { if (response.code === 200) { uploadedBlobs.value.push(...response.data) // è°ç¨éä»¶ä¿åæ¥å£ï¼å ³èå°ç¥è¯åº await saveAttachment() // è°ç¨ç¥è¯åºæä»¶ä¿åæ¥å£ï¼å ³èæä»¶å¹¶è§¦ååéå await saveKnowledgeBaseFiles() ElMessage.success('æä»¶ä¸ä¼ æåï¼æ£å¨å¤çåéå...') refreshVectorStatus() } else { @@ -333,13 +309,11 @@ } } // ä¿åéä»¶å ³èå°ç¥è¯åº const saveAttachment = async () => { await request.post('/storageAttachment/add', { recordType: 'knowledge_base', recordId: selectedKnowledgeBase.value, application: 'rag_file', storageBlobDTOs: uploadedBlobs.value.map(b => ({ id: b.id })) // ä¿åæä»¶å ³èå°ç¥è¯åºå¹¶è§¦ååéå const saveKnowledgeBaseFiles = async () => { await request.post('/knowledgeBase/file/save', { knowledgeBaseId: selectedKnowledgeBase.value, storageBlobIds: uploadedBlobs.value.map(b => b.id) }) uploadedBlobs.value = [] } @@ -409,7 +383,7 @@ } const deleteFile = async (row) => { await request.delete('/storageAttachment/delete', { data: [row.id] }) await request.delete('/knowledgeBase/file/delete', { data: [row.id] }) ElMessage.success('å 餿å') refreshFileList() } @@ -558,21 +532,26 @@ ## ä¸ãä¸å¡æµç¨ ### 7.1 æä»¶ä¸ä¼ æµç¨ ### 7.1 æä»¶ä¸ä¼ æµç¨ï¼éè¦ï¼ ``` å端è°ç¨ /common/upload ä¸ä¼ æä»¶ â è·å StorageBlobVO å表ï¼å å«blobIdãé¢è§URLãä¸è½½URLï¼ â è°ç¨ /storageAttachment/add å ³èæä»¶å°ç¥è¯åº è°ç¨ /knowledgeBase/file/save å ³èæä»¶å°ç¥è¯åº â å端çå¬éä»¶ä¿åäºä»¶ â æåæä»¶ææ¬ â åç â åéå å端å建åéè®°å½ â 弿¥è§¦ååéåå¤ç â åå ¥ Pinecone åéåºï¼å½å空é´: kb-{knowledgeBaseId}) 弿¥ä»»å¡ï¼æåæä»¶ææ¬ â åç â è°ç¨Embedding模åçæåé â åå ¥Pinecone â æ´æ° knowledge_base_vector è¡¨ç¶æä¸ºå·²å®æ ``` **å ³é®ç¹**ï¼ - å¿ é¡»è°ç¨ `/knowledgeBase/file/save` æ¥å£æè½è§¦ååéå - åé忝弿¥å¤ççï¼ä¸ä¼é»å¡è¯·æ± - å端å¯éè¿è½®è¯¢ `/knowledgeBase/vector/status/{knowledgeBaseId}` æ¥çå¤çè¿åº¦ ### 7.2 ç¥è¯åºé®çæµç¨ @@ -598,16 +577,9 @@ æ¯ä¸ªç¥è¯åºä½¿ç¨ç¬ç«å½å空é´ï¼`kb-{knowledgeBaseId}` ### 8.3 éä»¶å ³èåæ° | åæ° | å¼ | 说æ | |------|-----|------| | recordType | `knowledge_base` | 使ç¨å·²ææä¸¾ | | application | `rag_file` | æ è¯ä¸ºRAGæä»¶ | ## ä¹ã注æäºé¡¹ 1. æä»¶ä¸ä¼ 使ç¨ç³»ç»å·²æç `/common/upload` å `/storageAttachment/add` æ¥å£ 1. **æä»¶ä¸ä¼ å¿ é¡»è°ç¨ `/knowledgeBase/file/save` 触ååéå** 2. å 餿件æ¶åæ¥å é¤åéåºä¸çç¸å ³åç 3. 大æä»¶åéåå¯è½èæ¶è¾é¿ï¼å端éè½®è¯¢ç¶æææ¾ç¤ºè¿åº¦ 4. ç¥è¯åºé®çä¾èµåéæ£ç´¢è´¨éï¼å»ºè®®ä¼ååççç¥ src/main/java/com/ruoyi/RuoYiApplication.java
@@ -3,15 +3,17 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; /** * å¯å¨ç¨åº * * * @author ruoyi */ @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) @EnableScheduling @EnableAsync public class RuoYiApplication { public static void main(String[] args) src/main/java/com/ruoyi/ai/service/impl/KnowledgeRagServiceImpl.java
@@ -46,13 +46,15 @@ private static final int CHUNK_OVERLAP = 100; @Override @Async @Async("threadPoolTaskExecutor") public void processVectorAsync(Long vectorId) { log.info("å¼å§å¼æ¥åéåå¤ç: vectorId={}, thread={}", vectorId, Thread.currentThread().getName()); processVector(vectorId); } @Override public void processVector(Long vectorId) { log.info("å¼å§å¤çåéå: vectorId={}", vectorId); KnowledgeBaseVector vector = knowledgeBaseVectorService.getById(vectorId); if (vector == null) { log.error("åéè®°å½ä¸åå¨: {}", vectorId); @@ -61,30 +63,38 @@ try { // æ´æ°ç¶æä¸ºå¤çä¸ log.info("æ´æ°ç¶æä¸ºå¤çä¸: vectorId={}", vectorId); knowledgeBaseVectorService.updateVectorStatus(vectorId, KnowledgeBaseVector.STATUS_PROCESSING, null, null); // è·åæä»¶å 容 log.info("è·åæä»¶ä¿¡æ¯: storageBlobId={}", vector.getStorageBlobId()); StorageBlob blob = storageBlobService.getById(vector.getStorageBlobId()); if (blob == null) { throw new RuntimeException("æä»¶ä¸åå¨: " + vector.getStorageBlobId()); } File file = getFile(blob); log.info("æä»¶è·¯å¾: {}, æ¯å¦åå¨: {}", file.getAbsolutePath(), file.exists()); // ç´æ¥è¯»åæä»¶å 容ï¼ä¸ä½¿ç¨ MultipartFile å è£ log.info("æåæä»¶å 容: fileName={}", vector.getFileName()); String content = extractFileContent(file, vector.getFileName(), blob.getContentType()); log.info("æä»¶å 容é¿åº¦: {}", content != null ? content.length() : 0); if (content == null || content.trim().isEmpty()) { throw new RuntimeException("æä»¶å 容为空"); } // ææ¬åç log.info("å¼å§ææ¬åç"); List<TextSegment> chunks = splitText(content, vector); log.info("åç宿ï¼å ± {} 个å", chunks.size()); // æ¹éçæåµå ¥åéå¹¶åå¨ int chunkCount = 0; for (TextSegment chunk : chunks) { log.debug("å¤ç第 {} 个å", chunkCount + 1); Embedding embedding = embeddingModel.embed(chunk).content(); embeddingStore.add(embedding, chunk); chunkCount++; src/main/java/com/ruoyi/approve/controller/KnowledgeBaseController.java
@@ -4,14 +4,13 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.approve.dto.KnowledgeBaseVectorVO; import com.ruoyi.approve.pojo.KnowledgeBase; import com.ruoyi.approve.pojo.KnowledgeBaseVector; import com.ruoyi.approve.service.KnowledgeBaseService; import com.ruoyi.approve.service.KnowledgeBaseVectorService; import com.ruoyi.basic.dto.StorageAttachmentDTO; import com.ruoyi.basic.dto.StorageBlobDTO; import com.ruoyi.basic.pojo.StorageAttachment; import com.ruoyi.basic.pojo.StorageBlob; import com.ruoyi.basic.service.StorageAttachmentService; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.basic.service.StorageBlobService; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.framework.web.domain.AjaxResult; import io.swagger.v3.oas.annotations.Operation; @@ -31,6 +30,7 @@ private KnowledgeBaseService knowledgeBaseService; private KnowledgeBaseVectorService knowledgeBaseVectorService; private StorageAttachmentService storageAttachmentService; private StorageBlobService storageBlobService; /** * è·åå表 @@ -128,18 +128,10 @@ // å建åéè®°å½å¹¶è§¦ååéå for (Long blobId : dto.getStorageBlobIds()) { // è·åæä»¶ä¿¡æ¯ var blob = storageAttachmentService.getBaseMapper() .selectOne(com.baomidou.mybatisplus.core.toolkit.Wrappers.<StorageAttachment>lambdaQuery() .eq(StorageAttachment::getStorageBlobId, blobId) .eq(StorageAttachment::getRecordType, "knowledge_base") .eq(StorageAttachment::getRecordId, dto.getKnowledgeBaseId()) .last("limit 1")); StorageBlob blob = storageBlobService.getById(blobId); if (blob != null) { // è·åæä»¶åï¼éè¦ä» storage_blob 表è·å // è¿éç®åå¤çï¼å®é éè¦æ¥è¯¢ storage_blob 表 String fileName = "file_" + blobId; String fileType = "unknown"; String fileName = blob.getOriginalFilename(); String fileType = getFileExtension(fileName); knowledgeBaseVectorService.createVectorRecord( dto.getKnowledgeBaseId(), @@ -153,6 +145,13 @@ return AjaxResult.success(); } private String getFileExtension(String fileName) { if (fileName == null || !fileName.contains(".")) { return "unknown"; } return fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase(); } /** * å é¤ç¥è¯åºæä»¶ */ src/main/java/com/ruoyi/framework/config/AsyncConfig.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,36 @@ package com.ruoyi.framework.config; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; /** * 弿¥ä»»å¡é ç½® * ç¨äºé ç½® @Async 注解使ç¨ççº¿ç¨æ± */ @Configuration public class AsyncConfig implements AsyncConfigurer { @Autowired private ThreadPoolTaskExecutor threadPoolTaskExecutor; @Override public Executor getAsyncExecutor() { return threadPoolTaskExecutor; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return (throwable, method, params) -> { System.err.println("弿¥ä»»å¡æ§è¡å¼å¸¸: " + method.getName() + " åæ°: " + params); throwable.printStackTrace(); }; } }