构建 AI Agents 以查询你的数据库
表格、数据库以及更多:构建通用的 AI 查询层
Stanislas Polu
2025年3月13日 • 阅读时长 8 分钟
在 AI agents 的世界里,理解和分析结构化数据的能力是一项颠覆性的变革。 虽然大型语言模型擅长理解自然语言,但在数据以非结构化文本形式呈现时,它们在定量分析方面表现不佳。 这就是 Query Tables 的用武之地——它是一项强大的功能,使 agents 能够对结构化数据执行 SQL 查询,从而为分析问题提供精确的答案。
在 Dust,我们一直在努力构建和发展我们的 Query Table agent 工具,从一个简单的 CSV 文件解析器到一个可以连接到企业数据仓库的复杂系统。 这篇博文详细介绍了这个过程——我们面临的技术挑战、我们做出的架构决策,以及我们如何维护一个统一的抽象层,使我们的用户可以轻松地处理结构化数据,无论其来源如何。
问题:为什么语义搜索在定量分析方面表现不足。
在深入研究我们的解决方案之前,重要的是要了解我们试图解决的问题。 语义搜索非常适合从非结构化文本中检索相关信息块,但在定量分析方面存在重大局限性:
- 不完整的数据访问:语义搜索只检索数据块,而不是整个数据集,因此无法执行全面的分析。
- 没有计算能力:即使检索到所有数据,LLMs 也没有内置的执行计算或聚合的能力。
- 相关性 vs. 完整性:语义搜索针对相关性进行优化,而不是完整性,这对于需要完整数据集的分析查询来说是有问题的。
当我们用户尝试询问有关其 CSV 文件、Notion 数据库或 Google Sheets 的定量问题时,我们亲眼目睹了这种局限性。 答案通常是不完整或不准确的,因为 agent 无法“看到”整个数据集或执行必要的计算。
起源故事:CSV 文件和 SQLite。
早期概念化
我们的旅程始于用户开始询问将 CSV 文件导入 Dust 的问题。 最初,这些文件作为纯文本直接加载到上下文窗口中,这造成了两个主要限制:由于上下文窗口的限制,我们只能处理小文件;即使数据适合,LLM 也难以对原始表格文本执行准确的计算或分析。 结果通常是不一致且不可靠的,特别是对于最基本的算术运算之外的任何操作。
我们开始探索一种更复杂的方法:使用 SQLite 作为内存数据库来针对结构化数据执行 SQL 查询。 这一决定受到了以下几个因素的推动:
- 简单性:SQLite 是轻量级的,不需要额外的基础设施
- 安全性:内存数据库为用户数据提供强大的隔离
- 性能:早期测试表明它对于我们的用例来说足够快
- 熟悉度:SQL 是一种成熟的数据分析语言
技术实现
我们实现的核心是一个可以执行以下操作的系统:
- 解析 CSV 文件并推断其模式
- 创建一个内存 SQLite 数据库
- 将数据加载到数据库中
- 允许 agent 生成和执行 SQL 查询
- 将结果返回给用户
性能是一个问题,但我们的初步测试很有希望。 对于典型的 ~2MB 的 CSV 文件,从文件读取到查询执行的整个过程不到一秒:
File reading: 14 ms
Schema inference: 162 ms
Table creation and data insertion: ~600 ms
Querying: 15 ms
这种方法的一个重要优势是,我们在 AI 生成 SQL 查询的同时并发地执行数据加载——这使得这些操作在用户感知到的延迟方面实际上是不可见的,因为它们发生在模型生成文本的时间内。 对于大多数用例,这足够快,并且内存方法意味着我们不需要为每个表维护持久数据库实例。
为了优化资源使用,同时保持响应能力,我们为内存数据库实施了缓存机制。 在初始查询完成后,数据库实例会保持活动状态几分钟,允许回答后续问题,而无需重新创建数据库的开销。
每个活动数据库都通过轻量级的心跳系统进行跟踪,并且在可配置的时间段(通常为 5 分钟)内未查询的实例会自动关闭并回收其资源。 这种方法在多轮对话的性能和高效的资源管理之间取得了平衡,这在并发用户数量增长时尤为重要。
查询生成和执行
设置好数据库后,下一步是生成和执行 SQL 查询。 这包括:
- 向 agent 提供模式信息
- 让 agent 根据用户的问题生成 SQL 查询
- 验证查询以确保其安全且格式良好
- 针对内存数据库执行查询
- 格式化结果以呈现给用户
扩展到连接的数据源
连接的数据挑战
我们在处理 CSV 文件方面打下了坚实的基础,但我们的用户想要更多。 他们需要查询存储在连接系统(如 Notion 数据库、Google Sheets 和 Office 365 电子表格)中的数据——这些数据不断变化,无法通过手动上传进行有效管理。
这带来了新的挑战:
- 数据同步:如何在没有手动干预的情况下保持数据最新
- 模式发现:如何自动检测和映射来自不同来源的模式
- 分层结构:如何处理嵌套结构,如电子表格中的工作表
- 大规模性能:如何在更大、更复杂的数据集上保持性能
Notion 数据库
我们的第一个连接数据源是 Notion。 Notion 数据库是具有一致属性的页面的结构化集合,使其非常适合我们的 Query Tables agent 工具。
一个挑战是处理 Notion 灵活的属性类型,包括文本、数字、日期、选择、多选等。 我们必须开发强大的类型转换逻辑才能将这些属性映射到 SQL 数据类型。
Google Sheets 和 Office 365
接下来是 Google Sheets 和 Office 365 电子表格。 这些提出了他们自己的挑战:
- 工作表层次结构:单个电子表格文件可以包含多个工作表
- 数据质量和结构:许多电子表格包含松散结构或完全非结构化的数据——合并的单元格、多级标题、嵌入式图表、格式作为数据、不一致的值表示以及与表格数据混合的自由格式注释。 这种“对人类友好但对机器不友好”的格式使得很大一部分真实世界的电子表格在没有清理或预处理的情况下无法可靠地解析为结构化表。
- 标题检测:确定哪一行包含标题
- 类型推断:从单元格值推断数据类型
统一数据源抽象
一个关键的架构决策是维护所有数据源的统一抽象层。 无论数据来自 CSV 文件、Notion 数据库还是 Google Sheet,它都将表示为一个表,具有:
- 唯一的 ID
- 名称和描述
- 模式(列及其类型)
- 源 URL(用于跟踪回原始数据)
这种抽象使 agent 能够使用相同的 SQL 接口处理任何数据源,而无需了解底层细节。
这种方法之所以强大,是因为它允许用户连接来自完全不同来源的表——在一个 SQL 查询中将来自 CSV 文件的数据与 Notion 数据库和 Google Sheet 中的数据结合起来,这在手动操作中很难实现。
扩展到数据仓库:Snowflake 和 BigQuery。
企业数据挑战
然后我们准备好进行下一次演变:连接到企业数据仓库,如 Snowflake 和 BigQuery。 这是由强大的客户需求驱动的,特别是来自在这些平台上进行了大量投资的公司。
与我们以前的数据源不同,企业数据仓库:
- 可以包含 TB 甚至 PB 级的数据
- 具有复杂的权限模型
- 使用专门的 SQL 方言
- 通常位于具有严格访问控制的防火墙后面
将此数据导入 SQLite 是不可行的——我们需要一种不同的方法。
远程数据库架构
对于数据仓库,我们采用了“远程数据库”模式:
- 元数据同步:我们不同步数据本身,而是同步有关表、模式和关系的元数据
- 远程查询执行:查询直接在远程数据库上执行,而不是在 SQLite 中执行
- 结果限制:我们限制返回的数据量,以防止系统不堪重负
- 权限强制:在执行任何查询之前,我们运行一个
EXPLAIN
命令,该命令分析查询计划而不执行它。 这会显示查询将访问的所有表,从而允许我们验证它们是否与 agent 配置中定义的权限匹配。 如果查询尝试访问未经授权的表,则会立即拒绝该查询,即使数据库角色本身具有更广泛的权限。 这创建了一个关键的安全层,可防止 agent 访问其权限之外的数据,而与底层数据库权限无关。
这种方法使我们能够在维护统一抽象层的同时利用数据仓库的性能和规模。
即时表:查询结果也是表。
我们的 JIT 表功能将查询结果视为一流的表,从而实现强大的多步骤数据分析。
当助手针对 Snowflake、Google Sheets 或任何表格数据源执行查询时,结果会自动成为对话中可查询的表。 这允许用户在后续查询中将这些结果与来自其他来源的数据连接起来——例如,他们刚刚上传的 Datadog CSV 或来自另一个数据库的指标。
通过使每个查询结果成为下一个操作的潜在输入,用户可以在不离开对话的情况下构建复杂的分析工作流程,从而无缝地桥接以前隔离的来源的数据。
未来:Salesforce 及更多。
Salesforce 集成
我们的下一个前沿是 Salesforce,目前正在开发中。 Salesforce 提出了独特的挑战:
- 面向对象的数据模型:Salesforce 使用对象而不是表,对象之间存在关系
- SOQL 方言:Salesforce 使用自己的查询语言 (SOQL),该语言与标准 SQL 不同
- 混合数据类型:Salesforce 包含结构化数据和长篇文本字段
我们的方法结合了:
- 通过 SOQL 对结构化数据进行远程数据库查询
- 对长篇文本字段进行语义搜索
强制执行 Salesforce 查询的权限
Salesforce 集成最有趣的挑战之一是“沙盒化”agent 对 Salesforce 数据库的访问,以尊重我们的权限模型。 在我们基于空间的权限系统中,用户可以授予对特定 Salesforce 对象的访问权限,但不能授予对其他对象的访问权限。 但是,Salesforce 的查询语言 (SOQL) 允许复杂的关联遍历,这可能会访问未经授权的对象。
与 SQL 数据库不同,我们可以使用 EXPLAIN
来分析查询将访问哪些表,而 Salesforce 的查询计划仅显示正在查询的主对象,而不显示通过关系表示法访问的相关对象(如 Comment.Post.content
)。
在探索了几种方法(包括基于 regex 的解析和自定义 SOQL 解析器)之后,我们最终确定了一种基于 JSON 的查询格式,该格式使我们能够精确控制访问哪些对象。 agent 不直接生成 SOQL,而是生成查询的结构化 JSON 表示:
{
"object": "Account",
"fields": ["Id", "Name", "Industry"],
"where": {
"condition": "AND",
"filters": [
{ "field": "Industry", "operator": "=", "value": "Technology" },
{ "field": "AnnualRevenue", "operator": ">", "value": 1000000 }
]
},
"orderBy": [
{ "field": "Name", "direction": "ASC" }
],
"limit": 50
}
这种方法具有以下几个优点:
- 完全控制:我们可以准确验证正在访问哪些对象和字段
- 易于解析:与解析 SOQL 相比,JSON 更易于验证
- 面向未来:该格式可以扩展以支持其他功能
在后端,我们在将其发送到 Salesforce 之前将此 JSON 表示形式转换为 SOQL。 这种方法维护了我们的统一抽象层,同时尊重了我们平台核心的基于空间的权限模型。
结论:统一抽象的力量
我们对 Query Tables agent 工具采取的方法之所以强大,是因为我们在整个演变过程中维护了统一的抽象层。 无论数据是存储在 CSV 文件、Notion 数据库、Google Sheet 还是 Snowflake 数据仓库中,agent 都可以通过相同的 Query Tables 工具与其交互。
这种抽象使用户免受底层系统的复杂性影响,并允许我们添加新的数据源,而无需更改用户体验。
当我们继续发展我们的 Query Tables agent 工具时,我们仍然致力于这一原则:使 AI agents 能够访问结构化数据,无论其存储在何处或如何格式化。 通过这样做,我们正在启用新一代 agent,他们不仅可以理解自然语言,还可以执行复杂的数据分析,从而将数据的力量带给每个人。