代码级设计,Java 11 + Spring Boot。每个模块给出关键类的完整实现思路。
负责加载本体 + 推理,是系统的语义核心。
@Service
public class OntologyService {
private final OntModel model;
public OntologyService(@Value("${ontology.file:classpath:ontology/metric.ttl}") String ontologyFile) {
// OWL 规则推理模型(支持等价类、传递性、子类)
this.model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM_RULE_INF);
this.model.read(ontologyFile);
}
/** 等价指标识别:传入"成交额",返回规范指标(GMV) */
public String resolveEquivalentMetric(String rawMetric) {
String sparql = String.format(
"PREFIX : <http://example.org/metric#> " +
"PREFIX owl: <http://www.w3.org/2002/07/owl#> " +
"SELECT ?m WHERE { :%s owl:equivalentClass ?m . ?m a :Metric }", rawMetric);
try (QueryExecution qe = QueryExecutionFactory.create(sparql, model)) {
ResultSet rs = qe.execSelect();
if (rs.hasNext()) {
String m = rs.next().getResource("m").getLocalName();
return m.equals(rawMetric) ? rawMetric : m;
}
}
return rawMetric; // 无等价关系,返回原词
}
/** 维度层级展开:传入"华东区",返回子成员列表 */
public List<String> expandRegion(String region) {
String sparql = String.format(
"PREFIX : <http://example.org/metric#> " +
"SELECT ?r WHERE { ?r :partOf :%s }", region);
List<String> result = new ArrayList<>();
try (QueryExecution qe = QueryExecutionFactory.create(sparql, model)) {
ResultSet rs = qe.execSelect();
while (rs.hasNext()) result.add(rs.next().getResource("r").getLocalName());
}
return result; // 传递性推理自动展开所有层级
}
/** 指标推导:传入"客单价",返回组成指标 */
public List<String> getComponents(String metric) {
String sparql = String.format(
"PREFIX : <http://example.org/metric#> " +
"SELECT ?c WHERE { :%s :composedOf ?c }", metric);
// 返回组成指标列表
}
}
模板 + DSL,口径固化。LLM 不碰 SQL。
@Component
public class SqlGenerator {
private final Map<String, MetricDefinition> metrics; // 从 metrics.yaml 加载
public SqlGenerator(MetricConfigLoader loader) {
this.metrics = loader.load();
}
/** 生成 SQL:根据指标定义 + 维度展开 + 时间范围 */
public String generateSql(String metric, List<String> regions, TimeRange time) {
MetricDefinition def = metrics.get(metric);
if (def == null) throw new MetricNotFoundException(metric);
String sql;
if (def.getType() == MetricType.COMPOSITE) {
// 复合指标:DSL 拼子查询(客单价 = GMV / 订单数)
sql = buildCompositeSql(def, regions, time);
} else {
// 原子/派生指标:模板 + 参数填充
sql = buildSimpleSql(def, regions, time);
}
return sql;
}
private String buildSimpleSql(MetricDefinition def, List<String> regions, TimeRange time) {
// 模板:SELECT {calc} FROM {table} WHERE {filter} AND dt BETWEEN ... {region_filter}
String regionFilter = regions.isEmpty() ? "" :
"AND region_id IN (SELECT region_id FROM dim_region WHERE region_name IN (" +
regions.stream().map(r -> "'" + r + "'").collect(Collectors.joining(",")) + "))";
return String.format(
"SELECT %s AS value FROM %s WHERE %s AND dt BETWEEN '%s' AND '%s' %s",
def.getCalcExpr(), def.getTable(), def.getFilterExpr(),
time.start(), time.end(), regionFilter);
}
}
从指标定义读口径,校验 SQL 是否满足,双保险。
@Component
public class CaliberValidator {
/** 校验生成的 SQL 是否包含指标口径的必需过滤条件 */
public void validate(String metric, String sql, MetricDefinition def) {
for (String clause : def.getRequiredFilters()) {
// requiredFilters 例:["status='paid'", "refunded=false"]
if (!sql.contains(clause)) {
throw new CaliberViolationException(
String.format("指标 %s 的口径校验失败:SQL 缺少过滤条件 %s", metric, clause));
}
}
}
}
直调 DeepSeek(OpenAI 兼容),两个职责:槽位提取 + 归因分析。
@Service
public class LlmService {
private final String apiKey;
private final String baseUrl; // https://api.deepseek.com
/** 槽位提取:结构化输出(JSON) */
public QueryIntent parseIntent(String question) {
String prompt = """
你是指标查询的意图识别器。提取槽位,输出 JSON:
{"intent":"metric_query","metric":"<指标原话>","region":"<地区原话>",
"time":"<时间原话>","compare":"<对比方式>"}
不要做语义对齐,只提取原话。
问题:%s
""".formatted(question);
String json = call(prompt, true); // response_format=json_object
return objectMapper.readValue(json, QueryIntent.class);
}
/** 归因分析:基于真实查询结果 */
public String analyze(String question, String metric, Object result) {
String prompt = """
你是数据分析师。基于结果做归因分析,只解释数据反映的事实,不编造。
问题:%s
指标:%s
结果:%s
""".formatted(question, metric, result);
return call(prompt, false);
}
}
串起完整链路。
@Service
public class MetricQueryService {
private final LlmService llmService;
private final OntologyService ontologyService;
private final SqlGenerator sqlGenerator;
private final CaliberValidator caliberValidator;
private final MaxComputeQueryService queryService;
public QueryResponse query(String question) {
// ① LLM 槽位提取
QueryIntent intent = llmService.parseIntent(question);
// ② 本体推理:等价识别(成交额 → GMV)
String metric = ontologyService.resolveEquivalentMetric(intent.getMetric());
// ③ 本体推理:维度展开(华东区 → 7省)
List<String> regions = ontologyService.expandRegion(intent.getRegion());
// ④ 时间解析
TimeRange time = TimeParser.parse(intent.getTime());
// ⑤ SQL 生成 + 口径校验
MetricDefinition def = metricConfig.get(metric);
String sql = sqlGenerator.generateSql(metric, regions, time);
caliberValidator.validate(metric, sql, def);
// ⑥ 查询执行
List<Map<String, Object>> result = queryService.query(sql);
// ⑦ LLM 归因
String analysis = llmService.analyze(question, metric, result);
return new QueryResponse(metric, result, analysis);
}
}
public class MetricNotFoundException extends RuntimeException {} // 指标未定义
public class CaliberViolationException extends RuntimeException {} // 口径校验失败
public class IntentParseException extends RuntimeException {} // 槽位提取失败
MetricDefinition(calcExpr/filterExpr/table)从 YAML 加载,SQL 生成只读配置