Mehrstufige Gespräche in Python
Auf dieser Seite
Speicher, der eigentlich kein Speicher ist
Im Gegensatz zu einer Suchmaschine folgt ein Sprachmodell dem Faden eines Gesprächs: Es berücksichtigt das, was zuvor gesagt wurde, und baut seine Antworten auf diesem angesammelten Kontext auf. Dies wird als mehrstufiges Gespräch bezeichnet und ist es, was elliptische Fragen wie „und mit Argumenten?” möglich macht.
Technisch gesehen erinnert sich das Modell jedoch an nichts. Es wird kein Zustand auf dem Server zwischen Aufrufen gespeichert. Bei jedem Request müssen Sie die gesamte Historie erneut senden, die das Modell von Anfang bis Ende durchliest, bevor es antwortet. Dieser Mechanismus erklärt zwei Dinge, die wir später noch einmal betrachten werden: Sie tragen die alleinige Verantwortung für das Speichermanagement, und jede Runde des Gesprächs kostet mehr als die vorherige.
Die drei Rollen, die die Historie strukturieren
Jede Nachricht in der Historie trägt eine Rolle, die dem Modell mitteilt, wer spricht. Die Rolle system definiert das allgemeine Verhalten: Sie wird an erster Stelle platziert und beeinflusst jede nachfolgende Antwort.
{"role": "system", "content": "Sie sind ein Experte für französische Küche. Antworten Sie stets auf Französisch unter Verwendung präziser kulinarischer Begriffe."}
Die Rolle user enthält die Nachrichten des Nutzers – also die gestellte Frage oder die erteilte Anweisung:
{"role": "user", "content": "Wie fertige ich perfekte Blätterteig her?"}
Schließlich enthält die Rolle assistant die vorherigen Antworten des Modells. Dies sind die Nachrichten, die Sie in die Historie zurückführen, um die Kohärenz aufrechtzuerhalten. Wenn Sie diese vergessen, ist es gleichbedeutend damit, den Speicher des Gesprächs bei jeder Runde zu löschen.
{"role": "assistant", "content": "Um perfekten Blätterteig herzustellen, beginnen Sie damit..."}
Ein vollständiges Gespräch, Runde für Runde
Der folgende Code wendet dieses Prinzip an: Eine Funktion, die die Frage zur Historie hinzufügt, das Modell aufruft und die Antwort an der richtigen Stelle speichert, bevor sie zurückgegeben wird.
import os
from mistralai.client import Mistral
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
historique = [
{
"role": "system",
"content": "Sie sind eine Lehrkraft, die sich auf Python-Programmierung spezialisiert hat. Antworten Sie prägnant und fügen Sie Codebeispiele hinzu."
}
]
def poser_question(question: str) -> str:
"""Sendet eine Frage und aktualisiert die Historie."""
historique.append({"role": "user", "content": question})
response = client.chat.complete(
model="mistral-small-latest",
messages=historique,
)
reponse_texte = response.choices[0].message.content
historique.append({"role": "assistant", "content": reponse_texte})
return reponse_texte
# Runde 1
print("F: Was ist ein Decorator in Python?")
r1 = poser_question("Was ist ein Decorator in Python?")
print(f"A: {r1}\n")
# Runde 2 — das Modell kennt den Kontext
print("F: Können Sie mir ein konkretes Beispiel zeigen?")
r2 = poser_question("Können Sie mir ein konkretes Beispiel zeigen?")
print(f"A: {r2}\n")
# Runde 3 — implizite Bezugnahme auf vorherige Runden
print("F: Wie verwende ich es mit Argumenten?")
r3 = poser_question("Wie verwende ich es mit Argumenten?")
print(f"A: {r3}\n")
Bei der dritten Runde wird das Wort „Decorator” in der Frage nicht mehr erwähnt. Das Modell versteht dennoch, dass sich „es verwenden” auf dieses Konzept bezieht, da es den gesamten Austausch erneut durchliest. Entfernen Sie die Zeile, die die Antwort zur Historie hinzufügt, und führen Sie den Code erneut aus: Das Modell wird fragen, worüber Sie sprechen. Dies ist der schnellste Beweis dafür, dass Kontinuität nicht vom Modell, sondern von Ihrem Code stammt.
Verhindern, dass die Historie prohibitiv teuer wird
Jedes Modell verfügt über ein begrenztes Kontextfenster, gemessen in Tokens. Wenn die Historie wächst, wird jeder Request teurer, da alles erneut übertragen werden muss. Ein Gespräch mit dreißig Runden wird effektiv dreißigmal am Anfang abgerechnet.
Die einfachste Abhilfe besteht darin, die Historie zu kürzen, wobei die Systemnachricht und die letzten N-Austausche beibehalten werden:
MAX_MESSAGES = 20
def ajouter_message(historique, role, content):
historique.append({"role": role, "content": content})
# Behalten Sie die Systemnachricht + die letzten N Nachrichten
if len(historique) > MAX_MESSAGES + 1: # +1 für die Systemnachricht
system_msg = historique[0]
historique = [system_msg] + historique[-(MAX_MESSAGES):]
return historique
Dieser Ansatz ist kostengünstig, aber er ist brutal: Was der Nutzer in der zweiten Runde angegeben hat, verschwindet für immer. Wenn das Gespräch lang ist und dieser ältere Kontext wichtig ist – beispielsweise bei einer Anforderungserhebung – fassen Sie ihn zusammen, anstatt ihn wegzuschmeißen:
def resumer_historique(client, historique):
"""Fasst alte Nachrichten zusammen, um Platz im Kontext zu schaffen."""
texte_complet = "\n".join(
f"{m['role']}: {m['content']}"
for m in historique[1:-4] # Systemnachricht und die letzten 4 Nachrichten ausschließen
)
resume = client.chat.complete(
model="mistral-small-latest",
messages=[
{"role": "user", "content": f"Fassen Sie dieses Gespräch in 3-4 Sätzen zusammen:\n{texte_complet}"}
]
)
return [
historique[0], # Systemnachricht
{"role": "assistant", "content": f"[Gesprächszusammenfassung] {resume.choices[0].message.content}"},
] + historique[-4:] # Die letzten 4 Nachrichten
Die Idee besteht darin, die ferne Vergangenheit in wenigen Sätzen zu komprimieren, während die letzten vier Nachrichten intakt bleiben, da sie den unmittelbaren Kontext tragen. Die Zusammenfassung verbraucht einen zusätzlichen Aufruf, amortisiert sich aber in den nachfolgenden Runden vielfach.
Formulierung der Systemnachricht
Die Systemnachricht ist das mächtigste und am wenigsten genutzte Werkzeug in dieser Lektion. Viele begnügen sich mit einer generischen „Sie sind eine hilfreiche Assistentin”, die nichts hinzufügt. Eine gute Systemnachricht formulierbare Einschränkungen:
system_prompt = """Sie sind ein technischer Assistent von Corsen AI.
Regeln:
- Antworten Sie auf Französisch in einem professionellen Ton
- Fügen Sie Codebeispiele hinzu, wenn dies relevant ist
- Wenn Sie unsicher sind, sagen Sie dies, anstatt Dinge zu erfinden
- Begrenzen Sie Ihre Antworten auf 200 Wörter, es sei denn, Sie werden gebeten, dies zu ändern
Kontext: Der Nutzer arbeitet an einem Python 3.12-Projekt mit FastAPI."""
Jede Zeile hier erzeugt einen messbaren Effekt: Die Längenbegrenzung enthält Kosten, die Aufforderung, Unsicherheit anzuerkennen, reduziert Halluzinationen, und die Erwähnung des Stacks vermeidet generische, vom Thema abweichende Antworten. Prägnant, präzise, unmissverständlich – das ist alles, was sie sein muss.
Kernaussagen
- Die vollständige Historie wird bei jedem Request erneut gesendet – das Modell „erinnert” sich an nichts
- Drei Rollen (
system,user,assistant) strukturieren das Gespräch - Die
system-Nachricht definiert das allgemeine Verhalten – platzieren Sie sie immer an erster Stelle - Verwalten Sie die Kontextgröße, indem Sie alte Nachrichten kürzen oder zusammenfassen
- Je länger die Historie, desto mehr kostet jeder Request in Tokens