Zum Inhalt springen
KI & LLMs Fortgeschritten 35 min

Embeddings & RAG

Wie du LLMs externes Wissen gibst: Embeddings, Vector-Datenbanken und Retrieval-Augmented Generation (RAG). Die Basis fuer Doku-Chatbots, Wissens-Apps und mehr.

Aktualisiert:
Inhaltsverzeichnis

Embeddings & RAG

Ein LLM โ€œweissโ€ nur, was in seinem Training war. Fuer Firmendaten, eigene Dokumente oder aktuelle Infos brauchst du Retrieval-Augmented Generation (RAG) - der Pattern fast aller โ€œChat mit deinen Datenโ€-Apps.

Das Problem

Du willst einen Bot, der Fragen zu deiner Doku beantwortet. Der LLM kennt sie nicht. Was tun?

Naive Loesung: Dokumentation einfach in den Prompt packen.

Problem: Kontext-Fenster sind zu klein (oder teuer), und Relevanz leidet.

Richtige Loesung: RAG.

RAG in einem Satz

Finde die relevanten Textstuecke, packe nur die in den Prompt.

Wie findet man die Relevanten? Mit Embeddings und einer Vector-Datenbank.

Embeddings

Ein Embedding ist ein numerischer Vektor, der die Bedeutung eines Texts darstellt. Aehnliche Bedeutung = aehnlicher Vektor.

Beispiel:

  • โ€œHundโ€ โ†’ [0.12, -0.05, 0.88, โ€ฆ]
  • โ€œKatzeโ€ โ†’ [0.10, -0.07, 0.85, โ€ฆ] โ† sehr aehnlich
  • โ€œStaubsaugerโ€ โ†’ [0.91, 0.20, -0.15, โ€ฆ] โ† ganz anders

Die Vektoren sind typischerweise 768-3000 Dimensionen gross.

Embedding berechnen

Mit OpenAIs Embedding-API:

from openai import OpenAI
client = OpenAI()

response = client.embeddings.create(
    model="text-embedding-3-small",
    input="Rust ist eine Systemsprache."
)

vector = response.data[0].embedding
print(len(vector))     # 1536

Kosten: winzig (~$0.00002 pro Tausend Tokens).

Aehnlichkeit messen

Die Cosine Similarity zweier Vektoren zeigt, wie aehnlich sie sind:

import numpy as np

def cosine(a, b):
    a, b = np.array(a), np.array(b)
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

a = embed("Der Hund bellt laut.")
b = embed("Ein Wauwau macht Geraeusche.")
c = embed("Der Kuchen schmeckt suess.")

print(cosine(a, b))    # hoch (~0.85)
print(cosine(a, c))    # niedrig (~0.20)

Das Prinzip funktioniert sprachuebergreifend - ein deutscher und englischer Satz mit gleicher Bedeutung haben aehnliche Embeddings.

RAG-Pipeline - die 5 Schritte

Hier der klassische Ablauf:

1. Chunking

Deine Dokumente in kleine Haeppchen aufteilen (500-1500 Tokens pro Chunk):

def chunks(text: str, size: int = 500, overlap: int = 50):
    result = []
    for i in range(0, len(text), size - overlap):
        result.append(text[i:i + size])
    return result

Gute Chunks respektieren Absatz- und Satzgrenzen. Libraries wie LangChain oder LlamaIndex haben fertige Splitter.

2. Embedding berechnen

Fuer jeden Chunk einen Vektor erzeugen - einmalig, offline.

3. In Vector-DB speichern

Du brauchst eine Datenbank, die nach Aehnlichkeit sucht:

  • Qdrant - Open Source, Rust, top Performance
  • Pinecone - Managed Service, sehr einfach
  • pgvector - PostgreSQL-Extension
  • ChromaDB - simpel, lokal
  • Weaviate - Open Source mit Schema-Support

Vereinfacht kannst du auch ohne DB auskommen - fuer Prototypen reichen In-Memory-Vektoren.

4. Query retrieval

Bei einer User-Frage:

  1. Frage in Embedding umwandeln
  2. Top-K (z.B. 5) aehnlichste Chunks aus der DB holen

5. Augmented prompt

Die Chunks als Kontext in den LLM-Prompt:

Beantworte die Frage basierend auf dem Kontext.
Wenn die Antwort nicht im Kontext steht, sag das.

Kontext:
[Chunk 1]
[Chunk 2]
[Chunk 3]

Frage: [User-Frage]

Das LLM antwortet nur mit den gegebenen Infos.

Ein minimaler RAG (Python)

from openai import OpenAI
import numpy as np

client = OpenAI()

