RagGovernanceService.java 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. package com.agent.management.rag.capability;
  2. import com.agent.management.rag.bridge.RagAiBridgeClient;
  3. import com.agent.management.rag.model.RagSourceType;
  4. import lombok.RequiredArgsConstructor;
  5. import org.springframework.stereotype.Service;
  6. import java.util.LinkedHashMap;
  7. import java.util.List;
  8. import java.util.Map;
  9. @Service
  10. @RequiredArgsConstructor
  11. public class RagGovernanceService {
  12. private final RagCapabilityProfileService profiles;
  13. private final RagAiBridgeClient bridge;
  14. private final RagSemanticCatalogService semanticCatalog;
  15. private final GraphBusinessSubgraphSelector subgraphs;
  16. public Map<String,Object> suggest(RagSourceType type, String sourceId, String description) {
  17. RagCapabilityProfile profile = profiles.refresh(type, sourceId);
  18. String schema;
  19. if (type == RagSourceType.STRUCTURED_DATA) {
  20. List<String> tables = semanticCatalog.rank(description, profile, "table:", 12);
  21. schema = profile.sqlSchema().toDdl(tables);
  22. } else {
  23. schema = subgraphs.select(description, profile, profile.graphSchema(), 16, 20).toPromptText();
  24. }
  25. Map<String,Object> request = new LinkedHashMap<>();
  26. request.put("sourceType", type.name()); request.put("schemaText", schema);
  27. request.put("sourceDescription", description == null ? "" : description); request.put("maxExamples", 8);
  28. Map<String,Object> suggestion;
  29. try {
  30. suggestion = normalize(type, bridge.suggestGovernance(request), profile);
  31. suggestion.put("generator", "LLM");
  32. } catch (Exception error) {
  33. suggestion = fallback(profile, description);
  34. suggestion.put("generator", "SEMANTIC_FALLBACK");
  35. suggestion.put("warning", "模型建议不可用,已按语义目录生成可审核草稿:" + shortMessage(error));
  36. }
  37. suggestion.put("profileVersion", profile.version()); suggestion.put("sourceType", type.name());
  38. suggestion.put("sourceId", sourceId);
  39. return suggestion;
  40. }
  41. private Map<String,Object> normalize(RagSourceType type, Map<String,Object> raw, RagCapabilityProfile profile) {
  42. Map<String,Object> config = new LinkedHashMap<>();
  43. config.put("retrievalMode", "AUTO_GENERATE");
  44. config.put("generationRules", rules(raw.get("businessRules"), raw.get("rules")));
  45. if (type == RagSourceType.STRUCTURED_DATA) {
  46. config.put("allowTextToSql", true);
  47. var actualTables = new java.util.HashSet<>(profile.sqlSchema().tables().stream().map(SqlSchemaSnapshot.TableSchema::name).toList());
  48. config.put("allowedTables", strings(raw.containsKey("allowedTables") ? raw.get("allowedTables") : raw.get("tableWhitelist"))
  49. .stream().filter(actualTables::contains).toList());
  50. config.put("maxRows", 100);
  51. } else {
  52. config.put("allowTextToCypher", true);
  53. List<String> labels = strings(raw.get("allowedLabels")).stream().filter(profile.graphSchema().labelNames()::contains).toList();
  54. List<String> relationships = strings(raw.get("allowedRelationships")).stream().filter(profile.graphSchema().relationshipTypeNames()::contains).toList();
  55. config.put("allowedLabels", labels);
  56. config.put("allowedRelationships", relationships);
  57. config.put("allowedProperties", validProperties(raw.get("allowedProperties"), profile, labels, relationships));
  58. config.put("maxDepth", 3);
  59. }
  60. Map<String,Object> result = new LinkedHashMap<>();
  61. result.put("summary", String.valueOf(raw.getOrDefault("summary", "基于实际 Schema 生成的授权草稿")));
  62. result.put("suggestedConfig", config);
  63. result.put("examples", raw.get("examples") instanceof List<?> list ? list : List.of());
  64. return result;
  65. }
  66. private static List<String> rules(Object primary, Object secondary) {
  67. List<String> values = strings(primary);
  68. return values.isEmpty() ? strings(secondary) : values;
  69. }
  70. private static List<String> strings(Object value) {
  71. if (value instanceof List<?> list) return list.stream().map(String::valueOf).map(String::trim).filter(item -> !item.isEmpty()).toList();
  72. if (value == null) return List.of();
  73. return java.util.Arrays.stream(String.valueOf(value).split("[\\r\\n;;]+"))
  74. .map(String::trim).filter(item -> !item.isEmpty()).toList();
  75. }
  76. private static Map<String,List<String>> validProperties(Object value, RagCapabilityProfile profile,
  77. List<String> labels, List<String> relationships) {
  78. if (!(value instanceof Map<?,?> raw)) return Map.of();
  79. Map<String,List<String>> actual = new LinkedHashMap<>();
  80. profile.graphSchema().nodes().forEach(node -> actual.put(node.label(), node.properties().keySet().stream().toList()));
  81. profile.graphSchema().relationships().forEach(rel -> actual.put(rel.type(), rel.properties().keySet().stream().toList()));
  82. Map<String,List<String>> result = new LinkedHashMap<>();
  83. raw.forEach((owner, properties) -> {
  84. String name = String.valueOf(owner);
  85. if ((!labels.contains(name) && !relationships.contains(name)) || !actual.containsKey(name)) return;
  86. result.put(name, strings(properties).stream().filter(actual.get(name)::contains).toList());
  87. });
  88. return result;
  89. }
  90. private Map<String,Object> fallback(RagCapabilityProfile profile, String description) {
  91. Map<String,Object> result = new LinkedHashMap<>();
  92. Map<String,Object> config = new LinkedHashMap<>();
  93. config.put("retrievalMode", "AUTO_GENERATE");
  94. config.put("generationRules", List.of("仅生成只读查询", "只使用已审核的 Schema 元素", "结果必须限制返回规模"));
  95. if (profile.sourceType() == RagSourceType.STRUCTURED_DATA) {
  96. List<String> tables = semanticCatalog.rank(description, profile, "table:", 8);
  97. if (tables.isEmpty()) tables = profile.sqlSchema().tables().stream().limit(8).map(SqlSchemaSnapshot.TableSchema::name).toList();
  98. config.put("allowTextToSql", true); config.put("allowedTables", tables); config.put("maxRows", 100);
  99. result.put("examples", tables.stream().limit(4).map(table -> Map.of("question", "查询 " + table + " 的主要记录", "sql", "SELECT * FROM " + table + " LIMIT 20")).toList());
  100. } else {
  101. List<String> labels = semanticCatalog.rank(description, profile, "label:", 12);
  102. List<String> relationships = semanticCatalog.rank(description, profile, "relationship:", 12);
  103. if (labels.isEmpty()) labels = profile.graphSchema().labelNames().stream().limit(12).toList();
  104. List<String> selectedLabels = labels;
  105. if (relationships.isEmpty()) relationships = profile.graphSchema().relationships().stream()
  106. .filter(rel -> rel.startLabels().stream().anyMatch(selectedLabels::contains) || rel.endLabels().stream().anyMatch(selectedLabels::contains))
  107. .limit(12).map(com.agent.management.rag.graph.GraphSchemaSnapshot.RelationshipSchema::type).distinct().toList();
  108. var subgraph = profile.graphSchema().filter(labels, relationships, Map.of());
  109. Map<String,List<String>> properties = new LinkedHashMap<>();
  110. subgraph.nodes().forEach(node -> properties.put(node.label(), node.properties().keySet().stream().toList()));
  111. subgraph.relationships().forEach(rel -> properties.put(rel.type(), rel.properties().keySet().stream().toList()));
  112. config.put("allowTextToCypher", true); config.put("allowedLabels", subgraph.labelNames());
  113. config.put("allowedRelationships", subgraph.relationshipTypeNames()); config.put("allowedProperties", properties); config.put("maxDepth", 3);
  114. result.put("examples", subgraph.nodes().stream().limit(4).map(node -> Map.of("question", "查询 " + node.label() + " 及其关联信息", "cypher", "MATCH (n:`" + node.label() + "`) RETURN n LIMIT 20")).toList());
  115. }
  116. result.put("suggestedConfig", config);
  117. result.put("summary", "基于实际 Schema 与向量语义相似度生成的授权草稿,应用前需人工审核");
  118. return result;
  119. }
  120. private static String shortMessage(Exception error) {
  121. String value = error.getMessage();
  122. return value == null ? error.getClass().getSimpleName() : value.substring(0, Math.min(value.length(), 180));
  123. }
  124. }