通义大模型驱动 ChatBI:对话式数据分析与 SQL 生成实践
2026/9/18 12:48:06 网站建设 项目流程

简介:这是一份围绕通义大模型与对话式数据分析(ChatBI)的32页PPT讲义,面向数据分析师、BI工程师、产品经理及希望把大语言模型落地到取数分析场景的技术人员。内容从传统数仓与BI取数流程的痛点切入,梳理对话型数据分析的困难与挑战,并给出析言GBI的整体解决方案,包括产品架构、工作链路、多代理协作模式,以及数据问答、Selector召回、SQL Generator Team、校验与改写等关键环节,还涉及XiyanSQL在自然语言转SQL上的关键技术攻关与实验样例,可帮助读者理解NL2SQL与智能体编排如何支撑自然语言问数、指标查询与报表生成。压缩包共1个文件,为pptx演示文稿,约5.16MB,共32页,页面结构便于按背景、方案、架构、关键技术、实验与最佳实践逐节阅读。目前已有123人学习下载,适合需要快速把握ChatBI系统设计与落地方案的读者参考。

1. 从 SQL 到一句话:对话型数据分析为什么需要通义大模型

业务群里最常见的一句话是「上周华东的退货率怎么突然涨了」。放在传统 BI 里,它要经过需求登记、排期、建模、出报表,快则一天慢则一周,等图出来讨论热度已经过去了。对话型数据分析想压缩的正是这段时延:业务方用人话提问,系统自己完成指标理解、口径匹配、SQL 生成、执行取数、图表渲染。

通义大模型在这里不是聊天外壳,而是整条链路的语义中枢——把口语表达对齐到仓库里真实存在的字段和指标,产出可执行 SQL,再把结果翻译回人话。一份 32 页的分享材料要讲清的,基本就是这条链路的每一环。

后面按「链路拆解、语义层建设、落地实现、排错调优」推进,面向想把取数入口搬进对话的分析师、要评估通义大模型能否接入现有数仓的 BI 工程师,以及负责准确率兜底的数据平台研发,每一步都给能跑的代码和参数。

2. 通义大模型驱动 ChatBI 的链路拆解与最小调用

2.1 一次问数要经过的四段链路

把「一句话出图」当成一个黑盒,调试时会非常痛苦,因为出错之后你分不清是模型听错了、指标找错了,还是 SQL 本身写错了。稳妥的做法是拆成四段,每段都能单独打日志、单独回放。

第一段是意图识别:判断这句话是要取数、要解释已有结果、要下钻,还是纯粹闲聊,或者缺了时间范围必须先反问。第二段是指标召回:从语义层里捞出候选指标和维度,这一步决定了模型「知不知道你说的退货率是哪个口径」。第三段是 SQL 生成与校验:模型基于 schema、口径和少量示例产出查询,再由护栏程序解析、改写、拦截。第四段是执行与呈现:跑数、判断结果形状、选图表类型、生成一句自然语言结论。

四段分开的最大好处是排错有定位点。准确率掉了,先看召回 Top5 里有没有正确指标;召回没问题就去看 SQL 的 WHERE 条件是否被模型自己编了时间;SQL 没问题再看图表是不是把两个量纲不同的指标画在了同一根轴上。

2.2 通义大模型的最小可运行调用

先把最基础的一次调用跑通,再往上叠语义层。用 DashScope 的 Python SDK,二十行以内就能验证账号、网络和模型是否正常:

# pip install dashscope import os import dashscope from dashscope import Generation dashscope.api_key = os.environ["DASHSCOPE_API_KEY"] # 从环境变量读,不要硬编码进仓库 resp = Generation.call( model="qwen-plus", # 通用对话与改写够用;复杂 SQL 建议换更强的型号 messages=[ {"role": "system", "content": "你是数据助手,只输出 SQL,不要任何解释文字。"}, {"role": "user", "content": "查一下上周华东区的退货率,按天看趋势"}, ], result_format="message", # 返回结构化 message,取字段更稳 temperature=0.1, # 取数场景要确定性,温度压低到 0.1 左右 seed=1234, # 固定种子,便于回归时逐字比对差异 ) print(resp.output.choices[0].message.content)

参数里真正影响 ChatBI 效果的只有三个:model决定能力上限和单次成本;temperature决定同样的问法会不会每次给出不同 SQL,取数场景必须压低;seed让回归测试可复现,改一版提示词就能 diff 出 SQL 变化。result_format="message"是为了少写一层解析,直接取message.content即可。

