| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 |
- package com.agent.management.rag.capability;
- import com.agent.management.rag.bridge.RagAiBridgeClient;
- import com.agent.management.rag.model.RagSourceType;
- import lombok.RequiredArgsConstructor;
- import org.springframework.stereotype.Service;
- import java.util.LinkedHashMap;
- import java.util.List;
- import java.util.Map;
- @Service
- @RequiredArgsConstructor
- public class RagGovernanceService {
- private final RagCapabilityProfileService profiles;
- private final RagAiBridgeClient bridge;
- private final RagSemanticCatalogService semanticCatalog;
- private final GraphBusinessSubgraphSelector subgraphs;
- public Map<String,Object> suggest(RagSourceType type, String sourceId, String description) {
- RagCapabilityProfile profile = profiles.refresh(type, sourceId);
- String schema;
- if (type == RagSourceType.STRUCTURED_DATA) {
- List<String> tables = semanticCatalog.rank(description, profile, "table:", 12);
- schema = profile.sqlSchema().toDdl(tables);
- } else {
- schema = subgraphs.select(description, profile, profile.graphSchema(), 16, 20).toPromptText();
- }
- Map<String,Object> request = new LinkedHashMap<>();
- request.put("sourceType", type.name()); request.put("schemaText", schema);
- request.put("sourceDescription", description == null ? "" : description); request.put("maxExamples", 8);
- Map<String,Object> suggestion;
- try {
- suggestion = normalize(type, bridge.suggestGovernance(request), profile);
- suggestion.put("generator", "LLM");
- } catch (Exception error) {
- suggestion = fallback(profile, description);
- suggestion.put("generator", "SEMANTIC_FALLBACK");
- suggestion.put("warning", "模型建议不可用,已按语义目录生成可审核草稿:" + shortMessage(error));
- }
- suggestion.put("profileVersion", profile.version()); suggestion.put("sourceType", type.name());
- suggestion.put("sourceId", sourceId);
- return suggestion;
- }
- private Map<String,Object> normalize(RagSourceType type, Map<String,Object> raw, RagCapabilityProfile profile) {
- Map<String,Object> config = new LinkedHashMap<>();
- config.put("retrievalMode", "AUTO_GENERATE");
- config.put("generationRules", rules(raw.get("businessRules"), raw.get("rules")));
- if (type == RagSourceType.STRUCTURED_DATA) {
- config.put("allowTextToSql", true);
- var actualTables = new java.util.HashSet<>(profile.sqlSchema().tables().stream().map(SqlSchemaSnapshot.TableSchema::name).toList());
- config.put("allowedTables", strings(raw.containsKey("allowedTables") ? raw.get("allowedTables") : raw.get("tableWhitelist"))
- .stream().filter(actualTables::contains).toList());
- config.put("maxRows", 100);
- } else {
- config.put("allowTextToCypher", true);
- List<String> labels = strings(raw.get("allowedLabels")).stream().filter(profile.graphSchema().labelNames()::contains).toList();
- List<String> relationships = strings(raw.get("allowedRelationships")).stream().filter(profile.graphSchema().relationshipTypeNames()::contains).toList();
- config.put("allowedLabels", labels);
- config.put("allowedRelationships", relationships);
- config.put("allowedProperties", validProperties(raw.get("allowedProperties"), profile, labels, relationships));
- config.put("maxDepth", 3);
- }
- Map<String,Object> result = new LinkedHashMap<>();
- result.put("summary", String.valueOf(raw.getOrDefault("summary", "基于实际 Schema 生成的授权草稿")));
- result.put("suggestedConfig", config);
- result.put("examples", raw.get("examples") instanceof List<?> list ? list : List.of());
- return result;
- }
- private static List<String> rules(Object primary, Object secondary) {
- List<String> values = strings(primary);
- return values.isEmpty() ? strings(secondary) : values;
- }
- private static List<String> strings(Object value) {
- if (value instanceof List<?> list) return list.stream().map(String::valueOf).map(String::trim).filter(item -> !item.isEmpty()).toList();
- if (value == null) return List.of();
- return java.util.Arrays.stream(String.valueOf(value).split("[\\r\\n;;]+"))
- .map(String::trim).filter(item -> !item.isEmpty()).toList();
- }
- private static Map<String,List<String>> validProperties(Object value, RagCapabilityProfile profile,
- List<String> labels, List<String> relationships) {
- if (!(value instanceof Map<?,?> raw)) return Map.of();
- Map<String,List<String>> actual = new LinkedHashMap<>();
- profile.graphSchema().nodes().forEach(node -> actual.put(node.label(), node.properties().keySet().stream().toList()));
- profile.graphSchema().relationships().forEach(rel -> actual.put(rel.type(), rel.properties().keySet().stream().toList()));
- Map<String,List<String>> result = new LinkedHashMap<>();
- raw.forEach((owner, properties) -> {
- String name = String.valueOf(owner);
- if ((!labels.contains(name) && !relationships.contains(name)) || !actual.containsKey(name)) return;
- result.put(name, strings(properties).stream().filter(actual.get(name)::contains).toList());
- });
- return result;
- }
- private Map<String,Object> fallback(RagCapabilityProfile profile, String description) {
- Map<String,Object> result = new LinkedHashMap<>();
- Map<String,Object> config = new LinkedHashMap<>();
- config.put("retrievalMode", "AUTO_GENERATE");
- config.put("generationRules", List.of("仅生成只读查询", "只使用已审核的 Schema 元素", "结果必须限制返回规模"));
- if (profile.sourceType() == RagSourceType.STRUCTURED_DATA) {
- List<String> tables = semanticCatalog.rank(description, profile, "table:", 8);
- if (tables.isEmpty()) tables = profile.sqlSchema().tables().stream().limit(8).map(SqlSchemaSnapshot.TableSchema::name).toList();
- config.put("allowTextToSql", true); config.put("allowedTables", tables); config.put("maxRows", 100);
- result.put("examples", tables.stream().limit(4).map(table -> Map.of("question", "查询 " + table + " 的主要记录", "sql", "SELECT * FROM " + table + " LIMIT 20")).toList());
- } else {
- List<String> labels = semanticCatalog.rank(description, profile, "label:", 12);
- List<String> relationships = semanticCatalog.rank(description, profile, "relationship:", 12);
- if (labels.isEmpty()) labels = profile.graphSchema().labelNames().stream().limit(12).toList();
- List<String> selectedLabels = labels;
- if (relationships.isEmpty()) relationships = profile.graphSchema().relationships().stream()
- .filter(rel -> rel.startLabels().stream().anyMatch(selectedLabels::contains) || rel.endLabels().stream().anyMatch(selectedLabels::contains))
- .limit(12).map(com.agent.management.rag.graph.GraphSchemaSnapshot.RelationshipSchema::type).distinct().toList();
- var subgraph = profile.graphSchema().filter(labels, relationships, Map.of());
- Map<String,List<String>> properties = new LinkedHashMap<>();
- subgraph.nodes().forEach(node -> properties.put(node.label(), node.properties().keySet().stream().toList()));
- subgraph.relationships().forEach(rel -> properties.put(rel.type(), rel.properties().keySet().stream().toList()));
- config.put("allowTextToCypher", true); config.put("allowedLabels", subgraph.labelNames());
- config.put("allowedRelationships", subgraph.relationshipTypeNames()); config.put("allowedProperties", properties); config.put("maxDepth", 3);
- 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());
- }
- result.put("suggestedConfig", config);
- result.put("summary", "基于实际 Schema 与向量语义相似度生成的授权草稿,应用前需人工审核");
- return result;
- }
- private static String shortMessage(Exception error) {
- String value = error.getMessage();
- return value == null ? error.getClass().getSimpleName() : value.substring(0, Math.min(value.length(), 180));
- }
- }
|