Grounded Antworten: Retrieval, pgvector und Embedding-Konsistenz
Wie Dokki eine Frage in Top-k Chunks des aktiven Snapshots übersetzt — HNSW auf halfvec, ein geteiltes Embedding-Modell und Quellen, die man nachschlagen kann.
Die Query Plane von Dokki ist zustandslos und hat genau eine Aufgabe: eine Frage geerdet gegen den aktiven Snapshot beantworten. Der Weg dahin ist kurz, aber jeder Schritt hat eine Fallgrube.
Retrieval gegen genau einen Snapshot
Das Retrieval-SQL joint die Chunks über die snapshot_members des aktiven
Snapshots — so sieht eine Query nie etwas, das nicht promotet wurde:
SELECT c.content, c.metadata, c.embedding <=> $queryEmbedding AS distance
FROM vectors.chunks c
JOIN knowledge.snapshot_members sm ON sm.document_version_id = c.document_version_id
WHERE sm.snapshot_id = $activeSnapshotId
ORDER BY c.embedding <=> $queryEmbedding
LIMIT $k;
Welcher Snapshot aktiv ist, weiß der Service über einen gecachten Pointer,
den er bei snapshot.activated invalidiert — kein Tabellen-Lookup pro Frage.
halfvec-Index nutzen. Damit der HNSW-Index greift, muss der
ORDER BY-Ausdruck zur indexierten Expression passen. Beihalfvec-Cast also beidseitig casten:ORDER BY c.embedding::halfvec(1024) <=> $queryEmbedding::halfvec(1024)— sonst wird es ein Sequential Scan. Der Cast halbiert nebenbei den Speicher (4 → 2 Byte pro Dimension).
Dasselbe Modell auf beiden Seiten
Die subtilste Fehlerquelle in RAG: Frage und Dokumente werden mit verschiedenen Embedding-Modellen vektorisiert. Dann liegen Query- und Dokument-Vektoren nicht im selben Raum und die Cosine-Distanz ist Müll.
Dokki erzwingt Konsistenz strukturell:
- Ollama (
bge-m3) als eigenes Deployment, das Ingest und RAG aufrufen — eine Implementierung, kein dupliziertes Modell. embedding_modelwird pro Version und pro Snapshot festgehalten.- Bei einer Query liest der Service das
embedding_modeldes aktiven Snapshots und embeddet die Frage mit exakt diesem Modell. - Ein Modellwechsel ist deshalb kein In-Place-Update, sondern ein kompletter Re-Ingest in einen neuen Snapshot — erst nach Eval aktiviert.
Multilingual ist hier kein Nice-to-have
Eine Collection wie Lughaty enthält arabisches Lernmaterial. bge-m3 ist
explizit multilingual und läuft lokal — bei arabischem Text (RTL, Diakritika)
ist die AR-Abdeckung des Modells direkt retrieval-relevant. Das Embedding-Modell
ist damit keine reine Infrastruktur-Wahl, sondern eine Qualitätsentscheidung.
Optional: Reranking
Nach dem Vektor-Retrieval kann ein Cross-Encoder die Top-k Chunks neu sortieren,
bevor sie in den Prompt wandern — präziser, wenn reine Cosine-Distanz zu grob
ist. Per Request über rerank: true steuerbar.
Die Antwort kennt ihre Quellen
Eine Antwort enthält immer die verwendeten Quellen aus den Chunk-Metadaten — Dateiname, Ordinal/Seite, Distanz, Excerpt:
{
"answer": "Der Dual (المثنى) wird durch das Anhängen von ...",
"snapshotId": "s5a4b3c2-...",
"embeddingModel": "bge-m3",
"sources": [
{
"filename": "arabische-grammatik.pdf",
"ordinal": 142,
"distance": 0.18,
"excerpt": "Der Dual bezeichnet genau zwei ..."
}
]
}
Claudes native Citations API liefert dabei die Textspannen auf den
übergebenen Chunks; das sources[]-Mapping (Dateiname, Ordinal, Distanz) kommt
weiter aus den Chunk-Metadaten. Das Ergebnis ist eine Antwort, deren jede
Aussage sich bis zur Seite im Originaldokument zurückverfolgen lässt — der
ganze Sinn der Übung.
Damit schließt sich die Serie: von der Drei-Plane-Architektur über immutable Snapshots und das Quality Gate bis zur geerdeten Antwort hier.