Strona główna / Artykuły

RAG bez bazy danych wektorowych: pipeline w BigQuery SQL i miejsca, w których zawodzi

Stwórz generację wzbogaconą o wyszukiwanie wyłącznie za pomocą SQL w BigQuery, od embeddingów po konkretne odpowiedzi, a następnie sprawdź koszty, kwoty oraz ograniczenia dotyczące wersjonowania promptów.

Wydawca: REACTAPP.TOP11 min czytania2592 słów

Źródła: SQL Is All You Need: A Complete RAG Pipeline in SQL with BigQuery

Baza danych wektorowych, narzędzie orkiestracji, usługa embeddingów, klucze API rozproszone pomiędzy nimi oraz trzy kontenery tylko po to, by odpowiadać na pytania dotyczące własnych dokumentów. Taki jest obraz generowania wzbogaconego o wyszukiwanie wzorców w większości tutoriali, a w przypadku dużej grupy scenariuszy zastosowania może już nie być konieczny.

Funkcje AI w BigQuery pozwalają modelom LLM zachowywać się jak każdy inny operator SQL, tak jak to robią operatory JOIN lub WHERE. Proces embeddingowania, wyszukiwania wektorowego oraz generowania strukturyzowanego odbywa się tam, gdzie już znajdują się dane, w ramach mechanizmów IAM, rejestracji działań oraz kontroli kosztów, które już posiadasz. Nie jest to również zamknięty ekosystem oparty wyłącznie na modelu jednego dostawcy – można używać modeli Claude, Llama, Mistral oraz modeli open-source z Hugging Face.

Poniższe sekcje opisują ten sam proces w dwóch różnych sposobach na publicznym zbiorze danych, do którego każdy może uzyskać dostęp – korpusie wiadomości BBC dostępnym pod adresem bigquery-public-data.bbc_news.fulltext. Pierwszy sposób to wersja jednorazowa typu batch, wykorzystująca powszechnie dostępne funkcje. Drugi to wersja w fazie wstępnej, w której korpus jest stale aktualizowany w miarę pojawiania się nowych rekordów. Następnie przedstawiono szczere opisanie sytuacji, w których ten podejście zawodzi, a Python pozostaje lepszym rozwiązaniem.

Stack, który zakładają większość tutoriali

Standardowa konfiguracja RAG zazwyczaj obejmuje:

  1. Narzędzie do generowania embeddingów: hostowaną API lub punkt końcowy modelu, który należy uruchomić i utrzymywać
  2. Bazę danych wektorową, która dodaje kolejnego dostawcę, kolejny rachunek oraz kolejny proces synchronizacji, aby zapewnić spójność z źródłem prawdy
  3. Orkiestratora takiego jak LangChain, LlamaIndex lub ręcznie napisany kod łączący poszczególne komponenty
  4. API LLM z własnymi ograniczeniami szybkości i kwotami
  5. Hosting dla całego tego rozwiązania, zazwyczaj kilka kontenerów, które trzeba spakować i stale aktualizować

Policz elementy: pięć systemów i co najmniej trzy klucze API. Jest jeszcze koszt, o którym nikt nie mówi, a mianowicie to, że twoje dane opuszczają magazyn. Każda zasada zarządzania, polityka bezpieczeństwa na poziomie wierszy oraz wymóg audytu wbudowane w BigQuery przestają obowiązywać w momencie, gdy dane są eksportowane do zewnętrznego magazynu wektorowego.

Taka architektura istniała, ponieważ nie było alternatywy. Założenia się zmieniły.

LLM jako operator SQL

Rodzina funkcji AI.* w BigQuery jest już powszechnie dostępna. Dla RAG istotne są trzy elementy:

  • AI.GENERATE_EMBEDDING tworzy embeddingi masowo z dowolnej tabeli.
  • VECTOR_SEARCH realizuje wyszukiwanie semantyczne jako wbudowana funkcja GoogleSQL. Można dodać indeks IVF lub TreeAH, przy czym TreeAH opiera się na algorytmie ScaNN firmy Google.
  • AI.GENERATE to funkcja skalarna działająca wiersz po wierszu, która może zwracać strukturyzowany wynik za pomocą output_schema i pasuje wszędzie tam, gdzie pasuje wartość, w tym do warunków SELECT, WHERE, a nawet JOIN.

W twoim kodzie nie pojawia się żaden klucz. Autoryzacja odbywa się poprzez połączenie z BigQuery – zarządzany konto usługowe, któremu nadajesz rolę raz. Obecnie istnieje domyślne połączenie, co eliminuje większość kroków konfiguracji.

