上线验证的目标

演示不是「能跑就行」,而要证明三件事:

  1. 数据可信:宽表与元数据口径一致,过滤条件正确
  2. 链路可复现:同一问法多次返回相同 SQL 与结果
  3. 异常可解释:失败时有排障路径,而非沉默报错

本篇给出虚构企业场景的完整验证 playbook

测试数据 Seed 流程

1
2
3
4
5
6
7
8
9
10
11
12
13
flowchart TB
START([开始验证准备]) --> S1[1. 初始化 MySQL schema]
S1 --> S2[2. 导入 bi_domain / bi_metric 元数据]
S2 --> S3[3. 配置 bi_metric_extract_logic]
S3 --> S4[4. 注册 bi_table 白名单]
S4 --> S5[5. Seed dws_sales_metric_wide 宽表]
S5 --> S6[6. 构建 Chroma 知识索引]
S6 --> S7[7. 跑 validate_data_pipeline.py]
S7 --> S8[8. 执行标准问法快照测试]
S8 --> DEC{全部通过?}
DEC -->|是| OK([可演示])
DEC -->|否| FIX[按排障表修复]
FIX --> S7

Step 1–2:元数据初始化

1
2
3
4
5
6
7
8
INSERT INTO bi_domain (domain_code, domain_name, domain_keywords, priority)
VALUES ('SALES_OPS', '销售运营域', '销售收入,客户留存率,新增客户数,华东区,华北区', 10);

INSERT INTO bi_metric (metric_code, metric_name, domain_code, status)
VALUES
('M_SALES_REVENUE', '销售收入', 'SALES_OPS', 1),
('M_RETENTION', '客户留存率', 'SALES_OPS', 1),
('M_NEW_CUST', '新增客户数', 'SALES_OPS', 1);

Step 3:取数逻辑

1
2
3
4
5
6
7
8
9
10
11
INSERT INTO bi_metric_extract_logic (
metric_code, table_code, index_name_value,
period_field, time_field, org_field,
default_filter, is_default, priority
) VALUES
('M_SALES_REVENUE', 'dws_sales_metric_wide', '销售收入',
'period', 'stat_date', 'region_name', "is_valid = '1'", 1, 100),
('M_RETENTION', 'dws_sales_metric_wide', '客户留存率',
'period', 'stat_date', 'region_name', "is_valid = '1'", 1, 100),
('M_NEW_CUST', 'dws_sales_metric_wide', '新增客户数',
'period', 'stat_date', 'region_name', "is_valid = '1'", 1, 100);

Step 4:表白名单

1
2
INSERT INTO bi_table (table_code, table_name_cn, is_whitelisted)
VALUES ('dws_sales_metric_wide', '销售指标宽表', 1);

Step 5:宽表 Seed 数据(虚构)

1
2
3
4
5
6
7
8
9
10
11
12
INSERT INTO dws_sales_metric_wide (
index_name, period, stat_date, region_name,
value, val_yoy, is_valid
) VALUES
('销售收入', '季度', '2026-03-31', '华东区', 12850.6, 0.082, '1'),
('销售收入', '季度', '2026-03-31', '华北区', 9650.2, 0.051, '1'),
('客户留存率', '年', '2026-12-31', '华东区', 0.876, 0.012, '1'),
('客户留存率', '年', '2026-12-31', '华北区', 0.854, 0.008, '1'),
('新增客户数', '月', '2026-05-31', '华东区', 1250, 0.15, '1'),
('新增客户数', '月', '2026-05-31', '华北区', 980, 0.11, '1'),
-- 作废行:验证 default_filter
('销售收入', '季度', '2026-03-31', '华东区', 99999, 0, '0');

验证点:含 is_valid = '0' 的 99999 不应出现在任何查询结果。

指标对齐检查

检查项 方法 期望
index_name 一致 宽表 DISTINCT vs 元数据 完全一致,无空格差异
区域枚举 宽表 region_name 含华东区、华北区
默认过滤 抽查 SQL 均含 is_valid = '1'
时间字段 季/月/年样例各 1 条 stat_date 与 period 匹配
白名单 非白名单表查询 validate_sql 拒绝

对齐脚本示意:

1
2
3
4
5
def check_index_name_alignment():
db_names = query("SELECT DISTINCT index_name FROM dws_sales_metric_wide WHERE is_valid='1'")
meta_names = query("SELECT index_name_value FROM bi_metric_extract_logic")
diff = set(db_names) - set(meta_names)
assert not diff, f"宽表多出未配置指标: {diff}"

