大模型在左,工程化在右:大语言模型在有数BI的实践

图片

图片

2023年有数BI借助大语言模型的能力发布了一系列AIGC产品。本文回顾了其中两个产品在开发过程中的心路历程,并详细阐述了为何在大模型相关的数据分析产品中需要引入工程化手段。

其中「计算字段AI助手」项目是工程化手段的初步探索,而「有数ChatBI」产品则是工程化手段的深度应用。

图片

在有数BI中,用户可以连接数据库表,拖拽和操作表的字段来进行报表制作。如果表中的已有字段无法满足需求,用户可以创建计算字段。

计算字段的表达式是一门领域特定语言,它有自己的语法,并且提供了一系列的函数,允许用户对字段进行丰富的运算和操作。

图片

有数BI计算字段编辑框

免费试用有数BI >>

通过使用计算字段AI助手,用户只需要用自然语言描述需求,系统即可自动生成相应的计算字段表达式。

图片

计算字段AI助手界面

在开发计算字段AI助手时,大模型对我们来说还是一个比较新鲜的事物,所以只能摸着石头过河,不断摸索和试验。在开发该功能的过程中,我们共尝试了三种技术方案。

图片

我们拉取了数百条线上真实的计算字段,尝试理解用户编写这些计算字段的原始意图,并用自然语言描述这个意图。每一个计算字段,生成三个输入和输出的用例:

1. 自然语言描述的意图 => 计算字段表达式

2. 符合该计算字段的若干个合法的输入和输出(包含类型信息) => 计算字段表达式

3. (结合1、2)意图 + 输入&输出 => 计算字段表达式

这样,就得到了用于 fine-tuning 的几百条用例:

图片

大家一起撰写 fine-tuning 用例

免费试用有数BI >>

当时 gpt-3.5-turbo 还未发布(发布后很长一段时间也无法微调),我们在 AI 部门同学的帮助下,使用 text-davinci-003 模型对这些输入和输出进行了微调,以创建一个私有的大型模型。

然而,实际测试发现效果并不理想。我们在微调的每个用例的输入中都添加了“在有数BI的场景下”的限定词,并在提问时也带上了这个限定词,但模型的表现并不敏感。

大模型似乎并没有吸收我们传输给他的知识,更不用谈泛化了。我们当时推测,对大模型来说仅仅几百条用例显得太微不足道了。

图片

fine-tune 方案走不通,我们被迫重新回到 prompt 方案来,在 prompt 中告诉大模型有数的计算字段是什么样的,期望他能学会。

text-davinci-003 模型的 token 数限制为 4k,并不能把所有的语法描述和函数用法放到会话的上下文中,所以我们统计了线上环境高频使用的函数。

每次用户提问时,我们只在 prompt 中提及高频使用的函数,通过举例的方式把这些函数的使用方法告诉大模型,同时也兼顾透露一些表达式的语法信息,后来学习到这种方法叫 few-shot prompting.

使用 prompt 的方案,效果出奇的好,大模型拥有很强的泛化能力,在 prompt 中,我们基本上只列举了单个函数的使用方法,他就可以把这些函数组合起来使用,去解决复杂的用户需求,非常像那么回事。

但使用该方案仍然有两个当时无法回避的缺点:

第一个缺点是:text-davinci-003 模型较为昂贵,如果我们把 4k 的 token 用满,一次提问可能会花费3毛钱左右。

但在该功能开发好后没多久,gpt-3.5-turbo 就问世了,同等token数下费用一下就变成原来的十分之一。只不过仍然有 4k token 数的限制,仍然只能传达有限的 DSL 信息给大模型。(后来,微软在 office copilot 的论文中讲解了如何解决该问题。)

第二个缺点是:我们限制不住 DSL 的语法和函数的边界,大模型时常会出现幻觉,使用有数计算字段并不存在的语法或函数,导致生成的表达式不合法。受限于 token 数的限制和 gpt-3.5-turbo 的智商,该问题无法根除。

因为有上面两个缺点,我们就开始尝试第三条技术路线。

图片

有数的计算字段表达式与 Excel 的公式比较相像,都是一个表达式,都是可以基于字段进行运算。如果能把 Excel 的公式翻译到有数的计算字段就好了,这样有如下几个好处:

1. 几乎不需要 prompt,大模型本身就有关于 Excel 公式的知识;

2. 更短的 prompt 也带来更少的费用支出,实测还拥有更快的推理速度;

3. 由于 OpenAI 跟微软的关系,未来可能也会对自家的 Excel 公式有更好的支持

解析大模型返回的 Excel 公式,并且翻译到有数的计算字段。分为两部分来做:

图片

