办学质量监测教学评价系统
ageerle
2025-05-08 aa92d232bb49a275838a74ba9d28d9448ad188c6
feat: Weaviate操作向量库功能优化
已修改5个文件
已添加2个文件
279 ■■■■ 文件已修改
ruoyi-modules-api/ruoyi-knowledge-api/pom.xml 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/bo/QueryVectorBo.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/bo/StoreEmbeddingBo.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/VectorStoreService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/WeaviateVectorStoreImpl.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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) {