Hybridní vyhledávání na PostgreSQL a VectorChord: nastavení infrastruktury
Hybridní vyhledávání, které kombinuje vektorovou a plnotextovou metodu, se stává standardem pro moderní vyhledávací systémy. V tomto článku podrobně rozebereme nastavení infrastruktury na bázi PostgreSQL a VectorChord – řešení, které zjednodušuje implementaci hybridního vyhledávání bez nutnosti ruční instalace rozšíření a složité konfigurace.
Příprava prostředí: Docker a PostgreSQL
Pro start potřebujeme kontejnerizované prostředí. Použijeme Docker Compose k nasazení PostgreSQL s předinstalovanými rozšířeními VectorChord. Upozorňujeme: v konfiguraci explicitně neuvádíme rozšíření – budou přidána automaticky při prvním připojení přes VechordRegistry.
Příklad souboru docker-compose-dev.yml:
services:
postgres:
image: tensorchord/vchord-suite:pg18-latest
environment:
POSTGRES_DB: ${DB__NAME}
POSTGRES_USER: ${DB__USER}
POSTGRES_PASSWORD: ${DB__PASSWORD}
volumes:
- pgdata:/var/lib/postgresql
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB__USER} -d ${DB__NAME}"]
interval: 5s
timeout: 5s
retries: 5
volumes:
pgdata:
Klíčový moment: obrázek tensorchord/vchord-suite již obsahuje všechny potřebné komponenty, ale rozšíření se aktivují až při prvním volání přes VechordRegistry. To umožňuje vyhnout se ruční konfiguraci a zaručuje kompatibilitu verzí.
Návrh tabulek pro hybridní vyhledávání
Struktura dat je postavena na třech tabulkách. Základní tabulka BaseTable obsahuje společná pole:
from datetime import datetime, timezone
from functools import partial
import msgspec
from psycopg.types.json import Jsonb
from vechord import Table
class BaseTable(Table, kw_only=True):
metadata: Jsonb
created_at: datetime = msgspec.field(
default_factory=partial(datetime.now, timezone.utc))
updated_at: datetime = msgspec.field(
default_factory=partial(datetime.now, timezone.utc))
Pole metadata ukládá libovolná data ve formátu JSONB a časové značky se aktualizují automaticky při vytvoření záznamu. Upozorňujeme: updated_at se neaktualizuje automaticky při změně záznamu – to je třeba udělat ručně v metodách aktualizace.
Tabulka dokumentů Document:
import msgspec
from vechord.spec import PrimaryKeyUUID
from .base import BaseTable
class Document(BaseTable, kw_only=True):
uid: PrimaryKeyUUID = msgspec.field(default_factory=PrimaryKeyUUID.factory)
title: str
text: str
Pro primární klíč se používá UUID. Tabulka chunků Chunk je spojena s dokumentem přes cizí klíč:
from vechord.spec import ForeignKey, Keyword, PrimaryKeyUUID, Vector
class Chunk(BaseTable, kw_only=True):
uid: PrimaryKeyUUID = msgspec.field(default_factory=PrimaryKeyUUID.factory)
doc_id: Annotated[UUID, ForeignKey[Document.uid]]
content: str
content_tsv: Keyword
embedding: DenseVector
chunk_index: int
Zde je content_tsv určené pro plnotextové vyhledávání (BM25) a embedding ukládá vektorovou reprezentaci chunku. Cizí klíč doc_id automaticky realizuje kaskádové mazání.
Důležité: pokus vyjmout uid do základní třídy vede k chybě, protože ForeignKey hledá pole v BaseTable, nikoli v konkrétní tabulce. Proto se primární klíč duplikuje v každé tabulce.
VechordRegistry: centralizované řízení DB
Třída VechordRegistry slouží jako jednotné rozhraní pro práci s databází. Automatizuje:
- Instalaci rozšíření (
vchord,vchord_bm25,pg_tokenizer) - Vytváření tabulek a tokenizátorů
- Provádění CRUD operací a vyhledávání
Inicializace:
from vechord import VechordRegistry
from core import settings
from .document import Document
from .doc_chunk import Chunk
vr = VechordRegistry(
namespace=settings.db.namespace,
url=settings.db.db_url,
tables=[Document, Chunk]
)
Při prvním použití (v kontextu async with vr:) se spustí metoda init_extension, která instaluje rozšíření a nastavuje search_path. To eliminuje nutnost ruční konfigurace a minimalizuje chyby.
Příklad použití pro vložení dokumentu:
async with vr:
doc = Document(title='Note', text='Some text', metadata=Jsonb({}))
await vr.insert(doc)
Po provedení tohoto kódu se v DB objeví všechna potřebná rozšíření, tabulky a záznam.
Potrubí zpracování dat
Pro složité operace, jako je vytvoření dokumentu s následným chunkováním, se používá VechordPipeline. Umožňuje spojit několik kroků do konvejéru, kde každý stupeň automaticky ukládá výsledky do DB.
Krok 1: Vytvoření dokumentu. Dekorátor @vr.inject(output=Document) zaručuje uložení objektu:
@vr.inject(output=Document)
async def _create_document(doc_data: DocumentCreate) -> Document:
doc = Document(
title=doc_data.title,
text=doc_data.text,
metadata=Jsonb(doc_data.metadata)
)
return doc
Krok 2: Generování chunků. Přijímá data z tabulky Document a vrací seznam chunků:
@vr.inject(input=Document, output=Chunk)
async def create_chunks(uid: UUID, text: str) -> list[Chunk]:
chunks = await _chunker.segment(text)
return [
Chunk(
doc_id=uid,
content=chunk,
content_tsv=Keyword(chunk),
embedding=DenseVector(await _embedder.vectorize_chunk(chunk)),
metadata=Jsonb({}),
chunk_index=i
)
for i, chunk in enumerate(chunks, start=1)
]
Sestavení potrubí:
from db_models import vr
from .utils import create_chunks, create_document
class DocumentService:
async def create_document(self, doc_data: DocumentCreate):
pipeline = vr.pipeline(
_create_document,
create_chunks
)
await pipeline(doc_data)
Tento přístup zajišťuje atomaritu operací a minimalizuje počet volání do DB.
Co je důležité: klíčové aspekty implementace
Při zavádění hybridního vyhledávání na bázi VectorChord obraťte pozornost na:
- Automatická inicializace rozšíření: VechordRegistry instaluje všechny potřebné komponenty při prvním připojení, což zjednodušuje nasazení.
- Struktura tabulek: rozdělení na dokumenty a chunky je klíčové pro efektivní vyhledávání; cizí klíče zajišťují integritu dat.
- Potrubí zpracování: použití
VechordPipelineumožňuje zapouzdřit složité operace a zaručit konzistenci dat.
Tyto prvky tvoří základ pro škálovatelný vyhledávací systém, který kombinuje přesnost vektorového vyhledávání a flexibilitu plnotextového.
— Editorial Team
Zatím žádné komentáře.