ruoyi-modules-api/ruoyi-knowledge-api/pom.xml
@@ -48,17 +48,17 @@ </dependency> <!-- milvus java sdk --> <dependency> <groupId>io.milvus</groupId> <artifactId>milvus-sdk-java</artifactId> <version>2.3.2</version> </dependency> <!-- <dependency>--> <!-- <groupId>io.milvus</groupId>--> <!-- <artifactId>milvus-sdk-java</artifactId>--> <!-- <version>2.3.2</version>--> <!-- </dependency>--> <dependency> <groupId>io.weaviate</groupId> <artifactId>client</artifactId> <version>4.0.0</version> </dependency> <!-- <dependency>--> <!-- <groupId>io.weaviate</groupId>--> <!-- <artifactId>client</artifactId>--> <!-- <version>4.0.0</version>--> <!-- </dependency>--> <dependency> @@ -86,7 +86,12 @@ <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId> <artifactId>langchain4j-open-ai</artifactId> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-ollama</artifactId> </dependency> </dependencies> ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/bo/QueryVectorBo.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,43 @@ package org.ruoyi.domain.bo; import lombok.Data; /** * æ¥è¯¢åéæéåæ° * @author ageer */ @Data public class QueryVectorBo { /** * æ¥è¯¢å 容 */ private String query; /** * ç¥è¯åºkid */ private String kid; /** * æ¥è¯¢åéè¿åæ¡æ° */ private Integer maxResults; /** * 模ååç§° */ private String modelName; /** * 请æ±key */ private String apiKey; /** * 请æ±å°å */ private String baseUrl; } ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/bo/StoreEmbeddingBo.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,49 @@ package org.ruoyi.domain.bo; import lombok.Data; import java.util.List; /** * ä¿ååéæéåæ° * @author ageer */ @Data public class StoreEmbeddingBo { /** * ååææ¬åå表 */ private List<String> chunkList; /** * ç¥è¯åºkid */ private String kid; /** * ææ¡£id */ private String docId; /** * ç¥è¯åidå表 */ private List<String> fids; /** * 模ååç§° */ private String modelName; /** * 请æ±key */ private String apiKey; /** * 请æ±å°å */ private String baseUrl; } ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/VectorStoreService.java
@@ -1,20 +1,23 @@ package org.ruoyi.service; import org.ruoyi.domain.bo.QueryVectorBo; import org.ruoyi.domain.bo.StoreEmbeddingBo; import java.util.List; /** * @author ageer * åéåºç®¡ç * @author ageer */ public interface VectorStoreService { void storeEmbeddings(List<String> chunkList, String kid,String docId,List<String> fids); void storeEmbeddings(StoreEmbeddingBo storeEmbeddingBo); void removeByDocId(String kid,String docId); void removeByKid(String kid); List<String> getQueryVector(String query, String kid); List<String> getQueryVector(QueryVectorBo queryVectorBo); void createSchema(String kid); ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/WeaviateVectorStoreImpl.java
@@ -3,6 +3,7 @@ import dev.langchain4j.data.embedding.Embedding; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.model.ollama.OllamaEmbeddingModel; import dev.langchain4j.model.openai.OpenAiEmbeddingModel; import dev.langchain4j.model.output.Response; import dev.langchain4j.store.embedding.EmbeddingMatch; @@ -11,9 +12,12 @@ import dev.langchain4j.store.embedding.filter.Filter; import dev.langchain4j.store.embedding.filter.comparison.IsEqualTo; import dev.langchain4j.store.embedding.weaviate.WeaviateEmbeddingStore; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ruoyi.common.core.service.ConfigService; import org.ruoyi.domain.bo.QueryVectorBo; import org.ruoyi.domain.bo.StoreEmbeddingBo; import org.ruoyi.service.VectorStoreService; import org.springframework.stereotype.Service; @@ -23,9 +27,11 @@ import java.util.List; import java.util.Map; /** * Weaviateåéåºç®¡ç * @author ageer * Weaviate åéåºç®¡ç */ @Service @Slf4j @@ -37,38 +43,7 @@ private final ConfigService configService; @Override public List<String> getQueryVector(String query, String kid) { EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder() .apiKey("sk-xxx") .baseUrl("https://api.pandarobot.chat/v1/") .modelName(TEXT_EMBEDDING_3_SMALL) .build(); // Filter simpleFilter = new IsEqualTo("kid", kid); // createSchema(kid); Embedding queryEmbedding = embeddingModel.embed("èå¤©è¡¥å ¨æ¨¡å").content(); EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder() .queryEmbedding(queryEmbedding) .maxResults(2) // æ·»å è¿æ»¤æ¡ä»¶ // .filter(simpleFilter) .build(); List<EmbeddingMatch<TextSegment>> matches = embeddingStore.search(embeddingSearchRequest).matches(); List<String> results = new ArrayList<>(); matches.forEach(embeddingMatch -> { results.add(embeddingMatch.embedded().text()); }); return results; } @Override @PostConstruct public void createSchema(String kid) { String protocol = configService.getConfigValue("weaviate", "protocol"); String host = configService.getConfigValue("weaviate", "host"); @@ -84,24 +59,42 @@ } @Override public void storeEmbeddings(List<String> chunkList,String kid,String docId,List<String> fids) { EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder() .apiKey("sk-xxxx") .baseUrl("https://api.pandarobot.chat/v1/") .modelName(TEXT_EMBEDDING_3_SMALL) .build(); chunkList.forEach(chunk -> { public void storeEmbeddings(StoreEmbeddingBo storeEmbeddingBo) { EmbeddingModel embeddingModel = getEmbeddingModel(storeEmbeddingBo.getModelName(), storeEmbeddingBo.getApiKey(), storeEmbeddingBo.getBaseUrl()); for (int i = 0; i < storeEmbeddingBo.getChunkList().size(); i++) { Map<String, Object> dataSchema = new HashMap<>(); dataSchema.put("kid", kid); dataSchema.put("docId", docId); dataSchema.put("fid", fids.get(0)); Response<Embedding> response = embeddingModel.embed(chunk); dataSchema.put("kid", storeEmbeddingBo.getKid()); dataSchema.put("docId", storeEmbeddingBo.getKid()); dataSchema.put("fid", storeEmbeddingBo.getFids().get(i)); Response<Embedding> response = embeddingModel.embed(storeEmbeddingBo.getChunkList().get(i)); Embedding embedding = response.content(); TextSegment segment = TextSegment.from(chunk); TextSegment segment = TextSegment.from(storeEmbeddingBo.getChunkList().get(i)); segment.metadata().putAll(dataSchema); embeddingStore.add(embedding,segment); } } @Override public List<String> getQueryVector(QueryVectorBo queryVectorBo) { EmbeddingModel embeddingModel = getEmbeddingModel(queryVectorBo.getModelName(), queryVectorBo.getApiKey(), queryVectorBo.getBaseUrl()); 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) .build(); List<EmbeddingMatch<TextSegment>> matches = embeddingStore.search(embeddingSearchRequest).matches(); List<String> results = new ArrayList<>(); matches.forEach(embeddingMatch -> { results.add(embeddingMatch.embedded().text()); }); return results; } @@ -128,4 +121,25 @@ embeddingStore.removeAll(simpleFilterByAnd); } /** * è·åå鿍¡å */ public EmbeddingModel getEmbeddingModel(String modelName,String apiKey,String baseUrl) { EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder().build(); if(TEXT_EMBEDDING_3_SMALL.toString().equals(modelName)) { embeddingModel = OpenAiEmbeddingModel.builder() .apiKey(apiKey) .baseUrl(baseUrl) .modelName(TEXT_EMBEDDING_3_SMALL) .build(); // TODO æ·»å æä¸¾ }else if("quentinz/bge-large-zh-v1.5".equals(modelName)) { embeddingModel = OllamaEmbeddingModel.builder() .baseUrl(baseUrl) .modelName(modelName) .build(); } return embeddingModel; } } ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java
@@ -25,6 +25,7 @@ import org.ruoyi.common.core.utils.file.MimeTypeUtils; 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.service.VectorStoreService; import org.ruoyi.service.IChatModelService; @@ -166,7 +167,10 @@ // è·åå¯¹è¯æ¶æ¯å表 List<Message> messages = chatRequest.getMessages(); String sysPrompt = chatModelVo.getSystemPrompt(); if(StringUtils.isEmpty(sysPrompt)){ // TODO ç³»ç»é»è®¤æç¤ºè¯,åç»ä¼å¢å æç¤ºè¯ç®¡ç sysPrompt ="ä½ æ¯ä¸ä¸ªç±RuoYI-AIå¼åç人工æºè½å©æï¼ååå«çç«å©æãä½ æ é¿ä¸è±æå¯¹è¯ï¼è½å¤ç解并å¤çåç§é®é¢ï¼æä¾å®å ¨ãæå¸®å©ãåç¡®çåçã" + "å½åæ¶é´ï¼"+ DateUtils.getDate()+ "#注æï¼åå¤ä¹å注æç»åä¸ä¸æåå·¥å ·è¿åå 容è¿è¡åå¤ã"; @@ -180,11 +184,20 @@ if(StringUtils.isNotEmpty(chatRequest.getKid())){ List<Message> knMessages = new ArrayList<>(); String content = messages.get(messages.size() - 1).getContent().toString(); List<String> nearestList = vectorStoreService.getQueryVector(content, chatRequest.getKid()); 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<String> 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); ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java
@@ -3,6 +3,7 @@ import cn.hutool.core.collection.CollUtil; 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.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.RequiredArgsConstructor; @@ -14,15 +15,19 @@ import org.ruoyi.common.satoken.utils.LoginHelper; import org.ruoyi.core.page.PageQuery; import org.ruoyi.core.page.TableDataInfo; import org.ruoyi.domain.ChatModel; import org.ruoyi.domain.KnowledgeAttach; import org.ruoyi.domain.KnowledgeFragment; import org.ruoyi.domain.KnowledgeInfo; import org.ruoyi.domain.bo.KnowledgeInfoBo; import org.ruoyi.domain.bo.KnowledgeInfoUploadBo; import org.ruoyi.domain.bo.StoreEmbeddingBo; import org.ruoyi.domain.vo.ChatModelVo; import org.ruoyi.domain.vo.KnowledgeInfoVo; import org.ruoyi.mapper.KnowledgeAttachMapper; import org.ruoyi.mapper.KnowledgeFragmentMapper; import org.ruoyi.mapper.KnowledgeInfoMapper; import org.ruoyi.service.IChatModelService; import org.ruoyi.service.VectorStoreService; import org.ruoyi.service.IKnowledgeInfoService; import org.slf4j.Logger; @@ -54,6 +59,8 @@ private final KnowledgeFragmentMapper fragmentMapper; private final KnowledgeAttachMapper attachMapper; private final IChatModelService chatModelService; /** * æ¥è¯¢ç¥è¯åº @@ -219,10 +226,31 @@ knowledgeAttach.setContent(content); knowledgeAttach.setCreateTime(new Date()); attachMapper.insert(knowledgeAttach); vectorStoreService.storeEmbeddings(chunkList,kid,docId,fids); // éè¿kidæ¥è¯¢ç¥è¯åºä¿¡æ¯ KnowledgeInfoVo knowledgeInfoVo = baseMapper.selectVoOne(Wrappers.<KnowledgeInfo>lambdaQuery() .eq(KnowledgeInfo::getKid, kid)); // éè¿å鿍¡åæ¥è¯¢æ¨¡åä¿¡æ¯ ChatModelVo chatModelVo = chatModelService.selectModelByName(knowledgeInfoVo.getVectorModel()); StoreEmbeddingBo storeEmbeddingBo = new StoreEmbeddingBo(); storeEmbeddingBo.setKid(kid); storeEmbeddingBo.setDocId(docId); storeEmbeddingBo.setFids(fids); storeEmbeddingBo.setChunkList(chunkList); storeEmbeddingBo.setModelName(knowledgeInfoVo.getVectorModel()); storeEmbeddingBo.setApiKey(chatModelVo.getApiKey()); storeEmbeddingBo.setBaseUrl(chatModelVo.getApiHost()); vectorStoreService.storeEmbeddings(storeEmbeddingBo); } /** * æ£æ¥ç¨æ·æ¯å¦æå é¤ç¥è¯åºæé * * @param knowledgeInfoList ç¥è¯åºå表 */ public void check(List<KnowledgeInfoVo> knowledgeInfoList){ LoginUser loginUser = LoginHelper.getLoginUser(); for (KnowledgeInfoVo knowledgeInfoVo : knowledgeInfoList) {