| 编辑推荐: |
本文主要介绍了为大型语言模型设计语义引擎(以Wren Engine为例)的必要性、核心挑战及其解决方案,旨在通过提供业务上下文和标准化数据访问层,提升AI代理在商业智能和数据管理中的准确性与一致性。,希望对你的学习有帮助。
本文来自于getwren.ai,由火龙果软件Alice编辑,推荐。 |
|
文章总结:为了让 AI Agent 能准确、可靠地利用企业数据,需要一个专门的语义引擎作为桥梁。它通过丰富的业务上下文定义,将混乱的原始数据转化为 LLM 能真正理解的“知识”,从而生成精准、安全的 SQL 查询。
说明:本文中的图示采用 建模工具 EA 进行了建模。
AI 智能体( AI agents )的出现彻底改变了商业智能和数据管理的格局。在不久的将来,将会部署多个 AI 智能体,以收集和解读存储在数据库和数据仓库中的大量内部知识。为此,一个语义引擎至关重要。这个引擎会将数据模式映射到相关的业务背景中,使 AI 智能体能够理解数据的底层含义。通过提供对业务背景的结构化理解,语义引擎将使 AI 智能体能够生成符合特定业务需求的准确 SQL 查询,确保精确且具有上下文感知的数据检索。
LLM 与数据结构的问题?
为了使 AI 智能体能够直接与数据库进行交流。基础技术提供了一个将自然语言转换为 SQL 语句并查询数据库的接口。
然而,根据数据库中的上下文信息构建映射模式并非易事。仅仅存储模式和元数据是不够的。我们需要更深入地去理解并处理这些数据。
缺乏语义上下文
当直接在数据库之上启用 LLM 时,您可以利用数据库中已有的 DDL 信息来帮助 LLM 了解您的数据库结构和类型。您还可以根据提供的 DDL 添加标题和描述,帮助 LLM 理解每个表和列的定义。
为了使 LLM 达到最佳性能和准确性,仅仅具备数据定义语言( DDL )和模式定义是不够的。 LLM 需要理解各种实体之间的关系,并理解你企业内所使用的计算公式。提供诸如计算、指标和关系(连接路径)等额外信息这对于帮助 LLM 理解这些方面至关重要。
缺乏 LLM 与语义之间的接口定义
如前一节所述,具备一种语义上下文至关重要,这样可以让 LLM 理解计算、指标、关系等的复杂性。我们需要定义来对以下所面临的问题进行概括。
计算
预训练 LLM 对于每个术语都有其自身的定义标准,并且这并非每个公司自行设定其自身的关键绩效指标 ( KPI )或公式的方式。计算方面,我们则会给出相应的定义,比如营业利润率等于(收入 - 成本) / 收入。这些语言模型可能已经足够强大,能够理解诸如营业利润率、净利润率、客户终身价值等常见的关键绩效指标。
然而,在现实世界中,这些列通常是杂乱无章的,收入可能会被设定为列名“ rev ”,并且我们可能会看到“ rev1 ”、“ pre_rev_1 ”、“ rev2 ”等等。语言模型如果没有语义上下文的辅助,是无法理解这些含义的。
指标
“切片与切块”是一种在数据分析中常用的技巧,尤其适用于多维数据的情况,它能将数据分解并从不同角度进行审视。这种方法有助于更深入地探索和分析数据。
例如销售指标的示例:
总销售额:特定时间段内产生的总收入。
按地区划分的销售额:按地理区域划分的销售数据。
按产品划分的销售额:按单个产品或产品类别划分的销售数据。
按渠道划分的销售额:按不同销售渠道(如线上、零售、批发)划分的销售数据。
另一个使用客户指标的例子:
客户特征:按年龄、性别、所在地等对客户进行分类。
客户细分:根据行为、购买历史和偏好对客户进行分类。
客户获取:特定时间段内新获取的客户数量。
客户流失率:停止与公司开展业务的客户所占的百分比。
语义关系
语义关系与主键和外键并不相同,尽管它们在数据库和数据管理的范畴内是相关概念。
语义关系指的是不同数据项之间有意义的联系,这种联系通常基于它们在现实世界中的关系。这些关系描述了数据元素在概念上相互之间的联系,而不仅仅是由主键和外键所提供的结构链接;例如,客户表和订单表之间的语义关系可以被描述为“一个客户可以下多份订单”。这捕捉了这种关系的现实意义,而不仅仅是技术上的关联。
另一方面,主键和外键用于确保数据的完整性,并在数据库模式层面建立关系。语义关系用于描述和理解数据实体在更广泛背景下的相互关系,您还可以定义一对一、多对多、一对多的关系,而这些在主键和外键设置中是不存在的。
将 LLM 与异构数据源整合所面临的挑战
不稳定的 SQL 生成性能
将多个数据源连接起来,并期望 LLM 能够轻松处理各种不同的 SQL 语法规则,这是一项巨大的挑战:要确保在不同数据源之间保持性能的一致性。随着数据源数量的增加,这一挑战会变得更加严峻。一致性是建立对人工智能系统信任的关键。确保稳定的性能直接关系到您人工智能解决方案的整体可用性和可靠性。
不一致的访问控制
不同的数据源通常都有各自的访问控制机制。当这些源直接相连时,要保持一致的数据策略变得十分困难,这对于大规模的数据团队协作来说至关重要。为了解决这个问题,一个中央治理层对于管理所有 LLM 使用场景的访问控制是必不可少的。这个层确保数据策略得到统一执行,从而提高整个组织的安全性和合规性。
语义层的出现
直接连接多个数据源会带来一致性及性能方面的重大挑战。更为有效的方法是为 LLM 应用开发一个语义层。
什么是语义层?
语义架构的核心概念是“本体论”。本体论是对某一领域的形式化表述,其中包含代表实体和属性的类别,以及这些实体与其他实体之间的关系。
通过为数据集的领域提供一个本体框架, LLM 能够不仅了解如何呈现数据,还能理解数据所代表的意义。这使得系统能够处理甚至推断出数据集中未明确表述的新信息。
语义层的好处
语义层的作用远不止帮助 AI 智能体理解不同领域、实体和关系之间的语义差异。它还为 AI 智能体提供了一个框架,使其能够:
使用正确的公式进行计算
为连接路径和指标提供上下文信息
提供一个标准化的 SQL 层,以确保不同数据源之间的一致性。
应用封装的业务逻辑,并在运行时管理实体之间的复杂关系。
使用语义层技术能够增强 AI 智能体提供准确且一致见解的能力,它能够弥合来自不同数据源与复杂业务环境之间的差距。
“ Wren 引擎”—— LLM 的语义引擎
这就是我们设计“ Wren 引擎”的原因,这是一款专为 LLM 设计的语义引擎,旨在解决我们所提出的问题。
利用 Wren 引擎,我们定义了一种“建模定义语言”( MDL ),以为 LLM 提供上下文和恰当的语义元数据。该引擎能够依据不同的用户角色和语义数据建模方法,根据不同的需求重写 SQL 语句。借助该引擎,还可以在此基础上构建解决方案,例如访问控制和治理等,这些通常都位于语义层。
语义数据建模
本体论的基本概念涉及设计一种包含元数据和数据的图结构表示,通常称为知识图谱。借助“ Wren 引擎”,您可以在此基于图的架构中定义您的数据模型和指标。这使您能够明确不同模型中的列之间的关系以及这些关系的含义。这种结构化的定义不仅清晰地阐明了数据之间的关系,还增强了准确且高效地重写 SQL 查询的能力。
语义命名与描述
在 MDL 中,您可以轻松地为任何模型、列、视图以及关系定义语义名称和描述。借助这些语义定义,您可以帮助 LLM 理解数据结构的语义含义。
{ "name":"customers",
"columns":[
{ "name":"City", "type":"VARCHAR", "isCalculated":0, "notNull":0, "expression":"", // semantic properties, such as description, display name,and alias, could be added here. "properties":{ "description":"The Customer City, where the customer company is located. Also called \"customer segment\".", "displayName":"City" }
},
{ // semantic naming
"name":"UserId", "type":"VARCHAR", "isCalculated":0, "notNull":0, "expression":"Id", "properties":{ "description":"A unique identifier for each customer in the data model.", "displayName":"Id" } }
],
"refSql":"select * from main.customers",
"cached":0,
"refreshTime": null,
// semantic properties, such as description, display name,and alias, could be added here.
"properties":{
"schema":"main",
"catalog":"memory",
"description":"A table of customers who have made purchases, including their city",
"displayName":"customers" }, "primaryKey":"Id"
},
|
支持带有关系和计算的运行时 SQL 重写功能
使用 Wren 引擎,您可以设计语义表示形式,通过“建模定义语言”来实现。我们还在我们的 AI 应用程序 Wren AI 中围绕它构建了一个用户界面,该应用程序也是开源的。在 Wren AI 的背后,可以定义不同实体之间的关系,包括一对多、多对一、一对一等关系类型。
Wren 界面( Wren UI )
以下是一个简单的示例,展示了您如何定义关系。
{ "name":"CustomerOrders", "models":["Customer","Orders"], "joinType":"ONE_TO_MANY",// it's a one-to-many architecture "condition":"Customer.custkey = Orders.custkey" }
|
一段关系由以下要素构成:
名称:此关系的名称。
模型:与该关系相关的模型。瑞恩引擎在关系中只会关联两个模型。
连接类型:关系的类型。通常,两个模型之间的关系有四种类型:一对一( 1-1 )、一对多( 1-M )、多对一( M-1 )、多对多( M-M )。
条件:两个模型之间的连接条件。在生成 SQL 语句时,瑞恩引擎充当连接条件。
在模型中,您还可以在计算(表达式)中添加自定义计算。
{ "name":"Customer", "refSql":"select * from tpch.customer", "columns":[ { "name":"custkey", "type":"integer", "expression":"c_orderkey" }, { "name":"name", "type":"varchar", "expression":"c_name" }, { "name":"orders", "type":"Orders", "relationship":"CustomerOrders" }, { "name":"consumption", "type":"integer", "isCalculated": true, "expression":"sum(orders.totalprice)"// define expression
} ], "primaryKey":"custkey"
},
|
支持可重复使用的计算以及类似函数的宏操作
该代码定义了一个名为 Customer 的实体类,其结构和关系如下:
- custkey :整数类型,作为主键,同时也是外键,用于关联到 Orders 实体。
- name :可变长度字符串类型,存储客户名称。
- orders :类型为 Orders ,通过 CustomerOrders 关系进行关联。这表示一个客户可以拥有多个订单。
- consumption :整数类型,是一个计算字段,其值由表达式 sum(orders.totalprice) 计算得出,表示该客户的总消费金额。
该实体设计体现了客户与订单之间的一对多关系,其中 Customer 表的 custkey 作为主键,同时作为外键与 Orders 表关联。 consumption 字段通过聚合 orders 表中的 totalprice 字段来计算,从而实现对客户消费总额的动态计算。这种设计便于维护数据一致性,并能高效地查询客户的相关信息。
关于计算
Wren 引擎提供了计算字段,用于在模型中定义计算操作。计算可以使用同一模型中已定义的列,也可以通过关系使用另一个模型中的相关列。通常,一个通用指标会与许多不同的表相关联。通过计算字段,可以轻松定义一个能在不同模型之间相互作用的通用指标。
例如,下面是一个名为“订单”的已定义模型,它有 3 个列。为了完善这个模型,我们可能想要添加一个名为“客户上月订单价格”的列,以便了解每个客户的订单增长情况。我们可以定义一个计算字段如下:
"columns":[
{ "name":"orderkey", "type":"INTEGER"
},
{ "name":"custkey", "type":"INTEGER"
}
{ "name":"price", "type":"INTEGER"
},
{ "name":"purchasetimestamp", "type":"TIMESTAMP"
},
{ "name":"customer_last_month_orders_price", "type":"INTEGER", "isCalculated":"true", // column
"expression":"lag(price) over (partition by custkey order by date_trunc('YEAR', purchasetimestamp), 0, 0)" }
]
|
关于宏观功能
Macro 是一种用于建模定义语言( MDL )的模板功能。它有助于简化您的 MDL 或集中一些关键概念。 Macro 由 JinJava 实现, JinJava 是 JVM 中的一个模板引擎,遵循 Jinja 的规范。通过 Macro ,您可以定义一个模板来使用某些参数,并将其应用于任何表达式中。
在以下场景中, twdToUsd 表示的是整个 MDL 中的通用概念。相反,收入和 totalpriceUsd 则体现了各个车型特有的部分概念。
"macros":[ { "name":"twdToUsd", "definition":"(twd: Expression) => twd / 30"// Macro definition
}
],
"models":[ { "name":"Orders", "columns":[ { "name":"totalprice", "type":"double" } { "name":"totalpriceUsd", "expression":"{{ twdToUsd('totalprice') }}"// reuse Macro function
} ]
}, { "name":"Customer", "columns":[ { "name":"revenue", "isCalculated": true, "expression":"{{ twdToUsd('sum(orders.totalprice)') }}"// reuse Macro function
}, { "name":"orders", "Type":"Orders", "relationship":"OrdersCustomer", } ] }
]
|
支持标准的 SQL 语法
Wren 引擎内置了 SQL 处理器和转译器。通过 Wren 引擎,我们可以将查询 SQL 语句解析给 Wren 引擎,然后对其进行解包和翻译,将其从符合标准 ANSI SQL 的 WrenSQL 语法转换为诸如 BigQuery 、 PostgreSQL 、 Snowflake 等不同的语言变体。
Wren 引擎架构
下面是一个简单的示例:在这里,您需要为您的数据集定义一个模型。当您提交 SQL 语句时,其中的所有关系、计算和指标都会转换为针对目标数据库类型的特定 SQL 语句。
这是一个 MDL 文件的示例(请在 Gist 上查看)
如果您按照以下方式提交查询 ......
“ Wren 引擎”将根据特定方言下的 MDL 定义,将基于 Wren SQL 的内容进行转换,具体如下所示。
WITH
"order_items" AS (
SELECT "order_items"."FreightValue""FreightValue"
,"order_items"."ItemNumber""ItemNumber"
,"order_items"."OrderId""OrderId"
,"order_items"."Price""Price"
,"order_items"."ProductId""ProductId"
,"order_items"."ShippingLimitDate""ShippingLimitDate"
FROM ( SELECT
"order_items"."FreightValue""FreightValue" ,"order_items"."ItemNumber""ItemNumber" ,"order_items"."OrderId""OrderId" ,"order_items"."Price""Price" ,"order_items"."ProductId""ProductId" ,"order_items"."ShippingLimitDate""ShippingLimitDate" FROM
( SELECT
"FreightValue""FreightValue" ,"ItemNumber""ItemNumber" ,"OrderId""OrderId" ,"Price""Price" ,"ProductId""ProductId" ,"ShippingLimitDate""ShippingLimitDate" FROM
( SELECT * FROM
main.order_items
)"order_items" )"order_items" )"order_items" )
,"payments" AS ( SELECT
"payments"."Installments""Installments" ,"payments"."OrderId""OrderId" ,"payments"."Sequential""Sequential" ,"payments"."Type""Type" ,"payments"."Value""Value" FROM
( SELECT
"payments"."Installments""Installments" ,"payments"."OrderId""OrderId" ,"payments"."Sequential""Sequential" ,"payments"."Type""Type" ,"payments"."Value""Value" FROM
( SELECT
"Installments""Installments" ,"OrderId""OrderId" ,"Sequential""Sequential" ,"Type""Type" ,"Value""Value" FROM
( SELECT * FROM
main.payments
)"payments" )"payments" )"payments"
)
,"orders" AS ( SELECT
"orders"."ApprovedTimestamp""ApprovedTimestamp" ,"orders"."CustomerId""CustomerId" ,"orders"."DeliveredCarrierDate""DeliveredCarrierDate" ,"orders"."DeliveredCustomerDate""DeliveredCustomerDate" ,"orders"."EstimatedDeliveryDate""EstimatedDeliveryDate" ,"orders"."OrderId""OrderId" ,"orders"."PurchaseTimestamp""PurchaseTimestamp" ,"orders"."Status""Status" ,"RevenueA"."RevenueA""RevenueA" ,"Sales"."Sales""Sales" FROM
((( SELECT
"orders"."ApprovedTimestamp""ApprovedTimestamp" ,"orders"."CustomerId""CustomerId" ,"orders"."DeliveredCarrierDate""DeliveredCarrierDate" ,"orders"."DeliveredCustomerDate""DeliveredCustomerDate" ,"orders"."EstimatedDeliveryDate""EstimatedDeliveryDate" ,"orders"."OrderId""OrderId" ,"orders"."PurchaseTimestamp""PurchaseTimestamp" ,"orders"."Status""Status" FROM
( SELECT
"ApprovedTimestamp""ApprovedTimestamp" ,"CustomerId""CustomerId" ,"DeliveredCarrierDate""DeliveredCarrierDate" ,"DeliveredCustomerDate""DeliveredCustomerDate" ,"EstimatedDeliveryDate""EstimatedDeliveryDate" ,"OrderId""OrderId" ,"PurchaseTimestamp""PurchaseTimestamp" ,"Status""Status" FROM
( SELECT * FROM
main.orders
)"orders" )"orders"
)"orders"
LEFT JOIN ( SELECT
"orders"."OrderId" ,sum("order_items"."Price")"RevenueA" FROM
(( SELECT
"ApprovedTimestamp""ApprovedTimestamp" ,"CustomerId""CustomerId" ,"DeliveredCarrierDate""DeliveredCarrierDate" ,"DeliveredCustomerDate""DeliveredCustomerDate" ,"EstimatedDeliveryDate""EstimatedDeliveryDate" ,"OrderId""OrderId" ,"PurchaseTimestamp""PurchaseTimestamp" ,"Status""Status"
FROM
( SELECT * FROM
main.orders
)"orders" )"orders" LEFT JOIN "order_items" ON ("orders"."OrderId"="order_items"."OrderId")) GROUP BY 1 )"RevenueA" ON ("orders"."OrderId"="RevenueA"."OrderId")) LEFT JOIN ( SELECT
"orders"."OrderId" ,sum("payments"."Value")"Sales" FROM
(( SELECT
"ApprovedTimestamp""ApprovedTimestamp" ,"CustomerId""CustomerId" ,"DeliveredCarrierDate""DeliveredCarrierDate" ,"DeliveredCustomerDate""DeliveredCustomerDate" ,"EstimatedDeliveryDate""EstimatedDeliveryDate" ,"OrderId""OrderId" ,"PurchaseTimestamp""PurchaseTimestamp" ,"Status""Status" FROM
( SELECT * FROM
main.orders
)"orders" )"orders" LEFT JOIN "payments" ON ("payments"."OrderId"="orders"."OrderId")) GROUP BY 1 )"Sales" ON ("orders"."OrderId"="Sales"."OrderId"))
)
SELECT *
FROM orders
|
跨源的一致性访问控制
在不同的数据源之间管理访问控制可能会因为不同的访问控制机制而变得困难。而 Wren 引擎也旨在解决诸如 ...... 之类的问题。
定义数据政策:确保所有数据源都遵循相同的安全和访问规范。
统一的认证与授权:通过将不同的数据源整合到一个单一的引擎中,认证和授权流程得以简化。这种一致性降低了未经授权访问的风险,并确保用户在所有数据源上都能获得一致的访问权限。
基于角色的访问控制( RBAC ):实施 RBAC 模式,其中访问权限是根据角色而非单个用户来分配的。
在项目实施过程中,我们会陆续公布更多详细信息!
开放式与独立式架构
Wren 引擎是开源的,其设计为独立的语义引擎,您可以轻松地将其与任何 AI 智能体结合使用,也可以将其用作语义层的通用语义引擎。
Wren 引擎工作流程
结束语
Wren 引擎的使命是作为 LLM 的语义引擎,为语义层提供基础架构,并为商业应用和 LLM 提供业务背景信息。我们坚信要建立一个开放的社区,以确保该引擎与任何应用程序和数据源的兼容性。我们还致力于提供一种架构,使开发人员能够在此基础上自由构建 AI 智能体。
如果您对 Wren AI 和 Wren 引擎感兴趣,请访问我们的 GitHub 页面。所有的代码都是开源的!
本文模型图示部分采用建模工具 EA 进行了重新建模,欢迎试用 EA ( UML 和 SysML 建模工具) |