Zum Hauptinhalt springen
Lektion 11 von 20 · Produktion, Evaluierung und Optimierung

Kompaktierung: Komprimierung langer Gespräche

Lektion 11 von 20 Aktualisiert am 28. Juli 2026 Fortgeschritten
Auf dieser Seite
  1. Das Problem mit langen Gesprächen
  2. Zwei Strategien im Überblick
  3. Überprüfung, ob Kompaktierung sich lohnt
  4. Kernaussagen

Das Problem mit langen Gesprächen

Jede hinzugefügte Nachricht in einem Gespräch erhöht die Anzahl der an die API gesendeten Tokens. Ein Gespräch mit 50 Runden kann leicht 30.000 Eingabetokens erreichen, was sowohl die Latenz als auch die Kosten in die Höhe treibt. Das Problem ist heimtückisch: Die erste Runde eines Support-Assistenten kostet nur wenige hundert Tokens, während die dreißigste Runde mehrere tausend kostet, obwohl der Nutzer die exakt gleiche Art von Frage stellt. Sie zahlen immer mehr für einen identischen Service, und der Nutzer wartet immer länger auf eine Antwort.

Die Kompaktierung löst dies, indem sie die Historie komprimiert, ohne wesentliche Informationen zu verlieren. Dies beinhaltet das Zusammenfassen älterer Austausche, um die Token-Anzahl zu reduzieren, während der notwendige Kontext erhalten bleibt. Anstatt die gesamte Historie zu senden, senden Sie eine Zusammenfassung vergangener Austausche sowie die jüngsten Nachrichten. Das Modell weiß dann immer noch, dass der Kunde Dupont heißt, dass eine umstrittene Bestellung vorliegt und dass Sie bereits eine Rückerstattung angeboten haben, ohne die vierzig Nachrichten neu lesen zu müssen, die diese Fakten etabliert haben.

Der Mechanismus

Die folgende Funktion wendet diese Logik in ihrer einfachsten Form an: Sie schätzt das Volumen, tut nichts, bis die Schwelle überschritten ist, und teilt dann die Historie in zwei Teile auf. Ältere Nachrichten gehen in die Zusammenfassung; die jüngsten bleiben unverändert.

import openai

client = openai.OpenAI()