可以在 Google 搜索到一篇论文,里面给出了完整的 Excel 公式的文法,比想象中要复杂一些,经过调研,最健壮的 Excel Parser 恐怕是一个 C# 实现的,与我们的技术栈不符合,于是决定按照论文中提到的文法来实现 parser,但在实现过程中猛然发现,其实我们并不需要一个标准的 Excel Parser,原因在于:

1. GPT 完成大多数任务,只使用了 Excel 语法和函数的子集;

2. GPT 生成的 Excel 也不一定合法(大模型幻觉使然);

3. 未来我们可以在 prompt 中给 Excel 公式加语法(扩展性)。

我们首先获取模型比较喜欢实用的高频Excel函数。让大模型扮演一个 Tableau 用户,提出一些计算字段诉求,并把这些诉求继续输入给大模型,让其用 Excel 公式表达,如此迭代多轮,我们便得到了大模型高频使用的 Excel 函数有哪些(这一步得到的 Excel 公式保留下来不要丢,保留下来作为测试用例):

图片

获取大模型高频使用的Excel函数

然后,我们借助 ANTLR4,低成本的实现了一个 Excel Parser。

图片

免费试用有数BI >>

不断的使用上一步生成的 Excel 公式去测试,兼容大模型可能出现的各种忍一忍也能接受的语法幻觉,提高 Parser 对大模型的鲁棒性,比如:

图片

Excel公式其实没有 OR

图片

Excel 公式其实没有 % 操作符

图片

拿到 Excel 公式的语法树后,接下来就需要生成有数的计算字段了。

前面提到过 Excel 公式与有数计算字段的共性,但其实在语义层面,有许多的不同,比如:

1. Excel 公式是弱类型,有数计算字段是强类型;

2. 有数计算字段还存在 Granularity 属性,标识聚合粒度(比如 SUM([销售额]) 不能与 [成本] 相减);

3. Excel 有时间类型,有数只有日期和日期时间类型等。

因此,在翻译 Excel 公式的过程中,需要处理好这些细微差别,确保翻译得到的计算字段不论是语法层面、还是语义层面,都要合法。

一些特殊处理的情况举例:

图片

处理「时间类型」(有数不存在)

图片

使表达式类型合法

图片

嵌套的 Excel 函数调用,可以用有数函数表达

图片

根据 Granularity,生成 LOD 表达式

图片

首次上线后,有61%的计算字段生成需求走了Excel公式翻译方案,当 Excel 公式无法翻译时,才会降级到 few-shot 方案,后面又对准确率进行过一些针对性的优化提升。

在应用后也发现了该方案的一些不足,比如用户想表达“以xx开头”,Excel 只能使用 LEFT 函数,而有数计算字段有 STARTSWITH 函数,使用 few-shot 方案,写出来表达式反而更加的表意,没有那么死板。

当然,也可以在 prompt 中给 Excel 公式扩展一些函数。但是计算字段AI助手面向的并不是小白用户,而是 BI 报表开发者,即便生成的表达式无法直接使用,但若能启发用户思考,减少一些用户的编辑负担,也是有意义的。因此,走 Excel 公式翻译的百分比,可能也并不是越高越好。

图片

自然语言数据分析一直是增强分析领域中的一个重要课题,在大模型火爆出圈后,业界一直有将大模型应用到数据分析的探索,阿里达摩院甚至发表论文来研究GPT-4是否可以取代数据分析师。

有数BI在2018年左右推出智能问答模块,用户可以输入受约束的自然语言进行取数,但实际应用效果不太好,用户也没有用起来。

在大模型之前,国内外也有不少自然语言取数产品出现(如 Tableau 的 ask data 等),来到大语言模型时代,之前的差距被抹平了,不论之前采取了怎样的技术方案,大家几乎都要基于大语言模型重新开发,否则产品力就必然掉队,所有厂商可能都重新站回到了同一条起跑线上。

站在 BI 的角度,获取数据需要的是通用查询结构。站在数据库的角度,获取数据需要可执行的 SQL。于是就产生了两条路线,到底是 NL2SQL 还是 NL2JSON。

图片

DC 模块是有数的数据引擎模块,几乎所有的数据查询,都需要把指令发给这个模块来执行,他会把查询指令翻译成不同数据库的sql进行查询。

有数BI的图表查询,是基于数据模型的,数据模型是一个抽象大宽表,上面有设置好的维度和度量字段,不同字段有不同的额外信息,比如同一个日期字段的粒度是「年」还是「年-月-日」,度量字段的聚合方式是「平均数」还是「求和」等等。

查询还可能涉及到筛选器,比如维度字段的列表筛选正选与排除,日期字段的静态时间筛选器和动态时间筛选器。

实际查询的结构更加复杂,会基于上面的基本数据结构进行合理的组合,得到最终的查询 DSL,这个 DSL 是一个复杂的 JSON,作为有数 BI 数据引擎层(DC 模块)的输入,从而获取到对应的数据。