标准问法演示脚本

演示建议按「简单 → 多轮 → 安全」顺序:

演示 1:单轮完整问法

:华东区 2026 年一季度销售收入是多少?

期望 SQL

1
2
3
4
5
6
7
8
9
10
11
12
SELECT
region_name AS `区域`,
value AS `销售收入_当期值`,
val_yoy AS `同比`
FROM `dws_sales_metric_wide`
WHERE `index_name` = '销售收入'
AND `period` = '季度'
AND `stat_date` = '2026-03-31'
AND `region_name` = '华东区'
AND is_valid = '1'
ORDER BY value DESC
LIMIT 1000;

期望结果:12850.6(不含 99999 作废行)

演示 2:缺时间周期追问

:华北区销售收入多少

期望needs_clarification: truefallback_type: missing_time_period

追问:华北区 2026 年一季度销售收入

期望 SQLregion_name = '华北区',结果 9650.2

演示 3:客户留存率同比

:华东区 2026 年客户留存率同比

1
2
3
4
5
6
7
8
SELECT region_name, value, val_yoy
FROM dws_sales_metric_wide
WHERE index_name = '客户留存率'
AND period = '年'
AND stat_date = '2026-12-31'
AND region_name = '华东区'
AND is_valid = '1'
LIMIT 1000;

演示 4:新增客户数

:2026 年 5 月新增客户数

期望:华东区 1250,华北区 980(若未指定区域可返回多行或默认汇总,需在元数据配置 granularity)

排障对照表

现象 可能原因 排查步骤 修复
检索不到指标 bi_metric 未配置或 status=0 查 MySQL 关键词匹配 补 metric 记录
SQL 无 is_valid default_filter 为空 查 extract_logic is_valid = '1'
结果偏大 含作废行 对比去/加过滤 强制 default_filter
时间条件错误 时间解析规则不全 查 _extract_time_period 日志 补规则或追问
validate 失败 表不在白名单 查 bi_table 加 is_whitelisted=1
空结果 seed 未导入 / 区域拼写 查宽表 LIKE 修正数据或别名
图表空白 explain_result 未返回 option 查 API 响应 修 chart 生成逻辑
澄清死循环 slots 合并未写入 查 qa_session.slots_json 修 merge 逻辑

上线 Checklist

数据层

  • dws_sales_metric_wide 已 seed,含华东区、华北区样例
  • 作废行 is_valid='0' 存在且被过滤验证通过
  • 三个核心指标元数据与取数逻辑齐全

服务层

  • validate_data_pipeline.py 全绿
  • /api/ask 标准问法 10 条快照测试通过
  • validate_sql 攻击样例 5 条均被拦截

前端层

  • 问数抽屉可完成演示 1–4
  • 澄清 chips 可点击
  • SQL 折叠面板可展开复制
  • agent_flow 时间线有数据

运维层

  • 只读 DB 账号配置完成
  • sql_audit_log 有写入
  • 演示环境 API Key / 模型配置有效

自动化验证命令

1
2
3
4
5
6
7
8
# 管道离线验证
python scripts/validate_data_pipeline.py --env demo

# 标准问法 golden test
pytest tests/test_golden_questions.py -v

# 前端 e2e(可选)
npm run test:e2e -- --grep "data-agent demo"

演示话术建议

  1. 先讲元数据驱动而非 LLM 写 SQL
  2. 现场展示 agent_flow 各步耗时
  3. 展开 SQL 强调 is_valid = '1' 与表白名单
  4. 故意输入缺周期问法,展示追问
  5. 对比作废行不存在于结果,证明数据质量过滤

本章小结

  • 上线验证 = seed 流程 + 指标对齐 + 标准问法 + 排障表 + checklist
  • 宽表必须含 is_valid 正反样例,验证 default_filter 生效
  • 演示脚本覆盖单轮、追问、多指标三类场景
  • 自动化 golden test 保证回归可复现

思考题

  1. 若演示环境与生产元数据不同步,如何防止「演示行、生产崩」?
  2. golden file 中 SQL 字符串比对要不要规范化空白与反引号?
  3. 排障表里还应增加哪三类「用户可见」错误提示?

系列导航

主题
01 架构全景
02 Text-to-SQL
03 RAG 指标库
04 多轮澄清
05 安全校验
06 Vue3 工作台
07 本篇
08 反馈闭环

← 返回专题首页