首页 / 文章

没有向量数据库的RAG:BigQuery SQL中的处理流程及其缺陷所在

完全使用 BigQuery SQL 构建检索增强生成系统,涵盖从嵌入向量到具体答案的整个流程,同时可查看相关成本、配额及提示词版本限制。

发布者:REACTAPP.TOP阅读约 11 分钟2592 词

来源: 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架构通常包括:

  1. 用于生成嵌入向量的工具:托管的API,或是需要自行搭建并维护的模型端点
  2. 向量数据库,这会增加一个供应商、一笔额外费用,以及一个用于保持与数据源一致的同步流程
  3. 如LangChain、LlamaIndex之类的协调工具,或是自行编写的整合代码
  • 具有自身速率限制和配额的LLM API
  • 所有相关服务的托管,通常需要几个容器,且必须有人负责打包并保持其更新
  • 算一算所需组件:五个系统以及至少三把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中追踪每项任务的费用。
    • 无需运行同步任务。向量数据与它们所描述的行存储在同一位置,因此当仅存在一个向量存储时,它不会与数据仓库出现偏差。对于自主嵌入模型而言,同样无需指定负责刷新任务的人员。

    最好的基础设施是那种你根本无需部署的。

    它会出现问题的地方

    1. 计费是基于令牌的,Gemini 2.5也会对思考过程收费。这些模型除了输出结果外,还会根据推理所使用的令牌数量收费。对于分类类任务,在model_params中将思考预算设置为零可以降低延迟和成本。如果跳过这一步,成本会不知不觉地上升。建议先使用AI.COUNT_TOKENS进行估算。
    2. 复杂的查询可能会向模型提供比预期更多的数据行。通常的原因是JOIN操作以及带有LIMIT的ORDER BY语句。谷歌建议先将输入数据写入表格,然后再对该表格进行推理。
  • 配额是真实存在的。嵌入式吞吐量受每分钟令牌数配额限制,默认为几百万。这适用于大多数工作负载,但若要在一夜之间嵌入整个数据湖,则需要提前准备,或者通过request_type => 'dedicated'来使用预留吞吐量。
  • VECTOR_SEARCH按扫描字节数计费。按需定价时会计算基础表、索引以及查询内容。将STORING列放入索引中,这样就不必再关联到基础表,并且可以通过WHERE子句在这些存储列上提前进行过滤。
  • 提示词无需版本控制。提示词不过是 SQL 中的一串字符串,因此不存在注册表、A/B 测试或评估工具。那些每周都要重新编写生产助手提示词的团队会有这种体会,而此时,通过一个简单的 Python 层或将提示词作为带版本号的配置存储在 dbt 中,就能发挥很大作用。
  • 查看状态标签

    功能状态会发生变化,因此请务必核实。根据最初的报道,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功能、自主嵌入生成技术、搜索功能、向量索引管理以及版本更新说明。

    相关阅读

  • 实用笔记:基于 Oracle 的向量原生 RAG:嵌入模型、HNSW/IVF 以及混合方案 — 详细指导如何实现基于 Oracle 的向量原生 RAG:嵌入模型、HNSW/IVF 以及混合方案,包括相关契约、检查项以及供采用该模式的团队直接使用的代码片段。
  • 小规模 RAG 应用真的需要向量数据库吗?——探索 LangChain4j — LangChain4j 的 MemFile 功能可将向量存储在 JVM 内存中,而文本则存储在磁盘上。何时这种混合方案已足够,又何时需要升级到真正的向量数据库。