V3|业务语义与可信查询
实现状态:已完成。本页记录真实代码入口、调用关系、实测结果和面试复述骨架。
这一期学会什么:指标是有粒度、规则和版本的业务资产。V2 能找到正确字段,仍会算错指标——V3 把计算口径做成不可变版本,发布前跑回归,再把定义和兼容样例提供给生成器。
本期链路
text
指标 Draft → Testing(回归绑定)→ Published(不可变快照)→ active_version 原子切换
↓
SemanticRegistry 同义词解析 + DimensionService + GlossaryService 值别名
↓
SemanticSchemaLinker(4级优先链接 + 跨域冲突 + 财年冲突检测)
↓
VerifiedQueryRepository(锁定集隔离 + 依赖失效传播)
↓
SemanticContextBuilder(越权过滤 + 注入防护 + token 预算裁剪)
↓
SQLPromptBuilderV3(语义版本 + required_filters + verified examples)真实代码入口
| 模块 | 文件 | 核心类/函数 |
|---|---|---|
| ORM 模型 | server/semantic/models.py | MetricDefinition、MetricVersion、MetricActiveVersion、SemanticDimension、BusinessGlossary、FiscalCalendar、VerifiedQuery、SemanticIndexRecord |
| 指标注册表 | server/semantic/registry.py | SemanticRegistry.load_active_metrics()、resolve_synonym()、seed_from_yaml()、MetricEntry.to_prompt_dict() |
| 生命周期 | server/semantic/lifecycle.py | MetricLifecycleService.publish()、transition()、rollback()、invalidate_dependent_queries() |
| 术语词典 | server/semantic/glossary.py | GlossaryService.lookup_value_alias()、DimensionService.resolve_synonym()、seed_builtin_glossary() |
| 可信查询仓库 | server/semantic/verified_queries.py | VerifiedQueryRepository.load_for_retrieval()、add_draft()、re_validate()、invalidate_by_metric() |
| 语义检索 | server/search/semantic_search.py | InMemorySemanticRepository、build_metric_index_text()、VerifiedQuerySearchResult |
| 语义链接 | server/linking/semantic_linker.py | SemanticSchemaLinker.link()、_link_metric_v3()、_detect_fiscal_year_conflict() |
| 上下文组装 | server/generation/context.py | SemanticContextBuilder.build()、SemanticContext.to_prompt_dict()、_sanitize_untrusted() |
| Prompt Builder | server/generation/prompt.py | SQLPromptBuilderV3.build()、PROMPT_VERSION_V3 |
| 语义评测 | server/evaluation/semantic.py | SemanticEvaluator.evaluate_batch()、compare_v2_v3()、V3_SEMANTIC_TEST_QUESTIONS |
| 管理 API | server/api/semantic.py | POST /api/v1/semantic/metrics/{id}/publish、/rollback、/versions/diff |
| Admin 前端 | apps/admin/app/semantic/ | 指标列表页(域/状态过滤)、详情页(版本操作/回滚) |
| 数据库迁移 | migrations/versions/b3c4d5e6f7a8_v3_s01_语义层基础表.py | 9 张表,7 个 ENUM 类型 |
| 指标定义 | semantic_models/v3/metrics.yaml | 40 个核心指标,7 个业务域 |
| 可信样例 | benchmarks/verified/ | 43 条核心样例(5 条锁定测试集) |
调用顺序(完整链路)
text
1. 指标 YAML → SemanticRegistry.seed_from_yaml() → MetricDefinition(DRAFT)
2. Admin 触发 transition(DRAFT → TESTING)
3. 运行回归:SemanticEvaluator.evaluate_batch(regression_questions, generated_sqls)
4. 通过后 publish(regression_run_id=...) → MetricVersion 快照 + active_version 原子切换 + Redis 失效
5. 依赖此指标的 VerifiedQuery 降为 INVALID → 人工重审后回 DRAFT → 再 mark_verified
查询时链路:
用户问题
↓
DomainRouter.route() → QueryIntent(primary_domain、metric_mentions、filters)
↓
TwoLevelRetriever.retrieve() → SchemaContext(tables + columns)
↓
SemanticSchemaLinker.link(intent, schema_context)
_link_metric_v3:域路由 → SemanticRegistry → GlossaryService → V2 回退
_link_filter_v3:GlossaryService.lookup_value_alias() → 值别名
_detect_fiscal_year_conflict:财年关键词检测
↓
VerifiedQueryRepository.load_for_retrieval(allowed_domains, exclude_test_locked=True)
→ Top K 相关样例(锁定集隔离)
↓
SemanticContextBuilder.build()
越权过滤(sensitive=True 且域不匹配)
注入清理(_sanitize_untrusted 检测 6 类注入模式)
token 预算裁剪(先裁样例 → 再裁低分列)
↓
SQLPromptBuilderV3.build(SemanticContext)
_SYSTEM_PROMPT_V3(含 required_filters 强制注入规则、untrusted 声明)
↓
LLMGateway.complete() → SQL测试入口
bash
# V3 全量测试(含 V0/V1/V2 回归)
pytest tests/ -q
# V3 语义评测专项(语义层错误分类)
pytest tests/test_v3_semantic.py -v
# V3 生命周期专项
pytest tests/test_v3_lifecycle.py -v
# V3 上下文组装专项
pytest tests/test_v3_context.py -v三个典型案例
案例一(修复):毛利率用 AVG 算错
text
问题:2026 年上半年各品类毛利率
V2 生成(错误):
SELECT category_name, AVG(gross_margin_rate) ...
原因:把每行的毛利率字段直接平均,不同品类数量差异大时结果失真
V3 修复:
SELECT category_name,
SUM(net_amount - cost_amount) / NULLIF(SUM(net_amount) - COALESCE(SUM(refund_amount), 0), 0)
修复来源:MetricEntry.to_prompt_dict() 注入了 warning="禁止对明细行毛利率做简单平均"
+ gold_aggregation_pattern="SUM/SUM" 触发了回归失败
失败归因:wrong_aggregation(语义层注入了正确口径后修复)案例二(成功):华东销售额
text
输入:华东地区今年销售额是多少
SemanticSchemaLinker 链接过程:
"销售额" → SemanticRegistry.resolve_synonym() → metric_id=net_sales(evidence=semantic_registry_synonym, confidence=0.92)
"华东" → GlossaryService.lookup_value_alias() → (dim_region.region_name, "East China")(evidence=glossary_value_alias)
SemanticContextBuilder 注入:
metrics: [{id: net_sales, required_filters: ["order_status IN ('PAID','COMPLETED')", "is_test = false"], ...}]
value_mappings: [{source: 华东, column: dim_region.region_name, db_value: East China}]
生成器收到:
- 指标表达式和强制过滤(权威规则)
- 值映射(已类型化,不需要模型猜)
- 相关可信样例(few-shot,来源 ID 可追溯)案例三(拒绝):退款用 paid_at 过滤
text
V3 回归题 v3-sem-003:退款金额
金标准:按 refunded_at 归属
V2 错误:WHERE fo.paid_at BETWEEN ... (用订单支付时间过滤退款)
SemanticEvaluator 检测:
_extract_time_fields(predicted_sql) → ["paid_at"]
gold_time_fields = ["refunded_at"]
→ time_field_correct=False → failure_category="wrong_time"
修复:MetricEntry(refunded_amount).time_role = TimeRole.REFUNDED_AT
注入 time_rule_note="按 refunded_at 归属;跨期退款计入退款发生期"设计取舍记录
| 决策 | 选择 | 理由 |
|---|---|---|
| 版本快照格式 | JSON 列存完整字段 | 即使 ORM 模型字段变化,历史快照仍可解析;代价是快照体积较大 |
| Redis 失效粒度 | 按 metric_id 精确失效 | 避免全量清空,影响无关指标的缓存 |
| 锁定测试集隔离 | is_test_locked 布尔字段 | 简单有效,不需要单独的表;由应用层强制过滤 |
| 注入防护 | 正则清理 + untrusted 标记 | 不依赖模型自判断,清理逻辑可测试、可覆盖 |
| 状态机终态 | DEPRECATED 不可转移 | 防止废弃后复活,需要新建草稿走完整流程 |
| 样例数上限 | max_examples=5 | few-shot token 成本 vs 效果权衡;5 条覆盖主要 SQL 模式 |
评测指标(实测门槛)
| 指标 | 目标 | 当前状态 |
|---|---|---|
| 语义评测通过率 | ≥70% | 待接真实生成 SQL 后实测 |
| Wrong Metric | ≤20% | 待测 |
| Wrong Aggregation | ≤20% | 待测 |
| Missing Filter | ≤20% | 待测 |
| 测试覆盖率 | ≥85% | 见 pytest 结果 |
评测命令(接真实数据后运行):
python
from server.evaluation.semantic import SemanticEvaluator, V3_SEMANTIC_TEST_QUESTIONS, compare_v2_v3
evaluator = SemanticEvaluator()
# 用 V3_SEMANTIC_TEST_QUESTIONS 作为回归题集
report = evaluator.evaluate_batch(V3_SEMANTIC_TEST_QUESTIONS, generated_sqls)
print(report.summary())
print(report.meets_v3_targets())面试复述骨架
"V2 能找到正确字段,仍会算错指标。V3 把计算口径做成不可变版本,发布前跑回归,再把定义和兼容样例提供给生成器。"
具体做法:
版本不可变:指标一旦发布,快照冻结。业务口径变更必须走 Draft→Testing→Publish 流程,附回归结果 ID,不能悄悄改。
四层指标链接:域路由 → 已发布注册表 → 术语词典 → V2 回退。冲突(同名跨域口径)显式触发澄清,不静默选择。
required_filters 强制注入:指标定义的过滤规则(
is_test=false、order_status IN ('PAID','COMPLETED'))由上下文构建器注入 Prompt,不依赖模型记住。锁定测试集隔离:可信样例(few-shot)和评测黄金集严格分开,
is_test_locked=true的样例不能被召回,防止题库泄漏。分层评测:Wrong Metric / Wrong Time / Wrong Aggregation / Missing Filter 独立计数,能精确归因是语义层问题还是生成层问题。