没有向量数据库的RAG:BigQuery SQL中的处理流程及其缺陷所在
完全使用 BigQuery SQL 构建检索增强生成系统,涵盖从嵌入向量到具体答案的整个流程,同时可查看相关成本、配额及提示词版本限制。
来源: SQL Is All You Need: A Complete RAG Pipeline in SQL with BigQuery
向量数据库、调度器、嵌入服务,各种API密钥分散在各个组件中,再加上三个容器仅用于回答与自身文档相关的问题——这就是大多数教程中所展示的检索增强生成技术的架构,而对于大量应用场景而言,这种架构可能已不再必要。
BigQuery的AI功能能让大语言模型像其他SQL操作符一样工作,就像JOIN或WHERE语句那样。嵌入、向量搜索以及结构化生成都在数据所在的位置进行,受制于你已有的IAM权限、审计日志和成本控制机制。此外,它也不是局限于某一家供应商的模型,Claude、Llama、Mistral以及Hugging Face提供的开源模型都可以使用。
以下内容将在一个任何人都可以查询的公共数据集——位于 bigquery-public-data.bbc_news.fulltext 的BBC新闻语料库上,用两种方式构建相同的处理流程。第一种是使用通用功能的一次性批量处理版本;第二种则是处于预览阶段的动态版本,随着新数据的加入,语料库会持续更新自身内容。随后会如实说明该方法的局限性,以及为何Python仍然是更优的选择。
大多数教程所假设的技术栈
标准的RAG架构通常包括:
- 用于生成嵌入向量的工具:托管的API,或是需要自行搭建并维护的模型端点
- 向量数据库,这会增加一个供应商、一笔额外费用,以及一个用于保持与数据源一致的同步流程
- 如LangChain、LlamaIndex之类的协调工具,或是自行编写的整合代码
算一算所需组件:五个系统以及至少三把API密钥。此外还有没人提及的成本问题,那就是数据会离开数据仓库。一旦数据块被导出到外部向量存储中,BigQuery中所有的治理规则、行级安全策略和审计要求都将不再适用。
那种架构的存在是因为当时没有其他选择。但现在情况已经变了。
作为SQL操作器的LLM
BigQuery中的AI.*函数系列现已全面可用。对于RAG而言,有三个功能尤为重要:
AI.GENERATE_EMBEDDING可以批量从任何表中生成嵌入向量。VECTOR_SEARCH作为GoogleSQL内置函数可实现语义检索。您可以添加IVF或TreeAH索引,而TreeAH基于Google的ScaNN算法。AI.GENERATE是一种按行处理的标量函数,能够通过output_schema返回结构化输出,可应用于任何需要数值的地方,包括SELECT、WHERE甚至JOIN条件中。
您的代码中无需出现任何密钥。身份验证通过BigQuery连接完成,即您只需为托管服务账户授予一次角色权限。现在已有默认连接,大大简化了设置流程。
您不必局限于Gemini
人们常担心这会束缚您于Gemini,但实际上并非如此,只是相关细节常常被误解。这些函数分为两种类型。
通用功能如AI.GENERATE_TEXT、AI.GENERATE_TABLE和AI.GENERATE_EMBEDDING,它们带有MODEL参数,可让你完全控制要运行的模型。支持的远程模型包括Gemini系列,以及作为托管端点的Anthropic Claude、Meta Llama和Mistral AI,还有来自Hugging Face或谷歌代理平台(原Vertex AI)上的Model Garden的开源模型。对于开源模型,CREATE MODEL甚至可以自动部署该模型。
托管功能(AI.IF、AI.CLASSIFY、AI.SCORE,以及在提供endpoint时的标量型AI.GENERATE)其实是基于Gemini的快捷方式。BigQuery会自动选择模型并处理提示词相关的工作。
据说合规性要求使用自托管的多语言E5或Qwen模型生成嵌入向量,再由Claude输出答案。每种方式都从一条CREATE MODEL语句开始。对于嵌入向量的处理则是唯一的变更点,因为后续流程无需调整。而文本生成则存在一个注意事项。
方案一:一次性处理流程(普遍可用)
这种模式适用于批量处理和分析工作,即先对语料库进行一次或按计划定期生成嵌入向量,之后再对其进行查询。
步骤0:一次性设置
连接设置是大多数针对数据仓库的教程都会忽略的步骤。它指的是BigQuery用来访问模型的托管服务账户,且只需创建一次即可。
如果您拥有 BigQuery 管理员角色,最快捷的方法是使用默认连接。在语句中写入 WITH CONNECTION DEFAULT,BigQuery 会在首次使用时自动配置该连接并授予相应的权限。
如果您希望自行管理连接——即路径二中所需的 us.my_connection——则需要执行两个命令:首先创建一个云资源连接,然后为其服务账户分配名为 Agent Platform User 的角色(roles/aiplatform.user,旧称为 Vertex AI User)。
PROJECT_ID=my-poject-id
# 1. Create a Cloud resource connection (its location must match your dataset)
bq mk --connection --location=US --project_id=$PROJECT_ID \
--connection_type=CLOUD_RESOURCE my_connection
# 2. Grant the connection's service account the Agent Platform User role
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$(bq show --format=prettyjson \
--connection $PROJECT_ID.US.my_connection | jq -r .cloudResource.serviceAccountId)" \
--role=roles/aiplatform.user
角色的授予位置取决于模型的引用方式:如果传递的是名称,则在拥有该模型的项目中;如果传递的是完整URL,则在URL对应的项目中。认证到此结束,仅设置一个角色且不涉及任何密钥。
接下来是嵌入模型,会复用默认的连接方式:
CREATE OR REPLACE MODEL `rag_demo_us.emb_model`
REMOTE WITH CONNECTION DEFAULT
OPTIONS (ENDPOINT = 'gemini-embedding-001');
该语句就是整个嵌入服务部署的内容。无需容器,无需可扩展的端点,也无需更换密钥。如果您部署的是开源模型,只需替换其端点URL即可。
步骤1:嵌入语料库
以下语句即可嵌入BBC的文章:
CREATE OR REPLACE TABLE `rag_demo_us.news_emb` AS
SELECT *
FROM AI.GENERATE_EMBEDDING(
MODEL `rag_demo_us.emb_model`,
(
SELECT title, body AS content
FROM `bigquery-public-data.bbc_news.fulltext`
WHERE category = 'tech'
),
STRUCT('RETRIEVAL_DOCUMENT' AS task_type)
);
有两个细节容易被忽略。输入查询必须包含名为content的列,必要时可为其指定别名;另外添加title列能提升RETRIEVAL_DOCUMENT的嵌入质量。此外task_type也很重要:语料库应使用RETRIEVAL_DOCUMENT,而用户问题则应使用RETRIEVAL_QUERY,因为非对称嵌入能显著提升检索质量。
生成的表格会包含您原有的列,还会新增三列。embedding字段存储向量,statistics是一个JSON值,其中包含token_count和truncated字段,而status字段则用于报告错误。需要注意的是,较新的AI.GENERATE_EMBEDDING接口返回的是embedding,而非旧版ML.GENERATE_EMBEDDING指南中使用的ml_generate_embedding_result。将这两种格式混用是典型的复制粘贴错误。
第一个隐患:隐性部分失败。 虽然批量作业整体上可能成功,但由于配额限制或服务故障,个别数据行仍可能处理失败。BigQuery会在status字段中逐行报告这一情况,其中空字符串表示成功,因此务必使用WHERE status = ''进行过滤。如果并行处理的批次出现资源耗尽的错误,Google提供了远程推理重试脚本以及Dataform包,可不断循环处理直到所有数据行都完成。
第二个隐患:隐性截断。 每行数据的statistics字段会显示其词元数量以及截断标志。一篇新闻文章的词元数通常只有几百,但长度超过模型输入限制的文档会以截断形式被处理,且不会产生任何错误。对于长文档,应提前监控截断标志并将其拆分处理。
步骤2:可选地添加索引
小型语料库可以跳过此步骤,因为VECTOR_SEARCH会回退到精确的暴力搜索方式。在大规模应用时,则需要构建一个:
CREATE OR REPLACE VECTOR INDEX news_idx
ON `rag_demo_us.news_emb`(embedding)
OPTIONS (index_type = 'IVF', distance_type = 'COSINE');
可能出现的一些情况:索引的维护会在后台自动处理,之后添加的行可以立即被搜索(BigQuery会通过暴力扫描未索引的部分),小于10 MB的表不会生成索引,而且标准版不支持索引功能。
步骤3:在单个查询中完成检索与生成
嵌入查询语句,获取排名前五的文章,基于提示生成书面答案:
SELECT
AI.GENERATE(
(
'Answer using only this context:\n',
STRING_AGG(base.content, '\n---\n'),
'\nQuestion: what were the major browser security stories?'
),
endpoint => 'gemini-2.5-flash',
output_schema => 'answer STRING, confidence FLOAT64, sources ARRAY<STRING>'
).*
FROM VECTOR_SEARCH(
TABLE `rag_demo_us.news_emb`, 'embedding',
(
SELECT embedding
FROM AI.GENERATE_EMBEDDING(
MODEL `rag_demo_us.emb_model`,
(SELECT 'major browser security stories' AS content),
STRUCT('RETRIEVAL_QUERY' AS task_type)
)
),
top_k => 5
);
核心流程仅包含十几行代码,无需使用 LangChain,也不需要外部向量存储,更无需部署任何组件。output_schema 会为答案生成带类型的列、置信度分数以及来源信息,而非那种需要解析且结果不确定的 JSON 数据块。
不过,单次处理版本的局限性相当严重——数据是不断变化的。新文章会不断出现,描述会更改,工单也会更新。每一次插入或编辑操作都意味着要重新运行嵌入语句,检测变化差异,处理重试情况,并监控数据是否过时。
第二条路径:能够自我维护的语料库(预览版)
自主生成嵌入向量才是解决方案。将嵌入向量定义为生成列,由 BigQuery 负责保持其最新状态:
CREATE TABLE `rag_demo_us.news_live` (
title STRING,
body STRING,
body_embedding STRUCT<result ARRAY<FLOAT64>, status STRING>
GENERATED ALWAYS AS (
AI.EMBED(
body,
connection_id => 'us.my_connection',
endpoint => 'gemini-embedding-001')
) STORED OPTIONS (asynchronous = TRUE)
);
然后从任意位置插入数据:
INSERT INTO `rag_demo_us.news_live` (title, body)
SELECT title, body
FROM `bigquery-public-data.bbc_news.fulltext`
WHERE category = 'tech';
这段设计说明值得仔细阅读两遍。通过使用GENERATED ALWAYS AS AI.EMBED(...),嵌入内容会变成表格元数据而非流程代码。只需插入或更新源列,BigQuery就会在后台创建或刷新向量数据。当设置asynchronous = TRUE时,插入操作无需等待模型调用,嵌入处理会在后台任务中执行,这类任务在任务历史记录中会显示gc_前缀。该列的类型为STRUCT<result, status>,因此失败情况会逐行显示,而不会被整体掩盖。
在模型选择方面,该示例使用了 gemini-embedding-001,因为它当时已广泛可用,且在多语言MTEB排行榜上表现优异。文档中的标准示例则使用 text-embedding-005,其效果同样出色。需要注意的一点是:默认输出维度为3072,而 text-embedding 模型的输出维度为768,这会增加存储和索引成本。为避免此问题,可通过 AI.EMBED 的 model_params 参数指定更小的维度。该模型采用俄罗斯套娃式训练方式,因此可以轻松截断为768或256维度。
查询操作也会变得更简单。AI.SEARCH 会自动识别表格所使用的模型,并为您嵌入搜索字符串:
SELECT base.title, base.body, distance
FROM AI.SEARCH(
TABLE `rag_demo_us.news_live`, 'body',
'major browser security stories',
top_k => 5
);
文本会在文本列中进行检索,而仓库会在后台处理向量。此处也支持向量索引:在源列上构建向量索引,一旦80%的行包含嵌入向量,训练就会开始。
对于任何要投入实际使用的系统而言,这才是重要的版本。一次性处理方式仅用于演示,而自我维护的语料库则是可以长期运行的设计方案。
不过有一点需要注意:当前此功能仍处于预览阶段,遵循的可能是未来会变更的预发布版本规则。另外要记住,每个表格都会自动生成一列嵌入字段;插入操作应批量进行,而非频繁执行DML语句,因为并发的DML操作可能会延迟数据生成;而复制、克隆和快照仅能携带数据,不具备生成设置功能。发布说明指出,可以通过CREATE TABLE或ALTER TABLE语句在新建或现有表格上启用该功能。BigQuery的发布说明是了解功能状态的权威来源,因此在开发之前请务必查看。
无需额外操作即可获得的功能
这是数据团队最应关注的部分,却往往最容易被忽视。
- 行级和列级安全机制同样适用于
VECTOR_SEARCH。用户无权查看的行永远不会作为RAG上下文被传递给处理程序。要通过外部同步的向量数据库实现同样的保障十分困难。 - IAM是唯一的认证模型。环境中不存在密钥,流程中也无需设置密钥轮换机制。
- 审计日志、成本归属及配额均来自平台团队已有的基础设施。通过
bigquery_job_id_prefix标签,甚至可以在Cloud Billing中追踪每项任务的费用。 - 无需运行同步任务。向量数据与它们所描述的行存储在同一位置,因此当仅存在一个向量存储时,它不会与数据仓库出现偏差。对于自主嵌入模型而言,同样无需指定负责刷新任务的人员。
最好的基础设施是那种你根本无需部署的。
它会出现问题的地方
- 计费是基于令牌的,Gemini 2.5也会对思考过程收费。这些模型除了输出结果外,还会根据推理所使用的令牌数量收费。对于分类类任务,在
model_params中将思考预算设置为零可以降低延迟和成本。如果跳过这一步,成本会不知不觉地上升。建议先使用AI.COUNT_TOKENS进行估算。 - 复杂的查询可能会向模型提供比预期更多的数据行。通常的原因是JOIN操作以及带有LIMIT的ORDER BY语句。谷歌建议先将输入数据写入表格,然后再对该表格进行推理。
request_type => 'dedicated'来使用预留吞吐量。VECTOR_SEARCH按扫描字节数计费。按需定价时会计算基础表、索引以及查询内容。将STORING列放入索引中,这样就不必再关联到基础表,并且可以通过WHERE子句在这些存储列上提前进行过滤。查看状态标签
功能状态会发生变化,因此请务必核实。根据最初的报道,AI.GENERATE 和 AI.GENERATE_TABLE 于 2026 年 1 月底正式上线,而合作伙伴模型(Claude、Llama、Mistral)则在 2025 年上半年正式推出。像自主嵌入这样的预览功能可能在此之后有所变动,发布说明是确认情况的最佳途径。
真的只需要 SQL 吗?
由此可以得出三个结论。
对于批量处理和分析型RAG应用,值得认真考虑这种方案。文档分类、语义去重、数据增强流程以及基于受管控数据的内部问答功能,在治理能力、简单性及总体成本方面都更有利于采用仓库原生方案,同时还能让你自行选择模型。
内容不断变化的场景则有所不同,自主嵌入技术改变了原有的平衡。在实战型RAG系统中,搜索本身从来都不是难点,真正困难的是让向量与当前数据保持一致,而通过生成列可以完全免除这类工作。
关键在于明确各环节的边界。面向用户的亚秒级聊天功能仍需要专用的服务层,提示词生命周期管理仍需版本控制,每条令牌的成本问题也仍需监控。这些限制并未消失,只是位置发生了变化。
如需进一步了解,Google的BigQuery文档涵盖了生成式AI概述与模型支持矩阵、合作伙伴模型及开源模型的CREATE MODEL命令、AI.GENERATE、AI.GENERATE_EMBEDDING、AI.SEARCH功能、自主嵌入生成技术、搜索功能、向量索引管理以及版本更新说明。
相关阅读
- 实用指南:向量数据库安全——RAG系统中的新攻击面 — 该指南提供了详细的操作步骤,包括合同规范、检查项以及供采用该模式的团队直接使用的代码片段。