Nie jesteś przywiązany do Gemini

Częstym obawą jest to, że wiąże to cię z Gemini. W rzeczywistości tak nie jest, chociaż szczegóły są często błędnie interpretowane. Funkcje te występują w dwóch rodzajach.

Funkcje uniwersalne takie jak AI.GENERATE_TEXT, AI.GENERATE_TABLE i AI.GENERATE_EMBEDDING z argumentem MODEL dają pełną kontrolę nad tym, który model zostanie uruchomiony. Wśród obsługiwanych modeli zdalnych znajdują się rodzina Gemini, a także Anthropic Claude, Meta Llama i Mistral AI jako zarządzane punkty końcowe, oraz modele otwarteźródłowe z Hugging Face lub Model Garden na platformie agentów Google (dawniej Vertex AI). W przypadku modeli otwartych CREATE MODEL może nawet automatycznie je wdrożyć.

Funkcje zarządzane (AI.IF, AI.CLASSIFY, AI.SCORE oraz skalarny AI.GENERATE przy podaniu endpoint) to skróty oparte na technologii Gemini. BigQuery wybiera model i zajmuje się formułowaniem zapytań.

Mówi się, że zgodność wymaga użycia embeddingów z samodzielnie hostowanego wielojęzycznego modelu E5 lub Qwen, przy czym odpowiedzi generuje Claude. Każdy z tych podejść zaczyna się od pojedynczego polecenia CREATE MODEL. W przypadku embeddingów jest to całkowita zmiana, ponieważ nic dalej w łańcuchu nie ulega zmianie. Generowanie ma jednak jedną zastrzeżenie.

Szlak pierwszy: pipeline typu one-shot (dostępny powszechnie)

Ten wzorzec nadaje się do pracy zbiorczej i analitycznej, gdzie korpus jest embedowany raz lub według określonego harmonogramu, a następnie są wykonywane zapytania do niego.

Krok 0: jednorazowa konfiguracja

Łączenie jest krokiem, którego najczęściej pomijają tutoriali skierowane do użytkowników systemów typu warehouse. Jest to konto usługi zarządzanej, za pośrednictwem którego BigQuery uzyskuje dostęp do modeli, i jest tworzone tylko raz.

Jeśli masz rolę administratora BigQuery, najszybszym rozwiązaniem jest użycie domyślnej połączenia. Wpisz w instrukcji WITH CONNECTION DEFAULT, a BigQuery je przygotuje i przyzna odpowiednie role przy pierwszym użyciu.

Jeśli wolisz mieć własne połączenie, którego Path two wymaga w formie us.my_connection, potrzebne są dwa polecenia: najpierw utworzyć połączenie z zasobem chmurowym, a następnie nadać jego kontu usługowemu rolę o nazwie Agent Platform User (roles/aiplatform.user, wcześniej 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

Miejsce, w którym przypisujesz rolę, zależy od sposobu odwoływania się do modelu: w projekcie, który posiada model, gdy podajesz nazwę, lub w projekcie z adresu URL końcowego, gdy podajesz pełny adres URL. Autoryzacja kończy się w tym momencie – przy jednej roli i bez żadnych kluczy.

Następnie używany jest model embedding, przy czym ponownie wykorzystywane jest domyślne połączenie:

CREATE OR REPLACE MODEL `rag_demo_us.emb_model`
  REMOTE WITH CONNECTION DEFAULT
  OPTIONS (ENDPOINT = 'gemini-embedding-001');

To stwierdzenie stanowi całą implementację usługi embedding. Nie ma żadnego kontenera, żadnego punktu końcowego do skalowania ani żadnego klucza do rotacji. Jeśli zaimplementowałeś model otwartego oprogramowania, zastąp go odpowiednią adresem URL punktu końcowego.

Krok 1: włączenie korpusu

Jedno stwierdzenie zawiera artykuły 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)
);

Dwa szczegóły łatwo przeoczyć. Zapytanie wejściowe musi zawierać kolumnę o nazwie content, ewentualnie z aliasem, a dodanie kolumny title poprawia jakość embeddingów dla RETRIEVAL_DOCUMENT. Ponadto task_type ma znaczenie: należy używać RETRIEVAL_DOCUMENT dla korpusu, a RETRIEVAL_QUERY dla pytania użytkownika, ponieważ asymetryczne embeddingi znacząco poprawiają jakość wyszukiwania.

