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.

Inoffizielle Community-Lernseite. Basiert auf dem MIT-lizenzierten NousResearch/hermes-agent-Quellcode.