我们希望让大模型能够直接给出有数 BI 的通用查询结构的 JSON,这样可以充分利用有数 BI 的多数据源的支持能力和复杂查询的表达能力。

由于这个结构非常复杂,不适合用于大模型直接生成,所以就考虑设计一个简化版的结构,然后从这个结构恢复出完整的查询 DSL。

另外,当时的大模型还没有提供 functionCall 的能力,我们选择了使用 Typescript 来描述这种复杂的类型信息,因为 ts 有一定的代数数据类型的表达能力,且语法噪音较少,可以减少token的使用,并且实际测试下来 gpt 模型也能比较好的理解 Typescript 的类型描述。

图片

使用 Typescript 的 prompt 片段

在实践时发现,对于简单的提问,gpt-3.5 表现得比较好,但如果是复杂的查询,gpt-3.5 会时不时的把 JSON 结构搞错,比如错误的把比较内层的字段,生成到了外层。针对模型生成出错的这种情况,可以采取几个措施:

1. 迭代优化这个简化版的数据结构,比如把一些内层的结构提到外层,减少出错几率;

2. 优化 prompt,配合 one-shot 的方式解释字段的用途;

3. 后处理修正,尝试把字段恢复到他应该出现的位置。

采取上面的措施后,端到端的成功率有了一定的提升,但该方案仍然有几个弊端:

1. 光是类型描述就已经占用了两千个 token 了,剩下的那点 token 还需要给大模型传宽表元信息;

2. 还是会经常生成错 JSON 的结构,且补救不过来,切换到 gpt-4 后效果好很多,但费用贵,且生成速度极慢;

3. 用户的提问是非常灵活的,这个结构一定承载不了用户的所有提问,而且无论 prompt 如何继续迭代,也一定无法囊括用户的所有提问表达方式,复杂的需求可能还需要生成有数的计算字段,莫非又要引入「计算字段AI助手」?

图片

ChatBI 调研期间的 demo

虽然花费了许多精力去优化 Prompt,但最终效果还是不尽人意,因此最后放弃了该方案。

(过了很久之后,gpt 推出了 FunctionCall 功能,使用 JSON schema 来描述函数签名,经过我们的测试,生成同样的 JSON 结构表现得更稳定了些,但仍然有出错的情况发生,再后来 OpenAI 似乎在许多版本都提及又加强了生成 JSON 的能力,但我们也没有再去评测了)。

图片

把表的元信息和用户提问传给大模型,大模型生成的 SQL 发给数据库执行,看起来很顺风顺水,开箱即用,但其实存在很多问题。

有数BI的查询是基于数据模型的,而数据模型往往是多张表关联和筛选得到的。如果仅仅把原始表和关联关系发给大模型,这无疑给 nl2sql 造成了很大的难度和不确定性。

因此,我们对大模型暴露的表只是逻辑上的表,即有数的数据模型宽表视图,大模型基于逻辑表生成逻辑 SQL,再把 SQL 中出现的逻辑表名替换成子查询,即可得到一个可真实执行的物理 SQL 。

由于不同的数据库有不同的语法和函数,采用这种方案,需要在 prompt 中告诉大模型,生成的 SQL要遵循目标数据库的语法,否则替换后的物理sql,也会执行失败。

实际测试发现,生成 postgresql 效果还不错,但生成 clickhouse 的 sql 经常会语法错误,导致物理 sql 无法执行。因此需要对不同数据源类型进行定制化的 prompt,这就又陷入了 prompt 深渊。

不仅如此,也不能只关注 SQL 的合法性,还要关注 SQL 的准确性、易用性等。比如希望大模型在处理日期时多用 date_trunc 而不是 date_part、整数的除法在特定数据源下是否被自动取整了、百分比类型的数据到底要不要预先乘以100等等。

另外,还有数据解释的问题,我们的 ChatBI 产品面向的用户群体是不懂 SQL 的小白用户,因此得用大白话的方式告诉用户,大模型生成的 SQL 做了什么事情,查询了什么数据。

这个问题看起来工程化手段较难解决,所以也几乎没有任何选择,只能让大模型自己也返回一个自然语言解释。

在实践中有两种选择,一种是一个 prompt 让大模型同时返回sql和自然语言解释,另一种是先让大模型返回一个逻辑SQL,再把逻辑SQL发给大模型获取解释。

经过测试发现,采取后者,串行请求的总耗时,反而比一个请求的耗时快一些,且耗时更加稳定,看来有时候给大模型一个复杂的任务,也许不如串行处理若干个子任务来得快。

图片

任务拆分后,性能更加稳定

