Quellcode durchsuchen

修改质控逻辑和导入知识库功能

feng vor 6 Monaten
Ursprung
Commit
eae5bb0475

+ 21 - 0
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/controller/QualityControlRuleController.java

@@ -5,6 +5,7 @@ import java.util.List;
 import java.util.Map;
 
 import cn.dev33.satoken.annotation.SaIgnore;
+import jakarta.annotation.Resource;
 import lombok.RequiredArgsConstructor;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.constraints.*;
@@ -22,6 +23,7 @@ import org.ruoyi.domain.vo.QualityControlRuleExampleVo;
 import org.ruoyi.domain.vo.QualityControlRuleVo;
 import org.ruoyi.service.IQualityControlRuleExampleService;
 import org.ruoyi.service.IQualityControlRuleService;
+import org.ruoyi.service.VectorStoreService;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.validation.annotation.Validated;
 
@@ -43,6 +45,9 @@ public class QualityControlRuleController {
 
     private final IQualityControlRuleExampleService qualityControlRuleExampleService;
 
+    @Resource
+    private VectorStoreService vectorStoreService;
+
     /**
      * 查询质控规则列表
      */
@@ -120,4 +125,20 @@ public class QualityControlRuleController {
                              @PathVariable Long[] ids) {
         return R.ok(qualityControlRuleService.deleteWithValidByIds(List.of(ids), true));
     }
+
+    /**
+     * 一键导入知识库
+     * @return
+     */
+    @PostMapping("/knowledgeImport")
+    public R<Boolean> knowledgeImport(){
+        List<Map<String, Object>> rules = qualityControlRuleService.knowledgeImport();
+        vectorStoreService.deleteAllFromClass("Quality_control");
+         vectorStoreService.saveQualityRulesWithVector(rules,"Quality_control");
+         return  R.ok(true);
+
+
+
+    }
+
 }

+ 5 - 0
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/QualityControlRule.java

@@ -70,4 +70,9 @@ public class QualityControlRule extends TenantEntity {
     @TableField(value = "qc_type_list", typeHandler = JacksonTypeHandler.class)
     private List<Integer> qcTypeList;
 
+    /**
+     * 状态
+     */
+    private int delFlag;
+
 }

+ 2 - 0
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/bo/QualityControlRuleBo.java

@@ -65,4 +65,6 @@ public class QualityControlRuleBo extends BaseEntity {
     private String qcBehavior;
 
     private List<Integer> qcTypeList;
+
+    private int delFlag;
 }

+ 3 - 0
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/IQualityControlRuleService.java

@@ -8,6 +8,7 @@ import org.ruoyi.domain.vo.QualityControlRuleVo;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 质控规则Service接口
@@ -66,4 +67,6 @@ public interface IQualityControlRuleService {
      * @return 是否删除成功
      */
     Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    List<Map<String, Object>> knowledgeImport();
 }

+ 5 - 0
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/VectorStoreService.java

@@ -24,6 +24,11 @@ public interface VectorStoreService {
     void removeByDocId( String docId);
 
     void removeById(String id,String modelName);
+    void saveQualityRulesWithVector(List<Map<String,Object>>  chunkList,String classname);
+
+     void deleteAllFromClass(String classname);
+
+    public List<Map<String, Object>> getQualityRuleVector(String queryText, double certainty, int limit);
 
 
 }

+ 35 - 3
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/QualityControlRuleServiceImpl.java

@@ -11,15 +11,16 @@ import org.ruoyi.common.core.utils.StringUtils;
 import org.ruoyi.core.page.PageQuery;
 import org.ruoyi.core.page.TableDataInfo;
 import org.ruoyi.domain.QualityControlRule;
+import org.ruoyi.domain.QualityControlRuleExample;
 import org.ruoyi.domain.bo.QualityControlRuleBo;
 import org.ruoyi.domain.vo.QualityControlRuleVo;
+import org.ruoyi.mapper.QualityControlRuleExampleMapper;
 import org.ruoyi.mapper.QualityControlRuleMapper;
 import org.ruoyi.service.IQualityControlRuleService;
 import org.springframework.stereotype.Service;
 