Wynikowa tabela zawiera Twoje kolumny oryginalne oraz trzy dodane automatycznie. embedding przechowuje wektor, statistics to wartość JSON zawierająca token_count i truncated, a status informuje o błędach. Należy zauważyć, że nowsza wersja AI.GENERATE_EMBEDDING zwraca embedding, a nie ml_generate_embedding_result używany w starszych instrukcjach ML.GENERATE_EMBEDDING. Mieszanie tych dwóch formatów to typowy błąd wynikający z przypadkowego skopiowania i wklejenia.

Pierwsza pułapka: cichy częściowy niepowodzenie. Zadanie zbiorcze może udać się ogólnie, mimo że poszczególne wiersze zawiodą z powodu ograniczeń kwot lub problemów z usługą. BigQuery raportuje to dla każdego wiersza w polu status – pusta strona oznacza sukces, dlatego zawsze należy filtrować za pomocą WHERE status = ''. Jeśli równoległe zadania napotkają błędy związane wyczerpaniem zasobów, Google udostępnia skrypty do ponownych prób przetwarzania zdalnego oraz pakiet Dataform, które iterują, aż każdy wiersz zostanie przetworzony.

Druga pułapka: ciche skracanie. W polu statistics każdego wiersza widnieje liczba tokenów oraz flaga wskazująca na skrócenie treści. Artykuł prasowy składa się z kilkuset tokenów, ale dokumenty dłuższe od limitu wejściowego modelu są przetwarzane w skróconej formie bez żadnego błędu. W przypadku długich dokumentów należy monitorować flagę skrócenia i wcześniej je dzielić na fragmenty.

Krok 2: dodanie indeksu, opcjonalnie

Mniejsze korpusy mogą to pominąć, ponieważ VECTOR_SEARCH przechodzi na dokładne poszukiwanie siłowe. W większych skali należy je stworzyć:

CREATE OR REPLACE VECTOR INDEX news_idx
ON `rag_demo_us.news_emb`(embedding)
OPTIONS (index_type = 'IVF', distance_type = 'COSINE');

Niektóre zachowania, których należy się spodziewać: utrzymanie indeksu odbywa się w tle automatycznie, nowe wiersze dodane później mogą być natychmiast wyszukiwane (BigQuery przeszukuje nienadrukowany pozostały obszar metodą prób i błędów), tabele o wielkości poniżej 10 MB nie mają utworzonego indeksu, a edycja Standard nie obsługuje indeksów.

Krok 3: pobieranie i generowanie w jednej zapytaniu

Wbuduj pytanie, pobierz pięć najlepszych artykułów, sformułuj odpowiedź na podstawie tego pytania i wygeneruj tekstową odpowiedź:

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
);

Podstawowy proces składa się z około tuzina linijek kodu – nie ma tu LangChain, żadnego zewnętrznego magazynu wektorowego ani niczego, co trzeba by zainstalować. Funkcja output_schema generuje kolumny z typami danych dla odpowiedzi, wynik pewności oraz źródła, a nie plik JSON, który trzeba analizować i w którym trzeba mieć nadzieję na poprawne dane.

Jednak ograniczenie wersji jednorazowej jest poważne – dane nie pozostają nieruchome. Pojawiają się nowe artykuły, zmieniają się opisy, a zgłoszenia są aktualizowane. Każda operacja dodawania lub edycji oznacza konieczność ponownego uruchomienia instrukcji embedding, wykrywania zmian, obsługi prób ponownych działania oraz monitorowania stanu starych danych.

Druga droga: korpus, który sam się utrzymuje (przegląd)

Rozwiązaniem jest autonomiczna generacja embeddingów. Należy zadeklarować embedding jako kolumnę generowaną, a BigQuery przejmie zadanie jej aktualizacji:

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)
);

Następnie wstaw dane z dowolnego miejsca:

INSERT INTO `rag_demo_us.news_live` (title, body)
SELECT title, body
FROM `bigquery-public-data.bbc_news.fulltext`
WHERE category = 'tech';

Ten opis warto przeczytać dwukrotnie. Dzięki GENERATED ALWAYS AS AI.EMBED(...) embedding staje się metadanymi tabeli, a nie kodem procesu. Po wprowadzeniu lub aktualizacji kolumny źródłowej, BigQuery tworzy lub odświeża wektor w tle. Przy użyciu asynchronous = TRUE wprowadzanie danych nie czeka na wywołania modelu, a praca z embeddingiem odbywa się w tle, przy czym zlecenia te mają w historii zadań przedrostek gc_. Kolumna ma postać STRUCT<result, status>, więc błędy są widoczne dla każdego wiersza i nie są ignorowane.

