Ihr
LLM
hat\gerade
selbstbewusst
eine
Forschungsarbeit
zitiert,
die
es
nicht
gibt.
Sie\haben
es
nach
Ihren
API-Dokumenten
für
Ihr
Unternehmen
gefragt,
und
es
beschrieb
Endpunkte,
die
2019
depreziert
wurden.
Das
passiert,
weil
Sprachmodelle
Text
basierend
auf
Mustern
in
Trainingsdaten
generieren,
nicht
indem
sie
Ihre
tatsächlichen
Informationen
abfragen.
Retrieval Augmented Generation
(RAG)
löst
dieses
Problem.
Nicht,
indem
Modelle
schlauer
gemacht
werden,
sondern
indem
ihnen
Zugriff
auf
echte
Daten
gewährt
wird,
bevor
sie
eine
Antwort
generieren.
Die
Technik
ist
essenziell
für
Produktionssysteme
geworden,
aber
die
meisten
Implementierungen
scheitern
leise
—
entweder
indem
sie
irrelevante
Dokumente
zurückgeben
oder
die
Abrufung
so
verbessern,
dass
das
Modell
von
zu
viel
Kontext
verwirrt
wird.
Diese
Anleitung
führt
durch,
wie
RAG
tatsächlich
funktioniert,
warum
einfache
Setups
scheitern
und
welche
spezifischen
Mustern
in
der
Produktion
funktionieren.
Was
RAG
tatsächlich
leistet
RAG
besteht
aus
drei
Schritten:
- Abrufen
(Retrieve):
Durchsuchen
Sie
Ihren
Dokumentenspeicher
nach
Inhalten,
die
für
die
Benutzerfrage
relevant
sind. - Erweitern
(Augment):
Fügen
Sie
die
abgerufenen
Dokumente
zusammen
mit
der
ursprünglichen
Frage
in
den
Prompt
ein. - Generieren
(Generate):
Das
LLM
antwortet
basierend
auf
der
Frage
und
dem
abgerufenen
Kontext.
Die
kritische
Erkenntnis:
Das
Modell
halluziniert
nie
über
Ihre
Daten,
weil
es
Ihre
tatsächlichen
Daten
beim
Generieren
der
Antwort
betrachtet.
Es
hat
die
Informationen
direkt
vor
sich.
Aber
hier
trennen
sich
die
Implementierungen.
Ein
naives
RAG-System
ruft
jedes
vage
relevante
Dokument
ab,
überflutet
das
Kontextfenster
und
das
Modell
verliert
sich
im
Lärm.
Ein
gut
abgestimmtes
RAG-System
ruft
genau
das
ab,
was
wichtig
ist,
ordnet
es
nach
Relevanz
und
das
Modell
erhält
eine
kla
re
Anleitung.
Warum
einfaches
RAG
scheitert:
Die
drei
häufigsten
Fehlerquellen
Bevor
Sie
etwas
reparieren,
verstehen
Sie,
wo
es
bricht.
Fehlerquelle
1:
Die
Abrufung
liefert
Lärm.
Sie
durchsuchen
Ihre
Dokumentendatenbank
mit
einfacher
Schlüsselwortsuche
oder
simplen
Embeddings
und\erhalten
10
Ergebnisse,
von
denen
nur
2
relevant
sind.
Das
LLM
sieht
den
Lärm
und
wird
entweder
verwirrt
oder
ignoriert
die
guten
Informationen
vollständig.
Das
ist
das
häufigste
Fehlermuster.
Fehlerquelle
2:
Abgerufene
Dokumente
sind
zu
lang.
Selbst
wenn
die
Abrufung
funktioniert,
rufen
Sie
ganze
Dokumente
ab
(je
800+
Tokens).
Ihr
4K-Kontextfenster
ist
weg.
Claude
oder
GPT-4o
verschwendet
Tokens,
um
irrelevante
Abschnitte
zu
parsen,
und
hat
keinen
Raum
für
Nuancen
in
der
Antwort.
Fehlerquelle
3:
Der
Abruf-Generierungs-Mismatch.
Ihr
Abrufsystem
findet
Dokumente,
die
für
einen
Abfragetyp
optimiert
sind
(faktische
Nachschlagevorgänge),
aber
der
Generierungsschritt
benötigt
anders
strukturierte
Dokumente
(für
Schlussfolgerungen,
für
Beispiele,
für
Sonderfälle).
Sie
lösen
zwei
unterschiedliche
Probleme
mit
einem
Retriever.
Erstellung
einer
Produktions-RAG-Pipeline:
Schritt
für
Schritt
Ein
funktionierendes
RAG-System
benötigt
vier
Komponenten:
einen
Dokumentenspeicher,
einen
Retriever,
einen
Ranker
und
eine
Prompt-Struktur.
Bauen
wir
eins.
Schritt
1:
Dokumentenaufnahme
und
Chunking
Rohe
Dokumente
sind
zu
groß,
um
sie
effizient
abzurufen.
Sie
müssen\sie
in
Chunks
aufteilen
—
typischerweise
300-500
Tokens
je,
mit
10-20%
Überlappung
zwischen
den
Chunks.
#
Beispiel:
Chunking
eines
Dokuments
für
RAG
documents
=
load_documents("company_docs/")
chunks
=
[]
for
doc
in
documents:
text
=
doc.content
#
Naives
Vorgehen:
Teilen
bei
jedem
400
Tokens
for
i
in
range(0,
len(text),
300):
chunk
=
text[i:i+400]
chunks.append({
"id":
f"{doc.name}_chunk_{len(chunks)}",
"content":
chunk,
"source":
doc.name,
"metadata":
doc.metadata
})
#
Besseres
Vorgehen:
Chunking
an
semantischen
Grenzen
from
langchain.text_splitter
import
RecursiveCharacterTextSplitter
splitter
=
RecursiveCharacterTextSplitter(
chunk_size=400,
chunk_overlap=50,
separators=[\"
\",
\"
\",
\"
\",
\"\"]
)
chunks
=
[]
for
doc
in
documents:
splits
=
splitter.split_text(doc.content)
for
split
in
splits:
chunks.append({
"id":
f"{doc.name}_chunk_{len(chunks)}",
"content":
split,
"source":
doc.name
})
Der
Schlüssel
hier:
Nicht
zufällig
teilen.
Verwenden
Sie
RecursiveCharacterTextSplitter,
um
zuerst
bei
Absatzgrenzen,
dann
bei
Sätzen
und
zule
bei
Wörtern
zu
teilen.
Das
hält
semantische
Einheiten
intakt.
Schritt
2:
Embedding
und
Speicherung
Wandeln
Sie
jeden
Chunk
in
ein
Vektor-Embedding
um.
Das
ist,
was
semantische
Suche
möglich
macht
—
ähnliche
Bedeutungen
erzeugen
ähnliche
Vektoren,
unabhängig
von
exakten
Wortübereinstimmungen.
#
Embedding
von
Chunks
und
Speicherung
from
openai
import
OpenAI
from
pinecone
import
Pinecone
client
=
OpenAI()
pc
=
Pinecone(api_key="your-key")
index
=
pc.Index("rag-index")
#
Jeden
Chunk
embedden
embedded_chunks
=
[]
for
chunk
in
chunks:
response
=
client.embeddings.create(
input=chunk["content"],
model="text-embedding-3-small"
)
embedding
=
response.data[0].embedding
embedded_chunks.append({
"id":
chunk["id"],
"values":
embedding,
"metadata":
{
"text":
chunk["content"],
"source":
chunk["source"]
}
})
#
Upsert
in
vektorbasierte
Datenbank
index.upsert(vectors=embedded_chunks)
Verwenden
Sie
text-embedding-3-small
(oder
ähnliches
wie
Mistral
Embed):
es
ist
günstiger
als
große
Modelle,
schnell
genug
für
Echtzeitabruf
und
leistungsfähig
genug
für
semantische
Suche.
Für
die
meisten
Produktionsanwendungsfälle
benötigen
Sie
keine
großen
Embeddings.
Schritt
3:
Abruf
mit
Ranking
Hier
machen
die
meisten
Systeme
alles
falsch.
Der
Abruf
findet
Kandidaten;
das
Ranking
ordnet
sie
nach
Relevanz.
Sie
brauchen
beides.
#
Abruf
+
Ranking-Pipeline
def
retrieve_and_rank(query,
top_k=10,
final_k=3):
#
Schritt
1:
Vektorsuche
(fokussiert
auf
Recall)
query_embedding
=
client.embeddings.create(
input=query,
model="text-embedding-3-small"
).data[0].embedding
candidates
=
index.query(
vector=query_embedding,
top_k=top_k,
include_metadata=True
)
#
Schritt
2:
Neubewertung
(fokussiert
auf
Präzision)
#
Verwenden
Sie
ein
kleines
Ranker-Modell
oder
LLM
zum
Neubewerten
documents
=
[
{
"id":
match["id"],
"text":
match["metadata"]["text"],
"source":
match["metadata"]["source"]
}
for
match
in
candidates[\"matches\"]
]
#
Bewerten
Sie
jeden
Kandidaten
bezüglich
der
Abfrage
scores
=
[]
for
doc
in
documents:
#
Verwenden
Sie
Claude,
um
die
Relevanz
zu
prüfen
response
=
client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=10,
system="Bewerte
die
Relevanz
dieses
Dokuments
für
die
Abfrage
(nur
Zahl
von
1-10).",
messages=[
{
"role":
"user",
"content":
f"Abfrage:
{query}
Dokument:
{doc['text']}"
}
]
)
score
=
int(response.content[0].text)
scores.append((doc,
score))
#
Geben
Sie
die
am
höchsten
bewerteten
Dokumente
zurück
ranked
=
sorted(scores,
key=lambda
x:
x[1],
reverse=True)
return
[doc
for
doc,
score
in
ranked[:final_k]]
Dieser
zweigeteilte
Ansatz
funktioniert,
weil
die
Vektorsuche
schnell,
aber
unvollkommen
ist.
Ein
zweites
Ranking-Verfahren
—
sogar
ein
simpleres
—
verbessert
das,
was
tatsächlich
beim
LLM
ankommt,
dramatisch.
In
der
Produktion
bei
AlgoVesta
reduzierte
das
Hinzufügen
von
Re-Ranking
unsere
irrelevante
Abrufung
um
ca.
60%.
Schritt
4:
Prompt-Konstruktion
Nachdem
Sie
relevante
Dokumente
haben,
wie
bringen
Sie
sie
in
den
Prompt
ein?
Der
Kontext
ist
väußerst
wichtig.
#
Schlechter
Prompt:
zu
vage
bezüglich
des
Kontexts
bad_prompt
=
f"""
Beantworten
Sie
diese
Frage:
{user_question}
Hier
ist
etwas
Hintergrund:
{retrieved_documents}
"""
#
Besserer
Prompt:
explizite
Anweisungen
zur
Verwendung
des
Kontexts
better_prompt
=
f"""
Sie
haben
Zugriff
auf
die
f\u00fclgenden
Dokumente:
---
{retrieved_documents}
---
Beantworten
Sie
die
Benutzerfrage,
indem
Sie
NUR
Informationen
aus
den
obigen
Dokumenten
verwenden.
Wenn
die
Dokumente
nicht
genug
Informationen
zur
Beantwortung
enthalten,
sagen
Sie
dies
eindeutig.
Zitieren
Sie\immer
das
Quellendokument,
wenn
Sie
Informationen
daraus
verwenden.
Frage:
{user_question}
"""
Die
Verbesserung:
explizite
Anweisung,
was
mit
dem
Kontext
zu
tun
ist.
Sagen
Sie
dem
Modell,
es
nur
die
bereitgestellten
Dokumente
soll
verwenden,
es
sagen
soll,
wenn
Informationen
fehlen,
und
Quellen
zitieren.
Ohne
diese
Anweisungen
fallen
Modelle
manchmal
auf
Trainingsdaten
zurück,
wenn
Dokumente
mehrdeutig
sind.
Gängige
RAG-Muster
und
wann
sie
funktionieren
Drei
Muster
dominieren
Produktions-RAG-Systeme.
Jedes
löst
unterschiedliche
Probleme.
Muster
1:
Retrieval-Augmented
Generation
(Standard-RAG)
Funktionsweise:
Dokumente
abrufen,
zum
Prompt
hinzufügen,
Antwort
in
einem
Durchgang
generieren.
Wann
es
funktioniert:
Faktische
Nachschlagevorgänge,
Fragen
und
Antworten
über
Dokumentation,
problemloses
Beantworten
von
Fragen.
Die
Antwortzeit
spielt
eine
Rolle.
Sie
benötigen
eine
einzige,
kohärente
Antwort.
Latenz:
~500ms-1s
(Embedding
+
Abruf
+
LLM-Aufruf)
Anwendungsfall:
Beispiel: RAG erdet LLMs mit Ihren echten Daten und eliminiert Halluzinationen. Diese Anleitung erklärt, wie RAG in der Produktion funktioniert, warum einfache Setups scheitern und welche spezifischen Muster funktionieren — mit Codebeispielen und Abwägungen.
Wichtige Erkenntnisse: [