导语
企业级 NL2SQL 的天花板在哪?亿问 Data Agent 自研语义层 SemanticDB,用实体-事件建模业务世界,通过 NL2LF2SQL 三层架构把业务意图稳定翻译成可信查询,从机制上杜绝幻觉,让每个数字可追溯。
亿问 Data Agent 是一款面向企业经营分析场景的私有化数据分析 Agent,通过自研的NL2LF2SQL引擎杜绝幻觉,帮助业务分析师高效生产可信分析报告。
上一期,我们拆开了亿问 Data Agent 的产品全貌——一个敢在经营分析会上使用的 Agent,需要同时跨过业务语义、数据可信与闭环交付三道关。其中反复出现的一个关键词是"自研的NL2LF2SQL引擎"。
从这期开始,我们进入技术解析。第一个要讲的,是整个系统的地基——语义层 SemanticDB:它为什么存在,解决什么问题,以及为什么"让大模型直接写 SQL"这条看起来最短的路,在真实企业场景里走不通。
这篇文章讲什么
在亿问 Data Agent里,我们自研的SemanticDB 承担语义层的角色。
本篇文章介绍了 SemanticDB 为什么是亿问 Data Agent的核心基础设施:它如何用实体和事件表达业务世界,如何用 LogicForm 承接自然语言理解结果,如何把业务意图稳定地落到 SQL、API 或 URL 等不同执行方式,以及为什么这条 NL2LogicForm2SQL 路线比直接 NL2SQL 更适合真实企业场景。
亿问 Data Agent 不直接把自然语言翻译成 SQL。我们的整体链路是:
自然语言 -> LogicForm -> SQL
其中,自然语言到 LogicForm 由亿问 Data Agent的上层理解模块(Alisa)完成。
语义数据库 SemanticDB 负责两件事:
- 用实体和事件表达业务世界。
- 把 LogicForm 稳定地翻译成不同数据库可以执行的查询。
所以,SemanticDB 不是 SQL 生成器前面的一个"薄封装",而是亿问 Data Agent能不能稳定理解业务、复用口径、跨库执行的基础。
为什么不是直接 NL2SQL
直接 NL2SQL 看起来路径最短:用户问一句话,系统生成一段 SQL,然后去数据库执行。
但真实业务里,问题很少这么简单。
用户说"销售额""门店""本月""新客""同比",这些词并不等同于数据库里的某个字段。它们背后通常有固定的业务口径、跨表关系、时间规则、权限限制和数据库方言差异。
如果直接让大模型生成 SQL,大模型就必须同时完成几件事:
- 听懂用户真正想问什么。
- 找到相关的数据表和字段。
- 判断指标口径。
- 推理表之间的关系。
- 处理时间范围和同比环比。
- 生成特定数据库能执行的 SQL。
这条链路太长,任何一步错了,最后的答案都会错。更麻烦的是,很多错误不是 SQL 执行失败,而是 SQL 能执行,但业务含义错了。
语义层要解决的,就是把这些不应该临场猜测的东西提前沉淀下来,让大模型不再直接面对混乱的物理数据结构,而是面对一套更接近业务语言的结构化表达。
用实体和事件表达业务世界
我们的语义层采用实体和事件的方式建模。
实体表示相对稳定的业务对象,例如:客户、产品、门店、品牌、区域、组织。
事件表示围绕这些实体发生的业务事实,例如:销售、下单、访问、支付、入库、出库。
这种建模方式接近业务人员理解世界的方式。业务不是围绕数据库表发生的,而是围绕"谁、在什么时间、对什么对象、发生了什么事"展开的。
例如,"客户购买产品"是一类事件;客户和产品是实体,购买是事件。再比如,"门店产生销售"也是事件;门店是实体,销售是事件。
当系统用实体和事件表达业务世界后,很多自然语言问题就会变得更清晰:
- "华东地区女装销售额"可以理解为:在销售事件中,找到门店所属区域和产品品类,再统计销售额。
- "最近三个月新客数"可以理解为:围绕客户实体和首次发生的相关事件,按时间窗口统计。
- "各城市门店销售排名"可以理解为:销售事件关联门店实体,再沿着门店的地理关系聚合。
这里的关键是:系统不再把问题看成"从哪几张表查什么字段",而是先把问题理解成"哪些业务对象之间发生了什么关系"。
语义层表达的不只是字段
传统数据字典通常告诉我们:某张表里有哪些字段,每个字段是什么类型。
语义层要表达的更多。
它需要知道一个业务对象有哪些属性,也需要知道这些属性在业务上意味着什么。例如:
- 一个字段是金额,还是普通数字。
- 一个字段是比例,还是可累加指标。
- 一个字段是地址,还是普通文本。
- 一个字段是否有枚举值。
- 一个字段是否表示层级,例如区域、省、市、区县。
- 一个对象是否有生命周期,例如门店开店和关店。
这些信息对查询结果非常重要。
如果系统知道"销售额"是金额,它就可以按金额指标处理;如果系统知道"区域"有层级,它就可以支持按省、市、区县聚合;如果系统知道某些存储值只是内部编码,它就可以在结果里还原成业务人员能读懂的名称。
所以,语义层不是简单给数据库字段起中文名,而是让字段进入业务语境。
LogicForm 是中间语义表达
在亿问 Data Agent的方案里,自然语言不会直接变成 SQL,而是先变成 LogicForm。
可以把 LogicForm 理解成一种中间语义表达:它既不是自然语言,也不是数据库 SQL,而是结构化地描述"用户想查什么"。
它表达的重点包括:
- 要查询哪个业务对象或事件。
- 用什么条件筛选。
- 需要哪些指标。
- 按什么维度分组。
- 如何排序、分页或限制结果。
- 是否需要同比、环比、占比、排名等分析逻辑。
例如,用户问"今年华东地区女装销售额及同比最高的 10 个省市",SemanticDB 里的 LogicForm 可以写成这样:
{
"schema": "sales",
"query": {
"产品": {
"schema": "product",
"query": {
"品类": "女装"
}
},
"店铺_地址": {
"schema": "geo",
"query": {
"name": "华东地区"
}
},
"日期": {
"$offset": {
"year": 0
}
}
},
"preds": [
{
"pred": "销售额",
"operator": "$sum",
"name": "总销售额"
},
{
"pred": "销售额",
"operator": "$yoy",
"name": "销售额同比"
}
],
"groupby": {
"_id": "店铺_地址",
"level": "省市"
},
"sort": {
"总销售额": -1
},
"limit": 10
}
这段 JSON 看起来比自然语言复杂,但它表达的能力也更强。它不是 SQL,也不是给业务人员手写的格式,而是自然语言和数据库查询之间的中间语义。它可以同时表达业务对象、跨对象筛选、时间条件、聚合指标、同比分析、分组层级、排序和结果数量。LogicForm 能表达得越丰富,亿问 Data Agent能够稳定覆盖的业务问题也就越多。
这种中间表达的好处是,上游模型只需要生成业务意图,不需要直接拼 SQL;下游执行器再根据语义模型、时间规则、数据库类型,把它翻译成真正可执行的查询。
这就是 NL2LogicForm2SQL 的核心:让模型负责理解,让语义层负责约束,让执行层负责落地。
从 LogicForm 到 SQL,语义层做了什么
LogicForm 不是简单套模板生成 SQL。中间会经过一系列语义处理。
1. 把不完整表达补成可执行表达
用户问题和上游模型生成的结果,往往不会包含所有执行细节。
例如,"销售额"可能需要被理解为某个金额指标的求和;"今年"需要变成明确的起止时间;"按区域"可能需要找到业务对象之间的地理关系。
语义层会把这些简略表达补全,变成可以执行的结构。
这一步非常重要,因为它降低了对大模型输出格式的苛刻要求。上游只要表达对了业务意图,语义层就能继续把它整理成稳定的查询。
2. 统一时间语义
时间是业务分析里最容易出错的地方。
"今年""本月""截至目前""同比""环比"这些词看起来简单,但在企业数据里往往涉及数据更新延迟、统计周期、表粒度和业务口径。
例如,今天是 9 月 10 日,但销售数据只更新到 9 月 9 日。那么"今年销售额同比"应该比较到 9 月 10 日,还是 9 月 9 日?
如果没有统一规则,每次都让模型临时生成 SQL,很容易出现同一个问题不同次回答口径不一致。
语义层会把这些时间表达统一处理,让不同问法、不同指标、不同数据表尽量遵循一致的时间口径。
3. 处理跨业务对象的关系
很多问题天然是跨对象的。
用户问"上海各区县销售额",底层可能需要从销售事件关联到门店,再从门店关联到地理区域。用户不会说这些关联关系,也不应该要求用户说。
语义层提前定义了业务对象之间的关系,所以执行时可以沿着这些关系找到正确路径。
这让跨表查询从"模型临场猜 Join"变成"系统按业务关系推理"。
4. 拆解复杂指标
一些指标不是单条简单查询能完成的。
例如:
- 同比需要当前周期和同期周期。
- 环比需要当前周期和上一周期。
- 占比需要分子和分母。
- 达成率需要实际值和目标值。
- 多指标分析可能涉及不同业务对象。
语义层会把复杂指标拆成多个可执行部分,再把结果合并回来。
这相当于在查询执行前做了一次语义层面的规划:先理解一个复杂问题由哪些子问题构成,再决定如何执行和合并。
5. 适配不同数据源
企业里的数据源往往不止一种。
同样的业务问题,底层可能落在 ClickHouse、MySQL、PostgreSQL、Oracle、SQL Server、StarRocks 或其他数据库上。不同数据库在时间函数、正则、数组、子查询、分页、聚合语法上都有差异。
语义层把上层业务表达和下层执行方式隔离开。
大多数场景下,SemanticDB 会把 LogicForm 翻译成 SQL;但这不是唯一形态。有些数据并不来自传统数据库,而是来自业务 API、外部服务或 URL 型数据源。对于这类场景,同一套 LogicForm 也可以被转换成 API 请求或 URL 查询。
上游只需要表达业务问题;至于最终应该生成 SQL、调用 API,还是拼出 URL 查询,由语义层根据数据源类型处理。
这样,亿问 Data Agent就不会被某一种数据库或某一种访问方式绑定住。
6. 把结果还原成业务可读的数据
数据库返回的是原始结果,但用户需要的是业务结果。
有些数据库里存的是编码,展示时应该还原成业务名称;有些分组字段背后是一个实体,展示时不应该只是一个 ID;有些时间、金额、比例、层级字段,也需要按业务语义进行包装。
所以语义层不仅负责生成 SQL,也负责让查询结果重新回到业务语义中。
最终返回给上层的,不只是数据库查出来的行列,而是带有业务解释的数据。
有语义层之后,NL2SQL 的问题变小了
严格来说,我们并不是在做传统意义上的 NL2SQL。
我们是在把问题拆成两段:
NL2LogicForm:理解业务意图
LogicForm2SQL:稳定执行业务意图
这样做之后,难度被重新分配了。
大模型不再需要一次性完成所有事情。它主要负责理解用户语言,并选择合适的业务对象、指标、维度和时间条件。
语义层负责:
- 约束可用的业务概念。
- 复用统一的指标口径。
- 推理对象之间的关系。
- 处理时间规则。
- 适配数据库差异。
- 包装业务结果。
这使得系统更稳定,也更容易治理。
语义层带来的几个核心优势
优势一:减少模型自由发挥
直接 NL2SQL 时,模型面对的是整个数据库结构。表名、字段名、关系路径、指标口径都可能需要模型猜。
有了语义层,模型面对的是经过整理的业务世界。它需要选择业务概念,而不是临时发明 SQL。
这会显著降低幻觉空间。
优势二:业务口径可以复用
没有语义层时,很多业务规则只能写在提示词里。提示词可以提醒模型,但不能真正保证一致性。
有了语义层后,指标、时间、关系和结果解释都可以沉淀成系统能力。
这意味着同一个指标在不同问题、不同报表、不同用户那里可以复用同一套口径。
优势三:跨表问题更可靠
NL2SQL 最容易错的地方之一,就是跨表关联。
语义层提前描述业务对象之间的关系,系统知道销售和门店、产品、客户之间如何关联,也知道门店和区域之间如何关联。
因此,用户问跨对象问题时,系统可以走业务关系,而不是让模型临时猜连接路径。
优势四:时间分析更一致
经营分析中,时间口径一旦错,结论就会错。
语义层把"今年""本月""同比""环比""截至目前"等表达统一处理,避免每次都让模型单独生成时间条件。
这对 BI 类问题尤其关键。
优势五:数据库变化对上层影响更小
上层表达的是业务意图,不是某一种数据库的 SQL。
当底层数据源变化,或者同一套业务模型运行在不同数据库上时,语义层可以承担方言适配工作,上层自然语言理解部分不需要大幅调整。
这让系统更适合多项目、多数据源、多客户环境。
优势六:更容易测试和排错
有了 LogicForm 作为中间表达,系统就不再是一个黑盒。
当结果不符合预期时,可以逐层判断:
- 自然语言是否理解错了?
- 业务对象或指标是否选错了?
- 时间条件是否归一化正确?
- 跨对象关系是否走对了?
- SQL 生成是否符合数据库方言?
- 结果包装是否符合业务预期?
这比直接检查一段模型生成的 SQL 更可控。
从 Agent 视角看,语义层真正提供了什么
对亿问 Data Agent的上层智能体来说,SemanticDB 提供的不是一个普通查询接口,而是一个可执行的业务语义运行时。
它提供的是:
- 一个稳定的业务世界模型。
- 一套结构化的中间语义表达。
- 一条从业务意图到数据库查询的执行链路。
- 一层对数据库差异的屏蔽。
- 一套可持续沉淀的业务口径资产。
所以,上层模型真正要做的事情,不再是"直接写对 SQL",而是:
- 识别用户问题中的业务意图。
- 选择正确的业务对象、维度、指标和时间条件。
- 生成合格的 LogicForm。
剩下的跨表关系、时间对齐、数据库方言、复杂指标拆解、结果包装,都由语义层处理。
这就是 NL2LogicForm2SQL 相比直接 NL2SQL 更工程化的地方。它不是让模型一次性做完所有事,而是让模型做它擅长的语义理解,把结构化执行交给系统。
总结
语义层不是为了让 SQL 生成多一层流程,而是为了把数据问答从"一次性生成"升级成"可建模、可复用、可测试、可治理"的系统。
它把底层物理数据抽象成业务世界,把自然语言问题收敛成结构化意图,再把结构化意图稳定翻译成数据库查询。
对一个真正要在复杂业务场景里工作的亿问 Data Agent来说,SemanticDB 这样的语义层不是可选项,而是系统成立的前提。
下一篇:Alisa
和传统指标中台里的 MQL 相比,LogicForm 的表达能力更高一个级别。
它不只是表达"取哪个指标、按哪个维度聚合",还可以表达更复杂的分析结构。例如:
- from 嵌套查询能力,用来表达先查询、再基于中间结果继续分析的场景。
- piecewise 动态分组能力,用来表达按业务规则临时切分区间或分组的场景。
- currency 汇率计算能力,用来表达多币种、多汇率口径下的金额分析。
- 同比、环比、占比、达成率、跨对象筛选等更复杂的业务分析逻辑。
这也带来了下一个关键问题:既然 LogicForm 的表达能力更强,它的语法和语义也更复杂,那么在 NL2LogicForm 阶段,大模型如何稳定生成正确的 LogicForm?
进一步说,当一个企业有数百张表、成千上万个字段、复杂的业务对象关系和大量指标口径时,大模型又如何突破上下文长度限制,准确找到和当前问题相关的 schema 知识?
这就是亿问 Data Agent的另一个核心系统要解决的问题。
下一篇文章,我们会介绍 Alisa:它如何在复杂 schema 空间中选择正确语义上下文,以及如何让 NL2LogicForm 从"看模型发挥"变成一个稳定、可控、可持续优化的工程系统。
下期预告
语义层定义了业务世界的结构,LogicForm 定义了查询的语义。但 LogicForm 不会凭空出现——用户说的是"华东女装今年怎么样",不是一段 JSON。
下一期,我们回答一个更前面的问题:NL2LogicForm 这一层到底为什么必须存在? 如果没有它,系统会怎样失稳?有了它之后,企业问答从"能演示"到"真能用",中间到底跨过了什么?敬请期待。