8. Konversationszustand und Speicher
Bis jetzt war jede LLM-Interaktion, die wir erstellt haben, zustandslos. Zustandslos bedeutet, dass jede Anfrage unabhängig ist – das Modell hat keine Erinnerung an vorherige Konversationen. Das funktioniert gut für einmalige Aufgaben wie Dokumentenzusammenfassungen oder Einzelfragen-Q&A.
Aber einen konversationsfähigen Agent zu erstellen, ist eine andere Geschichte. Sie benötigen, dass der Agent sich daran erinnert, was zuvor besprochen wurde, Pronomen wie "es" oder "das" versteht und den Kontext während des gesamten Dialogs aufrechterhält. Um dies zu tun, müssen Sie explizit den Zustand verwalten.
(Hier bezieht sich "Zustand" auf Informationen, die ein Programm sich merkt. Für konversationsfähige Agents ist der vorherige Konversationsverlauf der Zustand.)
In diesem Kapitel werden wir behandeln:
- Warum LLMs sich nicht an Konversationen "erinnern"
- Wie man Konversationsspeicher mit LangChains Nachrichtenverlaufs-Tools implementiert
- Wie man Token-Budgets verwaltet, um Kontextüberlauf zu verhindern
8.1) Warum LLMs vergessen
LLMs haben keinen Speicher
LLMs haben eine kritische Eigenschaft: Sie erinnern sich an nichts aus vorherigen Konversationen.
Wenn Sie eine LLM-API aufrufen, verarbeitet das Modell Ihre Eingabe und generiert eine Antwort. Aber es speichert diesen Datensatz nirgendwo. Es gibt keinen Speicher innerhalb des Modells, der den Zustand aufrechterhält, keinen Konversationsverlauf. Jeder API-Aufruf ist vollständig unabhängig. Es ist, als würde man jedes Mal von vorne anfangen.
Das ist beabsichtigt. LLMs funktionieren wie zustandslose Funktionen: Sie geben Eingaben, sie produzieren Ausgaben, und nichts wird beibehalten. Das Modell, das gerade auf OpenAIs Servern läuft, hat keine Aufzeichnung davon, was Sie gerade gefragt haben.
Warum LLMs sich zu erinnern scheinen
Aber warten Sie – wenn Sie ChatGPT oder Claude verwenden, fühlt es sich an, als würden sie sich an Ihre Konversation erinnern. Sie können sagen "Erzähl mir über Paris", dann mit "Was ist die Bevölkerung?" nachfragen, und das Modell weiß, dass Sie immer noch über Paris sprechen. Wie funktioniert das?
So funktioniert es: Die Anwendung sendet den vorherigen Konversationsverlauf zusammen mit jeder neuen Nachricht.
Hier ist, was tatsächlich passiert:
Das LLM "erinnert" sich nicht daran, dass Sie früher nach Paris gefragt haben – es weiß es nur, weil die Anwendung den vorherigen Konversationsverlauf zusammen mit der neuen Nachricht gesendet hat. Letztendlich ist es die Anwendung, die den Zustand verwaltet, nicht das Modell.
Warum "Zustand" in Ihrer Anwendung verwaltet werden muss, nicht im Modell
Denken Sie an ein LLM als eine reine Funktion: Bei gegebener Eingabe produziert es eine Ausgabe. Es gibt keinen internen Zustand, den das LLM über die von Ihnen bereitgestellten Nachrichten hinaus verwaltet. Das ist beabsichtigt.
Für konversationsfähige Agents bedeutet dies, dass der Zustand in Ihrer Anwendung verwaltet werden muss.
Der Zustand – der Konversationsverlauf – lebt in Ihrem Anwendungscode, nicht im Modell. Das bedeutet, Sie sind verantwortlich für:
- Speichern des Konversationsverlaufs
- Senden des relevanten Verlaufs mit jeder neuen Anfrage
- Verwalten der Größe des Verlaufs (behandelt in Abschnitt 8.3)
In Abschnitt 8.2 werden wir dies mit LangChains Nachrichtenverlaufs-Tools implementieren.
Was passiert, wenn Sie den Zustand nicht verwalten?
Wenn Sie den Zustand nicht verwalten, kann Ihr Agent (Ihre Anwendung) keine kohärente Konversation aufrechterhalten. Hier sind die häufigsten Fehler:
1. Kann sich nicht an vorherige Konversation erinnern
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
llm = ChatOpenAI(model="gpt-4o-mini")
# Erste Frage
response1 = llm.invoke([HumanMessage(content="Mein Name ist Alice")])
print(response1.content) # Ausgabe: Schön, Sie kennenzulernen, Alice!
# Zweite Frage (kein Verlauf gesendet)
response2 = llm.invoke([HumanMessage(content="Wie ist mein Name?")])
print(response2.content) # Ausgabe: Ich kenne Ihren Namen nicht...Das Modell hat keine Ahnung, dass Sie gesagt haben, Ihr Name sei Alice, weil wir diese Information beim zweiten Aufruf nicht gesendet haben.
2. Kann nicht erkennen, worauf sich Pronomen beziehen
# Benutzer fragt nach einem Thema
response1 = llm.invoke([HumanMessage(content="Erzähl mir über Python")])
print(response1.content)
# Ausgabe: Python ist eine hochrangige Programmiersprache, die für ihre Lesbarkeit bekannt ist...
# Benutzer folgt mit einem Pronomen
response2 = llm.invoke([HumanMessage(content="Was sind seine Hauptmerkmale?")])
print(response2.content)
# Ausgabe: Ich helfe gerne! Könnten Sie angeben, wonach Sie fragen?Ohne die vorherige Nachricht kann das Modell nicht erkennen, worauf sich "seine" bezieht.
Auswirkungen in der Praxis:
Stellen Sie sich vor, Sie erstellen einen Kundensupport-Agent ohne Zustandsverwaltung:
Benutzer: "Ich habe Probleme mit meiner Bestellung #12345"
Agent: "Das tut mir leid zu hören. Was scheint das Problem zu sein?"
Benutzer: "Die Lieferadresse ist falsch"
Agent: "Ich kann Ihnen dabei helfen. Könnten Sie Ihre Bestellnummer angeben?"
Benutzer: "Ich habe sie Ihnen gerade gesagt..."Diese Erfahrung frustriert Benutzer und verliert ihr Vertrauen. Zustandsverwaltung ist nicht optional für konversationsfähige Agents – sie ist essentiell für die Erstellung kohärenter, nützlicher Interaktionen.
In Abschnitt 8.2 werden wir Zustandsverwaltung mit LangChains Nachrichtenverlaufs-Tools implementieren und Konversationsspeicher zum CLI-Chat aus Kapitel 3 hinzufügen.
8.2) Verwaltung des Konversationszustands
Jetzt, da wir verstehen, warum Zustandsverwaltung kritisch ist, lernen wir, wie man sie implementiert. Wir werden LangChains integrierte Nachrichtenverlaufs-Tools verwenden, um den Konversationsverlauf zu verwalten. Am Ende werden wir das Gelernte anwenden, um Zustandsverwaltung zum CLI-Chat aus Kapitel 3 hinzuzufügen.
Verständnis der Nachrichtentypen: HumanMessage, AIMessage und SystemMessage
Bevor Sie lernen, wie man Konversationszustand verwaltet, müssen Sie die Nachrichtentypen kennen, die im Konversationszustand verwendet werden. LangChain verwendet drei Nachrichtentypen, um Konversationen darzustellen: HumanMessage (Benutzereingabe), AIMessage (Modellantwort) und SystemMessage (Anweisungen). Jede Nachricht hat eine Rolle (Nachrichtentyp) und Inhalt (der tatsächliche Text).
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
# SystemMessage: Anweisungen für das Verhalten des Modells
system_msg = SystemMessage(content="Sie sind ein hilfreicher Assistent, der sich auf Python-Programmierung spezialisiert hat.")
# HumanMessage: Benutzereingabe
user_msg = HumanMessage(content="Wie lese ich eine Datei in Python?")
# AIMessage: Antwort des Modells
# (In der Praxis umhüllt LangChain die Antwort des Modells in diesem Objekt - hier zur Veranschaulichung gezeigt)
ai_msg = AIMessage(content="Sie können die Funktion `open()` mit einem Kontextmanager verwenden...")Warum separate Nachrichtentypen?
Damit das Modell den Konversationsverlauf effektiv versteht, muss es den Zweck jeder Nachricht und wer sie gesagt hat, kennen. Die drei Nachrichtentypen dienen unterschiedlichen Zwecken:
- SystemMessage: Anweisungen, die definieren, wie sich das Modell verhalten soll (z.B. "Sei prägnant", "Du bist ein Python-Tutor")
- HumanMessage: Was der Benutzer gesagt hat
- AIMessage: Was das Modell zuvor geantwortet hat
Diese Struktur ermöglicht es dem Modell, zwischen Anweisungen, Benutzerfragen und seinen eigenen früheren Antworten zu unterscheiden – was für die Aufrechterhaltung kohärenter mehrstufiger Konversationen essentiell ist.
Manuelles Erstellen des Konversationsverlaufs
Jetzt, da wir die drei Nachrichtentypen verstehen, sehen wir uns an, wie man einen Konversationsverlauf manuell erstellt:
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
llm = ChatOpenAI(model="gpt-4o-mini")
# Konversationsverlauf erstellen
# SystemMessage setzt Anweisungen (einmal am Anfang)
# Dann: Benutzereingabe → KI-Antwort → Benutzereingabe (natürlicher Konversationsfluss)
messages = [
SystemMessage(content="Sie sind ein prägnanter Python-Tutor."),
HumanMessage(content="Was ist eine List Comprehension?"),
AIMessage(content="Eine List Comprehension ist eine prägnante Möglichkeit, Listen zu erstellen: [x*2 for x in range(5)]"),
HumanMessage(content="Können Sie mir ein komplexeres Beispiel zeigen?")
]
# Gesamten Verlauf mit neuer Frage senden
response = llm.invoke(messages)
print(response.content)Ausgabe:
Sicher! Hier ist eine List Comprehension, die filtert und transformiert:
[x**2 for x in range(10) if x % 2 == 0]
Dies produziert:
[0, 4, 16, 36, 64]Das Modell versteht "ein komplexeres Beispiel" bezieht sich auf ein komplexeres List Comprehension-Beispiel, weil wir den vollständigen Konversationsverlauf gesendet haben.
Verwendung von InMemoryChatMessageHistory
Das manuelle Erstellen von Nachrichtenlisten wird umständlich, wenn Konversationen wachsen. LangChains InMemoryChatMessageHistory vereinfacht dies, indem es Methoden zum Hinzufügen von Nachrichten und Abrufen des vollständigen Verlaufs bereitstellt.
Wichtige Methoden:
add_message(message): Fügt eine einzelne Nachricht hinzu (HumanMessage, AIMessage, SystemMessage)add_messages(messages): Fügt mehrere Nachrichten auf einmal hinzumessages: Eigenschaft, die die vollständige Liste der Nachrichten zurückgibtclear(): Entfernt alle Nachrichten (nützlich für einen Neustart)
Beispiel:
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
# Nachrichtenverlaufs-Speicher erstellen
history = InMemoryChatMessageHistory()
# Mehrere Nachrichten auf einmal hinzufügen
history.add_messages([
SystemMessage(content="Sie sind ein hilfreicher Python-Tutor."),
HumanMessage(content="Was ist ein Decorator in Python?")
])
# Nachrichten einzeln hinzufügen
history.add_message(AIMessage(content="Ein Decorator ist eine Funktion, die das Verhalten einer anderen Funktion modifiziert..."))
history.add_message(HumanMessage(content="Können Sie ein Beispiel zeigen?"))
# Alle Nachrichten abrufen
messages = history.messagesPraktisches Beispiel: Zustandsverwaltung mit InMemoryChatMessageHistory:
from langchain_openai import ChatOpenAI
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import SystemMessage, HumanMessage
llm = ChatOpenAI(model="gpt-4o-mini")
history = InMemoryChatMessageHistory()
# System-Nachricht setzen und zum Verlauf hinzufügen (einmal am Anfang)
system_msg = SystemMessage(content="Sie sind ein hilfreicher Python-Tutor.")
history.add_message(system_msg)
# Chat-Funktion
def chat(user_input):
"""Verarbeitet Benutzereingabe, sendet an LLM und verwaltet automatisch den Konversationsverlauf"""
# Benutzereingabe zum Verlauf hinzufügen
history.add_message(HumanMessage(content=user_input))
# Benutzereingabe zusammen mit vorherigem Konversationsverlauf senden
response = llm.invoke(history.messages)
# Modellantwort zum Verlauf hinzufügen
history.add_message(response)
return response.content
# Konversation simulieren
print(chat("Was ist eine Lambda-Funktion?"))
print(chat("Zeig mir ein Beispiel")) # Modell erinnert sich an Kontext
print(chat("Was ist der Unterschied zu einer regulären Funktion?")) # Erinnert sich immer nochAusgabe:
Eine Lambda-Funktion ist eine anonyme Funktion, die mit dem lambda-Schlüsselwort definiert wird...
Hier ist ein Beispiel: square = lambda x: x**2
Sie können es so verwenden: square(5) # Gibt 25 zurück
Lambda-Funktionen sind auf einen einzelnen Ausdruck beschränkt, während reguläre Funktionen...Die chat()-Funktion übernimmt die Zustandsverwaltung automatisch: Sie sendet jede Benutzeranfrage zusammen mit dem vorherigen Konversationsverlauf an das LLM und fügt sowohl die Anfrage als auch die Antwort zurück zum Verlauf hinzu. Durch einfaches Aufrufen von chat() wird der Konversationszustand ohne zusätzliche Verwaltung aufrechterhalten.
Refactoring Kapitel 3: Hinzufügen von Speicher zu Ihrem CLI-Chat
Nehmen wir den Streaming-CLI-Chat aus Kapitel 3 und fügen Konversationsspeicher hinzu. Hier ist die ursprüngliche zustandslose Version:
# chapter3_cli.py (original - zustandslos)
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
llm = ChatOpenAI(model="gpt-4o-mini")
def chat_loop():
print("Chat gestartet. Geben Sie 'quit' ein, um zu beenden.\n")
while True:
user_input = input("Sie: ")
if user_input.lower() == "quit":
break
# Zustandslos - kein Verlauf
response = llm.stream([HumanMessage(content=user_input)])
print("KI: ", end="", flush=True)
for chunk in response:
print(chunk.content, end="", flush=True)
print("\n")
if __name__ == "__main__":
chat_loop()Refaktorierte Version mit Speicher:
# chapter8_cli.py (mit Speicher)
from langchain_openai import ChatOpenAI
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
llm = ChatOpenAI(model="gpt-4o-mini")
def chat_loop():
print("Chat gestartet. Geben Sie 'quit' ein, um zu beenden.\n")
# Verlauf erstellen und System-Nachricht hinzufügen
history = InMemoryChatMessageHistory()
history.add_message(SystemMessage(content="Sie sind ein hilfreicher Assistent."))
while True:
user_input = input("Sie: ")
if user_input.lower() == "quit":
break
# Benutzernachricht zum Verlauf hinzufügen
history.add_message(HumanMessage(content=user_input))
# Antwort streamen
print("KI: ", end="", flush=True)
full_response = ""
for chunk in llm.stream(history.messages):
print(chunk.content, end="", flush=True)
full_response += chunk.content
print("\n")
# KI-Antwort zum Verlauf hinzufügen
history.add_message(AIMessage(content=full_response))
if __name__ == "__main__":
chat_loop()Was hat sich geändert:
- Verlaufsspeicher hinzugefügt:
InMemoryChatMessageHistory()-Instanz innerhalb vonchat_loop()erstellt - System-Nachricht: Einmal am Anfang zum Verlauf hinzugefügt
- Benutzereingabe mit Verlauf senden:
llm.stream(history.messages)enthält vorherige Konversation - Konversation verfolgen: Sowohl Benutzereingabe als auch LLM-Antwort zum Verlauf hinzufügen
Testen des refaktorierten Chats:
Chat gestartet. Geben Sie 'quit' ein, um zu beenden.
Sie: Mein Name ist Alice
KI: Schön, Sie kennenzulernen, Alice! Wie kann ich Ihnen heute helfen?
Sie: Wie ist mein Name?
KI: Ihr Name ist Alice.
Sie: Was habe ich Sie gerade gefragt?
KI: Sie haben mich gefragt, wie Ihr Name ist.
Sie: quitDas Modell behält jetzt den Kontext über die gesamte Konversation bei. Es erinnert sich an Ihren Namen, vorherige Fragen und kann auf frühere Teile des Dialogs verweisen.
Persistente Speicherung: Über In-Memory-Optionen hinausgehen
InMemoryChatMessageHistory ist praktisch für die lokale Entwicklung, aber der Übergang zur Produktion erfordert den Ersatz durch eine Speicherlösung, die Persistenz garantiert.
Technische Einschränkungen von InMemory:
- Flüchtiger RAM: Wenn der Serverprozess beendet oder neu gestartet wird, wird der gesamte im Speicher gespeicherte Konversationsverlauf sofort gelöscht. Updates oder Fehlerwiederherstellung führen zu vollständigem Verlust des Benutzerkontexts.
- Keine horizontale Skalierung: Wenn Ihr Service auf mehrere Serverinstanzen skaliert, verwaltet jeder Server seinen eigenen isolierten Speicher. Benutzer, die sich mit verschiedenen Servern verbinden, können den Konversationsverlauf nicht teilen.
- Ressourcenineffizienz: Das Speichern des gesamten Konversationsverlaufs im RAM ist speicherintensiv und bedroht die Systemstabilität, wenn gleichzeitige Benutzer zunehmen.
Professionelle Alternativen:
PostgresChatMessageHistory(Empfohlen): Die robusteste und am weitesten verbreitete Wahl. Verwendet PostgreSQL für permanente Speicherung und zeichnet sich durch komplexe Abfragen und Datenanalyse aus.SQLChatMessageHistory: Nutzt bestehende SQL-Datenbanken wie MySQL. Ermöglicht es Ihnen, Ihre aktuelle Infrastruktur ohne Änderungen zu verwenden.RedisChatMessageHistory: Ideal für Services, bei denen Antwortgeschwindigkeit kritisch ist. Speicherbasiert mit Persistenzoptionen, spezialisiert auf Hochverkehrsabwicklung.
"Speicher ändert sich, Code bleibt gleich"
LangChain bietet eine einheitliche Schnittstelle über alle Speicher-Backends hinweg. Die gleichen Methoden, die Sie mit InMemoryChatMessageHistory verwendet haben – wie add_message() und add_messages() – funktionieren identisch mit anderen Speicheroptionen. Das bedeutet, dass Ihre Geschäftslogik (Konversationsbehandlungscode) keine Änderungen erfordert, wenn Sie den Speicher wechseln.
# [Entwicklung] Lokaler In-Memory
# from langchain_core.chat_history import InMemoryChatMessageHistory
# history = InMemoryChatMessageHistory()
# [Produktion] PostgreSQL persistente Speicherung
import psycopg
from langchain_postgres import PostgresChatMessageHistory
# PostgreSQL-Verbindung erstellen
sync_connection = psycopg.connect(
"postgresql://user:password@10.1.1.100:5432/agent_db",
autocommit=True,
)
# DB-Verbindung und Session-ID angeben
# session_id: Eindeutiger Schlüssel zur Identifizierung von Konversationen
# - Pro-Benutzer-Verwaltung: session_id = user_id (eine Konversation pro Benutzer)
# - Pro-Session-Verwaltung: session_id = uuid (neue ID für jede Konversation)
history = PostgresChatMessageHistory(
table_name="chat_history",
session_id="user_123",
sync_connection=sync_connection
)
# --- Gleiche Schnittstelle unabhängig vom Speichertyp ---
history.add_message(HumanMessage(content="Zeigen Sie mir meine vorherige Bestellung."))
print(history.messages)Wichtig: Dieser Leitfaden verwendet InMemory für schnellen Fortschritt, aber Produktionsbereitstellungen müssen auf persistente Speicherung wie PostgresChatMessageHistory umstellen.
8.3) Verwaltung der Konversationslänge
Die Konversationsspeicher-Funktion, die wir im vorherigen Abschnitt implementiert haben, hat ein wichtiges Problem: Sie fügt nur Nachrichten zum Verlauf hinzu. Das bedeutet, der Verlauf wächst weiter, was zwei große Probleme schafft:
- Kosten: Der gesamte Verlauf wird mit jeder Anfrage gesendet, sodass die Kosten pro Anfrage weiter steigen, wenn der Verlauf wächst
- Kontextfenster-Limits: Modelle haben eine maximale Eingabegröße, die sie in einer einzelnen Anfrage verarbeiten können (z.B. 400K Tokens für GPT-5). Wenn der Konversationsverlauf dieses Limit überschreitet, kann das Modell die Anfrage nicht ordnungsgemäß verarbeiten.
Eine der einfachsten Lösungen ist das Sliding Window-Muster.
Sliding Window-Muster (Nur die letzten N Nachrichten behalten)
Das Sliding Window-Muster löst die beiden oben genannten Probleme, indem es nur die letzten N Nachrichten im Konversationsverlauf behält. Es verwaltet die Verlaufsgröße, indem es alte Nachrichten verwirft, und bietet die folgenden Vorteile:
- Kostenkontrolle: Indem der Verlauf unabhängig von der Konversationslänge unter einer bestimmten Größe gehalten wird, verhindert es, dass die Kosten pro Anfrage unendlich wachsen
- Kein Überlauf: Hält die Eingabegröße innerhalb des maximalen Limits des Modells
Konzeptdiagramm:
Mit einer Fenstergröße von 4 behalten wir nur die letzten 4 Nachrichten (3, 4, 5, 6) und verwerfen die älteren Nachrichten (1, 2). Wenn eine neue Nachricht (7) eintrifft, bewegt sich das Fenster zur neuesten Nachricht, verwirft die älteste Nachricht (3) und behält die Nachrichten 4, 5, 6, 7.
Sliding Window-Kompromisse:
- Vorteile: Begrenzt die Verlaufsgröße, um die Kosten konstant zu halten und verhindert Kontextfenster-Überlauf
- Nachteile: Nachrichten außerhalb der Fenstergröße werden verworfen, sodass das Modell nicht auf sie verweisen kann
Dieser Kompromiss kann problematisch sein. Die Lösung besteht darin, ein Sliding Window für die aktuelle Konversation zu verwenden, während notwendige vergangene Informationen bei Bedarf aus einem separaten Speicher abgerufen werden. Dies kann mit RAG (Retrieval-Augmented Generation) implementiert werden, das wir in Kapitel 9 behandeln werden.
Fenstergrößen-Einheiten: Nachrichtenanzahl vs. Token-Anzahl
Das obige Diagramm zeigt ein Beispiel für die Festlegung der Fenstergröße nach Nachrichtenanzahl. In Produktionsumgebungen wird jedoch häufiger token-basierte Fenstergrößenbestimmung verwendet, da Nachrichtengrößen variieren:
Nachrichtenanzahl-basiertes Trimmen:
Begrenzt die Fenstergröße nach Nachrichtenanzahl (z.B. nur die letzten 20 Nachrichten behalten).
- Eigenschaften: Feste Nachrichtenanzahl, aber die Gesamtanzahl der Tokens kann dennoch variieren
- Verwenden, wenn: Nachrichtengrößen kontrolliert sind (SMS, zeichenbegrenzte Chats)
- Risiko: Eine lange Nachricht kann dennoch das Kontextfenster überschreiten
Token-Anzahl-basiertes Trimmen (Empfohlen für Produktion):
Begrenzt die Fenstergröße nach Token-Anzahl, der Eingabeeinheit, die von LLMs verarbeitet wird (z.B. nur die letzten 5.000 Tokens behalten).
- Eigenschaften: Überschreitet niemals das Kontextfenster, unabhängig von der Nachrichtenlänge
- Verwenden, wenn: Nachrichtengrößen variieren
Implementierung von Token-basiertem Trimmen: trim_messages()
LangChain bietet ein trim_messages()-Dienstprogramm, das das token-basierte Sliding Window-Muster implementiert.
Wie trim_messages() funktioniert:
Diese Funktion nimmt die vollständige Nachrichtenliste und die maximale Token-Anzahl und gibt nur die neuesten Nachrichten zurück, die innerhalb des Token-Limits passen.
Wichtige Parameter:
messages: Zu trimmende Nachrichtenlistemax_tokens: Maximale Token-Anzahl, die beibehalten werden solltoken_counter: Funktion zur Berechnung der Token-Anzahl für jede Nachricht (verwendet den Tokenizer des Modells, um die Token-Anzahl der Nachricht zurückzugeben)include_system: Ob SystemMessage immer behalten werden soll (normalerweise True)
Einrichten des Token-Zählers:
import tiktoken
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, SystemMessage
from langchain_core.messages.utils import trim_messages
# Tokenizer für Ihr Modell abrufen (verschiedene Modellfamilien verwenden verschiedene Tokenizer)
# Modellnamen angeben, um den entsprechenden Tokenizer zu erhalten
# Claude-Modelle: Verwenden Sie Anthropics Tokenizer (tiktoken ist OpenAI-spezifisch)
enc = tiktoken.encoding_for_model("gpt-4o")
def token_counter(msg: BaseMessage) -> int:
"""Zählt Tokens in einer einzelnen Nachricht."""
return len(enc.encode(msg.content or ""))
# Konversationsverlauf erstellen
messages = [
SystemMessage(content="Sie sind ein hilfreicher Assistent."),
HumanMessage(content="Hallo!"),
AIMessage(content="Hallo! Wie kann ich helfen?"),
HumanMessage(content="Was ist 2+2?"),
AIMessage(content="2+2 ist gleich 4."),
HumanMessage(content="Was ist 3+3?"),
AIMessage(content="3+3 ist gleich 6."),
HumanMessage(content="Was ist 4+4?"),
]
# Nur Nachrichten innerhalb der maximalen Token-Anzahl behalten
trimmed = trim_messages(
messages,
max_tokens=30,
token_counter=token_counter,
include_system=True,
)
print(f"Original: {len(messages)} Nachrichten")
print(f"Getrimmt: {len(trimmed)} Nachrichten")
for m in trimmed:
print(f"{type(m).__name__}: {m.content}")Hinweis: Die Anzahl der beibehaltenen Nachrichten hängt vom max_tokens-Wert und der tatsächlichen Token-Anzahl jeder Nachricht ab. Im obigen Beispiel ist max_tokens=30 ein sehr kleiner Wert, der zu Testzwecken gewählt wurde. In der Produktion sollten Sie einen angemessenen Wert unter Berücksichtigung der durchschnittlichen Nachrichtengröße und des gewünschten Konversationsbereichs festlegen.
Beispiel: Anwenden von Trimmen auf Chat-Funktion
import tiktoken
from langchain_openai import ChatOpenAI
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import SystemMessage, HumanMessage, BaseMessage
from langchain_core.messages.utils import trim_messages
llm = ChatOpenAI(model="gpt-4o-mini")
history = InMemoryChatMessageHistory()
enc = tiktoken.encoding_for_model("gpt-4o-mini")
def token_counter(msg: BaseMessage) -> int:
"""Zählt Tokens in einer einzelnen Nachricht."""
return len(enc.encode(msg.content or ""))
def chat_with_trimming(user_input: str, max_tokens: int = 1000) -> str:
"""Chat mit automatischem Verlaufs-Trimmen."""
history.add_message(HumanMessage(content=user_input))
system_msg = SystemMessage(content="Sie sind ein hilfreicher Assistent.")
all_messages = [system_msg] + history.messages
# Auf maximale Token-Anzahl trimmen
trimmed_messages = trim_messages(
all_messages,
max_tokens=max_tokens,
token_counter=token_counter,
include_system=True,
)
response = llm.invoke(trimmed_messages)
history.add_message(response)
return response.content
# Verwendungsbeispiel
print(chat_with_trimming("Ich plane eine Reise nach Japan"))
print(chat_with_trimming("Was sollte ich in Tokio besuchen?"))
print(chat_with_trimming("Wie viele Tage sollte ich dort verbringen?"))
# Auch wenn der Verlauf wächst, wird nur die aktuelle Konversation innerhalb der maximalen Token-Anzahl an das LLM gesendetNächster Schritt: Einschränkungen der Konversationszustandsverwaltung und Lösungen (Kapitel 9: RAG)
Das Bereitstellen des Konversationsverlaufs für das Modell hilft, den Konversationskontext aufrechtzuerhalten. Dies allein ist jedoch in einigen Fällen nicht ausreichend. Zum Beispiel:
- Wenn Sie Informationen in Firmendokumenten oder Handbüchern finden müssen
- Wenn Sie auf alten Konversationsverlauf verweisen müssen, der aus dem Sliding Window herausgeschoben wurde
Hier wird RAG (Retrieval-Augmented Generation) benötigt. RAG funktioniert wie folgt:
- Speicherung: Informationen in einer Vektordatenbank für semantische Suche speichern
- Abruf: Informationen mit ähnlicher Bedeutung zu dem abfragen, wonach Sie suchen
Wenn RAG verwendet wird, um die Einschränkungen des Sliding Window-Musters zu ergänzen:
- Sliding Window: Letzte 20 Nachrichten behalten (aktueller Kontext)
- RAG: Relevanten Inhalt aus Nachrichten suchen und abrufen, die aus dem Fenster herausgeschoben wurden
Anstatt sich nur an die aktuelle Konversation zu erinnern, ermöglicht RAG Ihnen, ein Langzeitgedächtnissystem zu erstellen.
RAG ermöglicht es Agents, durch externe Wissensnutzung und Abruf vergangener Konversationen breiteres Wissen und längeren Kontext zu nutzen.
Kapitel 9 wird behandeln, wie man RAG im Detail implementiert.
Kapitelzusammenfassung:
In diesem Kapitel haben Sie gelernt:
- Warum LLMs vergessen: Modelle sind zustandslos – Speicher ist eine Illusion, die durch erneutes Senden des Konversationsverlaufs erzeugt wird
- Nachrichtentypen: SystemMessage (Anweisungen), HumanMessage (Benutzereingabe), AIMessage (Modellantworten)
- Zustandsverwaltung: Verwaltung des Konversationsverlaufs mit
InMemoryChatMessageHistory - Verwaltung der Konversationslänge: Warum unbegrenzter Verlauf Kosten- und Kontextfenster-Probleme verursacht
- Sliding Windows: Ein Muster, das nur aktuelle Nachrichten behält, um zu verhindern, dass der Verlauf unendlich wächst
- Token-basiertes Trimmen: Implementierung von Sliding Windows mit
trim_messages()und Token-Zählung