def compact_history(
    messages: list[dict],
    token_threshold: int = 10_000,
    keep_recent: int = 6,
) -> list[dict]:
    """Kompaktiert die Historie, wenn sie die Schwelle überschreitet."""

    # Schätzung der Token-Anzahl (Approximation)
    estimated_tokens = sum(len(m["content"]) // 4 for m in messages)

    if estimated_tokens < token_threshold:
        return messages  # Keine Kompaktierung notwendig

    # Aufteilung: alte Nachrichten vs. jüngste
    old_messages = messages[:-keep_recent]
    recent_messages = messages[-keep_recent:]

    # Zusammenfassung der alten Nachrichten
    old_text = "\n".join(
        f"{m['role']}: {m['content']}" for m in old_messages
    )

    response = client.responses.create(
        model="gpt-5.6-terra",
        input=(
            "Fassen Sie dieses Gespräch kurz zusammen. "
            "Behalten Sie wichtige Fakten, getroffene Entscheidungen "
            "und den Kontext bei, der notwendig ist, um das Folgende zu verstehen.\n\n"
            f"{old_text}"
        ),
        max_output_tokens=500,
    )

    # Neuaufbau der kompaktierten Historie
    summary_message = {
        "role": "system",
        "content": f"Zusammenfassung des vorherigen Gesprächs:\n{response.output_text}",
    }

    return [summary_message] + recent_messages

Zwei Details verdienen Ihre Aufmerksamkeit. Der Zusammenfassungs-Prompt fordert explizit getroffene Entscheidungen an, da eine Zusammenfassung, die nur Themen behält, dazu führt, dass das Modell sich seiner eigenen Verpflichtungen nicht mehr erinnert. Und max_output_tokens=500 begrenzt die Zusammenfassung: ohne diese Begrenzung wächst die Zusammenfassung mit jeder Kompaktierung, und Sie landen wieder beim Problem, das Sie zu eliminieren versucht haben.

Zwei Strategien im Überblick

Gleitendes Fenster mit Zusammenfassung

Die folgende Klasse industrialisiert das Prinzip. Sie verwaltet eine einzige Zusammenfassung, die bei jeder Kompaktierung neu geschrieben wird, indem sie neue Austausche integriert, während sie ein Fenster mit rohen Nachrichten behält. Der an die API gesendete Kontext besteht daher immer aus drei Blöcken: dem System-Prompt, der kumulativen Zusammenfassung und dem jüngsten Fenster.

class CompactedConversation:
    """Verwaltet ein Gespräch mit automatischer Kompaktierung."""

    def __init__(
        self,
        system_prompt: str,
        compaction_threshold: int = 8_000,
        window_size: int = 8,
    ):
        self.system_prompt = system_prompt
        self.threshold = compaction_threshold
        self.window_size = window_size
        self.summary: str | None = None
        self.messages: list[dict] = []

    def build_context(self) -> list[dict]:
        """Erstellt den Kontext, der an die API gesendet wird."""
        context = [{"role": "system", "content": self.system_prompt}]

        if self.summary:
            context.append({
                "role": "system",
                "content": f"Vorheriger Kontext: {self.summary}",
            })

        context.extend(self.messages[-self.window_size:])
        return context

    def add_message(self, role: str, content: str):
        self.messages.append({"role": role, "content": content})
        self._check_compaction()

    def _check_compaction(self):
        tokens = sum(len(m["content"]) // 4 for m in self.messages)
        if tokens > self.threshold:
            self._compact()

    def _compact(self):
        old_messages = self.messages[:-self.window_size]
        if not old_messages:
            return

        text = "\n".join(
            f"{m['role']}: {m['content']}" for m in old_messages
        )

        existing_context = f"Bestehende Zusammenfassung: {self.summary}\n\n" if self.summary else ""

        response = client.responses.create(
            model="gpt-5.6-terra",
            input=(
                f"{existing_context}"
                f"Neue Austausche zur Integration in die Zusammenfassung:\n{text}\n\n"
                "Erstellen Sie eine aktualisierte, kurze und faktische Zusammenfassung."
            ),
            max_output_tokens=400,
        )

        self.summary = response.output_text
        self.messages = self.messages[-self.window_size:]

Beachten Sie, dass _compact die bestehende Zusammenfassung in den Prompt vor den neuen Austauschen wieder einfügt. Dies verhindert progressiven Gedächtnisverlust: ohne diese Wiedereinjection würde jede Kompaktierung alles löschen, was die vorherige behielt.

Selektive Kompaktierung

Zusammenfassung ist destruktiv, und manche Inhalte überleben die Neuformulierung nicht. Ein Code-Schnipsel, eine JSON-API-Antwort oder eine Tabelle mit Zahlen verliert seine Nützlichkeit, sobald sie paraphrasiert wird. Die selektive Kompaktierung sortiert daher alte Nachrichten vor der Zusammenfassung: Nachrichten mit Code oder strukturierten Daten gehen unverändert durch, während nur gesprochener Text komprimiert wird.

def selective_compaction(
    messages: list[dict],
    keep_recent: int = 6,
) -> list[dict]:
    """Kompaktiert, während strukturierte Nachrichten intakt bleiben."""
    old_messages = messages[:-keep_recent]
    recent_messages = messages[-keep_recent:]

    to_summarize = []
    to_keep = []

    for msg in old_messages:
        content = msg["content"]
        # Behalten Sie Nachrichten mit Code oder Daten
        if "```" in content or "{" in content:
            to_keep.append(msg)
        else:
            to_summarize.append(msg)

    # Zusammenfassung nur von gesprochenerm Text
    summary = ""
    if to_summarize:
        text = "\n".join(f"{m['role']}: {m['content']}" for m in to_summarize)
        response = client.responses.create(
            model="gpt-5.6-terra",
            input=f"Fassen Sie diese Austausche in 3 Sätzen zusammen:\n{text}",
            max_output_tokens=200,
        )
        summary = response.output_text

    result = []
    if summary:
        result.append({
            "role": "system",
            "content": f"Zusammenfassung der Austausche: {summary}",
        })
    result.extend(to_keep)
    result.extend(recent_messages)
    return result

Das hier verwendete Sortierungskriterium – das Vorhandensein von drei Backticks oder einer geschweiften Klammer – ist absichtlich grob. Passen Sie es an das an, was Ihre Tools tatsächlich produzieren: Für einen Assistenten, der sich mit Fallreferenzen befasst, möchten Sie vielleicht jede Nachricht erhalten, die eine Kunden-ID enthält.

Überprüfung, ob Kompaktierung sich lohnt

Schlecht kalibrierte Kompaktierung kostet einen zusätzlichen API-Aufruf ohne spürbaren Gewinn oder komprimiert so aggressiv, dass das Modell den Faden verliert und Fragen stellt, die bereits beantwortet wurden. Messen Sie daher das Verhältnis, bevor Sie Ihre endgültigen Schwellenwerte festlegen, unter Verwendung einer Stichprobe echter Gespräche.

def compaction_ratio(
    before: list[dict],
    after: list[dict],
) -> dict:
    tokens_before = sum(len(m["content"]) // 4 for m in before)
    tokens_after = sum(len(m["content"]) // 4 for m in after)
    reduction = 1 - (tokens_after / tokens_before) if tokens_before else 0

    return {
        "tokens_before": tokens_before,
        "tokens_after": tokens_after,
        "reduction": f"{reduction:.0%}",
        "messages_before": len(before),
        "messages_after": len(after),
    }

Nehmen Sie ein Dutzend Transkripte aus Ihrer Produktionsumgebung, führen Sie sie durch die Kompaktierung und lesen Sie die erzeugten Zusammenfassungen: Wenn Sie das Gespräch nicht allein auf Basis der Zusammenfassung fortsetzen können, ist Ihre Schwelle zu niedrig oder Ihr Zusammenfassungs-Prompt zu vage.

Kernaussagen

  • Kompaktierung reduziert Tokens durch das Zusammenfassen älterer Nachrichten
  • Behalten Sie immer die jüngsten Nachrichten intakt (gleitendes Fenster)
  • Bewahren Sie Nachrichten mit Code oder strukturierten Daten auf
  • Injectieren Sie die bestehende Zusammenfassung bei jeder neuen Kompaktierung, um progressiven Gedächtnisverlust zu vermeiden
  • Messen Sie das Kompressionsverhältnis, um Ihre Schwellenwerte zu kalibrieren