把“智能问数”想成一个套了 ChatGPT 外壳的 SQL 查询工具,是我见过的最贵的误会。
不少团队立项的时候,PPT 里写的是“自然语言取数”“AI 生成报表”“对话式数据分析”,听起来很性感。可一旦进入需求评审,问题马上转向:用户问“这个月卖得怎么样”,到底是指订单金额、回款金额还是毛利?“东北区”是按订单收货地址算,还是按分公司归属算?大模型生成的 SQL 如果真的把全表扫了一遍,数据权限怎么控?回答错了,责任归算法还是归产品?
这些才是智能问数项目的真实阻力。今天这篇文章,我会用 AI 产品经理的视角,把一个智能问数(也叫生成式 BI)项目从 0 到 1 落地涉及的需求拆解、技术链路、提示词设计、权限治理、效果评估全部过一遍。不是泛泛讲概念,而是把它当成一个需要长期运营的数据产品来拆。
如果你正准备在公司内部做智能问数,或者刚接手类似项目,这篇文章可以帮你避开前三板斧的坑。哪怕你现在是后端开发、数据分析师或测试工程师,看完也能理解为什么这个产品不能只靠一个“大模型 API + 数据库”堆出来。
1. 智能问数到底解决什么问题
先看三个真实场景。
第一个场景来自业务线。销售负责人想看一眼华南区本月的 Top 10 客户贡献,正常流程是:找数据分析师提需求,分析师根据排期决定今天还是明天给,如果数据口径有疑问,还要来回确认。一个简单取数需求,链路是三到五个工作日。
第二个场景来自数据部门。你问三个业务主管“本月续费率是多少”,可能得到三个数字。有人按合同金额算,有人按回款算,有人把试用期客户也算进去了。口径不统一,数据越看越乱,业务对数据部门的信任一点点流失。
第三个场景来自 BI 平台。公司采购了成熟的 BI 工具,也建好了数据仓库。但业务人员打开报表工具后发现,要自己拖拽维度、理解字段含义、知道过滤条件怎么加,学习成本远超预期。最后报表平台成了少数几个分析师的自留地。
智能问数试图解决的核心问题,不是“提高取数速度”,而是降低从业务问题到数据结果之间的认知成本。它把“会 SQL、懂数据模型、知道口径”这些门槛,用自然语言交互的方式包裹起来,让业务人员用提问的方式获得数据。
这里要给出一个明确判断:智能问数不是数据分析师的替代品,而是消化“简单但高频”的取数请求。它最适合的场景是那些重复发生、答案相对标准、口径已经固化的查询。真正复杂的归因分析、AB 实验解读、业务异动排查,仍然需要数据分析师介入。
反过来,如果一家公司连数据仓库都是乱的,指标口径散落在 Excel 和邮件里,那智能问数项目的第一优先级不是买模型,而是先做指标治理。否则大模型越“聪明”,生成的错误结果传播得越快。
2. 核心概念:NL2SQL、生成式 BI 与智能问数 Agent
聊智能问数之前,几个基础概念先对齐。
2.1 NL2SQL 是什么
NL2SQL 全称 Natural Language to SQL,就是让机器把自然语言问题自动转换成可执行的 SQL 查询语句。它是智能问数最底层的技术能力。
比如用户输入“上个月各区域的销售额”,NL2SQL 模块需要把它转换成类似下面的查询:
SELECT region, SUM(amount) AS total_amount FROM sales_order WHERE order_date BETWEEN '2025-01-01' AND '2025-01-31' GROUP BY region一般认为 NL2SQL 的历史可以追溯到 2015 年前后的 WikiSQL 数据集,但那时的模型只能处理单表简单查询。真正让它进入工程视野的是大语言模型带来的语义理解能力提升,以及 Text2SQL 评测集出现后,模型效果可以被量化评估。
2.2 生成式 BI 与传统 BI 的区别
生成式 BI 不是简单地把报表工具加一个搜索框,而是在交互方式、分析路径和交付形态上都发生了变化。
| 维度 | 传统 BI | 自助式 BI | 生成式 BI |
|---|---|---|---|
| 使用门槛 | 管理员做报表,业务看结果 | 业务拖拽字段,理解维度模型 | 直接自然语言提问 |
| 交互方式 | 固定看板、定时邮件 | 拖拽图表 | 对话式追问 |
| 数据口径 | 报表层固定 | 用户自行选择,易出错 | 语义层统一管理 |
| 分析深度 | 浅,看结果 | 中,靠用户操作 | 中高,依赖提示词与模型能力 |
| 部署成本 | 中等 | 中等 | 较高,涉及模型、提示词、评估体系 |
传统 BI 的逻辑是“先定义报表,再给业务使用”。生成式 BI 的逻辑是“先定义数据语义,再让模型动态生成分析”。前者是固定答案,后者是实时生成答案,这也意味着答案的不确定性必须被产品机制对冲。
2.3 智能问数 Agent 的组成
在实际项目中,智能问数产品通常是一个 Agent 应用,而不只是单个模型接口。一个最小可用的智能问数 Agent 至少包含五个模块:
- 自然语言理解模块:识别用户意图、提取时间条件、地区、品类等实体。
- 指标与语义映射模块:把用户口语化的表达匹配到标准指标和维度。比如“卖了多少”可能对应“销售额”或“销售量”。
- SQL 生成模块:基于数据字典和对话上下文生成 SQL。
- 执行与权限控制模块:校验权限、改写 SQL、执行查询、设置返回行数上限。
- 结果生成模块:把 SQL 查询结果转换成自然语言摘要,或调用图表能力生成可视化。
这五个模块拆开看都不难,难在串联。产品经理要把用户从“提问”到“理解答案”的全流程体验设计好,技术团队则要保证每一环可观测、可干预、可回退。
2.4 指标语义层:最容易忽略的部分
很多团队做智能问数,一上来就研究大模型提示词,却忘了先搭指标语义层。
所谓指标语义层,是在物理表和用户问题之间增加一层“业务翻译”。它定义了每个指标的看数公式、可选维度、数据来源表。比如“GMV”在语义层里应该是“已支付订单金额之和”,并且限定订单状态为已支付。
没有这一层,大模型面对“销售额”时只能靠猜。同一个词在不同表里含义不同,同一个指标在不同部门口径不同,最终生成的 SQL 五花八门,业务根本不敢信。
从产品经理的角度,指标语义层不只是技术组件,更是数据治理的抓手。建好了它,智能问数、传统报表、数据 API 都能复用同一套口径定义。
3. AI 产品经理如何拆智能问数需求
产品经理拿到智能问数项目,第一步不是画原型,而是回答三个问题:给谁用、回答什么问题、哪些问题不能回答。
3.1 用户分层
智能问数的用户通常可以分为三类:
- 一线业务人员:比如销售、运营、客服。他们的特点是问题口语化、高频、简单,往往只关心自己负责的板块。
- 中层管理者:比如区域经理、部门负责人。他们关注目标完成率、趋势变化、团队排名,问题带有比较和归因倾向。
- 管理层:关注核心经营指标,问题更宏观,有时会涉及跨域数据。
产品经理需要明确首期服务哪一层。我的建议是先做透一线业务人员的高频取数场景,原因有三:场景标准化程度高、口径容易收敛、价值感知最快。
3.2 问题清单整理
启动前,可以找业务方收集过去三个月的取数需求记录,整理成一张问题清单。每条记录包含:
- 用户原始提问
- 用户角色
- 背后对应的真实指标
- 涉及的数据表与过滤条件
- 需求频率
这张清单既是产品需求文档的输入,也是后续效果评估的测试集基础。没有这个动作,智能问数上线后很容易出现“业务问的问题模型答不了,模型答得好的问题业务根本不问”的尴尬。
3.3 需求优先级矩阵
建议从四个维度评估每个需求优先级:
- 发生频率:一个月出现几次。
- 口径标准化程度:是否已经被数据团队定义过。
- 业务价值:答错或不出结果的损失有多大。
- 实现难度:涉及几张表、需要多少上下文理解。
只有频率高、口径明确、价值高且难度可控的需求,才值得放进 MVP 范围。那种一个月只问一次的复杂分析,让它走人工取数通道反而更稳妥。
3.4 一个关键取舍
智能问数产品在 MVP 阶段必须接受一个事实:不是所有问题都应该用大模型回答。
遇到以下情况,产品里要设计“转人工”或“不支持”的兜底路径:
- 问题涉及数据权限之外的表。
- 问题本身是开放式归因分析,SQL 无法直接回答。
- 指标尚未在语义层定义。
- 模型生成 SQL 的置信度低于阈值。
聪明的产品不是让用户觉得“什么都能问”,而是让用户知道“哪些问题问得最好”,然后逐步扩大能力边界。
4. 技术链路与架构设计
智能问数项目的技术链路,从用户提问到结果展示,大致可以拆成六个环节:
用户输入 → 查询改写与意图识别 → 指标匹配与上下文管理 → SQL 生成 → SQL 安全执行 → 结果解析与可视化
下面逐一说明。
4.1 查询改写与意图识别
用户输入往往是不完整的。比如“跟上周比呢”,如果没有上下文,模型不知道“上周”指什么。因此系统需要维护多轮对话状态,自动把省略部分补全。
意图识别则要做分类:是取数、看图、做比较,还是闲聊。建议在入口处做一层判断,业务问题才进入后续链路,无关问题直接拦截。
4.2 指标匹配与上下文管理
这一层连接语义层。用户的“卖了多少”“表现怎么样”,要通过倒排索引或模型匹配到标准指标。匹配结果建议返回多个候选,并把候选指标及解释让用户确认。
这看起来多了一步交互,但在实际项目中非常有用。因为直接让模型猜,猜错了用户也看不出来。让用户点选候选指标,本质上是把“隐含的口径确认”显性化。
4.3 SQL 生成
这是大模型主要发挥作用的环节。推荐做法不是直接把整个问题丢给模型,而是把“数据字典 Schema + 指标定义 + 用户原始问题 + 对话历史 + 少量示例”组合成结构化提示词,要求模型只输出 SQL 或 JSON。
4.4 SQL 安全执行
大模型生成的 SQL 不能直接执行。必须经过三层防线:
- 只读校验:把 SQL 改写成只读模式,禁止 UPDATE、DELETE、DDL。
- 权限注入:根据登录用户的数据权限,自动拼接行级过滤条件。
- 资源限制:强制限制返回行数,比如最多返回 1000 行,超时自动终止。
这三层防线,从产品规划第一天就要设计进去。它解决的不是性能问题,而是数据安全问题。
4.5 结果解析与可视化
拿到执行结果后,需要根据用户问题和数据特征自动决定展示形态。一个数字可以直接输出,时间趋势用折线图,地区对比用柱状图,地区分布用地图。这一步的规则引擎要尽量简单稳定,不依赖模型自由发挥。
5. 提示词设计与代码实现示例
这一节给出可落地的示例。需要注意,不同大模型 API 格式不同,下面代码以通用理解为主,注意适配自己项目的模型服务。
5.1 系统提示词框架
智能问数项目的提示词不是一句话,而是一整套结构化指令。建议放在单独的prompt_templates/sql_generation.txt文件中管理。
假设数据有两张表,分别是订单表sales_order和区域表dim_region,提示词可以这样设计:
你现在是一名资深数据分析师,负责根据用户的业务问题生成 SQL 查询语句。 请严格按照以下数据字典和业务规则执行,不要臆造不存在的字段。 【数据表结构】 表名:sales_order - order_id VARCHAR 订单号 - order_date DATE 下单日期 - amount DECIMAL 订单金额 - region_id INT 区域ID - product_category VARCHAR 产品品类 - customer_level VARCHAR 客户等级(普通/银卡/金卡) 表名:dim_region - region_id INT 区域ID - region_name VARCHAR 区域名称 - company_id INT 分公司ID 【业务规则】 1. amount 指的是订单金额,单位是元,不包含退款订单。 2. 查询“销售额”时,对 amount 做 SUM 汇总。 3. 默认时间范围为最近 30 天,除非用户明确指定时间。 4. 生成 SQL 时禁止使用事务、禁止修改数据,只能使用 SELECT。 【输出要求】 只输出合法的 SQL 语句,不要解释过程。 如果问题不涉及数据查询,请输出:NOT_SUPPORTED这个提示词框架有三个好处:约束了字段范围、固定了计算口径、规定了模型输出的边界。实际项目中,提示词里还可以加入少量正确示例,即 few-shot 示例,帮助模型理解复杂查询模式。
5.2 后端调用与 SQL 生成模块
接下来是一个 Python 服务端的示例代码。假设我们封装了一个LLMClient,实际项目里可以替换为任意模型服务 SDK。重点看整体逻辑,不纠结具体客户端实现。
# 文件路径:service/sql_generator.py import json from typing import Dict, Optional class SQLGenerator: """负责把用户问题转换为 SQL 的模块""" def __init__(self, llm_client, template_loader): self.llm_client = llm_client self.template_loader = template_loader def generate( self, user_question: str, context: Dict[str, Optional[str]], username: str, ) -> str: system_prompt = self.template_loader.load("sql_generation.txt") user_prompt = self._build_user_prompt(user_question, context) messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}, ] response = self.llm_client.chat( messages=messages, temperature=0.1, # SQL 生成场景尽量降低随机性 ) return self._extract_sql(response) def _build_user_prompt(self, user_question: str, context: Dict) -> str: # 把多轮上下文、当前问题、时间条件拼成结构化输入 history = context.get("history", "") current_date = context.get("current_date", "") return ( f"当前日期:{current_date}\n" f"对话历史:\n{history}\n" f"用户问题:{user_question}\n" ) def _extract_sql(self, response: str) -> str: # 如果模型返回了 markdown 代码块,需要去掉外壳 if "```sql" in response: return response.split("```sql")[1].split("```")[0].strip() if "```" in response: return response.split("```")[1].split("```")[0].strip() return response.strip()这里有两个容易被忽略的细节:第一,temperature要调低,SQL 生成不是创意写作,随机性越低越好;第二,模型输出要做格式化清洗,很多模型习惯返回 Markdown 代码块,直接拼接会执行失败。
5.3 权限注入与安全校验示例
生成的 SQL 不能直接执行。下面是一个行级权限注入函数,它的作用是根据用户所属区域,自动把 SQL 包装成带过滤条件的子查询。
# 文件路径:service/sql_security.py import re from typing import Dict def validate_readonly(sql: str) -> bool: """校验 SQL 是否为只读查询,禁止非 SELECT 开头""" stripped = sql.strip().lower() return stripped.startswith("select") def add_row_permission_filter(sql: str, user_permission: Dict[str, list]) -> str: """ 根据用户权限注入行级过滤条件。 示例:user_permission = {"region_name": ["华南区", "华东区"]} """ if not user_permission: return sql region_list = user_permission.get("region_name", []) if not region_list: return sql placeholders = ",".join(["'{}'".format(r) for r in region_list]) wrapped_sql = ( f"SELECT * FROM (\n{sql}\n) AS protected_query " f"WHERE region_name IN ({placeholders})" ) return wrapped_sql def limit_result_rows(sql: str, max_rows: int = 1000) -> str: """给 SQL 增加返回行数上限,防止一次查询拖垮数据库。""" if "limit" in sql.lower(): return sql return f"{sql.rstrip().rstrip(';')} LIMIT {max_rows}"在服务层,执行 SQL 前应该按照“先校验、再注入权限、最后限制行数”的顺序依次调用。
# 文件路径:service/query_service.py from service.sql_security import ( validate_readonly, add_row_permission_filter, limit_result_rows, ) class QueryService: def execute(self, sql: str, user_permission: dict): if not validate_readonly(sql): raise ValueError("仅支持 SELECT 查询") safe_sql = add_row_permission_filter(sql, user_permission) safe_sql = limit_result_rows(safe_sql, max_rows=1000) # 这里的 db 连接对象由项目自行管理 result = db_connector.query(safe_sql) return result这套逻辑的价值在于:即使大模型生成的 SQL 漏掉了权限过滤条件,最终执行层的安全函数也会强制兜底。权限控制绝不能依赖模型自觉。
6. 效果评估:智能问数怎么量化好不好
智能问数和传统报表不同,它没有唯一的“正确答案”,所以必须建立一套评估机制。否则你无法回答“模型到底行不行”这个问题。
6.1 离线评估
在开发阶段,建议先建一个评估数据集。这个数据集不用很大,但必须是真实用户问题。每一条包括:
- 原始问题文本。
- 对应的标准 SQL。
- 涉及的指标和维度。
- 期望回答类型。
评估指标可以有四类:
| 指标类别 | 指标名称 | 说明 |
|---|---|---|
| SQL 正确率 | Execution Accuracy | 生成 SQL 执行结果与标准 SQL 结果是否一致 |
| SQL 逻辑正确率 | Logical Match | 忽略表别名差异,SQL 逻辑是否等价 |
| 指标识别准确率 | Metric Accuracy | 是否正确识别用户问的指标 |
| 业务可接受率 | Human Accept Rate | 业务人员是否认可这个结果 |
这里最推荐优先看Execution Accuracy。因为它容易被自动化,而且直接反映用户最终看到的数据对不对。逻辑等价判断目前自动化还不够可靠,建议配合人工抽检。
6.2 评测脚本示例
下面是一个简单的离线评测脚本结构,实际使用时可以把评测集放在 SQLite 或本地文件中。
# 文件路径:evaluate/evaluate_dataset.py from service.sql_generator import SQLGenerator TEST_CASES = [ { "question": "上个月每个区域的销售额是多少?", "expected_sql": "SELECT region_name, SUM(amount) ...", "metric": "销售额", }, { "question": "最近7天金卡客户的订单量", "expected_sql": "SELECT COUNT(*) ...", "metric": "订单量", }, ] def run_offline_evaluation(sql_generator: SQLGenerator): total = len(TEST_CASES) exec_ok = 0 for case in TEST_CASES: generated_sql = sql_generator.generate( user_question=case["question"], context={}, username="evaluate_user", ) # 这里需要接入 SQL 执行器,对比结果是否一致 if execute_and_compare(generated_sql, case["expected_sql"]): exec_ok += 1 print(f"Execution Accuracy: {exec_ok / total:.2%}")评测集要持续维护。每上线一个新版本模型,都要在同样的测试集上跑一遍,确保效果没有回退。
6.3 线上体验评估
离线评测通过后,还需要做线上灰度。建议挑选一到两个业务团队,以小范围灰度方式试用,用三个指标判断是否值得全量推广:
- 回答成功率:多少比例的问题最终成功返回了结果。
- 用户留存率:试用用户第二周是否还在使用。
- 提问重复率:用户是否会以更高阶的方式继续追问。
回答成功率不是越高越好,高也可能是因为问题太简单。要结合用户业务数据来判断系统是否真正消化了高频取数需求。
7. 常见问题与排查思路
智能问数项目上线后,问题往往是综合性的。这里整理几个高频问题,方便排查。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 用户问题明明有数据,模型却说查询不到 | 指标未在语义层定义,或匹配到了错误指标 | 查看日志中指标匹配的候选结果 | 补充语义层指标和同义词 |
| 生成的 SQL 字段名不存在 | Schema 未更新,模型看到了过期结构 | 对比数据字典与数据库实际结构 | 建立 Schema 自动同步机制 |
| 查询结果正确,但回答语气生硬 | 结果生成 prompt 缺少解释性指令 | 查看结果生成模块的提示词 | 增加数据分析口径说明要求 |
| 用户问的比较问题回答不了 | 多轮上下文丢失,模型看不到上一轮指标 | 查看对话历史传递是否完整 | 修复上下文管理逻辑 |
| SQL 执行超时 | 大表缺少分区过滤条件,或指标模型复杂 | 查看数据库慢查询日志 | 增加执行超时与提示词约束 |
| 同一问题两次结果不同 | 模型随机性过高或数据实时变化 | 检查 temperature 设置与数据仓库更新频率 | 调低 temperature,固定时间口径 |
排查时需要先看链路日志。智能问数项目一定要在最初设计时就埋好日志:用户原问题、改写后问题、匹配到的指标、生成的 SQL、执行的 SQL、执行耗时、最终回答文本。没有日志,问题只能靠猜。
8. 工程化落地的关键治理与最佳实践
8.1 指标治理是前提
智能问数能不能让业务放心用,取决于指标口径是不是统一。建议在项目启动前,先由数据团队输出一本“指标字典”,每个指标至少包含:指标名称、指标定义、计算公式、统计维度、数据来源、负责人。
这不是一次性工作。业务调整、新增业务线时,指标字典要同步更新,否则模型能力越强,错误信息传播越广。
8.2 权限设计要前置
不要把权限控制留给模型。最佳的权限设计是“数据源层 + 语义层 + SQL执行层”三层同时控制。
- 数据源层保证用户只能连接被授权的数据源。
- 语义层保证未授权的指标和维度不会出现在候选列表里。
- SQL执行层强制注入行级过滤条件。
当用户问了一个超出权限范围的问题时,产品不要直接给出空结果,而应该友好提示“当前账号没有查看该数据的权限,如需开通请联系数据管理员”。
8.3 兜底与转人工
无论模型多强,智能问数系统都必须设计“不知道”的回答路径。推荐的做法是:
- 当模型无法确定用户意图时,主动给出候选指标让用户确认。
- 当生成 SQL 置信度低时,显示“暂不支持该问题,可以换个说法或联系数据分析师”。
- 当连续多次失败时,自动推荐人工取数入口。
兜底不是系统能力不足的表现,而是产品成熟的表现。它告诉用户边界在哪里,比让用户对结果产生误判要安全得多。
8.4 灰度发布与反馈闭环
上线智能问数,建议按以下节奏推进:
- 内部小范围测试:数据团队、产品团队自己先用。
- 单一业务团队灰度:选一个配合度高、需求明确的团队。
- 效果复盘:评估回答成功率、问题覆盖度、用户满意度。
- 分阶段扩大范围:按业务线逐步放开,每次放开都观察权限和效果指标。
每次用户对回答点“赞”或“踩”,都要进入反馈数据库。这个数据的价值不亚于模型训练数据——它直接告诉你产品在真实场景里的薄弱点。
8.5 大模型之外的稳定性投入
智能问数有一个容易被忽略的工程问题:大模型接口延迟和故障影响用户体验。建议在架构上做如下设计:
- 模型调用设置超时时间,超时后切换备用模型或返回统一提示语。
- 对高频问题做缓存,模型生成过的 SQL 可以按“语义指纹”缓存,减少重复调用成本。
- 维护模型输入输出的敏感信息过滤,避免用户输入的手机号、合同号等泄露到外部模型服务。
生成式 BI 不是“模型很聪明就能上线”的产品,它更像是一个需要长期维护的数据基础设施,稳定性、成本、安全都需要产品经理和工程团队一起兜住。
9. 总结与后续学习方向
智能问数项目真正考验的不是大模型调参能力,而是产品经理能不能把模糊的业务诉求,拆成指标、口径、权限、上下文、兜底策略这些可工程化的部分。
如果你准备上手这样的项目,建议按这个顺序实践:先收集整理高频问题清单,再协同数据团队建立指标语义层,然后开发最小链路跑通一个场景,最后才逐步扩大模型能力边界。不要一开始就追求“什么都能问”的泛化效果,那是很多项目失控的起点。
后续值得继续深挖的方向包括:Text2SQL 模型的微调与评测方法、指标语义层建模的最佳实践、多轮对话中的指代消解、以及智能问数结果的归因解释能力。今天的文章偏工程落地,下一期可以专门聊聊如何搭建一套完整的智能问数评测集,把答案正确率真正管起来。
希望这篇文章能在你立项或调研时帮上忙。如果你正在做类似项目,欢迎在评论区聊聊你踩过的坑。