|
@@ -13,9 +13,11 @@ import io.weaviate.client.Config;
|
|
|
import io.weaviate.client.WeaviateClient;
|
|
import io.weaviate.client.WeaviateClient;
|
|
|
import io.weaviate.client.base.Result;
|
|
import io.weaviate.client.base.Result;
|
|
|
import io.weaviate.client.v1.data.model.WeaviateObject;
|
|
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.GraphQLQuery;
|
|
|
import io.weaviate.client.v1.graphql.model.GraphQLResponse;
|
|
import io.weaviate.client.v1.graphql.model.GraphQLResponse;
|
|
|
import io.weaviate.client.v1.graphql.query.Raw;
|
|
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.Property;
|
|
|
import io.weaviate.client.v1.schema.model.Schema;
|
|
import io.weaviate.client.v1.schema.model.Schema;
|
|
|
import io.weaviate.client.v1.schema.model.WeaviateClass;
|
|
import io.weaviate.client.v1.schema.model.WeaviateClass;
|
|
@@ -24,6 +26,7 @@ import lombok.SneakyThrows;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.ruoyi.chain.xinference.XinferenceClient;
|
|
import org.ruoyi.chain.xinference.XinferenceClient;
|
|
|
import org.ruoyi.common.core.service.ConfigService;
|
|
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.KnowledgeAttachBo;
|
|
|
import org.ruoyi.domain.bo.QueryVectorBo;
|
|
import org.ruoyi.domain.bo.QueryVectorBo;
|
|
|
import org.ruoyi.domain.bo.StoreEmbeddingBo;
|
|
import org.ruoyi.domain.bo.StoreEmbeddingBo;
|
|
@@ -58,7 +61,8 @@ public class VectorStoreServiceImpl implements VectorStoreService {
|
|
|
@Value("${file.base-url}")
|
|
@Value("${file.base-url}")
|
|
|
private String baseUrl;
|
|
private String baseUrl;
|
|
|
|
|
|
|
|
-
|
|
|
|
|
|
|
+ @Value("${file.xinference-url}")
|
|
|
|
|
+ private String xinferenceUrl;
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
@@ -150,6 +154,7 @@ public class VectorStoreServiceImpl implements VectorStoreService {
|
|
|
.consistencyLevel("ALL")
|
|
.consistencyLevel("ALL")
|
|
|
.build();
|
|
.build();
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
@Override
|
|
@Override
|
|
|
public void storeEmbeddings(StoreEmbeddingBo storeEmbeddingBo) {
|
|
public void storeEmbeddings(StoreEmbeddingBo storeEmbeddingBo) {
|
|
|
String protocol = configService.getConfigValue("weaviate", "protocol");
|
|
String protocol = configService.getConfigValue("weaviate", "protocol");
|
|
@@ -197,14 +202,14 @@ public class VectorStoreServiceImpl implements VectorStoreService {
|
|
|
properties.put("chunkIndex", i);
|
|
properties.put("chunkIndex", i);
|
|
|
|
|
|
|
|
HashMap<String, Object> hashMap = new HashMap<>();
|
|
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);
|
|
hashMap.put("chunkIndex", i);
|
|
|
ObjectMapper mapper = new ObjectMapper();
|
|
ObjectMapper mapper = new ObjectMapper();
|
|
|
try {
|
|
try {
|
|
|
String jsonString = mapper.writeValueAsString(hashMap);
|
|
String jsonString = mapper.writeValueAsString(hashMap);
|
|
|
- properties.put("metadata",jsonString);
|
|
|
|
|
- } catch (JsonProcessingException e) {
|
|
|
|
|
|
|
+ properties.put("metadata", jsonString);
|
|
|
|
|
+ } catch (JsonProcessingException e) {
|
|
|
throw new RuntimeException(e);
|
|
throw new RuntimeException(e);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -409,7 +414,7 @@ public class VectorStoreServiceImpl implements VectorStoreService {
|
|
|
XinferenceClient xinferenceClient = new XinferenceClient(queryVectorBo.getBaseUrl());
|
|
XinferenceClient xinferenceClient = new XinferenceClient(queryVectorBo.getBaseUrl());
|
|
|
String embeddingModelName = queryVectorBo.getEmbeddingModelName();
|
|
String embeddingModelName = queryVectorBo.getEmbeddingModelName();
|
|
|
String[] embeddingModelNameArr = embeddingModelName.split("/");
|
|
String[] embeddingModelNameArr = embeddingModelName.split("/");
|
|
|
- String modelUid = embeddingModelNameArr[embeddingModelNameArr.length-1];
|
|
|
|
|
|
|
+ String modelUid = embeddingModelNameArr[embeddingModelNameArr.length - 1];
|
|
|
List<Double> queryVector;
|
|
List<Double> queryVector;
|
|
|
try {
|
|
try {
|
|
|
queryVector = xinferenceClient.getEmbedding(modelUid, queryVectorBo.getQuery());
|
|
queryVector = xinferenceClient.getEmbedding(modelUid, queryVectorBo.getQuery());
|
|
@@ -621,6 +626,7 @@ public class VectorStoreServiceImpl implements VectorStoreService {
|
|
|
if (classData == null || classData.isEmpty()) return Collections.emptyList();
|
|
if (classData == null || classData.isEmpty()) return Collections.emptyList();
|
|
|
return classData;
|
|
return classData;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
// 提取 GraphQL 返回的 data -> Get -> className 数据
|
|
// 提取 GraphQL 返回的 data -> Get -> className 数据
|
|
|
private Map<String, Object> extractDataMap(Result<GraphQLResponse> result) {
|
|
private Map<String, Object> extractDataMap(Result<GraphQLResponse> result) {
|
|
|
Object data = result.getResult().getData();
|
|
Object data = result.getResult().getData();
|
|
@@ -785,4 +791,260 @@ public class VectorStoreServiceImpl implements VectorStoreService {
|
|
|
return floatArray;
|
|
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;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
+
|