前端是流式对话的话,把stream打开,并加上增量输出参数,避免每个分片都重复推送全文:

responses = Generation.call( model="qwen-plus", messages=[{"role": "user", "content": "上个月各品类的销售额占比"}], result_format="message", stream=True, incremental_output=True, # 只推增量片段,前端直接做打字机效果 ) for chunk in responses: print(chunk.output.choices[0].message.content, end="")

提示:流式输出适合自然语言结论部分,SQL 生成建议关闭流式并做完整性校验,避免半截语句被误执行。

2.3 三种接入方式的取舍

接入方式适用场景注意点
原生 SDK 调用快速验证、内部工具依赖具体 SDK 版本,升级前先看变更说明
OpenAI 兼容模式已有 LangChain 等框架代码只需改base_urlapi_key,但部分高级参数不生效
私有化部署数据不出域、强合规要求显存成本高,量化后复杂 SQL 能力会下降

我一般的判断是:数据可以出域就先用托管服务把链路跑通,把准确率的天花板摸清楚,再评估是否值得为私有化牺牲一部分生成质量。反过来先做私有化,很容易把「模型能力不够」误判成「方案不成立」。

2.4 按任务分工选模型

不同环节对模型的要求完全不同。意图识别是短文本分类,快而便宜最重要;SQL 生成要求结构严谨、字段不幻觉;结果解释要求语言自然。把这三个环节用同一个模型跑,成本高且某一环必然将就。常见做法是分流:分类和改写用小模型,SQL 生成用强模型,解释结论回到中等模型。这个分工要写进配置而不是散在代码里,否则调优时改一处忘一处。

3. 语义层建设:让通义大模型看懂你的业务指标

3.1 为什么必须单独建一层语义层

大模型见过海量公开语料,但它没见过你们公司的口径。同叫「活跃用户」,增长团队指七日内有登录,商业化团队指七日内有付费行为;同叫「销售额」,有的含税有的不含税,有的扣退款有的不扣。如果不把口径显式喂给模型,它只能猜,猜错的概率随指标数量线性上升。

语义层的本质是把「业务语言」到「物理表字段 + 计算表达式」的映射固化成元数据:指标名、同义词、口径说明、计算表达式、可用维度、责任人。模型每次生成 SQL 前,先查这层映射,拿到的是确定的口径而不是猜测。这层建好之后,换模型、换提示词都不会动摇准确率的地基。

3.2 指标元数据表的一份可落库 DDL

元数据不必设计得很复杂,下面这张表是我用得多、覆盖也够的结构:

CREATE TABLE meta_metric ( metric_code VARCHAR(64) NOT NULL, -- 指标唯一编码,如 order_refund_rate metric_name VARCHAR(128) NOT NULL, -- 业务名称:退货率 aliases TEXT, -- 同义词,逗号分隔:退款率,退货比例,退单率 biz_caliber TEXT, -- 口径说明:签收后7天内退款单量 / 签收单量 expr_sql TEXT NOT NULL, -- 计算表达式,直接可嵌入 SELECT default_dims TEXT, -- 默认可下钻维度:region,category,dt time_col VARCHAR(64), -- 时间字段名,用于强制补时间过滤 owner VARCHAR(64), -- 责任人,口径变更时能找到人 status TINYINT DEFAULT 1, -- 1 生效 0 下线,避免旧口径被召回 PRIMARY KEY (metric_code) );

几个字段值得强调:aliases直接决定召回率,把业务同事在群里用过的口语说法都收集进去,包括错别字和简称;expr_sql存表达式而不是完整 SQL,方便和不同维度自由组合;time_col是护栏程序强制补时间条件的依据,能挡掉相当一部分全表扫描;status用来下线旧口径,历史口径不清理是准确率长期劣化的主要原因之一。

3.3 指标召回:向量打底,关键词和拼音兜底

召回的目标是「用户说退货率,Top5 里必须有 order_refund_rate」。纯向量检索对同义改写友好,但对专有名词和编码类词不敏感;纯关键词检索反过来。两者混合效果最稳:

# pip install dashscope numpy import os import numpy as np import dashscope from dashscope import TextEmbedding dashscope.api_key = os.environ["DASHSCOPE_API_KEY"] def embed(texts): r = TextEmbedding.call(model="text-embedding-v3", input=texts) # 以控制台可用型号为准 return [d["embedding"] for d in r.output["embeddings"]] # 离线阶段:把 metric_name + aliases + biz_caliber 拼成一句话算好向量存库 doc = "退货率 退款率 退货比例 退单率 签收后7天内退款单量/签收单量" doc_vec = np.array(embed([doc])[0]) # 在线阶段:用户问题向量化后算余弦相似度 q_vec = np.array(embed(["上个月华东退货情况怎么样"])[0]) score = float(q_vec @ doc_vec / (np.linalg.norm(q_vec) * np.linalg.norm(doc_vec))) print(round(score, 4))

向量分数拿到后不要直接取 Top1,而是取 Top5 交给后续环节,同时用关键词和拼音匹配做一路并行召回,两路结果合并去重。用户打「thl」这种拼音缩写时,向量几乎必然失效,关键词兜底就派上用场。阈值上,我的经验是相似度低于某个线(实践里常在 0.6 到 0.7 之间)就别硬猜,直接触发反问「你是想看退货率还是退款金额」,反问一次的成本远低于给错数的成本。

3.4 塞进提示词的元数据要控制在多少

召回回来的元数据会全部进提示词,这里最容易失控。把整张指标表塞进去,动辄上万 token,既贵又会让模型注意力被无关指标稀释。实践中的做法是:只放 Top3 到 Top5 的指标,每个指标只保留metric_codemetric_namebiz_caliberexpr_sqldefault_dims五个字段,口径说明超过两句话就精简。维度列表同理,别把几百个维度的全量表塞进去,按主题域分组,只给相关的那一组。

注意:提示词长度和准确率不是正相关。信息过载时模型更容易混用两个相似指标的口径,反而比只给三个候选时错得更多。

4. 对话型数据分析的落地实现:从提问到图表

4.1 意图识别先分流

把取数请求和闲聊、解释类请求混在一起处理,会让提示词互相干扰。分流一步用短提示词就能做,且成本极低:

INTENT_PROMPT = """你是数据问答路由,判断用户问题属于哪一类,只输出标签本身。 可选标签: - QUERY 需要查数、看趋势、看对比或占比 - EXPLAIN 对已有结果做归因、解释或下钻建议 - CLARIFY 指标名或时间范围缺失,需要先反问 - CHITCHAT 与数据无关 用户问题:{question} 输出:"""

CLARIFY这一类最容易被忽略,却是体验分水岭。「看看销售情况」这种问法如果不反问就直接生成 SQL,模型只能随便挑一个指标,用户看到结果的第一反应是「这不是我要的」,然后就不再信任这套系统。把反问做成显式分支,宁可多问一句。

4.2 SQL 生成的提示词模板与硬性约束

提示词要写成「约束清单」而不是「描述」。下面这版结构我用了很久,重点在最后的硬性约束和结构化输出:

SQL_PROMPT = """你是一名 {dialect} 数据分析工程师,根据表结构、指标口径和历史示例生成一条可直接执行的查询。 【表结构】 {schema_ddl} 【命中指标口径】 {metric_meta} 【可用维度】 {dim_list} 【历史示例】 {few_shot} 【硬性约束】 1. 只生成一条 SELECT,禁止 INSERT/UPDATE/DELETE/DROP/ALTER。 2. 时间过滤必须使用 {time_col},且区间必须显式写出,不允许使用 now() 之类的相对函数。 3. 禁止 SELECT *,所有聚合列必须起英文别名。 4. 不确定的口径假设写进 assumptions,不要自己发明字段。 5. 只输出 JSON:{{"sql": "...", "used_metrics": [...], "assumptions": [...]}} 用户问题:{question}"""

要求输出 JSON,是为了让程序能稳定解析出used_metricsassumptionsused_metrics用来做埋点统计哪些指标被问得最多,assumptions用来在 UI 上提示「本次结果按含税口径计算」——把模型的假设暴露给用户,比让它默默猜完再出错要好得多。few_shot不要放太多,三到五条覆盖趋势、对比、占比三种形态即可,示例过多会让模型倾向于照抄示例里的维度。

4.3 执行前的三道护栏

模型生成的 SQL 绝不能直接打到生产库。上线前至少要有语法解析、权限改写、行数限制三道:

# pip install sqlglot import sqlglot from sqlglot import exp FORBIDDEN = (exp.Insert, exp.Update, exp.Delete, exp.Drop, exp.Alter, exp.Create) def guard(sql: str, dialect: str = "mysql", max_rows: int = 5000, tenant_field: str = "tenant_id", tenant_id: str = "T001") -> str: tree = sqlglot.parse_one(sql, read=dialect) if any(tree.find(t) for t in FORBIDDEN): raise ValueError("检测到非查询语句,已拦截") if not isinstance(tree, exp.Select): raise ValueError("顶层节点不是 SELECT,已拦截") # 行级权限:没有租户条件的查询自动补上,防止越权看到别家数据 if not tree.find(exp.Column, lambda c: c.name == tenant_field): tree = tree.where(f"{tenant_field} = '{tenant_id}'") if tree.args.get("limit") is None: tree = tree.limit(max_rows) # 没写 LIMIT 就补一个,防止全表扫描拖垮库 return tree.sql(dialect=dialect)

三个动作的逻辑:parse_one把 SQL 变成 AST,判断顶层是不是Select、有没有危险节点,这是最可靠的黑名单方式,比正则匹配强得多;行级权限在 AST 上补WHERE条件,业务代码不用关心每个用户能看到哪些数据;补LIMIT是最后一道保险,取数场景没人真的需要一次拉一百万行,前端展示几十行就够了。

4.4 结果到图表的自动选型规则

SQL 跑完之后,图表类型不该让用户选,按结果集的形状判断即可:

结果特征推荐图表说明
1 个维度 + 1 个度量,维度为时间折线图趋势场景,默认按时间升序
1 个维度 + 1 个度量,维度为类别柱状图类别超过 15 个时改横向条形图
1 个维度 + 1 个度量,单一结果行指标卡加同比环比数字更直观
1 个维度 + 多个度量组合图或分组柱状图量纲差异大时必须用双轴
2 个维度 + 1 个度量热力或透视表交叉分析优先给表格

判断逻辑用返回值的列类型和行数就能实现:先看维度列是不是时间类型,再看度量列数量,最后看行数是否超过阈值决定要不要截断。这套规则写在服务端,前端只负责渲染,同一份数据在不同终端上看到的图才是一致的。

5. ChatBI 排错与准确率调优的实战技巧

5.1 五类高频失败与排查动作

准确率出问题时,不要笼统地说「模型不行」,按失败类型定位会快很多:

失败现象大概率原因排查动作
答非所问,指标完全错召回 Top5 未命中打印召回候选和相似度分数
指标对但数字不对口径表达式或时间字段用错比对expr_sql与人工 SQL
时间范围每次都不同提示词未禁用相对时间函数检查约束条款是否生效
越权看到其他租户数据护栏未做行级权限改写用低权限账号跑一次回归
多轮对话后跑偏上下文里塞了完整历史 SQL只保留上一轮的结果摘要

其中最后一条最隐蔽。多轮对话里如果把每一轮的历史 SQL 全量带进上下文,模型会被前面的写法带偏,第三轮开始自己发明字段。稳妥做法是上下文只保留「上一轮问了什么指标、给了什么结果」的结构化摘要,完整的 SQL 不进入下一轮提示词。

5.2 用评测集把「感觉变准了」变成可回归的数字

每次改提示词都靠人工试几个问题,是没法持续迭代的。至少要攒一套 100 到 200 条的真实问题集,每条标注出期望命中的指标和关键的 WHERE 条件,然后做两个层面的自动比对:指标召回是否命中(可用准确率和 Top5 召回率两个指标看),SQL 语义是否等价(用 AST 归一化后比对表名、字段、过滤条件,而不是字符串比对)。改一版提示词就重跑一次,指标召回率掉了 3 个点以上就该回滚。这套机制建起来之后,团队才敢放心地换模型版本。

5.3 一个具体技巧:把 SQL 骨架缓存成模板复用

高频问题高度重复,与其每次都让通义大模型从头生成,不如把稳定问法沉淀成模板。做法是:正常走完一次链路后,把生成的 SQL 做 AST 归一化——把具体的日期常量、地区常量替换成占位符,得到一条「骨架」,以骨架的哈希作为键缓存起来。下次用户问「这周华南的退货率」,召回命中同一指标、维度组合一致时,直接取骨架并把占位符替换成新参数,跳过生成环节。

这个技巧在真实场景里通常能覆盖三到五成的高频问法,收益有三块:响应从秒级降到毫秒级、单次成本降下来、这部分问法的准确率直接变成百分之百,因为骨架是人工确认过的。缓存要设失效时间,并在语义层的expr_sqltime_col变更时主动清空对应指标的骨架,否则口径改了而缓存没改,会出现一类极难复现的错数。最后一点,骨架命中要在日志里单独打标签,别和模型生成的混在一起统计,不然准确率数据会虚高。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询