Skip to content

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.pyMetricDefinition、MetricVersion、MetricActiveVersion、SemanticDimension、BusinessGlossary、FiscalCalendar、VerifiedQuery、SemanticIndexRecord
指标注册表server/semantic/registry.pySemanticRegistry.load_active_metrics()、resolve_synonym()、seed_from_yaml()、MetricEntry.to_prompt_dict()
生命周期server/semantic/lifecycle.pyMetricLifecycleService.publish()、transition()、rollback()、invalidate_dependent_queries()
术语词典server/semantic/glossary.pyGlossaryService.lookup_value_alias()、DimensionService.resolve_synonym()、seed_builtin_glossary()
可信查询仓库server/semantic/verified_queries.pyVerifiedQueryRepository.load_for_retrieval()、add_draft()、re_validate()、invalidate_by_metric()
语义检索server/search/semantic_search.pyInMemorySemanticRepository、build_metric_index_text()、VerifiedQuerySearchResult
语义链接server/linking/semantic_linker.pySemanticSchemaLinker.link()、_link_metric_v3()、_detect_fiscal_year_conflict()
上下文组装server/generation/context.pySemanticContextBuilder.build()、SemanticContext.to_prompt_dict()、_sanitize_untrusted()
Prompt Builderserver/generation/prompt.pySQLPromptBuilderV3.build()、PROMPT_VERSION_V3
语义评测server/evaluation/semantic.pySemanticEvaluator.evaluate_batch()、compare_v2_v3()、V3_SEMANTIC_TEST_QUESTIONS
管理 APIserver/api/semantic.pyPOST /api/v1/semantic/metrics/{id}/publish、/rollback、/versions/diff
Admin 前端apps/admin/app/semantic/指标列表页(域/状态过滤)、详情页(版本操作/回滚)
数据库迁移migrations/versions/b3c4d5e6f7a8_v3_s01_语义层基础表.py9 张表,7 个 ENUM 类型
指标定义semantic_models/v3/metrics.yaml40 个核心指标,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=5few-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 把计算口径做成不可变版本,发布前跑回归,再把定义和兼容样例提供给生成器。"

具体做法:

  1. 版本不可变:指标一旦发布,快照冻结。业务口径变更必须走 Draft→Testing→Publish 流程,附回归结果 ID,不能悄悄改。

  2. 四层指标链接:域路由 → 已发布注册表 → 术语词典 → V2 回退。冲突(同名跨域口径)显式触发澄清,不静默选择。

  3. required_filters 强制注入:指标定义的过滤规则(is_test=false、order_status IN ('PAID','COMPLETED'))由上下文构建器注入 Prompt,不依赖模型记住。

  4. 锁定测试集隔离:可信样例(few-shot)和评测黄金集严格分开,is_test_locked=true 的样例不能被召回,防止题库泄漏。

  5. 分层评测:Wrong Metric / Wrong Time / Wrong Aggregation / Missing Filter 独立计数,能精确归因是语义层问题还是生成层问题。


延伸阅读 ​

需求 → 代码 → 验证 → 复盘