Jeśli chodzi o wybór modelu, w przykładzie użyto gemini-embedding-001, ponieważ był powszechnie dostępny i dobrze radził sobie na liście najlepszych modeli wielojęzycznych w MTEB w tamtym czasie. Kanoniczny przykład w dokumentacji wykorzystuje text-embedding-005, który również działa równie dobrze. Należy uwzględnić jeden aspekt: domyślna liczba wymiarów wynosi 3072, podczas gdy w modelach text-embedding jest to 768, co zwiększa koszty przechowywania i indeksowania. Aby tego uniknąć, należy poprosić o mniejszą liczbę wymiarów za pomocą parametru model_params w funkcji AI.EMBED. Model jest trenowany w stylu Matryoszki, więc jego wymiary można łatwo skrócić do 768 lub 256.

Zapytania również stają się prostsze w obsłudze. Funkcja AI.SEARCH sprawdza, z jakiego modelu zbudowano tabelę, i samodzielnie wstawia tekst zapytania do modelu:

SELECT base.title, base.body, distance
FROM AI.SEARCH(
  TABLE `rag_demo_us.news_live`, 'body',
  'major browser security stories',
  top_k => 5
);

Tekst jest wyszukiwany w kolumnie tekstowej, a magazyn obsługuje wektory w tle. Tutaj również obsługiwane są indeksy wektorowe: utwórz jeden w kolumnie źródłowej, a szkolenie rozpocznie się, gdy 80% wierszy będzie zawierać embeddingi.

Dla wszystkiego, co ma trafić do produkcji, istotna jest właśnie ta wersja. Podejście typu one-shot to tylko demonstracja, natomiast samodzielnie utrzymywany korpus to rozwiązanie, które można używać przez lata.

Obowiązuje jednak jedno zastrzeżenie: ta część znajduje się w wersji wstępnej, zgodnie z warunkami sprzed wdrożenia GA, które mogą ulec zmianie. Należy również pamiętać, że każda tabela ma jedną kolumnę embeddingu wygenerowaną automatycznie, że operacje insert powinny być wykonywane partiami, a nie jako część częstych zapytań DML, ponieważ jednoczesne zapytania DML mogą opóźnić proces generowania, oraz że kopie, klony i zrzuty stanu przenoszą dane bez konieczności konfiguracji generowania. Notatki wydawnicze wskazują, że można to włączyć w nowych lub istniejących tabelach za pomocą polecenia CREATE TABLE lub ALTER TABLE. Notatki wydawnicze BigQuery stanowią autorytatywne źródło informacji o stanie funkcji, więc sprawdź je przed rozpoczęciem pracy.

Co otrzymujesz bez dodatkowej pracy

To jest ta część, która powinna najbardziej interesować zespoły zajmujące się danymi, a jednocześnie otrzymuje najmniej uwagi.

  • Zabezpieczenia na poziomie wierszy i kolumn są przenoszone na VECTOR_SEARCH. Wiersz, którego użytkownik nie ma prawa zobaczyć, nigdy nie trafia do interfejsu jako kontekst RAG. Odtworzenie takiej gwarancji za pomocą zsynchronizowanej zewnętrznej bazy wektorowej jest trudne.
  • IAM jest jedynym modelem autoryzacji. W zmiennych środowiskowych nie ma żadnych kluczy, a także nie istnieją procedury rotacji dla całego procesu.
  • Łogi audytowe, przypisywanie kosztów i kwoty pochodzą wszystkie z infrastruktury, którą już zarządza zespół platformy. Można nawet śledzić koszty poszczególnych zadań w Cloud Billing za pomocą etykiety bigquery_job_id_prefix.
  • Nie ma żadnych zadań synchronizacyjnych do uruchomienia. Wektory znajdują się obok wierszy, które opisują, więc magazyn wektorów nie może się oddalić od magazynu danych, gdy istnieje tylko jeden magazyn. W przypadku autonomicznych embeddingów również nie potrzeba właściciela dla zadań odświeżania.

Najlepsza infrastruktura to taka, której nigdy nie wdrażasz.

