Abruf: Chunking und Ranking
Auf dieser Seite
Abruf: Chunking und Ranking
Die Dateisuche stützt sich auf einen vollständigen Abruf-Pipeline: Ihre Dokumente werden in Segmente unterteilt, vektorisiert, indiziert und anschließend bei eintreffenden Abfragen nach Relevanz sortiert. Das Verständnis dieser Pipeline ermöglicht die Optimierung der Antwortqualität.
Die Abruf-Pipeline
Wenn Sie eine Datei in einen Vektorspeicher hochladen, führt OpenAI automatisch folgende Schritte aus:
- Parsing: Extrahiert rohen Text aus dem Quelldateiformat
- Chunking: Teilt den Text in Segmente mit kontrollierter Größe auf
- Embedding: Vektorisiert jedes Segment
- Indizierung: Speichert die Vektoren im Vektorindex
Zum Zeitpunkt der Abfrage:
- Abfrage-Embedding: Die Frage wird vektorisiert
- Vektorsuche: Identifiziert die nächstgelegenen Segmente
- Ranking: Sortiert die Ergebnisse nach Relevanz neu
- Einbindung: Bindet die besten Segmente in den Kontext des Modells ein
Konfigurierung der Chunking-Strategie
Chunking ist die Einstellung, die alle anderen beeinflusst, und der Zielkonflikt ist einfach zu beschreiben. Kurze Segmente liefern präzise Suchergebnisse, liefern aber Auszüge ohne Kontext – Sie finden den richtigen Satz, verlieren aber den Bezug, auf den er sich bezieht. Lange Segmente bewahren den Kontext, vergraben jedoch relevante Informationen in irrelevanter Textmasse, was den Ähnlichkeitswert schwächt.
Es gibt keinen universellen Wert: Die richtige Einstellung hängt von der Struktur Ihres Dokuments ab, genau wie in der nachfolgenden Tabelle zusammengefasst.
from openai import OpenAI
client = OpenAI()
# Standardstrategie: auto
vector_store = client.vector_stores.create(
name="legal-base",
chunking_strategy={"type": "auto"}
)
# Benutzerdefinierte Strategie: kleinere Segmente für höhere Präzision
vector_store = client.vector_stores.create(
name="support-faq",
chunking_strategy={
"type": "static",
"static": {
"max_chunk_size_tokens": 400,
"chunk_overlap_tokens": 100
}
}
)
Auswahl der richtigen Segmentgröße
| Inhaltstyp | max_chunk_size | Overlap | Warum |
|---|---|---|---|
| FAQ, Q&A | 200-400 | 50 | Kurze, eigenständige Antworten |
| Technische Dokumentation | 600-800 | 200 | Absätze mit Kontext |
| Verträge, Rechtstexte | 800-1200 | 300 | Lange, miteinander verknüpfte Klauseln |
| Quellcode | 400-600 | 100 | Funktionen und Klassen |
Auswirkung des Overlaps
Overlap löst ein spezifisches Problem: Informationen, die eine Segmentgrenze überschreiten, würden sonst halbiert, sodass weder die eine noch die andere Hälfte die Abfrage trifft. Durch das Überlappen von Segmenten garantieren Sie, dass jeder Satz in mindestens einem Segment vollständig erscheint. Der Nachteil ist Redundanz: Ein Overlap von 300 Tokens bei 800-Token-Segmenten speichert fast 40 % doppelten Text.
# Originaltext (vereinfacht)
# "Klausel 5.1 besagt, dass der Lieferant innerhalb von 30 Tagen liefern muss.
# Im Falle einer Verzögerung greifen Sanktionen in Höhe von 1 % pro Tag.
# Klausel 5.2 legt fest, dass Sanktionen auf 15 % begrenzt sind."
# Ohne Overlap: Klausel 5.1 und Sanktionen könnten in verschiedenen Segmenten landen
# Mit Overlap: der Zusammenhang zwischen Klausel und Sanktionen bleibt erhalten
Relevanzschwellenwert und Ranking
Das Ranking bestimmt, welche Segmente beibehalten werden. Sie können einen Filter konfigurieren:
response = client.responses.create(
model="gpt-5.6-terra",
input="Was ist die Rückgabepolitik?",
tools=[{
"type": "file_search",
"vector_store_ids": [vector_store.id],
"max_num_results": 10,
"ranking_options": {
"ranker": "auto",
"score_threshold": 0.5
}
}]
)
Der score_threshold filtert Ergebnisse unterhalb eines Relevanzschwellenwerts heraus. Ein Wert von 0,0 gibt alles zurück, während 1,0 zu restriktiv wäre. In der Praxis liefern Werte zwischen 0,3 und 0,6 gute Ergebnisse.
Optimierung der Abrufqualität
Vorbereitung der Dokumente
Dies ist der kosteneffektivste Hebel in dieser gesamten Lektion und der einzige, der in den Einstellungen nichts kostet: Ein gut strukturiertes Dokument wird gut indiziert. Verwenden Sie explizite Überschriften, einen einleitenden Satz für jeden Abschnitt und vermeiden Sie Tabellen, die in Textschleim umgewandelt wurden. Umgekehrt wird kein Chunking-Einstellung ein PDF retten, dessen Extraktion verflochtene Spalten erzeugt.
# Schlecht: Gescanntes PDF mit ungenauer OCR
# -> Inkohärente Segmente, Embeddings schlechter Qualität
# Gut: Strukturiertes Dokument mit Überschriften und Abschnitten
# -> Segmente, die mit logischen Abschnitten übereinstimmen
# Muster: Vorverarbeitung vor dem Hochladen
def prepare_document(raw_text: str) -> str:
"""Reinigt und strukturiert Text vor der Indizierung."""
# Entfernt wiederkehrende Kopfzeilen/Fußzeilen
lines = raw_text.split("\n")
lines = [l for l in lines if not is_header_footer(l)]
# Normalisiert Leerzeichen
text = "\n".join(lines)
text = re.sub(r"\n{3,}", "\n\n", text)
return text
Anreicherung mit Metadaten
Metadaten ermöglichen eine Vorfilterung vor der Vektorsuche, was die Präzision verbessert:
# Hochladen mit reichhaltigen Metadaten
documents = [
{"file": "hr-policy-2026.pdf", "department": "hr", "type": "policy", "year": 2026},
{"file": "supplier-contract-a.pdf", "department": "procurement", "type": "contract", "year": 2026},
{"file": "it-security-guide.pdf", "department": "it", "type": "guide", "year": 2025},
]
for doc in documents:
with open(doc["file"], "rb") as f:
file_obj = client.files.create(file=f, purpose="assistants")
client.vector_stores.files.create(
vector_store_id=vector_store.id,
file_id=file_obj.id,
attributes={
"department": doc["department"],
"document_type": doc["type"],
"year": doc["year"]
}
)
Messung der Abrufqualität
Ohne Messung ist jede Chunking-Einstellung nur Intuition. Eine Reihe von zwanzig Fragen, bei denen Sie die erwartete Antwort und das enthaltende Dokument kennen, reicht aus, um den Prozess zu objektivieren: Ändern Sie einen Parameter, führen Sie den Testsatz erneut aus und vergleichen Sie die Ergebnisse. Das ist wenig Arbeit im Vergleich zu Stunden, die mit der Debatte über die Segmentgröße ohne Daten verbracht werden.
def evaluate_retrieval(test_questions: list[dict], vs_id: str) -> dict:
"""Bewertet die Abrufqualität an einem Testsatz.
Jedes Element in test_questions enthält:
- question: die zu stellende Frage
- expected_answer: erwartete Schlüsselwörter oder Phrasen in der Antwort
"""
results = {"total": 0, "relevant": 0, "irrelevant": 0}
for test in test_questions:
response = client.responses.create(
model="gpt-5.6-terra",
input=test["question"],
tools=[{
"type": "file_search",
"vector_store_ids": [vs_id],
"max_num_results": 5
}]
)
answer = response.output_text.lower()
found_keywords = sum(
1 for word in test["expected_answer"]
if word.lower() in answer
)
results["total"] += 1
if found_keywords >= len(test["expected_answer"]) * 0.5:
results["relevant"] += 1
else:
results["irrelevant"] += 1
results["relevance_rate"] = results["relevant"] / results["total"]
return results
Erweiterte Strategien
Mehrere Vektorspeicher
Führen Sie Abfragen an mehreren Dokumentenbasen parallel durch:
response = client.responses.create(
model="gpt-5.6-terra",
input="Vergleichen Sie unsere HR-Richtlinie mit gesetzlichen Verpflichtungen",
tools=[{
"type": "file_search",
"vector_store_ids": [
vs_internal_policies,
vs_legal_texts
],
"max_num_results": 15
}]
)
Zerlegte Abfragen
Eine Frage, die mehrere Anforderungen enthält, erzeugt einen durchschnittlichen Vektor, der keiner davon ähnelt, was zu schlechten Suchergebnissen für alle führt. Das Aufbrechen in separate Teilfragen liefert deutlich bessere Ergebnisse – dies ist das gleiche Prinzip wie bei Websuchen: Eine präzise Abfrage schlägt eine erschöpfende.
def decomposed_search(complex_question: str, vs_id: str) -> str:
"""Zerlegt eine komplexe Frage in Teilabfragen."""
# Schritt 1: Zerlegung der Frage
decomposition = client.responses.create(
model="gpt-5.6-terra",
input=f"Zerlegen Sie diese Frage in 2-3 einfache Teilfragen: {complex_question}",
)
# Schritt 2: Suche für jede Teilfrage
sub_results = []
for sub_q in decomposition.output_text.split("\n"):
if sub_q.strip():
r = client.responses.create(
model="gpt-5.6-terra",
input=sub_q.strip(),
tools=[{"type": "file_search", "vector_store_ids": [vs_id]}]
)
sub_results.append(r.output_text)
# Schritt 3: Synthese
synthesis = client.responses.create(
model="gpt-5.6-terra",
input=f"Ursprüngliche Frage: {complex_question}\n\n"
f"Zwischenergebnisse:\n" + "\n---\n".join(sub_results) +
"\n\nSynthetisieren Sie eine vollständige und kohärente Antwort."
)
return synthesis.output_text
Kernaussagen
staticChunking mit benutzerdefinierter Größe und Overlap verbessert die Präzision- Kleine Segmente für FAQs, große Segmente für Rechtstexte
score_thresholdfiltert irrelevante Ergebnisse heraus- Bereiten und strukturieren Sie Ihre Dokumente vor der Indizierung
- Bewerten Sie die Abrufqualität mit einem Testsatz