-import java.util.List;
-import java.util.Map;
-import java.util.Collection;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * 质控规则Service业务层处理
@@ -33,6 +34,7 @@ import java.util.Collection;
 public class QualityControlRuleServiceImpl implements IQualityControlRuleService {
 
     private final QualityControlRuleMapper baseMapper;
+    private final QualityControlRuleExampleMapper qualityControlRuleExampleMapper;
 
     /**
      * 查询质控规则
@@ -61,6 +63,7 @@ public class QualityControlRuleServiceImpl implements IQualityControlRuleService
         lqw.eq(StringUtils.isNotBlank(bo.getCategory()), QualityControlRule::getCategory, bo.getCategory())
                 .eq(StringUtils.isNotBlank(bo.getQcBehavior()), QualityControlRule::getQcBehavior, bo.getQcBehavior())
                 .eq(StringUtils.isNotBlank(bo.getRuleCode()), QualityControlRule::getRuleCode, bo.getRuleCode())
+                .eq(QualityControlRule::getDelFlag, bo.getDelFlag())
                 .like(StringUtils.isNotBlank(bo.getRuleContent()), QualityControlRule::getRuleContent, bo.getRuleContent());
         IPage<QualityControlRuleVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
         return TableDataInfo.build(result);
@@ -85,6 +88,7 @@ public class QualityControlRuleServiceImpl implements IQualityControlRuleService
         lqw.eq(StringUtils.isNotBlank(bo.getCategory()), QualityControlRule::getCategory, bo.getCategory());
         lqw.eq(StringUtils.isNotBlank(bo.getRuleCode()), QualityControlRule::getRuleCode, bo.getRuleCode());
         lqw.eq(StringUtils.isNotBlank(bo.getRuleContent()), QualityControlRule::getRuleContent, bo.getRuleContent());
+        lqw.eq(QualityControlRule::getDelFlag, bo.getDelFlag());
         return lqw;
     }
 
@@ -136,4 +140,32 @@ public class QualityControlRuleServiceImpl implements IQualityControlRuleService
     public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
         return baseMapper.deleteByIds(ids) > 0;
     }
+
+    @Override
+    public List<Map<String, Object>> knowledgeImport() {
+
+        //查询所有规则
+        List<QualityControlRule> qualityControlRulesList = baseMapper.selectList(null);
+
+        // 查询所有正反例
+        List<QualityControlRuleExample> qualityControlRuleExamplesList =
+                qualityControlRuleExampleMapper.selectList(null);
+
+        // 按 ruleCode 分组
+        Map<String, QualityControlRule> ruleMap = qualityControlRulesList.stream()
+                .collect(Collectors.toMap(QualityControlRule::getRuleCode, r -> r, (a, b) -> a));
+        List<Map<String, Object>> result = new ArrayList<>();
+        // 转换格式
+        for (QualityControlRuleExample ex : qualityControlRuleExamplesList) {
+            QualityControlRule rule = ruleMap.get(ex.getRuleCode());
+            if (rule == null) continue;
+            Map<String, Object> json = new LinkedHashMap<>();
+            json.put("ruleCode", rule.getRuleCode());
+            json.put("ruleContent", rule.getRuleContent());
+            json.put("type", ex.getType());
+            json.put("exampleContent", ex.getExampleContent());
+            result.add(json);
+        }
+        return result;
+    }
 }

+ 268 - 6
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/VectorStoreServiceImpl.java

@@ -13,9 +13,11 @@ import io.weaviate.client.Config;
 import io.weaviate.client.WeaviateClient;
 import io.weaviate.client.base.Result;
 import io.weaviate.client.v1.data.model.WeaviateObject;
+import io.weaviate.client.v1.filters.Operator;
 import io.weaviate.client.v1.graphql.model.GraphQLQuery;
 import io.weaviate.client.v1.graphql.model.GraphQLResponse;
 import io.weaviate.client.v1.graphql.query.Raw;
+import io.weaviate.client.v1.graphql.query.builder.GetBuilder;
 import io.weaviate.client.v1.schema.model.Property;
 import io.weaviate.client.v1.schema.model.Schema;
 import io.weaviate.client.v1.schema.model.WeaviateClass;
@@ -24,6 +26,7 @@ import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 import org.ruoyi.chain.xinference.XinferenceClient;
 import org.ruoyi.common.core.service.ConfigService;
+import org.ruoyi.common.core.utils.StringUtils;
 import org.ruoyi.domain.bo.KnowledgeAttachBo;
 import org.ruoyi.domain.bo.QueryVectorBo;
 import org.ruoyi.domain.bo.StoreEmbeddingBo;
@@ -58,7 +61,8 @@ public class VectorStoreServiceImpl implements VectorStoreService {
     @Value("${file.base-url}")
     private String baseUrl;
 
-
+    @Value("${file.xinference-url}")
+    private String xinferenceUrl;
 
 
     @Override
@@ -150,6 +154,7 @@ public class VectorStoreServiceImpl implements VectorStoreService {
                 .consistencyLevel("ALL")
                 .build();
     }
+
     @Override
     public void storeEmbeddings(StoreEmbeddingBo storeEmbeddingBo) {
         String protocol = configService.getConfigValue("weaviate", "protocol");
@@ -197,14 +202,14 @@ public class VectorStoreServiceImpl implements VectorStoreService {
             properties.put("chunkIndex", i);
 
             HashMap<String, Object> hashMap = new HashMap<>();
-            hashMap.put("docId",docId);
-            hashMap.put("filename",storeEmbeddingBo.getFileName());
+            hashMap.put("docId", docId);
+            hashMap.put("filename", storeEmbeddingBo.getFileName());
             hashMap.put("chunkIndex", i);
             ObjectMapper mapper = new ObjectMapper();
             try {
                 String jsonString = mapper.writeValueAsString(hashMap);
-                properties.put("metadata",jsonString);
-              } catch (JsonProcessingException e) {
+                properties.put("metadata", jsonString);
+            } catch (JsonProcessingException e) {
                 throw new RuntimeException(e);
             }
 
@@ -409,7 +414,7 @@ public class VectorStoreServiceImpl implements VectorStoreService {
         XinferenceClient xinferenceClient = new XinferenceClient(queryVectorBo.getBaseUrl());
         String embeddingModelName = queryVectorBo.getEmbeddingModelName();
         String[] embeddingModelNameArr = embeddingModelName.split("/");
-        String modelUid = embeddingModelNameArr[embeddingModelNameArr.length-1];
+        String modelUid = embeddingModelNameArr[embeddingModelNameArr.length - 1];
         List<Double> queryVector;
         try {
             queryVector = xinferenceClient.getEmbedding(modelUid, queryVectorBo.getQuery());
@@ -621,6 +626,7 @@ public class VectorStoreServiceImpl implements VectorStoreService {
         if (classData == null || classData.isEmpty()) return Collections.emptyList();
         return classData;
     }
+
     // 提取 GraphQL 返回的 data -> Get -> className 数据
     private Map<String, Object> extractDataMap(Result<GraphQLResponse> result) {
         Object data = result.getResult().getData();
@@ -785,4 +791,260 @@ public class VectorStoreServiceImpl implements VectorStoreService {
         return floatArray;
     }
 
+    @Override
+    public void saveQualityRulesWithVector(List<Map<String, Object>> chunkList, String classname) {
+        // 1️⃣ 获取配置
+        createSchemaRule(classname);
+        String protocol = configService.getConfigValue("weaviate", "protocol"); // http
+        String host = configService.getConfigValue("weaviate", "host"); // 127.0.0.1:8080
+        String baseUrl = this.xinferenceUrl;   // http://127.0.0.1:9997
+        String modelName = "bge-large-zh-v1.5";
+
+        // 2️⃣ 初始化客户端
+        WeaviateClient client = new WeaviateClient(new Config(protocol, host));
+        XinferenceClient xinferenceClient = new XinferenceClient(baseUrl);
+
+        // 3️⃣ 批量写入
+        for (int i = 0; i < chunkList.size(); i++) {
+            Map<String, Object> map = chunkList.get(i);
+            // 统一字段名:example_content → exampleContent
+            String ruleCode = String.valueOf(map.getOrDefault("ruleCode", ""));
+            String ruleContent = String.valueOf(map.getOrDefault("ruleContent", ""));
+            String type = String.valueOf(map.getOrDefault("type", ""));
+            String exampleContent = String.valueOf(
+                    map.getOrDefault("example_content", map.getOrDefault("exampleContent", ""))
+            );
+
+            if (ruleCode.isBlank() || ruleContent.isBlank()) {
+                log.warn("⚠️ 第 {} 条规则数据不完整,已跳过", i);
+                continue;
+            }
+            // 构造对象
+
+
+
+            // ✅ 自定义属性(按你的格式)
+            Map<String, Object> properties = new LinkedHashMap<>();
+            properties.put("ruleCode", ruleCode);
+            properties.put("ruleContent", ruleContent);
+            properties.put("type", type);
+            properties.put("exampleContent", exampleContent);
+
+
+            // 4️⃣ 生成向量
+            List<Double> vector;
+            try {
+                vector = xinferenceClient.getEmbedding(modelName, ruleContent);
+                List<Double> exampleVector = xinferenceClient.getEmbedding(modelName, exampleContent);
+                properties.put("exampleVector", exampleVector);
+            } catch (IOException e) {
+                log.error("❌ 第 {} 条生成向量失败: {}", i, e.getMessage());
+                continue;
+            }
+
+            try {
+                Result<WeaviateObject> result = client.data().creator()
+                        .withClassName(classname)
+                        .withProperties(properties)
+                        .withVector(toFloatArray(vector))
+                        .run();
+
+                if (result.hasErrors()) {
+                    log.error("❌ 保存第 {} 条失败: {}", i, result.getError().toString());
+                } else {
+                    log.info("✅ 成功保存第 {} 条规则到 Weaviate", i);
+                }
+            } catch (Exception e) {
+                log.error("❌ 存储异常 (index={}): {}", i, e.getMessage(), e);
+            }
+        }
+
+        log.info("🎯 已完成 {} 条规则写入向量数据库 (class={})", chunkList.size(), classname);
+    }
+
+    /**
+     * ❌ 删除整个类结构(包括 schema)
+     * 注意:删除后该类及其所有数据都会被移除
+     */
+    @Override
+    public void deleteAllFromClass(String classname) {
+        String protocol = configService.getConfigValue("weaviate", "protocol");
+        String host = configService.getConfigValue("weaviate", "host");
+
+        WeaviateClient client = new WeaviateClient(new Config(protocol, host));
+
+        log.warn("⚠️ 正在删除整个 Weaviate 类结构:{}", classname);
+        try {
+            Result<Boolean> result = client.schema().classDeleter()
+                    .withClassName(classname)
+                    .run();
+
+            if (result.hasErrors()) {
+                log.error("❌ 删除类 {} 失败: {}", classname, result.getError().toString());
+            } else {
+                log.info("✅ 已彻底删除类结构:{}", classname);
+            }
+        } catch (Exception e) {
+            log.error("❌ 删除类异常: {}", e.getMessage(), e);
+        }
+    }
+
+
+    /**
+     * 🏗️ 创建质控规则类 Quality_control(用于规则知识库)
+     * 包含字段:ruleCode、ruleContent、type、exampleContent
+     */
+    public void createSchemaRule(String classname) {
+        // 1️⃣ 读取配置
+        String protocol = configService.getConfigValue("weaviate", "protocol");
+        String host = configService.getConfigValue("weaviate", "host");
+        // 2️⃣ 初始化客户端
+        WeaviateClient client = new WeaviateClient(new Config(protocol, host));
+
+        // 3️⃣ 获取当前 Schema
+        Result<Schema> existingSchema = client.schema().getter().run();
+        if (existingSchema.hasErrors()) {
+            throw new RuntimeException("获取 Weaviate Schema 失败: " + existingSchema.getError().toString());
+        }
+
+        // 4️⃣ 检查是否存在同名类
+        boolean classExists = existingSchema.getResult().getClasses().stream()
+                .anyMatch(c -> c.getClassName().equals(classname));
+
+        if (!classExists) {
+            log.info("🏗️ 正在创建规则类:{}", classname);
+
+            // 5️⃣ 定义属性结构
+            List<Property> properties = Arrays.asList(
+                    Property.builder().name("ruleCode").dataType(List.of("text")).description("规则编码").build(),
+                    Property.builder().name("ruleContent").dataType(List.of("text")).description("规则内容").build(),
+                    Property.builder().name("type").dataType(List.of("text")).description("规则类型或索引编号").build(),
+                    Property.builder().name("exampleContent").dataType(List.of("text")).description("规则示例内容").build(),
+                    Property.builder().name("exampleVector").dataType(List.of("number[]")).description("示例向量").build()
+            );
+
+            // 6️⃣ 构建类定义
+            WeaviateClass weaviateClass = WeaviateClass.builder()
+                    .className(classname)
+                    .description("质控规则知识库类")
+                    .properties(properties)
+                    .vectorizer("none") // 手动传入 embedding 向量
+                    .build();
+
+            // 7️⃣ 创建类
+            Result<Boolean> result = client.schema().classCreator().withClass(weaviateClass).run();
+            if (result.hasErrors()) {
+                throw new RuntimeException("创建类失败: " + result.getError().toString());
+            }
+            log.info("✅ 已创建规则类:{}", classname);
+
+        } else {
+            log.info("✅ 规则类 {} 已存在,检查字段是否完整", classname);
+
+            // 8️⃣ 检查字段完整性
+            WeaviateClass existingClass = existingSchema.getResult().getClasses().stream()
+                    .filter(c -> c.getClassName().equals(classname))
+                    .findFirst().orElse(null);
+
+            Set<String> existingFields = existingClass.getProperties().stream()
+                    .map(Property::getName)
+                    .collect(Collectors.toSet());
+
+            Map<String, String> requiredFields = Map.of(
+                    "ruleCode", "text",
+                    "ruleContent", "text",
+                    "type", "text",
+                    "exampleContent", "text",
+                    "exampleVector","number[]"
+            );
+
+            for (Map.Entry<String, String> entry : requiredFields.entrySet()) {
+                if (!existingFields.contains(entry.getKey())) {
+                    Property prop = Property.builder()
+                            .name(entry.getKey())
+                            .dataType(List.of(entry.getValue()))
+                            .description("自动补全字段")
+                            .build();
+                    client.schema().propertyCreator()
+                            .withClassName(classname)
+                            .withProperty(prop)
+                            .run();
+                    log.info("🧩 已为类 {} 自动补全字段:{}", classname, entry.getKey());
+                }
+            }
+        }
+
+        // 9️⃣ 初始化 embeddingStore
+        embeddingStore = WeaviateEmbeddingStore.builder()
+                .scheme(protocol)
+                .host(host)
+                .objectClass(classname)
+                .avoidDups(true)
+                .consistencyLevel("ALL")
+                .build();
+
+        log.info("🎯 createSchemaRule 完成,类结构已就绪:{}", classname);
+    }
+
+    /**
+     * 从 Weaviate 向量库中搜索相似规则
+     */
+    public List<Map<String, Object>> getQualityRuleVector(String queryText, double certainty, int limit) {
+        // 获取 Weaviate 配置
+        String protocol = configService.getConfigValue("weaviate", "protocol");
+        String host = configService.getConfigValue("weaviate", "host");
+        String className = "Quality_control" ;
+
+        // 初始化客户端
+        WeaviateClient client = new WeaviateClient(new Config(protocol, host));
+
+        // 1️⃣ 生成向量(用 Xinference)
+        XinferenceClient xinferenceClient = new XinferenceClient(xinferenceUrl);
+        List<Double> vector;
+        try {
+            vector = xinferenceClient.getEmbedding("bge-large-zh-v1.5", queryText);
+        } catch (IOException e) {
+            throw new RuntimeException("生成查询向量失败:" + e.getMessage(), e);
+        }
+
+        // 2️⃣ Double -> Float 转换
+        List<Float> floatVector = vector.stream().map(Double::floatValue).collect(Collectors.toList());
+        String vectorString = floatVector.stream().map(String::valueOf).collect(Collectors.joining(", "));
+
+        // 3️⃣ 构建 GraphQL 查询
+        String graphqlQuery = String.format(
+                "{" +
+                        "  Get {%n" +
+                        "    %s(nearVector: {vector: [%s], certainty: %f}, limit: %d) {%n" +
+                        "      ruleCode%n" +
+                        "      ruleContent%n" +
+                        "      type%n" +
+                        "      exampleContent%n" +
+                        "      exampleVector%n" +
+                        "      _additional { id distance certainty vector }%n" +  // ✅ 加上vector
+                        "    }%n" +
+                        "  }%n" +
+                        "}",
+                className, vectorString, certainty, limit
+        );
+
+
+        // 4️⃣ 执行查询
+        Raw raw = client.graphQL().raw().withQuery(graphqlQuery);
+        Result<GraphQLResponse> result = raw.run();
+
+        // 5️⃣ 提取数据
+        if (result.hasErrors()) {
+            throw new RuntimeException("Weaviate 查询失败:" + result.getError().toString());
+        }
+
+        Map<String, Object> dataMap = extractDataMap(result);
+        if (dataMap == null) return Collections.emptyList();
+
+        List<Map<String, Object>> classData = (List<Map<String, Object>>) dataMap.get(className);
+        if (classData == null || classData.isEmpty()) return Collections.emptyList();
+
+        return classData;
+    }
 }
+

Datei-Diff unterdrückt, da er zu groß ist
+ 218 - 252
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/MedicalRecordQc123Service.java


Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.