RAG sans base de données vectorielle : un pipeline en SQL BigQuery et les points de défaillance
Créez une génération améliorée par la récupération d’informations entièrement à l’aide de SQL dans BigQuery, des embeddings aux réponses fondées sur des données réelles, puis consultez les coûts, les quotas et les limites de versionnement des prompts.
Sources: SQL Is All You Need: A Complete RAG Pipeline in SQL with BigQuery
Une base de données vectorielle, un orchestrateur, un service d’incorporation d’embodiments, des clés API réparties dans tous ces éléments, ainsi que trois conteneurs rien que pour répondre aux questions concernant vos propres documents. Telle est la forme que prend la génération augmentée par la récupération de données dans la plupart des tutoriels, et pour une grande catégorie de cas d’usage, elle peut ne plus être nécessaire.
Les fonctions d’IA de BigQuery permettent à l’LLM de se comporter comme n’importe quel autre opérateur SQL, de la même manière que JOIN ou WHERE. L’incorporation d’embodiments, la recherche vectorielle et la génération structurée ont toutes lieu là où se trouvent déjà les données, sous le contrôle IAM, des journaux d’audit et des mécanismes de gestion des coûts que vous disposez déjà. Il ne s’agit pas non plus d’un écosystème fermé autour d’un modèle unique d’un fournisseur : Claude, Llama, Mistral ainsi que les modèles open source de Hugging Face peuvent tous être utilisés.
Les sections suivantes décrivent la même chaîne de traitement de deux manières sur un ensemble de données public accessible à tous, à savoir le corpus des nouvelles de la BBC à l’adresse bigquery-public-data.bbc_news.fulltext. La première méthode est une version batch ponctuelle utilisant des fonctionnalités généralement disponibles. La seconde est une version en version bêta, où le corpus est constamment mis à jour à mesure que de nouvelles lignes arrivent. Ensuite, il est présenté de manière honnête où cette approche échoue et pourquoi Python reste la meilleure solution.
La stack que la plupart des tutoriels supposent
Une configuration standard RAG signifie généralement :
- Un outil pour générer des embeddings : une API hébergée, ou un point de terminaison de modèle que l’on met en place et que l’on maintient
- Une base de données vectorielle, ce qui ajoute un fournisseur supplémentaire, une facture supplémentaire et un pipeline de synchronisation supplémentaire pour rester en cohérence avec la source de vérité
- Un orchestrateur tel que LangChain, LlamaIndex ou un code personnalisé
- Une API LLM avec ses propres limites de vitesse et quotas
- Hébergement de tout cela, généralement quelques conteneurs que quelqu’un doit emballer et maintenir à jour
Comptez les composants : cinq systèmes et au moins trois clés API. Il y a aussi le coût dont personne ne parle, à savoir que vos données quittent l’entrepôt. Chaque règle de gouvernance, politique de sécurité au niveau des lignes et exigence d’audit intégrées à BigQuery cesse d’être applicable dès que des données sont exportées vers un stockage vectoriel externe.
Cette architecture existait parce qu’il n’y avait pas d’alternative. La situation a changé.
L’LLM en tant qu’opérateur SQL
La famille de fonctions AI.* dans BigQuery est désormais disponible de manière générale. Trois éléments sont importants pour RAG :
AI.GENERATE_EMBEDDINGcrée des embeddings en masse à partir de n’importe quelle table.VECTOR_SEARCHeffectue une recherche sémantique en tant que fonction intégrée de GoogleSQL. Vous pouvez ajouter un index IVF ou TreeAH, lequel repose sur l’algorithme ScaNN de Google.AI.GENERATEest une fonction scalaire par ligne qui peut retourner des résultats structurés via unoutput_schema, et elle peut être utilisée partout où un champ de valeur est requis, y compris dans les instructions SELECT, WHERE et même dans les conditions JOIN.
Aucune clé n’apparaît dans votre code. L’authentification s’effectue via une connexion BigQuery, un compte de service géré auquel vous accordez un rôle une seule fois. Une connexion par défaut existe déjà, ce qui élimine la plupart des étapes de configuration.
Vous n’êtes pas lié à Gemini
Une inquiétude courante est que cela vous lie à Gemini. Ce n’est pas le cas, bien que les détails soient souvent mal compris. Ces fonctions existent en deux types.
Fonctions polyvalentes telles que AI.GENERATE_TEXT, AI.GENERATE_TABLE et AI.GENERATE_EMBEDDING, qui prennent en paramètre un MODEL, vous donnent un contrôle total sur le modèle qui s’exécute. Les modèles distants pris en charge incluent la famille Gemini, ainsi que Anthropic Claude, Meta Llama et Mistral AI en tant qu’extrémités gérées, ainsi que des modèles open source provenant de Hugging Face ou du Model Garden sur la plateforme d’agents de Google (anciennement Vertex AI). Pour les modèles ouverts, CREATE MODEL peut même déployer automatiquement le modèle.
Fonctions gérées (AI.IF, AI.CLASSIFY, AI.SCORE, ainsi que AI.GENERATE sous forme scalaire lorsqu’un endpoint est fourni) sont des raccourcis pris en charge par Gemini. BigQuery choisit le modèle et s’occupe de la formulation des instructions.
On indique que la conformité exige des embeddings provenant d’un modèle multilingue E5 ou Qwen hébergé en interne, avec Claude qui génère les réponses. Chacun de ces systèmes part d’une seule instruction CREATE MODEL. Pour les embeddings, il s’agit du seul changement nécessaire, car rien d’autre n’est modifié par la suite. La génération présente cependant une contrainte.
Voie 1 : un pipeline à usage unique (disponible en général)
Cette approche convient aux travaux en lot et analytiques, où un corpus est embeddé une fois ou selon un calendrier défini, puis interrogé ultérieurement.
Étape 0 : configuration unique
La connexion constitue l’étape que la plupart des tutoriels destinés aux entrepôts de données omettent. Il s’agit du compte de service géré par le biais duquel BigQuery accède aux modèles, et il est créé une seule fois.
La solution la plus simple, si vous occupez le rôle d’administrateur de BigQuery, consiste à utiliser la connexion par défaut. Écrivez WITH CONNECTION DEFAULT dans la commande, et BigQuery la mettra en place ainsi que les rôles nécessaires lors de la première utilisation.
Si vous préférez gérer vous-même la connexion, dont le chemin requis est us.my_connection, il faut exécuter deux commandes : créer une connexion de ressource Cloud, puis attribuer au compte de service associé le rôle nommé Agent Platform User (roles/aiplatform.user, anciennement 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
Le fait de définir quel rôle accorder dépend de la manière dont le modèle est référencé : dans le projet qui possède le modèle lorsque vous passez un nom, ou dans le projet indiqué dans l’URL de l’endpoint lorsque vous passez une URL complète. L’authentification s’arrête là, avec un seul rôle et aucune clé.
Ensuite vient le modèle d’embedding, qui reprend la connexion par défaut :
CREATE OR REPLACE MODEL `rag_demo_us.emb_model`
REMOTE WITH CONNECTION DEFAULT
OPTIONS (ENDPOINT = 'gemini-embedding-001');
Cette instruction constitue l’intégralité du déploiement du service d’incorporation. Il n’y a ni conteneur, ni point de terminaison à échaler, ni clé à renouveler. Si vous avez déployé un modèle open source, remplacez-le par son URL de point de terminaison.
Étape 1 : incorporer le corpus
Une seule instruction permet d’incorporer les articles de la 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)
);
Deux détails peuvent facilement être manqués. La requête d’entrée doit présenter une colonne nommée content, éventuellement sous un alias, et l’ajout d’une colonne title améliore la qualité des embeddings pour RETRIEVAL_DOCUMENT. De plus, task_type est important : utilisez RETRIEVAL_DOCUMENT pour le corpus et RETRIEVAL_QUERY pour la question de l’utilisateur, car des embeddings asymétriques améliorent considérablement la qualité de la recherche.
Le tableau résultant contient vos colonnes de passage et ajoute trois colonnes générées. embedding contient le vecteur, statistics est une valeur JSON comprenant token_count et truncated, tandis que status indique les erreurs. Notez que la version plus récente AI.GENERATE_EMBEDDING renvoie embedding, et non ml_generate_embedding_result comme dans les anciennes directives ML.GENERATE_EMBEDDING. Mélanger ces deux formats est une erreur classique due à une copie-collage incorrecte.
Premier piège : échec partiel silencieux. Une tâche par lots peut réussir dans son ensemble tout en échouant pour certaines lignes en raison de contraintes de quota ou de problèmes de service. BigQuery indique cela pour chaque ligne dans la colonne status ; une chaîne vide signifie succès, il faut donc toujours filtrer avec WHERE status = ''. Si les lots parallèles rencontrent des erreurs dues à l’épuisement des ressources, Google fournit des scripts de tentative de traitement à distance ainsi qu’un package Dataform qui itère jusqu’à ce que chaque ligne soit traitée.
Deuxième piège : troncature silencieuse. Les statistics de chaque ligne affichent le nombre de tokens ainsi qu’un indicateur de troncature. Un article de presse compte quelques centaines de tokens, mais les documents dépassant la limite d’entrée du modèle sont affichés de manière tronquée sans aucun message d’erreur. Pour les documents longs, surveillez l’indicateur de troncature et divisez-les en parties à l’avance.
Étape 2 : ajouter un index, facultatif
Les petits corpus peuvent l’omettre, car VECTOR_SEARCH recourt alors à une recherche par force brute exacte. À grande échelle, il convient d’en créer un :
CREATE OR REPLACE VECTOR INDEX news_idx
ON `rag_demo_us.news_emb`(embedding)
OPTIONS (index_type = 'IVF', distance_type = 'COSINE');
Quelques comportements à attendre : la maintenance de l’index s’effectue en arrière-plan pour vous, les lignes ajoutées ultérieurement peuvent être recherchées immédiatement (BigQuery parcourt le reste non indexé par force brute), les tables de moins de 10 MB ne disposent pas d’index, et l’édition Standard ne prend pas en charge les index.
Étape 3 : récupération et génération en une seule requête
Intégrer la question, récupérer les cinq meilleurs articles, formuler la réponse et produire une réponse tapée :
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
);
Le pipeline de base compte une douzaine de lignes, sans LangChain, sans stock vectoriel externe et rien à déployer. De plus, output_schema génère des colonnes typées pour la réponse, un score de confiance et les sources, plutôt qu’un bloc JSON qu’il faut parser et dont on doit espérer la bonne interprétation.
Cependant, la limitation de la version one-shot est sérieuse : les données ne restent pas immuables. De nouveaux articles apparaissent, les descriptions changent et les tickets sont mis à jour. Chaque insertion ou modification implique de relancer l’instruction d’incorporation, de détecter les modifications, de gérer les tentatives répétées et de surveiller le vieillissement des données.
Deuxième approche : un corpus qui s’entretient lui-même (aperçu)
La génération autonome d’incorporations est la solution. Déclarez l’incorporation comme une colonne générée, et BigQuery s’occupe de la maintenir à jour :
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)
);
Ensuite, insérez des données depuis n’importe où :
INSERT INTO `rag_demo_us.news_live` (title, body)
SELECT title, body
FROM `bigquery-public-data.bbc_news.fulltext`
WHERE category = 'tech';
Ce document mérite d’être lu deux fois. Avec GENERATED ALWAYS AS AI.EMBED(...), l’embedding devient des métadonnées de tableau et non du code de pipeline. En insérant ou en mettant à jour la colonne source, BigQuery crée ou met à jour le vecteur en arrière-plan. Avec asynchronous = TRUE, les insertions n’attendent pas les appels au modèle, et le traitement des embeddings s’exécute sur des slots en arrière-plan, dont les tâches affichent un préfixe gc_ dans l’historique des tâches. La colonne est de type STRUCT<result, status>, de sorte que les échecs apparaissent pour chaque ligne sans être masqués.
Pour le choix du modèle, l’exemple utilise gemini-embedding-001 car il était généralement disponible et performant sur le classement multilingue MTEB à l’époque. L’exemple canonique de la documentation utilise text-embedding-005, qui fonctionne tout aussi bien. Prévoyez une conséquence : la sortie par défaut a 3072 dimensions contre 768 pour les modèles text-embedding, ce qui augmente les coûts de stockage et d’indexation. Pour l’éviter, demandez une taille plus petite via model_params sur AI.EMBED. Le modèle est entraîné en style matryoska, ce qui lui permet d’être tronqué proprement à 768 ou 256 dimensions.
La consultation devient également plus simple. AI.SEARCH détermine quel modèle a été utilisé pour créer la table et incorpore automatiquement la chaîne de recherche.
SELECT base.title, base.body, distance
FROM AI.SEARCH(
TABLE `rag_demo_us.news_live`, 'body',
'major browser security stories',
top_k => 5
);
Le texte est recherché dans une colonne de texte, et l’entrepôt gère les vecteurs en arrière-plan. Les index vectoriels sont également pris en charge ici : créez-en un sur la colonne source, et l’entraînement commence dès que 80 % des lignes contiennent des embeddings.
Pour tout ce qui est destiné à la production, c’est cette version qui compte. L’approche one-shot n’est qu’une démonstration, tandis qu’un corpus auto-géré est un système que l’on peut faire fonctionner pendant des années.
Cependant, une règle s’applique : cette partie est en version préliminaire, selon des conditions antérieures à la généralisation qui peuvent changer. Gardez également à l’esprit que chaque tableau dispose d’une colonne d’embedding générée automatiquement, que les insertions doivent être effectuées en lots et non de manière fréquente via des opérations DML, car des opérations DML simultanées peuvent retarder la génération, et que les copies, clones et snapshots transmettent les données sans la configuration de génération. Les notes de version indiquent qu’il est possible de l’activer sur des tableaux nouveaux ou existants via CREATE TABLE ou ALTER TABLE. Les notes de version de BigQuery constituent la source officielle concernant l’état des fonctionnalités, il convient donc de les consulter avant de développer.
Ce que vous obtenez sans effort supplémentaire
C’est le aspect qui devrait le plus intéresser les équipes de données, mais c’est celui qui reçoit le moins d’attention.
- La sécurité au niveau des lignes et des colonnes s’applique également à
VECTOR_SEARCH. Une ligne que l’utilisateur n’a pas le droit de voir ne parvient jamais au système en tant que contexte RAG. Il est difficile d’obtenir cette même garantie grâce à une base de données vectorielle externe synchronisée. - IAM est le seul modèle d’authentification. Il n’y a pas de clés dans les variables d’environnement, ni de procédures de rotation pour le pipeline.
- Les journaux d’audit, l’attribution des coûts et les quotas proviennent tous de l’infrastructure déjà gérée par l’équipe de votre plateforme. Vous pouvez même suivre les frais par tâche dans Cloud Billing grâce à l’étiquette
bigquery_job_id_prefix. - Aucune tâche de synchronisation à exécuter. Les vecteurs se trouvent à côté des lignes qu’ils décrivent, de sorte qu’une base de données vectorielle ne peut pas s’éloigner du stockage principal lorsqu’il n’y a qu’une seule base. Avec les embeddings autonomes, aucune tâche de mise à jour n’a non plus besoin d’un responsable.
La meilleure infrastructure est celle que l’on n’implémente jamais.
Où cela échoue
- La facturation se fait par jeton, et Gemini 2.5 facture également pour les processus de réflexion. Ces modèles facturent des jetons de raisonnement en plus du résultat généré. Pour les tâches de classification, en définissant le budget de réflexion à zéro dans
model_params, on réduit à la fois la latence et les coûts. En omettant cette étape, les coûts par lot augmentent sans que l’on s’en aperçoive. Utilisez d’abordAI.COUNT_TOKENSpour faire une estimation. - Les requêtes complexes peuvent fournir au modèle un nombre de lignes différent de celui attendu. Les JOINs et les commandes ORDER BY avec LIMIT en sont généralement la cause. Google recommande d’abord d’écrire les données dans une table, puis d’exécuter l’inférence sur cette table.
request_type => 'dedicated'.VECTOR_SEARCH est facturé en fonction des octets scannés. Le tarif à la demande prend en compte la table de base, l’index ainsi que la requête. Placez les colonnes STORING dans l’index afin de ne pas avoir à faire de jointure avec la table de base, et filtrez tôt en utilisant WHERE sur ces colonnes stockées.Vérifier l’étiquette de statut
L’état des fonctionnalités peut changer, il convient donc de le vérifier. Comme rapporté initialement, AI.GENERATE et AI.GENERATE_TABLE sont devenus disponibles pour tous à la fin de janvier 2026, tandis que les modèles partenaires (Claude, Llama, Mistral) l’ont été au premier semestre 2025. Les fonctionnalités en version bêta, comme les embeddings autonomes, ont pu évoluer depuis, et les notes de version sont le moyen de s’en assurer.
SQL est-il vraiment tout ce dont vous avez besoin ?
Trois conclusions en ressortent.
Pour les systèmes RAG en lot et analytiques, il convient d’y réfléchir sérieusement. La classification des documents, la déduplication sémantique, les pipelines d’enrichissement ainsi que les systèmes de Q&R internes sur des données gérées favorisent tous l’approche native du entrepôt en termes de gouvernance, de simplicité et de coût total, tout en vous permettant de choisir votre modèle.
Un corpus en constante évolution relève d’un cas différent, et les embeddings autonomes modifient l’équilibre. La recherche n’a jamais été la partie difficile dans les systèmes RAG en production. Ce qui était difficile, c’était de maintenir les vecteurs en cohérence avec les données actuelles, et une colonne générée élimine complètement cette tâche.
La compétence réside dans la capacité à déterminer où se situent les limites. Les conversations en temps réel destinées aux utilisateurs exigent toujours une couche de service dédiée, le cycle de vie des prompts nécessite encore la gestion des versions, et le coût par token requiert toujours un suivi. Ces contraintes n’ont pas disparu ; elles se sont simplement déplacées.
Pour en savoir plus, la documentation de BigQuery de Google présente un aperçu de l’IA générative et la matrice de prise en charge des modèles, les commandes CREATE MODEL pour les modèles partenaires et ouverts, AI.GENERATE, AI.GENERATE_EMBEDDING, AI.SEARCH, la génération autonome d’embeddings, les fonctions de recherche, la gestion des index vectoriels ainsi que les notes de version.