Skip to content

turn_context und Prompt-Zusammenbau ​

源码版本v2026.7.20

Verantwortung ​

Vor jedem API-Aufruf werden «Historien-Nachrichten + Nutzereingabe des aktuellen Turns + vom Gateway (gateway) angehängter Kontext (context) + Kompressions-Marker (compression marker)» zu den api_messages für den Provider (provider) zusammengebaut. TurnContext ist der Container dieses Turn-Produkts, build_turn_context der Assembly-Einstieg. Es injiziert außerdem die vom Gateway gesammelten Notes in multimodale Inhalte.

Schlüsseldateien ​

Datenfluss ​

  1. Die Hauptschleife ruft jeden Turn (turn) build_turn_context:268 auf und erhält ein TurnContext.
  2. compose_user_api_content (agent/turn_context.py:44) baut die rohe Nutzereingabe zu API-Inhalten zusammen.
  3. Hat das Gateway Notes gesammelt (aus Anhängen, Kontext-Hinweisen), holt consume_gateway_turn_context_notes (agent/turn_context.py:124) sie heraus und injiziert sie über append_notes_to_multimodal_content.
  4. substitute_api_content (agent/turn_context.py:79) ersetzt Platzhalter; drop_stale_api_content bereinigt veraltete Inhalte.
  5. Hat im vorherigen Turn eine Kompression stattgefunden, korrigiert reanchor_current_turn_user_idx (agent/turn_context.py:164) die Position der aktuellen User-Nachricht in der Historie.
  6. Die fertigen api_messages treten in die API-Aufruf-Wiederholungs-Teilschleife:1227 ein.

TurnContext ist eine ganz gewöhnliche Dataclass; sie bündelt die Werte aus dem Prolog in einer einzigen Instanz (Schlüsselfelder siehe agent/turn_context.py:242-268): user_message ist die desinfizierte Turn-Nachricht; original_user_message bewahrt den rohen Text für Memory und Log-Anfragen (Nudge wird hier nicht injiziert); conversation_history kann von der Preflight-Kompression auf None gesetzt werden — Kompression öffnet eine neue Sitzung (session), die alte History ist nicht mehr verwendbar; active_system_prompt ist der zwischengespeicherte System-Prompt (system prompt) des Turns, der ebenfalls von der Kompression neu gebaut werden kann.

Die Konsum-Logik der Gateway-Notes siehe agent/turn_context.py:124-145:

python
def consume_gateway_turn_context_notes(agent: Any) -> str:
    """Pop the gateway's per-turn must-deliver notes off the agent (one-shot).
    ... so the composed system prompt stays byte-stable turn-over-turn.
    ... this consumes them so a cached agent can never replay a stale note."""
    notes = getattr(agent, "_gateway_turn_context_notes", "") or ""
    if hasattr(agent, "_gateway_turn_context_notes"):
        try:
            agent._gateway_turn_context_notes = ""
        except Exception:
            pass
    return notes if isinstance(notes, str) else ""

Nach dem Holen sofort nullen — das ist ein einmaliger Pop, kein Peek. Auch wenn der Agent zwischengespeichert und wiederverwendet wird, können nicht verbrauchte Notes eines vorherigen Turn nicht in den nächsten Turn lecken.

Designmotiv ​

Warum den Prolog als build_turn_context auslagern? Ursprünglich steckte alles «einmal pro Turn» (stdio-Guard, Retry-Zähler-Reset, User-Message-Desinfektion, Todo-/Nudge-Injektion, System-Prompt-Aufbau, Preflight-Kompression, pre_llm_call-Hooks, externes Memory-Vorabholen, Crash-Recovery-Persistenz) inline am Anfang von run_conversation, rund 130 Zeilen. Als Funktion ausgelagert, bleibt in der Hauptschleife nur «Schleife + Wiederholung»; wer die Hauptschleife liest, wird nicht von Vorbereitungslogik unterbrochen, der Prolog lässt sich separat testen und der Subagent-Pfad kann ihn wiederverwenden.

Warum Notes über einen Seitenpfad der User-Nachricht statt eingespeist in den System-Prompt? Der System-Prompt muss turn-übergreifend byte-stabil sein — ändert er sich, verfällt das zuvor gecachte Prefix und das Prompt-Caching des Providers bricht. Das Gateway zieht veränderliche Inhalte (erstmalige Selbstvorstellung, Sprachkanal-Wechsel, temporärer Hinweis bei Auto-Reset) aus dem System-Prompt heraus und liefert sie über einen api_content-Sidecar der User-Nachricht; so kann jeder Turn neue Fakten tragen, ohne die Cache-Trefferquote zu gefährden.

Dass _compression_made_progress 5 % als Materialitätsschwelle setzt, verhindert endloses Kompressions-Kreisen: Ein Turn, der 220 Nachrichten auf 220 Nachrichten komprimiert, aber die Token von 288k auf 183k senkt, würde nach Zeilenzahl fälschlich als «komprimiert nicht weiter» gewertet und Auto-Reset auslösen.

Grenzen und Fehler ​

  • Multimodale Nachrichten verlieren Notes: compose_user_api_content gibt für nicht-stringige Inhalte None zurück; sendet der User in diesem Turn ein Bild, gehen sidecar-Notes lautlos verloren. append_notes_to_multimodal_content fängt auf, indem es die Notes als Text-Part direkt anhängt — der Preis: Dieser Inhalt wird persistiert und im Transcript gespeichert; das Gateway darf also nur wirklich ablagbare Inhalte hineinlegen.
  • User-Nachrichten-Index driftet nach Kompression: Kompression fasst alte Nachrichten zu einer Summary zusammen, und die Position der User-Nachricht des aktuellen Turns in messages verschiebt sich. reanchor_current_turn_user_idx scannt die Liste neu und findet den Index; ohne Reanchor würde das Zurückfüllen des Werkzeugaufruf-Ergebnisses an eine falsche Stelle geschrieben.
  • Wenige, aber riesige Nachrichten: Die alte Version von _should_run_preflight_estimate sah nur auf die Nachrichtenzahl; ein paar gigantische Base64-Bilder lösten nie eine Kompression aus und knallten gegen den Hard-Overflow (#27405). Die neue Version hat zusätzlich einen char-basierten Schätz-Zweig, der auch große Nachrichten trifft.
  • Stale-Notes-Rest: consume_gateway_turn_context_notes fängt Schreibfehler mit try/except ab — selbst wenn die Agent-Eigenschaft ein read-only Property ist, bricht ein gescheitertes Nullen nicht die Assembly.

Zusammenfassung ​

turn_context ist die «Snapshot-Fabrik pro Turn»: Es friert dynamischen Kontext zu einer verschickbaren Struktur ein. Es arbeitet mit der Kontext-Kompression zusammen – sobald die Kompression die Historie umgeschrieben hat, richtet turn_context den Zeiger wieder aus.