Gdzie występują problemy

  1. Rachunkowość odbywa się na podstawie tokenów, a Gemini 2.5 pobiera opłaty również za procesowanie myśli. Te modele naliczają opłaty za tokeny używane do rozumowania oprócz samego wyniku. W zadałach typu klasyfikacja ustawienie budżetu na procesowanie myśli na zero w model_params zmniejsza zarówno czas reakcji, jak i koszty. Jeśli pominiesz ten krok, koszty w procesach zbiorczych wzrosną niepostrzeżenie. Najpierw użyj AI.COUNT_TOKENS, aby to oszacować.
  2. Złożone zapytania mogą dostarczyć modelowi inną liczbę wierszy, niż się spodziewasz. Zwykłymi przyczynami są operacje JOIN oraz użycie ORDER BY z LIMIT. Google zaleca najpierw zapisanie danych do tabeli, a następnie przeprowadzenie obliczeń na tej tabeli.
  3. Kwoty są rzeczywiste. Przepustowość wbudowania jest ograniczona kwotami w postaci tokenów na minutę, domyślnie kilku milionów. Odpowiada to większości obciążeń, ale plan wdrożenia całego jeziora danych za jedną noc wymaga przygotowań lub użycia przepustowości zapewnionej za pomocą request_type => 'dedicated'.
  4. VECTOR_SEARCH jest opłacany według liczby przeskanowanych bajtów. Cena według modelu na żądanie obejmuje tabelę bazową, indeks oraz zapytanie. Umieść kolumny STORING w indeksie, aby nie trzeba było łączyć się ponownie z tabelą bazową, oraz filtrować wcześnie za pomocą WHERE w tych przechowywanych kolumnach.
  5. Żadne wersje nie są stosowane do promptów. Prompt to po prostu ciąg znaków w SQL, więc nie ma rejestracji, testów A/B ani narzędzi do oceny. Zespoły, które co tydzień przepisują prompty asystenta produkcyjnego, odczuwają to na własnej skórze, i właśnie w takich przypadkach przydatna okazuje się niewielka warstwa w Pythonie lub dbt przechowujące prompty jako wersjonowane konfiguracje.

Sprawdź etykietę stanu

Stan funkcji może ulegać zmianom, więc warto to zweryfikować. Jak pierwotnie donoszono, AI.GENERATE i AI.GENERATE_TABLE stały się dostępne dla wszystkich pod koniec stycznia 2026 roku, natomiast modele partnerskie (Claude, Llama, Mistral) trafiły do ogólnej dostępności w pierwszej połowie 2025 roku. Funkcje w wersji wstępnej, takie jak autonomiczne embeddingi, mogły się od tamtej pory zmienić, więc notatki o wydaniu są odpowiednim miejscem do potwierdzenia.

Czy SQL to rzeczywiście wszystko, czego potrzebujesz?

Z tego wynikają trzy wnioski.

W przypadku przetwarzania partii danych oraz zastosowań analitycznych typu RAG warto to dokładnie rozważyć. Klasyfikacja dokumentów, usuwanie duplikatów semantycznych, procesy wzbogacania danych oraz systemy pytania-odpowiedź działające na zasadach określonych w regulacjach – wszystko to faworyzuje podejście oparte na lokalnych zasobach w zakresie zarządzania, prostoty i całkowitego kosztu, a ponadto pozwala zachować wybrany model.

Korpus danych stale się zmieniający to inna sytuacja, a autonomiczne embeddingi zmieniają równowagę. Poszukiwanie informacji nigdy nie było trudną częścią wdrożeń typu RAG w produkcji; trudnościem było utrzymywanie wektorów zgodnych z aktualnymi danymi, a kolumna generowana eliminuje całą tę kategorię zadań.

Umiejętnością kluczową jest znajomość granic możliwości. Chaty dostępne dla użytkowników w czasie krótszym niż sekunda nadal wymagają dedykowanej warstwy serwowania, cykl życia promptów nadal wymaga wersjonowania, a koszt za token nadal wymaga monitoringu. Te ograniczenia nie zniknęły – jedynie się przeniosły.

Dla dalszej lektury dokumentacja BigQuery firmy Google zawiera przegląd sztucznej inteligencji generatywnej oraz tabelę obsługi modeli, polecenie CREATE MODEL dla modeli partnerskich i otwartych, funkcje AI.GENERATE, AI.GENERATE_EMBEDDING, AI.SEARCH, mechanizm autonomicznej generacji embeddingów, funkcje wyszukiwania, zarządzanie indeksami wektorowymi oraz notatki o nowościach.

Podobne materiały