Kontextstrategien für Agenten
Auf dieser Seite
Die Kontext-Herausforderung für Agenten
Ein autonomer Agent verknüpft Dutzende API-Aufrufe: er überlegt, nutzt Werkzeuge, liest Ergebnisse und iteriert. An jedem Schritt wächst der Kontext. Der Unterschied zu einem gewöhnlichen Gespräch besteht darin, dass dieses Wachstum nicht vom Nutzer, sondern vom Agenten selbst stammt: eine SQL-Abfrage, die drei hundert Zeilen zurückgibt, eine vollständige Webseite, die abgerufen wurde, oder eine gesamte Protokolldatei, die gelesen wurde. Ohne eine Managementstrategie erreicht ein Agent die Kontextgrenze innerhalb weniger Iterationen, oft noch bevor er die ihm zugewiesene Aufgabe abgeschlossen hat.
Das Design eines Agenten, der sein Gedächtnis effektiv verwaltet, reduziert sich daher auf die Beantwortung dreier Fragen: Was muss intakt bleiben, was zusammengefasst werden kann und was gar nicht erst in den Kontext eingeführt werden sollte. Wir werden uns dieser Fragen in dieser Reihenfolge widmen.
Ein dreistufiges Gedächtnis
Die Struktur, die wir verwenden, stützt sich auf die klassische Unterscheidung zwischen Arbeitsgedächtnis, episodischem Gedächtnis und semantischem Gedächtnis. Das Arbeitsgedächtnis hält die jüngsten Aktionen in ihrer rohen Form, da der Agent sie genau so benötigt, um zu entscheiden, was als Nächstes zu tun ist. Das episodische Gedächtnis bewahrt Zusammenfassungen vergangener Schritte: Der Agent weiß, dass er eine bestimmte Spur untersucht hat und was dies ergab, ohne die Details erneut zu lesen. Schließlich sammelt das semantische Gedächtnis Fakten, die einmal und für immer festgestellt wurden – die Kunden-ID, die Version des Datenbank-Schemas, die Budgetbeschränkung –, die niemals in einer Zusammenfassung verloren gehen dürfen.
from dataclasses import dataclass, field
@dataclass
class AgentMemory:
"""Dreistufiges Gedächtnissystem für einen Agenten."""
# Ebene 1: Arbeitsgedächtnis (aktueller Kontext)
working: list[dict] = field(default_factory=list)
# Ebene 2: Episodisches Gedächtnis (Zusammenfassungen vergangener Schritte)
episodes: list[str] = field(default_factory=list)
# Ebene 3: Semantisches Gedächtnis (dauerhafte Fakten)
facts: list[str] = field(default_factory=list)
max_tokens_working: int = 20_000
max_episodes: int = 10
def add_action(self, action: str, result: str):
"""Fügt eine Aktion zum Arbeitsgedächtnis hinzu."""
self.working.append({
"action": action,
"result": result[:2000], # Abschneiden langer Ergebnisse
})
self._check_compaction()
def add_fact(self, fact: str):
"""Fügt einen dauerhaften Fakt hinzu."""
if fact not in self.facts:
self.facts.append(fact)
def _check_compaction(self):
tokens = sum(
len(str(e)) // 4 for e in self.working
)
if tokens > self.max_tokens_working:
self._compact()
def _compact(self):
"""Komprimiert das Arbeitsgedächtnis zu einem Episodeneintrag."""
import openai
client = openai.OpenAI()
text = "\n".join(
f"Aktion: {a['action']}\nErgebnis: {a['result']}"
for a in self.working[:-2]
)
response = client.responses.create(
model="gpt-5.6-terra",
input=(
"Fassen Sie diese Agentenaktionen in 3-5 Schlüsselpunkten zusammen. "
"Behalten Sie wichtige Fakten, Ergebnisse und Entscheidungen bei.\n\n"
f"{text}"
),
max_output_tokens=300,
)
self.episodes.append(response.output_text)
self.working = self.working[-2:] # Behalten Sie die letzten 2 Aktionen
# Begrenzen Sie die Anzahl der Episoden
if len(self.episodes) > self.max_episodes:
self.episodes = self.episodes[-self.max_episodes:]
def build_context(self) -> str:
"""Erstellt den vollständigen Kontext für den nächsten Aufruf."""
parts = []
if self.facts:
parts.append(
"Festgestellte Fakten:\n" + "\n".join(f"- {f}" for f in self.facts)
)
if self.episodes:
parts.append(
"Zusammengefasste Historie:\n" + "\n---\n".join(self.episodes)
)
if self.working:
parts.append(
"Jüngste Aktionen:\n" + "\n".join(
f"- {a['action']} → {a['result'][:500]}"
for a in self.working
)
)
return "\n\n".join(parts)
Zwei Parameter verdienen eine Diskussion, bevor Sie sie so übernehmen. Die Abschneidelänge von 2.000 Zeichen in add_action schützt vor ausführlichen Werkzeugen, aber sie schneidet blind ab: Wenn Ihre Werkzeuge JSON zurückgeben, landen Sie mit einem ungültigen Objekt mitten in Ihrem Kontext. Und max_episodes: 10 erzwingt eine begrenzte Amnesie: Beim elften Zusammenfassungseintrag verschwindet der erste. Für einen Agenten, der über einen langen Zeitraum läuft, sollten Sie wichtige Fakten in facts befördern, bevor sie aus dem episodischen Fenster fallen, sonst verlieren Sie sie endgültig.
Das Einengen von Werkzeugergebnissen
Werkzeuge geben oft sperrige Ergebnisse zurück: Webseiten, SQL-Ergebnisse, Dateiinhalte. Dies ist bei weitem die Hauptursache für die Kontextsättigung eines Agenten und gleichzeitig die einfachste zu beheben, da Sie dies bei der Aufnahme behandeln, nicht nachträglich.
Die folgende Funktion bietet drei Abschneidestrategien. Der Modus start eignet sich für Dokumente, bei denen das Wesentliche oben steht, wie ein Artikel oder ein Bericht. Der Modus start_end funktioniert, wenn das Ende die entscheidenden Informationen trägt: eine Ausführungsfolge, bei der der Fehler zuletzt erscheint, eine Protokolldatei oder eine paginierte Antwort, bei der die Gesamtzahl am Ende steht. Der Modus summary kostet einen zusätzlichen API-Aufruf, was nur für lange Inhalte gerechtfertigt ist, die für den Rest der Überlegung wirklich nützlich sind.
def truncate_tool_result(
result: str,
max_tokens: int = 2000,
strategy: str = "start_end",
) -> str:
"""Schneidet ein Werkzeugergebnis ab, während wichtige Informationen erhalten bleiben."""
geschätzte_tokens = len(result) // 4
if geschätzte_tokens <= max_tokens:
return result
max_chars = max_tokens * 4
match strategy:
case "start":
return result[:max_chars] + "\n[... abgeschnitten]"
case "start_end":
half = max_chars // 2
return (
result[:half]
+ "\n[... Mitte abgeschnitten ...]\n"
+ result[-half:]
)
case "summary":
import openai
client = openai.OpenAI()
response = client.responses.create(
model="gpt-5.6-terra",
input=f"Fassen Sie diese Daten zusammen, wobei das Wesentliche erhalten bleibt:\n{result}",
max_output_tokens=max_tokens,
)
return response.output_text
return result[:max_chars]
Tabellarische Ergebnisse erfordern eine besondere Behandlung. Das Abschneiden einer SQL-Tabelle in der Mitte entfernt den Kopf oder verzerrt den Eindruck des Umfangs: Der Agent sieht zwanzig Zeilen und schließt daraus, dass die Tabelle nur zwanzig Zeilen enthält. Der folgende Filter behält den Kopf, eine Auswahl aus oben und unten bei und – dies ist der entscheidende Punkt – gibt explizit die Anzahl der ausgelassenen Zeilen und die Gesamtzahl an. Der Agent weiß dann, dass er einen Auszug betrachtet, und kann entscheiden, eine aggregierte Abfrage auszuführen, statt auf einer Stichprobe zu überlegen.
def filter_sql_result(result: str, question: str) -> str:
"""Filtert ein SQL-Ergebnis, um nur das Relevante zu behalten."""
zeilen = result.strip().split("\n")
if len(zeilen) <= 20:
return result
# Behalten Sie den Kopf und die ersten/letzten Zeilen
header = zeilen[0]
erste_zeilen = zeilen[1:6]
letzte_zeilen = zeilen[-5:]
return (
f"{header}\n"
+ "\n".join(erste_zeilen)
+ f"\n[... {len(zeilen) - 11} Zeilen ausgelassen ...]\n"
+ "\n".join(letzte_zeilen)
+ f"\n\nGesamt: {len(zeilen) - 1} Zeilen"
)
Wenn mehrere Agenten zusammenarbeiten
Wenn mehrere Teilagenten parallel arbeiten, ist die Versuchung groß, ihnen alle den vollständigen Kontext zu geben, nur um sicherzugehen. Das ist der beste Weg, um Ihre Rechnung mit der Anzahl der Agenten zu vervielfachen und ihre Leistung zu verschlechtern, da ein unübersichtlicher Kontext das Modell bei seiner eigenen Aufgabe weniger präzise macht. Stattdessen teilen Sie den minimalen Kontext: das globale Ziel, damit der Agent versteht, wozu er beiträgt, seine eigene Rolle, gemeinsame Beschränkungen und einen begrenzten Auszug der von seinen Kollegen produzierten Ergebnisse.
@dataclass
class SharedContext:
"""Kontext, der zwischen Teilagenten geteilt wird."""
objective: str
constraints: list[str]
partial_results: dict[str, str] = field(default_factory=dict)
def context_for_sub_agent(self, role: str) -> str:
"""Generiert einen zielgerichteten Kontext für einen Teilagenten."""
return (
f"Globales Ziel: {self.objective}\n"
f"Ihre Rolle: {role}\n"
f"Beschränkungen: {', '.join(self.constraints)}\n"
f"Ergebnisse anderer Agenten:\n"
+ "\n".join(
f"- {k}: {v[:200]}"
for k, v in self.partial_results.items()
)
)
Die 200-Zeichen-Grenze pro Teilergebnis spiegelt eine Absicht wider: Ein Teilagent muss wissen, was andere gefunden haben, nicht wie sie es gefunden haben. Wenn Ihre Agenten die vollständigen Details benachbarter Ergebnisse benötigen, müssen Sie wahrscheinlich überdenken, wie Sie die Rollen aufgeteilt haben.
Kernaussagen
- Strukturieren Sie das Agentengedächtnis in drei Ebenen: Arbeitsgedächtnis, episodisches Gedächtnis, semantisches Gedächtnis
- Komprimieren Sie automatisch, wenn das Arbeitsgedächtnis die Schwelle überschreitet
- Schneiden Sie Werkzeugergebnisse ab, bevor Sie sie in den Kontext einfügen
- Signalisieren Sie immer, dass ein Ergebnis abgeschnitten wurde, und geben Sie an, um wie viel
- Bei Mehr-Agenten-Aufbauten teilen Sie einen minimalen, auf die Rolle zugeschnittenen Kontext