Im ersten Teil dieser Serie wurde erläutert, was pgvector ist, wie Embeddings entstehen und welche Distanzoperatoren für welche Anwendungsfälle geeignet sind. Dieser Beitrag schließt dort an und zeigt den vollständigen Weg von rohen Texten über die semantische Suche bis zur Antwort eines lokalen Sprachmodells.
Das Beispiel basiert auf einem Reiseangebotskatalog mit 20 Einträgen. Das Ziel: Eine Suchanfrage in natürlicher Sprache findet semantisch passende Reiseangebote, auch wenn kein einziges Wort der Anfrage in den Dokumenten vorkommt. Die Ergebnisse werden anschließend als Kontext an ein lokales LLM übergeben, das daraus eine strukturierte Antwort formuliert.
Die gesamte Pipeline läuft lokal. Es werden keine externen APIs benötigt, keine Daten verlassen das System.
Voraussetzungen und Einrichtung
Abhängigkeiten installieren
pip install psycopg2-binary pgvector langchain-ollama numpy ollama
Embedding-Modell und LLM herunterladen
ollama pull mxbai-embed-large
ollama pull llama3
mxbai-embed-large erzeugt Vektoren mit 1024 Dimensionen und liefert für deutschsprachige Texte zuverlässige semantische Repräsentationen. llama3 übernimmt die Antwortgenerierung auf Basis des bereitgestellten Kontexts.
Datenbankstruktur anlegen
Die Tabelle ist bewusst schlank gehalten. In einer Produktionsumgebung würden weitere Metadatenspalten ergänzt, etwa Kategorie, Sprache oder Datum, die später als WHERE-Filter in der Vektorsuche eingesetzt werden können.
Die Indexierung
Die Indexierungsphase läuft einmalig beim initialen Befüllen der Datenbank. Neue Dokumente können später einzeln nachträglich eingebettet und eingefügt werden.
Die Datenbasis
Jeder Eintrag beschreibt ein Reiseangebot in einem kurzen, prägnanten Satz. Das Wort „Japan” taucht in keinem Eintrag auf, obwohl Tokio, Kyoto und Osaka eindeutig Japan-Reisen beschreiben. Genau diese semantische Lücke wird die Vektorsuche später schließen.
Jeder der 20 Texte wird einzeln durch mxbai-embed-large verarbeitet. Das Modell gibt für jeden Text eine Liste von 1024 Dezimalzahlen zurück, die die semantische Bedeutung des Textes im Vektorraum repräsentieren. Anschließend werden Originaltext und Vektor gemeinsam in dieselbe Tabellenzeile geschrieben. Nach dem commit() enthält die Tabelle 20 Einträge, jeder mit Text und zugehörigem Embedding.
Entscheidend ist, dass für die Indexierung und die spätere Suche dasselbe Embedding-Modell verwendet wird. Nur wenn beide Seiten, die Dokumente und die Anfragen, im selben Vektorraum liegen, sind die berechneten Distanzen inhaltlich aussagekräftig.
Die semantische Suche
Die Suchanfrage vorbereiten:
Die Suchanfrage wird durch dasselbe Modell eingebettet wie die gespeicherten Dokumente. Das Ergebnis ist ein einzelner Vektor mit 1024 Dimensionen, der die semantische Bedeutung der Anfrage repräsentiert.
np.array(..., dtype=np.float32) konvertiert den Python-Vektor in das Format, das pgvector erwartet. register_vector(conn) registriert den pgvector-Datentyp bei psycopg2, damit der Python-Treiber und PostgreSQL den Typ korrekt austauschen.
Der Query-Vektor wird zweimal übergeben: einmal für die Berechnung des Ähnlichkeitswerts (1 - (embedding <=> %s) und einmal für die Sortierung (ORDER BY embedding <=> %s). PostgreSQL berechnet für jede Zeile in der Tabelle die Cosine Distance zum Query-Vektor und gibt die fünf ähnlichsten Einträge zurück.
Das Ergebnis
Das Wort „Japan” kommt in keinem der gefundenen Dokumente vor. Die Suchanfrage enthält umgekehrt weder „Tokio” noch „Kyoto” als exakten Treffer, dennoch landen alle drei Japan-Einträge unter den Top 3. Das Embedding-Modell hat gelernt, dass Tokio, Kyoto und Osaka zu Japan gehören, und bildet diese Beziehung im Vektorraum ab. Eine klassische SQL-Suche mit LIKE oder Full-Text-Search hätte hier keine Treffer geliefert.
Die RAG-Architektur
Die Vektorsuche liefert die semantisch relevantesten Textabschnitte. Im nächsten Schritt werden diese Abschnitte als Kontext in einen Prompt eingebettet und an ein lokales LLM übergeben. Das Ergebnis ist eine strukturierte, auf den gefundenen Dokumenten basierende Antwort.
RAG steht für Retrieval Augmented Generation und beschreibt ein Architekturmuster, das drei Schritte kombiniert. Im Retrieval-Schritt wird die Anfrage in einen Vektor umgewandelt und die semantisch ähnlichsten Dokumente werden aus der Datenbank abgerufen. Im Augmentation-Schritt werden die gefundenen Dokumente in einen Prompt eingebettet, der dem LLM als Kontext zur Verfügung gestellt wird. Im Generationsschritt erzeugt das LLM eine Antwort, die ausschließlich auf dem bereitgestellten Kontext basiert, nicht auf seinem Trainingswissen. Eine ausführliche Einführung in das RAG-Konzept, seine Vorteile und typische Einsatzgebiete bietet der Blogbeitrag „Wenn KI den Spickzettel zückt”.
Der entscheidende Mechanismus ist die explizite Einschränkung im Prompt: Das Modell wird angewiesen, ausschließlich auf Basis der bereitgestellten Informationen zu antworten. Das verhindert, dass das Modell Inhalte erfindet, die nicht in den Dokumenten vorhanden sind.
Das LLM antwortet auf Basis der übergebenen Chunks:
Das LLM hat ausschließlich die übergebenen Textabschnitte als Grundlage verwendet. Es hat keine eigenständigen Informationen über Japan ergänzt, sondern die vorhandenen Daten strukturiert und in eine lesbare Antwort überführt.
Erweiterungsmöglichkeiten
Das hier gezeigte Beispiel ist eine funktionierende Grundlage. In einer produktionsreifen Implementierung würden folgende Aspekte ergänzt. Die Vektorsuche kann durch WHERE-Bedingungen eingeschränkt werden, etwa auf bestimmte Kategorien, Zeiträume oder Zugriffsrechte, da PostgreSQL die Kombination von Vektorsuche und relationalen Filtern in einer einzigen Abfrage erlaubt. Längere Dokumente sollten vor der Indexierung in kleinere Abschnitte aufgeteilt werden, da zu große Chunks die semantische Aussagekraft des Vektors verwässern und zu kleine Chunks den Kontext verlieren. Überlappende Chunks mit einem definierten Stride-Wert sind hier eine bewährte Methode. In einer produktiven Umgebung sollten Datenbankverbindungen über einen Connection Pool verwaltet, alle Embedding-Aufrufe mit Timeouts versehen und fehlerhafte Anfragen protokolliert werden. Der Wechsel von mxbai-embed-large zu einem anderen Embedding-Modell erfordert eine vollständige Neuindexierung aller Dokumente, da sich der Vektorraum ändert. Eine Versionierung der Embeddings in der Datenbank erleichtert spätere Migrationen erheblich.
Fazit
Die drei Phasen, Indexierung, semantische Suche und RAG, bilden zusammen eine vollständige KI-Pipeline auf Basis von PostgreSQL. Das Embedding-Modell übersetzt Bedeutung in Zahlen. pgvector macht diese Zahlen durchsuchbar. Der Prompt-Mechanismus stellt sicher, dass das LLM ausschließlich auf Basis der gefundenen Dokumente antwortet und keine Informationen erfindet, die nicht in den Daten vorhanden sind.
Was dieses Beispiel zeigt, ist kein akademisches Konstrukt, sondern ein direkt übertragbares Muster. Der Austausch des Embedding-Modells oder des LLMs erfordert keine strukturellen Änderungen an der Architektur, solange die Konsistenz des Vektorraums gewahrt bleibt. Das Ergebnis ist eine lokal betreibbare RAG-Architektur ohne externe APIs und ohne zusätzliche Infrastruktur, die über eine bestehende PostgreSQL-Instanz hinausgeht.
Seminarempfehlungen
POSTGRESQL ADMINISTRATION – GRUNDLAGEN [DB-PG-01]
Mehr erfahrenPOSTGRESQL ADMINISTRATION – AUFBAU [DB-PG-02]
Mehr erfahren