Vektordatenbanken und Embeddings
Wie Embeddings Text in Zahlen verwandeln – und warum das die Grundlage für RAG, Suche und KI-Anwendungen ist.
Wie Embeddings funktionieren, welche Vektordatenbank du wählen solltest und wie du semantische Suche in der Praxis implementierst. Mit Python-Beispielen für Pinecone, Chroma und pgvector.
Was sind Embeddings?
Ein Embedding ist eine numerische Darstellung von Text (oder Bildern, Audio, etc.) als Vektor – eine Liste von Zahlen. Das Besondere: Texte mit ähnlicher Bedeutung haben ähnliche Vektoren, auch wenn sie völlig unterschiedliche Wörter verwenden.
"Wie trainiere ich ein ML-Modell?"
→ [0.023, -0.187, 0.445, 0.012, -0.334, ...] # 1536 Zahlen
"Welche Schritte brauche ich um Machine Learning anzuwenden?"
→ [0.019, -0.201, 0.438, 0.008, -0.341, ...] # sehr ähnliche Zahlen!
"Das Wetter ist heute schön."
→ [0.412, 0.089, -0.234, 0.567, 0.123, ...] # völlig andere Zahlen
Diese Eigenschaft macht Embeddings zur Grundlage für semantische Suche, RAG und viele andere KI-Anwendungen.
Wie Ähnlichkeit gemessen wird
Cosine Similarity
Die häufigste Metrik ist Cosine Similarity – sie misst den Winkel zwischen zwei Vektoren, nicht ihre Distanz.
Cosine Similarity = 1.0 → identische Bedeutung
Cosine Similarity = 0.9 → sehr ähnlich
Cosine Similarity = 0.7 → verwandt
Cosine Similarity = 0.0 → keine Beziehung
Cosine Similarity = -1.0 → gegensätzlich
import numpy as np
def cosine_similarity(a: list[float], b: list[float]) -> float:
a, b = np.array(a), np.array(b)
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# Beispiel
vec_frage = [0.023, -0.187, 0.445]
vec_antwort = [0.019, -0.201, 0.438]
vec_unrelated = [0.412, 0.089, -0.234]
print(cosine_similarity(vec_frage, vec_antwort)) # ~0.999 – sehr ähnlich
print(cosine_similarity(vec_frage, vec_unrelated)) # ~0.1 – nicht verwandt
Andere Metriken
- Euclidean Distance (L2): Misst den Abstand im Raum – funktioniert, aber Cosine ist bei normalisierten Vektoren meist besser
- Dot Product: Schneller als Cosine, aber abhängig von der Vektorgröße
- Manhattan Distance: Selten bei Embeddings genutzt
Embeddings generieren
OpenAI Embeddings API
from openai import OpenAI
client = OpenAI()
def get_embedding(text: str, model: str = "text-embedding-3-small") -> list[float]:
text = text.replace("\n", " ")
response = client.embeddings.create(input=[text], model=model)
return response.data[0].embedding
# Einzelner Text
embedding = get_embedding("Was ist Machine Learning?")
print(f"Dimensionen: {len(embedding)}") # 1536
# Batch (günstiger als einzeln)
texts = ["Text 1", "Text 2", "Text 3"]
response = client.embeddings.create(input=texts, model="text-embedding-3-small")
embeddings = [item.embedding for item in response.data]
Kosten: text-embedding-3-small kostet $0.02 pro Million Tokens – für die meisten Projekte vernachlässigbar.
Lokale Embeddings (kostenlos)
from sentence_transformers import SentenceTransformer
# Mehrsprachiges Modell – gut für Deutsch
model = SentenceTransformer("intfloat/multilingual-e5-large")
texts = ["Was ist Machine Learning?", "Wie funktioniert KI?"]
embeddings = model.encode(texts, normalize_embeddings=True)
print(embeddings.shape) # (2, 1024)
Vektordatenbanken im Vergleich
| Datenbank | Typ | Stärken | Wann nutzen? |
|---|---|---|---|
| Chroma | Lokal / Cloud | Einfach, Python-nativ, kostenlos | Prototypen, kleine Projekte |
| Pinecone | Cloud (managed) | Skalierbar, einfache API, Metadaten-Filter | Produktion, wenn kein eigener Server |
| Weaviate | Self-hosted / Cloud | Hybrid Search, GraphQL, sehr flexibel | Komplexe Anforderungen |
| pgvector | PostgreSQL-Extension | Kein neues System, SQL-Queries | Wenn PostgreSQL schon im Stack |
| Qdrant | Self-hosted / Cloud | Schnell, Rust-basiert, gute Filter | Performance-kritische Anwendungen |
| FAISS | Library (Meta) | Extrem schnell, kein Server | Wenn du alles selbst kontrollieren willst |
Chroma – für den Einstieg
import chromadb
from openai import OpenAI
client = OpenAI()
chroma = chromadb.Client()
collection = chroma.create_collection("meine-dokumente")
# Dokumente hinzufügen
dokumente = [
"Machine Learning ist ein Teilbereich der KI.",
"RAG kombiniert Retrieval mit Sprachmodellen.",
"Embeddings kodieren semantische Bedeutung als Vektoren.",
]
# Embeddings generieren und speichern
embeddings = [
client.embeddings.create(input=[doc], model="text-embedding-3-small").data[0].embedding
for doc in dokumente
]
collection.add(
documents=dokumente,
embeddings=embeddings,
ids=[f"doc_{i}" for i in range(len(dokumente))]
)
# Suche
results = collection.query(
query_embeddings=[
client.embeddings.create(
input=["Was sind Vektoren in der KI?"],
model="text-embedding-3-small"
).data[0].embedding
],
n_results=2
)
print(results["documents"])
pgvector – wenn PostgreSQL schon im Stack
-- Extension aktivieren
CREATE EXTENSION vector;
-- Tabelle mit Vektor-Spalte
CREATE TABLE dokumente (
id SERIAL PRIMARY KEY,
inhalt TEXT,
embedding vector(1536),
kategorie TEXT,
erstellt_am TIMESTAMP DEFAULT NOW()
);
-- Index für schnelle Suche
CREATE INDEX ON dokumente USING ivfflat (embedding vector_cosine_ops);
-- Ähnlichkeitssuche mit Metadaten-Filter
SELECT inhalt, 1 - (embedding <=> '[0.1, 0.2, ...]'::vector) AS similarity
FROM dokumente
WHERE kategorie = 'technik'
ORDER BY embedding <=> '[0.1, 0.2, ...]'::vector
LIMIT 5;
import psycopg2
from openai import OpenAI
client = OpenAI()
conn = psycopg2.connect("postgresql://user:pass@localhost/db")
def semantic_search(query: str, kategorie: str = None, top_k: int = 5):
query_embedding = client.embeddings.create(
input=[query], model="text-embedding-3-small"
).data[0].embedding
with conn.cursor() as cur:
if kategorie:
cur.execute("""
SELECT inhalt, 1 - (embedding <=> %s::vector) AS score
FROM dokumente WHERE kategorie = %s
ORDER BY embedding <=> %s::vector LIMIT %s
""", (query_embedding, kategorie, query_embedding, top_k))
else:
cur.execute("""
SELECT inhalt, 1 - (embedding <=> %s::vector) AS score
FROM dokumente ORDER BY embedding <=> %s::vector LIMIT %s
""", (query_embedding, query_embedding, top_k))
return cur.fetchall()
Metadaten-Filter: oft wichtiger als die Vektorsuche
Reine Vektorsuche findet semantisch ähnliche Dokumente – aber oft willst du zusätzlich filtern:
# Nur Dokumente aus 2024, Kategorie "Technik", Sprache "de"
results = collection.query(
query_embeddings=[query_embedding],
n_results=5,
where={
"$and": [
{"jahr": {"$eq": 2024}},
{"kategorie": {"$eq": "technik"}},
{"sprache": {"$eq": "de"}}
]
}
)
Praxistipp: Plane Metadaten von Anfang an mit. Nachträglich hinzufügen bedeutet alle Dokumente neu zu indexieren.
Hybrid Search: Vektor + Keyword
Reine Vektorsuche hat Schwächen bei exakten Begriffen (Produktnummern, Namen, Abkürzungen). Hybrid Search kombiniert Vektorsuche mit klassischer BM25-Keyword-Suche:
from rank_bm25 import BM25Okapi
# BM25 für Keyword-Suche
tokenized_docs = [doc.split() for doc in dokumente]
bm25 = BM25Okapi(tokenized_docs)
def hybrid_search(query: str, alpha: float = 0.5, top_k: int = 5):
"""alpha=1.0: nur Vektor, alpha=0.0: nur BM25"""
# Vektor-Scores
vector_scores = get_vector_scores(query) # normalisiert 0-1
# BM25-Scores
bm25_scores = bm25.get_scores(query.split())
bm25_scores = bm25_scores / bm25_scores.max() # normalisieren
# Kombinieren
combined = alpha * vector_scores + (1 - alpha) * bm25_scores
return sorted(enumerate(combined), key=lambda x: x[1], reverse=True)[:top_k]
Faustregel: alpha=0.6 (60% Vektor, 40% BM25) ist ein guter Startpunkt für die meisten Anwendungen.
Häufige Probleme
Problem: Suche findet irrelevante Ergebnisse
Ursache: Embedding-Modell passt nicht zum Domänenvokabular, oder Chunks zu groß. Lösung: Kleinere Chunks (200–400 Token), domänenspezifisches Embedding-Modell, Hybrid Search.
Problem: Suche ist zu langsam
Ursache: Kein Index, oder Index-Typ falsch gewählt.
Lösung: ivfflat für Geschwindigkeit, hnsw für bessere Recall-Rate. Bei pgvector: SET ivfflat.probes = 10.
Problem: Unterschiedliche Ergebnisse bei gleicher Anfrage
Ursache: Approximate Nearest Neighbor (ANN) Algorithmen sind nicht deterministisch.
Lösung: nprobe/ef_search erhöhen für mehr Genauigkeit (auf Kosten der Geschwindigkeit).
Problem: Embedding-Modell gewechselt, alte Daten passen nicht mehr
Ursache: Embeddings verschiedener Modelle sind nicht kompatibel. Lösung: Alle Dokumente neu einbetten wenn das Modell wechselt. Modellversion in Metadaten speichern.
Semantische Suche mit Chroma und OpenAI
Baue in 20 Minuten eine semantische Suche über eigene Texte – lokal, ohne Cloud-Datenbank.
- Installieren: `pip install chromadb openai`
- OpenAI Client initialisieren und Chroma-Collection erstellen
- 5-10 eigene Texte (z.B. FAQ-Antworten) als Dokumente hinzufügen
- Embeddings automatisch von Chroma generieren lassen (mit OpenAI-Embedding-Modell)
- Suchanfrage stellen: `collection.query(query_texts=['Deine Frage'], n_results=3)`
- Ergebnisse mit klassischer Keyword-Suche vergleichen – wo ist semantische Suche besser?
Was ist der Unterschied zwischen Embeddings und Tokens?
Tokens sind die kleinsten Einheiten in die Text zerlegt wird (Teilwörter). Embeddings sind numerische Vektoren die die semantische Bedeutung eines Textes kodieren – sie entstehen nachdem der Text tokenisiert wurde. Ein Embedding kann einen Token, ein Wort, einen Satz oder ein ganzes Dokument repräsentieren.
Brauche ich eine Vektordatenbank oder reicht ein Array?
Für unter 10.000 Dokumente reicht oft ein einfaches Array mit NumPy-Cosine-Similarity. Ab 100.000+ Dokumenten brauchst du eine echte Vektordatenbank für performante Suche. Chroma ist ein guter Einstieg der beides abdeckt.
Welches Embedding-Modell soll ich nutzen?
Für den Einstieg: OpenAI text-embedding-3-small (günstig, gut). Für Mehrsprachigkeit: multilingual-e5-large (kostenlos, lokal). Für Produktion mit hohem Volumen: Eigenes Modell oder Cohere Embed. Wichtig: Immer dasselbe Modell für Indexierung und Suche nutzen.
Kann ich Vektordatenbank und LLM von verschiedenen Anbietern kombinieren?
Ja, vollständig. Embeddings von OpenAI in Pinecone speichern und mit Claude als LLM nutzen – kein Problem. Embedding-Modell, Vektordatenbank und LLM sind unabhängige Komponenten.
- Embeddings sind Vektoren – Listen von Zahlen die semantische Bedeutung kodieren. Ähnliche Texte haben ähnliche Vektoren
- Cosine Similarity misst den Winkel zwischen Vektoren – nicht die Distanz. Werte nahe 1.0 = sehr ähnlich
- Für Prototypen: Chroma (lokal, kostenlos). Für Produktion: Pinecone oder pgvector je nach Stack
- Embedding-Modell und Vektordatenbank sind unabhängig – du kannst sie frei kombinieren
- Metadaten-Filter sind oft wichtiger als die Vektorsuche selbst – immer mitplanen