Zum Hauptinhalt springen
Lektion 8 von 20 · API-Tools: Suche, Dateien und Code Interpreter

Abruf: Chunking und Ranking

Lektion 8 von 20 Entwickler
Auf dieser Seite
  1. Abruf: Chunking und Ranking
  2. Die Abruf-Pipeline
  3. Konfigurierung der Chunking-Strategie
  4. Relevanzschwellenwert und Ranking
  5. Optimierung der Abrufqualität
  6. Messung der Abrufqualität
  7. Erweiterte Strategien
  8. Kernaussagen

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:

  1. Parsing: Extrahiert rohen Text aus dem Quelldateiformat
  2. Chunking: Teilt den Text in Segmente mit kontrollierter Größe auf
  3. Embedding: Vektorisiert jedes Segment
  4. Indizierung: Speichert die Vektoren im Vektorindex

Zum Zeitpunkt der Abfrage:

  1. Abfrage-Embedding: Die Frage wird vektorisiert
  2. Vektorsuche: Identifiziert die nächstgelegenen Segmente
  3. Ranking: Sortiert die Ergebnisse nach Relevanz neu
  4. 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

Inhaltstypmax_chunk_sizeOverlapWarum
FAQ, Q&A200-40050Kurze, eigenständige Antworten
Technische Dokumentation600-800200Absätze mit Kontext
Verträge, Rechtstexte800-1200300Lange, miteinander verknüpfte Klauseln
Quellcode400-600100Funktionen 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

  • static Chunking mit benutzerdefinierter Größe und Overlap verbessert die Präzision
  • Kleine Segmente für FAQs, große Segmente für Rechtstexte
  • score_threshold filtert irrelevante Ergebnisse heraus
  • Bereiten und strukturieren Sie Ihre Dokumente vor der Indizierung
  • Bewerten Sie die Abrufqualität mit einem Testsatz