# 1) Unsere "Datenbank" - drei Texte
docs = [
    "Unsere Oeffnungszeiten: Mo-Fr 9-18 Uhr, Sa 10-14 Uhr.",
    "Ruecksendungen sind innerhalb 30 Tagen kostenlos moeglich.",
    "Unser Flagship-Produkt ist die Pro-Cloud fuer 29 EUR/Monat.",
]

# 2) Embeddings berechnen
def embed(text: str) -> np.ndarray:
    r = client.embeddings.create(
        model="text-embedding-3-small",
        input=text
    )
    return np.array(r.data[0].embedding)

doc_vectors = [embed(d) for d in docs]

# 3) Frage und Retrieval
def answer(frage: str, top_k: int = 2) -> str:
    q_vec = embed(frage)

    sims = [
        (np.dot(q_vec, dv) / (np.linalg.norm(q_vec) * np.linalg.norm(dv)), i)
        for i, dv in enumerate(doc_vectors)
    ]
    top = sorted(sims, reverse=True)[:top_k]

    context = "\n".join(docs[i] for _, i in top)

    r = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content":
                "Beantworte NUR mit den Infos aus dem Kontext. Auf Deutsch."},
            {"role": "user", "content":
                f"Kontext:\n{context}\n\nFrage: {frage}"}
        ]
    )
    return r.choices[0].message.content

# Ausprobieren:
print(answer("Wann habt ihr am Samstag offen?"))
print(answer("Kann ich was zurueckschicken?"))

Drei Zeilen Infrastruktur plus ein Paar API-Calls - fertig ist dein Doku-Bot.

Vector-DB mit Qdrant (Produktion)

Fuer echte Apps nutzt du eine Vector-DB:

from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct

qdrant = QdrantClient(":memory:")  # oder URL

qdrant.create_collection(
    collection_name="docs",
    vectors_config=VectorParams(size=1536, distance=Distance.COSINE),
)

# Insert
points = [
    PointStruct(id=i, vector=embed(d).tolist(), payload={"text": d})
    for i, d in enumerate(docs)
]
qdrant.upsert(collection_name="docs", points=points)

# Query
hits = qdrant.query_points(
    collection_name="docs",
    query=embed(frage).tolist(),
    limit=3,
).points

for hit in hits:
    print(hit.score, hit.payload["text"])

Qdrant skaliert auf Millionen Dokumente und ist extrem schnell.

Chunk-Groessen und Overlap

Zu klein (50 Tokens) โ†’ keine Context Zu gross (2000+ Tokens) โ†’ irrelevanter Fuellstoff

Faustregel: 500-1000 Tokens pro Chunk, mit 50-100 Tokens Overlap (damit wichtige Infos nicht an Grenzen zerrissen werden).

Reranking - die naechste Stufe

Einfaches Top-K liefert oft mittelmaessige Ergebnisse. Bessere Loesung:

  1. Retrieve: Top 20 Kandidaten per Embedding
  2. Rerank: Cross-Encoder-Modell bewertet jeden einzeln
  3. Nimm die besten 3-5 als Kontext

Cohereโ€™s /rerank-API ist dafuer beliebt.

Noch besser: Kombiniere Embedding-Search mit klassischer BM25-Volltext-Suche. Beide haben Staerken - zusammen ergeben sie die besten Ergebnisse.

Qdrant, Weaviate und viele andere unterstuetzen das nativ.

Frameworks

Fuer echte Apps lohnen sich Libraries:

  • LangChain - der bekannte Baukasten (Python + JS)
  • LlamaIndex - speziell fuer RAG
  • Haystack - reif, Enterprise-tauglich

Sie bringen Chunking, Retrieval, Prompt-Templates mit.

Wann RAG vs. Fine-Tuning?

  • RAG: Wenn du Wissen hinzufuegen willst (Doku, FAQ, Produktkatalog)
  • Fine-Tuning: Wenn du Stil oder Verhalten aendern willst

RAG ist fast immer der bessere Start - billiger, flexibler, schneller zu iterieren.

Zusammenfassung

  • Embeddings sind numerische Vektoren, die Bedeutung darstellen
  • RAG = Relevante Chunks finden + als Kontext in den LLM-Prompt
  • Pipeline: Chunk โ†’ Embed โ†’ Store โ†’ Retrieve โ†’ Generate
  • Tools: Qdrant, pgvector, Pinecone als Vector-DB
  • Reranking und Hybrid Search verbessern die Qualitaet
  • Fuer Wissen: RAG statt Fine-Tuning

Im naechsten Kapitel: LLM-Agenten, die Tools nutzen koennen.

Zurรผck zum KI & LLMs Kurs