diff --git a/README.md b/README.md
index 4aff8d35..cfb13aaa 100644
--- a/README.md
+++ b/README.md
@@ -34,6 +34,23 @@
提出新特性
+## 快速启动
+
+1. **克隆项目**
+ ```bash
+ git clone https://github.com/alanpeng/ruoyi-ai-docker-deploy
+ cd ruoyi-ai-docker-deploy
+ ```
+
+2. **启动全套应用**
+ ```bash
+ docker-compose up -d
+ ```
+
+3. **访问应用界面**
+ - 用户界面:`http://your-server-ip:8081`
+ - 管理员界面:`http://your-server-ip:8082`
+
## 目录
- [系统体验](#系统体验)
diff --git a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/KnowledgeInfo.java b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/KnowledgeInfo.java
index 230a1d65..34b1c2d0 100644
--- a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/KnowledgeInfo.java
+++ b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/KnowledgeInfo.java
@@ -1,5 +1,6 @@
package org.ruoyi.domain;
+import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -78,14 +79,19 @@ public class KnowledgeInfo extends BaseEntity {
private Long textBlockSize;
/**
- * 向量库
+ * 向量库模型名称
*/
- private String vector;
+ private String vectorModelName;
/**
- * 向量模型
+ * 向量化模型名称
*/
- private String vectorModel;
+ private String embeddingModelName;
+
+ /**
+ * 系统提示词
+ */
+ private String systemPrompt;
/**
* 备注
diff --git a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/bo/KnowledgeInfoBo.java b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/bo/KnowledgeInfoBo.java
index 00a22b7f..9a510a68 100644
--- a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/bo/KnowledgeInfoBo.java
+++ b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/bo/KnowledgeInfoBo.java
@@ -83,16 +83,22 @@ public class KnowledgeInfoBo extends BaseEntity {
private Long textBlockSize;
/**
- * 向量库
+ * 向量库模型名称
*/
@NotBlank(message = "向量库不能为空", groups = { AddGroup.class, EditGroup.class })
- private String vector;
+ private String vectorModelName;
/**
- * 向量模型
+ * 向量化模型名称
*/
@NotBlank(message = "向量模型不能为空", groups = { AddGroup.class, EditGroup.class })
- private String vectorModel;
+ private String embeddingModelName;
+
+
+ /**
+ * 系统提示词
+ */
+ private String systemPrompt;
/**
* 备注
diff --git a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/bo/QueryVectorBo.java b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/bo/QueryVectorBo.java
index 33e82049..ff3a26e5 100644
--- a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/bo/QueryVectorBo.java
+++ b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/bo/QueryVectorBo.java
@@ -26,9 +26,14 @@ public class QueryVectorBo {
private Integer maxResults;
/**
- * 模型名称
+ * 向量库模型名称
*/
- private String modelName;
+ private String vectorModelName;
+
+ /**
+ * 向量化模型名称
+ */
+ private String embeddingModelName;
/**
* 请求key
diff --git a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/bo/StoreEmbeddingBo.java b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/bo/StoreEmbeddingBo.java
index 95104037..e4d8c381 100644
--- a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/bo/StoreEmbeddingBo.java
+++ b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/bo/StoreEmbeddingBo.java
@@ -32,9 +32,14 @@ public class StoreEmbeddingBo {
private List fids;
/**
- * 模型名称
+ * 向量库模型名称
*/
- private String modelName;
+ private String vectorModelName;
+
+ /**
+ * 向量化模型名称
+ */
+ private String embeddingModelName;
/**
* 请求key
diff --git a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/vo/KnowledgeInfoVo.java b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/vo/KnowledgeInfoVo.java
index 6a5fdbf8..ed4f6aa6 100644
--- a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/vo/KnowledgeInfoVo.java
+++ b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/vo/KnowledgeInfoVo.java
@@ -98,16 +98,20 @@ public class KnowledgeInfoVo implements Serializable {
private Integer textBlockSize;
/**
- * 向量库
+ * 向量库模型名称
*/
- @ExcelProperty(value = "向量库")
- private String vector;
+ private String vectorModelName;
/**
- * 向量模型
+ * 向量化模型名称
*/
- @ExcelProperty(value = "向量模型")
- private String vectorModel;
+ private String embeddingModelName;
+
+
+ /**
+ * 系统提示词
+ */
+ private String systemPrompt;
/**
* 备注
diff --git a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/VectorStoreService.java b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/VectorStoreService.java
index e27c94cc..be445b99 100644
--- a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/VectorStoreService.java
+++ b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/VectorStoreService.java
@@ -13,14 +13,14 @@ public interface VectorStoreService {
void storeEmbeddings(StoreEmbeddingBo storeEmbeddingBo);
- void removeByDocId(String kid,String docId);
-
- void removeByKid(String kid);
-
List getQueryVector(QueryVectorBo queryVectorBo);
void createSchema(String kid,String modelName);
- void removeByKidAndFid(String kid, String fid);
+ void removeByKid(String kid,String modelName);
+
+ void removeByDocId(String kid,String docId,String modelName);
+
+ void removeByKidAndFid(String kid, String fid,String modelName);
}
diff --git a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/VectorStoreServiceImpl.java b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/VectorStoreServiceImpl.java
index d74176a4..b5f79628 100644
--- a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/VectorStoreServiceImpl.java
+++ b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/VectorStoreServiceImpl.java
@@ -1,5 +1,7 @@
package org.ruoyi.service.impl;
+import cn.hutool.core.util.RandomUtil;
+import com.google.protobuf.ServiceException;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
@@ -16,6 +18,7 @@ import dev.langchain4j.store.embedding.milvus.MilvusEmbeddingStore;
import dev.langchain4j.store.embedding.qdrant.QdrantEmbeddingStore;
import dev.langchain4j.store.embedding.weaviate.WeaviateEmbeddingStore;
import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.core.service.ConfigService;
import org.ruoyi.domain.bo.QueryVectorBo;
@@ -40,11 +43,10 @@ public class VectorStoreServiceImpl implements VectorStoreService {
private final ConfigService configService;
- Map> storeMap = new HashMap<>();
+ private EmbeddingStore embeddingStore;
@Override
public void createSchema(String kid,String modelName) {
- EmbeddingStore embeddingStore;
switch (modelName) {
case "weaviate" -> {
String protocol = configService.getConfigValue("weaviate", "protocol");
@@ -84,88 +86,83 @@ public class VectorStoreServiceImpl implements VectorStoreService {
embeddingStore = new InMemoryEmbeddingStore<>();
}
}
- storeMap.put(kid,embeddingStore);
}
@Override
public void storeEmbeddings(StoreEmbeddingBo storeEmbeddingBo) {
- EmbeddingStore store = storeMap.get(storeEmbeddingBo.getKid());
- EmbeddingModel embeddingModel = getEmbeddingModel(storeEmbeddingBo.getModelName(),
+ createSchema(storeEmbeddingBo.getKid(),storeEmbeddingBo.getVectorModelName());
+ EmbeddingModel embeddingModel = getEmbeddingModel(storeEmbeddingBo.getEmbeddingModelName(),
storeEmbeddingBo.getApiKey(), storeEmbeddingBo.getBaseUrl());
- for (int i = 0; i < storeEmbeddingBo.getChunkList().size(); i++) {
+ List chunkList = storeEmbeddingBo.getChunkList();
+ for (int i = 0; i < chunkList.size(); i++) {
Map dataSchema = new HashMap<>();
dataSchema.put("kid", storeEmbeddingBo.getKid());
dataSchema.put("docId", storeEmbeddingBo.getKid());
dataSchema.put("fid", storeEmbeddingBo.getFids().get(i));
- Response response = embeddingModel.embed(storeEmbeddingBo.getChunkList().get(i));
- Embedding embedding = response.content();
- TextSegment segment = TextSegment.from(storeEmbeddingBo.getChunkList().get(i));
+ Embedding embedding = embeddingModel.embed(chunkList.get(i)).content();
+ TextSegment segment = TextSegment.from(chunkList.get(i));
segment.metadata().putAll(dataSchema);
-
- store.add(embedding,segment);
+ embeddingStore.add(embedding,segment);
}
}
@Override
public List getQueryVector(QueryVectorBo queryVectorBo) {
- EmbeddingStore store = storeMap.get(queryVectorBo.getKid());
-
- EmbeddingModel embeddingModel = getEmbeddingModel(queryVectorBo.getModelName(),
+ createSchema(queryVectorBo.getKid(),queryVectorBo.getVectorModelName());
+ EmbeddingModel embeddingModel = getEmbeddingModel(queryVectorBo.getEmbeddingModelName(),
queryVectorBo.getApiKey(), queryVectorBo.getBaseUrl());
- Filter simpleFilter = new IsEqualTo("kid", queryVectorBo.getKid());
+ // Filter simpleFilter = new IsEqualTo("kid", queryVectorBo.getKid());
Embedding queryEmbedding = embeddingModel.embed(queryVectorBo.getQuery()).content();
EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(queryVectorBo.getMaxResults())
// 添加过滤条件
- .filter(simpleFilter)
+ // .filter(simpleFilter)
.build();
- List> matches = store.search(embeddingSearchRequest).matches();
-
+ List> matches = embeddingStore.search(embeddingSearchRequest).matches();
List results = new ArrayList<>();
-
matches.forEach(embeddingMatch -> results.add(embeddingMatch.embedded().text()));
return results;
}
@Override
- public void removeByKid(String kid) {
- EmbeddingStore store = storeMap.get(kid);
-
+ public void removeByKid(String kid,String modelName) {
+ createSchema(kid,modelName);
// 根据条件删除向量数据
Filter simpleFilter = new IsEqualTo("kid", kid);
- store.removeAll(simpleFilter);
+ embeddingStore.removeAll(simpleFilter);
}
@Override
- public void removeByDocId(String kid, String docId) {
- EmbeddingStore store = storeMap.get(kid);
+ public void removeByDocId(String kid, String docId,String modelName) {
+ createSchema(kid,modelName);
// 根据条件删除向量数据
Filter simpleFilterByDocId = new IsEqualTo("docId", docId);
- store.removeAll(simpleFilterByDocId);
+ embeddingStore.removeAll(simpleFilterByDocId);
}
@Override
- public void removeByKidAndFid(String kid, String fid) {
- EmbeddingStore store = storeMap.get(kid);
+ public void removeByKidAndFid(String kid, String fid,String modelName) {
+ createSchema(kid,modelName);
// 根据条件删除向量数据
Filter simpleFilterByKid = new IsEqualTo("kid", kid);
Filter simpleFilterFid = new IsEqualTo("fid", fid);
Filter simpleFilterByAnd = Filter.and(simpleFilterFid, simpleFilterByKid);
- store.removeAll(simpleFilterByAnd);
+ embeddingStore.removeAll(simpleFilterByAnd);
}
/**
* 获取向量模型
*/
- public EmbeddingModel getEmbeddingModel(String modelName,String apiKey,String baseUrl) {
- EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder().build();
+ @SneakyThrows
+ public EmbeddingModel getEmbeddingModel(String modelName, String apiKey, String baseUrl) {
+ EmbeddingModel embeddingModel;
if(TEXT_EMBEDDING_3_SMALL.toString().equals(modelName)) {
embeddingModel = OpenAiEmbeddingModel.builder()
.apiKey(apiKey)
.baseUrl(baseUrl)
- .modelName(TEXT_EMBEDDING_3_SMALL)
+ .modelName(modelName)
.build();
// TODO 添加枚举
}else if("quentinz/bge-large-zh-v1.5".equals(modelName)) {
@@ -173,6 +170,14 @@ public class VectorStoreServiceImpl implements VectorStoreService {
.baseUrl(baseUrl)
.modelName(modelName)
.build();
+ }else if("baai/bge-m3".equals(modelName)) {
+ embeddingModel = OpenAiEmbeddingModel.builder()
+ .apiKey(apiKey)
+ .baseUrl(baseUrl)
+ .modelName(modelName)
+ .build();
+ }else {
+ throw new ServiceException("未找到对应向量化模型!");
}
return embeddingModel;
}
diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java
index cc5f2896..13872306 100644
--- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java
+++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java
@@ -2,6 +2,7 @@ package org.ruoyi.chat.service.chat.impl;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollectionUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.protobuf.ServiceException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
@@ -29,6 +30,8 @@ import org.ruoyi.common.redis.utils.RedisUtils;
import org.ruoyi.domain.bo.ChatSessionBo;
import org.ruoyi.domain.bo.QueryVectorBo;
import org.ruoyi.domain.vo.ChatModelVo;
+import org.ruoyi.domain.vo.KnowledgeInfoVo;
+import org.ruoyi.service.IKnowledgeInfoService;
import org.ruoyi.service.VectorStoreService;
import org.ruoyi.service.IChatModelService;
import org.ruoyi.service.IChatSessionService;
@@ -67,6 +70,8 @@ public class SseServiceImpl implements ISseService {
private final IChatSessionService chatSessionService;
+ private final IKnowledgeInfoService knowledgeInfoService;
+
private ChatModelVo chatModelVo;
@@ -148,50 +153,61 @@ public class SseServiceImpl implements ISseService {
}
}
-
/**
* 构建消息列表
*/
private void buildChatMessageList(ChatRequest chatRequest){
- chatModelVo = chatModelService.selectModelByName(chatRequest.getModel());
+ String sysPrompt;
+ chatModelVo = chatModelService.selectModelByName(chatRequest.getModel());
// 获取对话消息列表
List messages = chatRequest.getMessages();
- String sysPrompt = chatModelVo.getSystemPrompt();
+ // 查询向量库相关信息加入到上下文
+ if(StringUtils.isNotEmpty(chatRequest.getKid())){
+ List knMessages = new ArrayList<>();
+ String content = messages.get(messages.size() - 1).getContent().toString();
+ // 通过kid查询知识库信息
+ KnowledgeInfoVo knowledgeInfoVo = knowledgeInfoService.queryById(Long.valueOf(chatRequest.getKid()));
+ // 查询向量模型配置信息
+ ChatModelVo chatModel = chatModelService.selectModelByName(knowledgeInfoVo.getEmbeddingModelName());
-
- if(StringUtils.isEmpty(sysPrompt)){
- // TODO 系统默认提示词,后续会增加提示词管理
- sysPrompt ="你是一个由RuoYI-AI开发的人工智能助手,名字叫熊猫助手。你擅长中英文对话,能够理解并处理各种问题,提供安全、有帮助、准确的回答。" +
- "当前时间:"+ DateUtils.getDate()+
- "#注意:回复之前注意结合上下文和工具返回内容进行回复。";
+ QueryVectorBo queryVectorBo = new QueryVectorBo();
+ queryVectorBo.setQuery(content);
+ queryVectorBo.setKid(chatRequest.getKid());
+ queryVectorBo.setApiKey(chatModel.getApiKey());
+ queryVectorBo.setBaseUrl(chatModel.getApiHost());
+ queryVectorBo.setVectorModelName(knowledgeInfoVo.getVectorModelName());
+ queryVectorBo.setEmbeddingModelName(knowledgeInfoVo.getEmbeddingModelName());
+ queryVectorBo.setMaxResults(knowledgeInfoVo.getRetrieveLimit());
+ List nearestList = vectorStoreService.getQueryVector(queryVectorBo);
+ for (String prompt : nearestList) {
+ Message userMessage = Message.builder().content(prompt).role(Message.Role.USER).build();
+ knMessages.add(userMessage);
+ }
+ messages.addAll(knMessages);
+ // 设置知识库系统提示词
+ sysPrompt = knowledgeInfoVo.getSystemPrompt();
+ if(StringUtils.isEmpty(sysPrompt)){
+ sysPrompt ="###角色设定\n" +
+ "你是一个智能知识助手,专注于利用上下文中的信息来提供准确和相关的回答。\n" +
+ "###指令\n" +
+ "当用户的问题与上下文知识匹配时,利用上下文信息进行回答。如果问题与上下文不匹配,运用自身的推理能力生成合适的回答。\n" +
+ "###限制\n" +
+ "确保回答清晰简洁,避免提供不必要的细节。始终保持语气友好" +
+ "当前时间:"+ DateUtils.getDate();
+ }
+ }else {
+ sysPrompt = chatModelVo.getSystemPrompt();
+ if(StringUtils.isEmpty(sysPrompt)){
+ sysPrompt ="你是一个由RuoYI-AI开发的人工智能助手,名字叫熊猫助手。你擅长中英文对话,能够理解并处理各种问题,提供安全、有帮助、准确的回答。" +
+ "当前时间:"+ DateUtils.getDate()+
+ "#注意:回复之前注意结合上下文和工具返回内容进行回复。";
+ }
}
// 设置系统默认提示词
Message sysMessage = Message.builder().content(sysPrompt).role(Message.Role.SYSTEM).build();
messages.add(0,sysMessage);
chatRequest.setSysPrompt(sysPrompt);
- // 查询向量库相关信息加入到上下文
- if(StringUtils.isNotEmpty(chatRequest.getKid())){
- List knMessages = new ArrayList<>();
- String content = messages.get(messages.size() - 1).getContent().toString();
- QueryVectorBo queryVectorBo = new QueryVectorBo();
- queryVectorBo.setQuery(content);
- queryVectorBo.setKid(chatRequest.getKid());
- queryVectorBo.setApiKey(chatModelVo.getApiKey());
- queryVectorBo.setBaseUrl(chatModelVo.getApiHost());
- queryVectorBo.setModelName(chatModelVo.getModelName());
- // TODO 查询向量返回条数,这里应该查询知识库配置
- queryVectorBo.setMaxResults(3);
- List nearestList = vectorStoreService.getQueryVector(queryVectorBo);
- for (String prompt : nearestList) {
- Message userMessage = Message.builder().content(prompt).role(Message.Role.USER).build();
- knMessages.add(userMessage);
- }
- // TODO 提示词,这里应该查询知识库配置
- Message userMessage = Message.builder().content(content + (!nearestList.isEmpty() ? "\n\n注意:回答问题时,须严格根据我给你的系统上下文内容原文进行回答,请不要自己发挥,回答时保持原来文本的段落层级" : "")).role(Message.Role.USER).build();
- knMessages.add(userMessage);
- messages.addAll(knMessages);
- }
// 用户对话内容
String chatString = null;
// 获取用户对话信息
diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java
index 3c5bb34f..d94d3205 100644
--- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java
+++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java
@@ -1,14 +1,11 @@
package org.ruoyi.chat.service.knowledge;
import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.ruoyi.chain.loader.ResourceLoader;
import org.ruoyi.chain.loader.ResourceLoaderFactory;
@@ -16,8 +13,6 @@ import org.ruoyi.common.core.domain.model.LoginUser;
import org.ruoyi.common.core.utils.MapstructUtils;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.common.satoken.utils.LoginHelper;
-import org.ruoyi.constant.DealStatus;
-import org.ruoyi.constant.FileType;
import org.ruoyi.core.page.PageQuery;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.domain.ChatModel;
@@ -35,15 +30,11 @@ import org.ruoyi.mapper.KnowledgeInfoMapper;
import org.ruoyi.service.IChatModelService;
import org.ruoyi.service.VectorStoreService;
import org.ruoyi.service.IKnowledgeInfoService;
-import org.ruoyi.system.domain.vo.SysOssVo;
-import org.ruoyi.system.service.ISysOssService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
-import org.springframework.scheduling.annotation.Async;
import java.io.IOException;
import java.util.*;
@@ -58,321 +49,216 @@ import java.util.*;
@Service
public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService {
- private static final Logger log = LoggerFactory.getLogger(KnowledgeInfoServiceImpl.class);
- private final KnowledgeInfoMapper baseMapper;
+ private static final Logger log = LoggerFactory.getLogger(KnowledgeInfoServiceImpl.class);
+ private final KnowledgeInfoMapper baseMapper;
- private final VectorStoreService vectorStoreService;
+ private final VectorStoreService vectorStoreService;
- private final ResourceLoaderFactory resourceLoaderFactory;
+ private final ResourceLoaderFactory resourceLoaderFactory;
- private final KnowledgeFragmentMapper fragmentMapper;
+ private final KnowledgeFragmentMapper fragmentMapper;
- private final KnowledgeAttachMapper attachMapper;
+ private final KnowledgeAttachMapper attachMapper;
- private final IChatModelService chatModelService;
+ private final IChatModelService chatModelService;
- private final ISysOssService ossService;
-
- /**
- * 查询知识库
- */
- @Override
- public KnowledgeInfoVo queryById(Long id) {
- return baseMapper.selectVoById(id);
- }
-
- /**
- * 查询知识库列表
- */
- @Override
- public TableDataInfo queryPageList(KnowledgeInfoBo bo, PageQuery pageQuery) {
- LambdaQueryWrapper lqw = buildQueryWrapper(bo);
- Page result = baseMapper.selectVoPage(pageQuery.build(), lqw);
- return TableDataInfo.build(result);
- }
-
- /**
- * 查询知识库列表
- */
- @Override
- public List queryList(KnowledgeInfoBo bo) {
- LambdaQueryWrapper lqw = buildQueryWrapper(bo);
- return baseMapper.selectVoList(lqw);
- }
-
- private LambdaQueryWrapper buildQueryWrapper(KnowledgeInfoBo bo) {
- Map params = bo.getParams();
- LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
- lqw.eq(StringUtils.isNotBlank(bo.getKid()), KnowledgeInfo::getKid, bo.getKid());
- lqw.eq(bo.getUid() != null, KnowledgeInfo::getUid, bo.getUid());
- lqw.like(StringUtils.isNotBlank(bo.getKname()), KnowledgeInfo::getKname, bo.getKname());
- lqw.eq(bo.getShare() != null, KnowledgeInfo::getShare, bo.getShare());
- lqw.eq(StringUtils.isNotBlank(bo.getDescription()), KnowledgeInfo::getDescription,
- bo.getDescription());
- lqw.eq(StringUtils.isNotBlank(bo.getKnowledgeSeparator()), KnowledgeInfo::getKnowledgeSeparator,
- bo.getKnowledgeSeparator());
- lqw.eq(StringUtils.isNotBlank(bo.getQuestionSeparator()), KnowledgeInfo::getQuestionSeparator,
- bo.getQuestionSeparator());
- lqw.eq(bo.getOverlapChar() != null, KnowledgeInfo::getOverlapChar, bo.getOverlapChar());
- lqw.eq(bo.getRetrieveLimit() != null, KnowledgeInfo::getRetrieveLimit, bo.getRetrieveLimit());
- lqw.eq(bo.getTextBlockSize() != null, KnowledgeInfo::getTextBlockSize, bo.getTextBlockSize());
- lqw.eq(StringUtils.isNotBlank(bo.getVector()), KnowledgeInfo::getVector, bo.getVector());
- lqw.eq(StringUtils.isNotBlank(bo.getVectorModel()), KnowledgeInfo::getVectorModel,
- bo.getVectorModel());
- return lqw;
- }
-
- /**
- * 新增知识库
- */
- @Override
- public Boolean insertByBo(KnowledgeInfoBo bo) {
- KnowledgeInfo add = MapstructUtils.convert(bo, KnowledgeInfo.class);
- validEntityBeforeSave(add);
- boolean flag = baseMapper.insert(add) > 0;
- if (flag) {
- bo.setId(add.getId());
- }
- return flag;
- }
-
- /**
- * 修改知识库
- */
- @Override
- public Boolean updateByBo(KnowledgeInfoBo bo) {
- KnowledgeInfo update = MapstructUtils.convert(bo, KnowledgeInfo.class);
- validEntityBeforeSave(update);
- return baseMapper.updateById(update) > 0;
- }
-
- /**
- * 保存前的数据校验
- */
- private void validEntityBeforeSave(KnowledgeInfo entity) {
- //TODO 做一些数据校验,如唯一约束
- }
-
- /**
- * 批量删除知识库
- */
- @Override
- public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) {
- if (isValid) {
- //TODO 做一些业务上的校验,判断是否需要校验
- }
- return baseMapper.deleteBatchIds(ids) > 0;
- }
-
- @Override
- @Transactional(rollbackFor = Exception.class)
- public void saveOne(KnowledgeInfoBo bo) {
- KnowledgeInfo knowledgeInfo = MapstructUtils.convert(bo, KnowledgeInfo.class);
- if (StringUtils.isBlank(bo.getKid())) {
- String kid = RandomUtil.randomString(10);
- if (knowledgeInfo != null) {
- knowledgeInfo.setKid(kid);
- knowledgeInfo.setUid(LoginHelper.getLoginUser().getUserId());
- }
- baseMapper.insert(knowledgeInfo);
- if (knowledgeInfo != null) {
- vectorStoreService.createSchema(String.valueOf(knowledgeInfo.getId()), bo.getVector());
- }
- } else {
- baseMapper.updateById(knowledgeInfo);
- }
- }
-
- @Override
- @Transactional(rollbackFor = Exception.class)
- public void removeKnowledge(String id) {
- Map map = new HashMap<>();
- map.put("kid", id);
- List knowledgeInfoList = baseMapper.selectVoByMap(map);
- check(knowledgeInfoList);
- // 删除向量库信息
- knowledgeInfoList.forEach(knowledgeInfoVo -> {
- vectorStoreService.removeByKid(String.valueOf(knowledgeInfoVo.getId()));
- });
- // 删除附件和知识片段
- fragmentMapper.deleteByMap(map);
- attachMapper.deleteByMap(map);
- // 删除知识库
- baseMapper.deleteByMap(map);
- }
-
- @Override
- public void upload(KnowledgeInfoUploadBo bo) {
- storeContent(bo.getFile(), bo.getKid());
- }
-
- public void storeContent(MultipartFile file, String kid) {
- if (file == null || file.isEmpty()) {
- throw new IllegalArgumentException("File cannot be null or empty");
+ /**
+ * 查询知识库
+ */
+ @Override
+ public KnowledgeInfoVo queryById(Long id){
+ return baseMapper.selectVoById(id);
}
- SysOssVo uploadDto = null;
+ /**
+ * 查询知识库列表
+ */
+ @Override
+ public TableDataInfo queryPageList(KnowledgeInfoBo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(bo);
+ Page result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(result);
+ }
- String fileName = file.getOriginalFilename();
- List chunkList = new ArrayList<>();
- KnowledgeAttach knowledgeAttach = new KnowledgeAttach();
- knowledgeAttach.setKid(kid);
- String docId = RandomUtil.randomString(10);
- knowledgeAttach.setDocId(docId);
- knowledgeAttach.setDocName(fileName);
- knowledgeAttach.setDocType(fileName.substring(fileName.lastIndexOf(".") + 1));
- String content = "";
- ResourceLoader resourceLoader = resourceLoaderFactory.getLoaderByFileType(
- knowledgeAttach.getDocType());
- List fids = new ArrayList<>();
- try {
- content = resourceLoader.getContent(file.getInputStream());
- chunkList = resourceLoader.getChunkList(content, kid);
- List knowledgeFragmentList = new ArrayList<>();
- if (CollUtil.isNotEmpty(chunkList)) {
- // Upload file to OSS
- uploadDto = ossService.upload(file);
+ /**
+ * 查询知识库列表
+ */
+ @Override
+ public List queryList(KnowledgeInfoBo bo) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(bo);
+ return baseMapper.selectVoList(lqw);
+ }
- for (int i = 0; i < chunkList.size(); i++) {
- String fid = RandomUtil.randomString(10);
- fids.add(fid);
- KnowledgeFragment knowledgeFragment = new KnowledgeFragment();
- knowledgeFragment.setKid(kid);
- knowledgeFragment.setDocId(docId);
- knowledgeFragment.setFid(fid);
- knowledgeFragment.setIdx(i);
- knowledgeFragment.setContent(chunkList.get(i));
- knowledgeFragment.setCreateTime(new Date());
- knowledgeFragmentList.add(knowledgeFragment);
+ private LambdaQueryWrapper buildQueryWrapper(KnowledgeInfoBo bo) {
+ Map params = bo.getParams();
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StringUtils.isNotBlank(bo.getKid()), KnowledgeInfo::getKid, bo.getKid());
+ lqw.eq(bo.getUid() != null, KnowledgeInfo::getUid, bo.getUid());
+ lqw.like(StringUtils.isNotBlank(bo.getKname()), KnowledgeInfo::getKname, bo.getKname());
+ lqw.eq(bo.getShare() != null, KnowledgeInfo::getShare, bo.getShare());
+ lqw.eq(StringUtils.isNotBlank(bo.getDescription()), KnowledgeInfo::getDescription, bo.getDescription());
+ lqw.eq(StringUtils.isNotBlank(bo.getKnowledgeSeparator()), KnowledgeInfo::getKnowledgeSeparator, bo.getKnowledgeSeparator());
+ lqw.eq(StringUtils.isNotBlank(bo.getQuestionSeparator()), KnowledgeInfo::getQuestionSeparator, bo.getQuestionSeparator());
+ lqw.eq(bo.getOverlapChar() != null, KnowledgeInfo::getOverlapChar, bo.getOverlapChar());
+ lqw.eq(bo.getRetrieveLimit() != null, KnowledgeInfo::getRetrieveLimit, bo.getRetrieveLimit());
+ lqw.eq(bo.getTextBlockSize() != null, KnowledgeInfo::getTextBlockSize, bo.getTextBlockSize());
+ return lqw;
+ }
+
+ /**
+ * 新增知识库
+ */
+ @Override
+ public Boolean insertByBo(KnowledgeInfoBo bo) {
+ KnowledgeInfo add = MapstructUtils.convert(bo, KnowledgeInfo.class);
+ validEntityBeforeSave(add);
+ boolean flag = baseMapper.insert(add) > 0;
+ if (flag) {
+ bo.setId(add.getId());
}
- }
- fragmentMapper.insertBatch(knowledgeFragmentList);
- } catch (IOException e) {
- log.error("保存知识库信息失败!{}", e.getMessage());
- }
- knowledgeAttach.setContent(content);
- knowledgeAttach.setCreateTime(new Date());
-
- if (ObjectUtil.isNotEmpty(uploadDto) && ObjectUtil.isNotEmpty(uploadDto.getOssId())) {
- knowledgeAttach.setOssId(uploadDto.getOssId());
- //只有pdf文件 才需要拆解图片和分析图片内容
- if (FileType.PDF.equals(knowledgeAttach.getDocType())) {
- knowledgeAttach.setPicStatus(DealStatus.STATUS_10);
- knowledgeAttach.setPicAnysStatus(DealStatus.STATUS_10);
- } else {
- knowledgeAttach.setPicStatus(DealStatus.STATUS_30);
- knowledgeAttach.setPicAnysStatus(DealStatus.STATUS_30);
- }
- //所有文件上传后,都需要同步到向量数据库
- knowledgeAttach.setVectorStatus(DealStatus.STATUS_10);
+ return flag;
}
- attachMapper.insert(knowledgeAttach);
- }
-
-
- /**
- * 检查用户是否有删除知识库权限
- *
- * @param knowledgeInfoList 知识库列表
- */
- public void check(List knowledgeInfoList) {
- LoginUser loginUser = LoginHelper.getLoginUser();
- for (KnowledgeInfoVo knowledgeInfoVo : knowledgeInfoList) {
- if (!knowledgeInfoVo.getUid().equals(loginUser.getUserId())) {
- throw new SecurityException("权限不足");
- }
+ /**
+ * 修改知识库
+ */
+ @Override
+ public Boolean updateByBo(KnowledgeInfoBo bo) {
+ KnowledgeInfo update = MapstructUtils.convert(bo, KnowledgeInfo.class);
+ validEntityBeforeSave(update);
+ return baseMapper.updateById(update) > 0;
}
- }
- /**
- * 定时 处理 附件上传后上传向量数据库和PDF文件图片拆解和分析内容
- */
- @Scheduled(fixedDelay = 3000) // 每3秒执行一次
- public void dealKnowledgeAttach() throws Exception {
- //处理 需要上传向量数据库的记录
- List knowledgeAttaches = attachMapper.selectList(
- new LambdaQueryWrapper()
- .eq(KnowledgeAttach::getPicStatus, DealStatus.STATUS_30)
- .eq(KnowledgeAttach::getPicAnysStatus, DealStatus.STATUS_30)
- .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_10)
- );
- if (ObjectUtil.isNotEmpty(knowledgeAttaches)) {
- for (KnowledgeAttach attachItem : knowledgeAttaches) {
- this.dealVectorStatus(attachItem);
- }
+ /**
+ * 保存前的数据校验
+ */
+ private void validEntityBeforeSave(KnowledgeInfo entity){
+ //TODO 做一些数据校验,如唯一约束
}
- }
- @Async
- public void dealVectorStatus(KnowledgeAttach attachItem) throws Exception {
- try {
- //锁定数据 更改VectorStatus 到进行中
- if (attachMapper.update(new LambdaUpdateWrapper()
- .set(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_20)
- .eq(KnowledgeAttach::getPicStatus, DealStatus.STATUS_30)
- .eq(KnowledgeAttach::getPicAnysStatus, DealStatus.STATUS_30)
- .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_10)
- .eq(KnowledgeAttach::getId, attachItem.getId())
- ) == 0) {
- return;
- }
- // 通过kid查询知识库信息
- KnowledgeInfoVo knowledgeInfoVo = baseMapper.selectVoOne(Wrappers.lambdaQuery()
- .eq(KnowledgeInfo::getKid, attachItem.getKid()));
-
- // 通过向量模型查询模型信息
- ChatModelVo chatModelVo = chatModelService.selectModelByName(
- knowledgeInfoVo.getVectorModel());
-
- List knowledgeFragments = fragmentMapper.selectList(
- new LambdaQueryWrapper()
- .eq(KnowledgeFragment::getKid, attachItem.getKid())
- .eq(KnowledgeFragment::getDocId, attachItem.getDocId())
- );
- if (ObjectUtil.isEmpty(knowledgeFragments)) {
- throw new Exception("文件段落为空");
- }
- List fids = knowledgeFragments.stream()
- .map(KnowledgeFragment::getFid)
- .collect(Collectors.toList());
- if (ObjectUtil.isEmpty(fids)) {
- throw new Exception("fids 为空");
- }
- List chunkList = knowledgeFragments.stream()
- .map(KnowledgeFragment::getContent)
- .collect(Collectors.toList());
-
- if (ObjectUtil.isEmpty(chunkList)) {
- throw new Exception("chunkList 为空");
- }
- StoreEmbeddingBo storeEmbeddingBo = new StoreEmbeddingBo();
- storeEmbeddingBo.setKid(attachItem.getKid());
- storeEmbeddingBo.setDocId(attachItem.getDocId());
- storeEmbeddingBo.setFids(fids);
- storeEmbeddingBo.setChunkList(chunkList);
- storeEmbeddingBo.setModelName(knowledgeInfoVo.getVectorModel());
- storeEmbeddingBo.setApiKey(chatModelVo.getApiKey());
- storeEmbeddingBo.setBaseUrl(chatModelVo.getApiHost());
- vectorStoreService.storeEmbeddings(storeEmbeddingBo);
-
- //设置处理完成
- attachMapper.update(new LambdaUpdateWrapper()
- .set(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_30)
- .eq(KnowledgeAttach::getPicStatus, DealStatus.STATUS_30)
- .eq(KnowledgeAttach::getPicAnysStatus, DealStatus.STATUS_30)
- .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_20)
- .eq(KnowledgeAttach::getId, attachItem.getId()));
- } catch (Exception e) {
- //设置处理失败
- attachMapper.update(new LambdaUpdateWrapper()
- .set(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_10)
- .eq(KnowledgeAttach::getPicStatus, DealStatus.STATUS_30)
- .eq(KnowledgeAttach::getPicAnysStatus, DealStatus.STATUS_30)
- .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_20)
- .eq(KnowledgeAttach::getId, attachItem.getId()));
- throw new RuntimeException(e);
+ /**
+ * 批量删除知识库
+ */
+ @Override
+ public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) {
+ if(isValid){
+ //TODO 做一些业务上的校验,判断是否需要校验
+ }
+ return baseMapper.deleteBatchIds(ids) > 0;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void saveOne(KnowledgeInfoBo bo) {
+ KnowledgeInfo knowledgeInfo = MapstructUtils.convert(bo, KnowledgeInfo.class);
+ if (StringUtils.isBlank(bo.getKid())){
+ String kid = RandomUtil.randomString(10);
+ if (knowledgeInfo != null) {
+ knowledgeInfo.setKid(kid);
+ knowledgeInfo.setUid(LoginHelper.getLoginUser().getUserId());
+ }
+ baseMapper.insert(knowledgeInfo);
+ if (knowledgeInfo != null) {
+ vectorStoreService.createSchema(String.valueOf(knowledgeInfo.getId()),bo.getVectorModelName());
+ }
+ }else {
+ baseMapper.updateById(knowledgeInfo);
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void removeKnowledge(String id) {
+ Map map = new HashMap<>();
+ map.put("kid",id);
+ List knowledgeInfoList = baseMapper.selectVoByMap(map);
+ check(knowledgeInfoList);
+ // 删除向量库信息
+ knowledgeInfoList.forEach(knowledgeInfoVo -> {
+ vectorStoreService.removeByKid(String.valueOf(knowledgeInfoVo.getId()),knowledgeInfoVo.getVectorModelName());
+ });
+ // 删除附件和知识片段
+ fragmentMapper.deleteByMap(map);
+ attachMapper.deleteByMap(map);
+ // 删除知识库
+ baseMapper.deleteByMap(map);
+ }
+
+ @Override
+ public void upload(KnowledgeInfoUploadBo bo) {
+ storeContent(bo.getFile(), bo.getKid());
+ }
+
+ public void storeContent(MultipartFile file, String kid) {
+ String fileName = file.getOriginalFilename();
+ List chunkList = new ArrayList<>();
+ KnowledgeAttach knowledgeAttach = new KnowledgeAttach();
+ knowledgeAttach.setKid(kid);
+ String docId = RandomUtil.randomString(10);
+ knowledgeAttach.setDocId(docId);
+ knowledgeAttach.setDocName(fileName);
+ knowledgeAttach.setDocType(fileName.substring(fileName.lastIndexOf(".")+1));
+ String content = "";
+ ResourceLoader resourceLoader = resourceLoaderFactory.getLoaderByFileType(knowledgeAttach.getDocType());
+ List fids = new ArrayList<>();
+ try {
+ content = resourceLoader.getContent(file.getInputStream());
+ chunkList = resourceLoader.getChunkList(content, kid);
+ List knowledgeFragmentList = new ArrayList<>();
+ if (CollUtil.isNotEmpty(chunkList)) {
+ for (int i = 0; i < chunkList.size(); i++) {
+ String fid = RandomUtil.randomString(10);
+ fids.add(fid);
+ KnowledgeFragment knowledgeFragment = new KnowledgeFragment();
+ knowledgeFragment.setKid(kid);
+ knowledgeFragment.setDocId(docId);
+ knowledgeFragment.setFid(fid);
+ knowledgeFragment.setIdx(i);
+ knowledgeFragment.setContent(chunkList.get(i));
+ knowledgeFragment.setCreateTime(new Date());
+ knowledgeFragmentList.add(knowledgeFragment);
+ }
+ }
+ fragmentMapper.insertBatch(knowledgeFragmentList);
+ } catch (IOException e) {
+ log.error("保存知识库信息失败!{}", e.getMessage());
+ }
+ knowledgeAttach.setContent(content);
+ knowledgeAttach.setCreateTime(new Date());
+ attachMapper.insert(knowledgeAttach);
+
+ // 通过kid查询知识库信息
+ KnowledgeInfoVo knowledgeInfoVo = baseMapper.selectVoOne(Wrappers.lambdaQuery()
+ .eq(KnowledgeInfo::getId, kid));
+
+ // 通过向量模型查询模型信息
+ ChatModelVo chatModelVo = chatModelService.selectModelByName(knowledgeInfoVo.getEmbeddingModelName());
+
+ StoreEmbeddingBo storeEmbeddingBo = new StoreEmbeddingBo();
+ storeEmbeddingBo.setKid(kid);
+ storeEmbeddingBo.setDocId(docId);
+ storeEmbeddingBo.setFids(fids);
+ storeEmbeddingBo.setChunkList(chunkList);
+ storeEmbeddingBo.setVectorModelName(knowledgeInfoVo.getVectorModelName());
+ storeEmbeddingBo.setEmbeddingModelName(knowledgeInfoVo.getEmbeddingModelName());
+ storeEmbeddingBo.setApiKey(chatModelVo.getApiKey());
+ storeEmbeddingBo.setBaseUrl(chatModelVo.getApiHost());
+ vectorStoreService.storeEmbeddings(storeEmbeddingBo);
+ }
+
+
+ /**
+ * 检查用户是否有删除知识库权限
+ *
+ * @param knowledgeInfoList 知识库列表
+ */
+ public void check(List knowledgeInfoList){
+ LoginUser loginUser = LoginHelper.getLoginUser();
+ for (KnowledgeInfoVo knowledgeInfoVo : knowledgeInfoList) {
+ if(!knowledgeInfoVo.getUid().equals(loginUser.getUserId())){
+ throw new SecurityException("权限不足");
+ }
+ }
}
- }
}
diff --git a/script/sql/update/20250514.sql b/script/sql/update/20250514.sql
new file mode 100644
index 00000000..61ec6d3d
--- /dev/null
+++ b/script/sql/update/20250514.sql
@@ -0,0 +1,6 @@
+LTER TABLE `knowledge_info`
+ADD COLUMN `system_prompt` varchar(255) NULL COMMENT '系统提示词' AFTER `vector_model`;
+
+ALTER TABLE `knowledge_info`
+ CHANGE COLUMN `vector` `vector_model_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '向量库' AFTER `text_block_size`,
+ CHANGE COLUMN `vector_model` `embedding_model_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '向量模型' AFTER `vector_model_name`;