最终,用户只需要输入一句自然语言,就可以获得一份数据查询结果,以及一份关于查询过程的自然语言的描述。但产品上线后仍然遇到了两个比较明显的问题:

其中一个问题是,经常收到用户(甚至是专业用户)的反馈:为什么大模型生成的 SQL 看起来没问题,但是数据查询的结果却明显有问题?

大模型经常会生成「看起来」正确,但实际执行不符合预期的 sql 。但往往仔细研究后会发现这个 SQL 确实在某个细微之处有问题。程序员都不一定可以肉眼 debug ,何况是小白用户?

大模型生成的 SQL 并不是开箱即用的,即便是业界的许多 SQL Copilot 产品,其生成的 SQL 往往需要专业人员进行修改与调试最终才可用。然而 ChatBI 不是 Copilot,否则就做成了 SQL IDE 或者 SQL 编辑器了,最终一切都靠修改 SQL 兜底。

另一个问题是:大模型生成的自然语言解释,跟实际执行的查询对不上。这并不是因为把生成 SQL 和生成解释拆成两个任务导致的,即便合成一个大任务,仍然会出现这个问题。这个问题,似乎无法根除,这也让用户产生信任危机。

图片

只返回了两行。。。数据问题?SQL问题?解释问题?

图片

有人说,大模型出现后,一句 prompt 就可以解决资深工程师需要干好几天的活儿,这个我们试过了,是真的。但也不能忽视这句话背后透露出的另一个信息,那就是某些任务其实是可以通过工程化手段解决的。

为了克服之前的种种问题,最终我们还是选择了最困难的道路,尝试把大模型生成的 SQL 转义成有数的通用查询 DSL。这意味着,大模型生成的逻辑 SQL,并不会直接执行,而成了另一个工程化系统的 “prompt”。

有数BI之前已经在解析 sql 层面有过一些实践,比如用户在有数平台编写的自定义SQL,在实际运行时,有数BI可以把数据权限注入到用户编写的 SQL 中,防止自定义 SQL 绕过单表数据权限的约束。ChatBI 基于之前已有的自研通用 SQL Parser,继续前进。

我们为 ChatBI 适配的 SQL 解析流程有如下优点:

1. 兼容数据库 SQL 语法的差异,也会兼容一些 SQL 语法错误、语义错误;

2. 可以混用各种数据库的函数,甚至是大模型编造出的不存在的函数;

3. SQL 中出现的复杂的表达式可以自动转译成有数的计算字段表达式。

解析后的产出物,将会提交给后续流程,组装为 DC 模块的输入。在组装过程中会有大量的基于 BI 场景的修正逻辑。

以往完成这些工作,得依赖 prompt ,期待模型能够听话,而这些确定性的要求,在工程化的框架内,其实是非常容易实现的,从提示工程,迈过门槛,进入确定性的工程化,又是一番广阔的天地。

图片

工程化手段,解决 NL2SQL 同环比难题

最终我们实现了把大模型基于模型宽表生成的逻辑 SQL,转译到仍然是基于该模型的查询结构,相比之前纯 NL2SQL 方案,有许多的优势:

1. 可信任:查询过程的自然语言解释由查询结构机械化生成,保证与实际查询对应,不存在幻觉和一致性问题;

2. 可干预:得益于数据查询已经脱离了最开始的 SQL 文本,用户可以在 GUI 界面通过交互干预查询条件;

3. 可交互:纯NL2SQL方案,查询结果是固化的,不可下钻,因为SQL中的聚合粒度已经固化了。SQL2DC 方案,用户可以继续对查询结果下钻;

4.可集成:查询结果仍然基于原始数据模型,ChatBI 的问答结果可以方便的集成回报告,并受到报告中已有筛选器的影响。

图片

查询过程可手动调整干预

免费试用有数ChatBI >>

在开发 ChatBI 的过程中,我们一直与 AI 部门保持深度的合作,我们的自研大模型准确率已经达到 GPT-4 水平,并且支持更多 SQL2DC 特性,经过测试统计,私有大模型生成的 SQL 能够 SQL2DC 的端到端成功率已接近 97%,这一数值还在持续优化提高。

另一方面,如果产品要做到可信,产品的代码也需要可信,我们还需要不断的加强 SQL2DC 的自动化测试,目前已经有 unit test 用例,未来可能也需要引入 property based test。

图片

GPTs 的发布让我们看到了通过大模型构建复杂系统的希望,但对数据分析场景来说,这个时刻可能还未到来。仅仅依靠 prompt 链条构建的系统,似乎看起来,总有些摇摇欲坠,但这不能否定 prompt 工程的价值,如果我们能划分出问题域中哪些部分靠大模型,哪些部分靠工程化,也许可以得到更好的结果。

图片


网易数帆 商机推荐赢奖励

图片

图片

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值