Das Kontextfenster
Kurz gesagt: Das Kontextfenster ist die Zahl der Tokens, die ein Modell in einer Anfrage sieht; das Modell hat kein Gedächtnis, deshalb trägt jede Anfrage den System-Prompt und den Verlauf, und wenn das Fenster voll ist, fallen die ältesten Nachrichten heraus.
Problem
Der Assistent hat die Nummer vergessen
Der Support-Assistent spricht mit einem Kunden. In der ersten Nachricht nennt der Kunde die Bestellnummer: 1042. Dann fragt er nach einer Filiale des Kuriers, nach einem Gutscheincode, nach dem Umtausch einer Jacke. Bei der fünften Frage („Wann genau kommt das Paket an?“) antwortet der Assistent: „Wie lautet deine Bestellnummer?“
Der Kunde ist verärgert: Er hat sie doch genannt. Und am Monatsende ist die Rechnung für das Modell höher, als die Zahl der Gespräche erklärt.
Beide Probleme haben eine Ursache. Sie liegt nicht im Modell, sondern in dem, was das Programm an das Modell sendet.
Die Idee
Das Modell hat kein Gedächtnis
Ein Sprachmodell erinnert sich zwischen zwei Anfragen an nichts. Es bekommt Text, schreibt eine Fortsetzung und vergisst. Das „Gedächtnis“ des Gesprächs lebt also in deinem Programm: Bei jeder Runde sendet es den System-Prompt und den ganzen Verlauf neu, alle bisherigen Nachrichten, dazu die neue Frage.
Das Modell sieht nur, was in der Anfrage steht, und nur bis zu einer bestimmten Zahl von Tokens. Diese Grenze heißt Kontextfenster. Alles kommt hinein: der System-Prompt, der Verlauf, die neue Frage und die Antwort, die das Modell gleich schreibt.
Wie groß das Fenster ist, hängt vom Modell ab. Als Beispiel: von einigen Zehntausend bis zu etwa einer Million Tokens. Das klingt nach viel, aber Tokens sind schneller aufgebraucht als Wörter, und jedes Token in der Anfrage wird bezahlt.
Sieh es dir an
Das Gespräch im Code
user ist der Kunde, assistant ist das Modell) und einem Text, genau so, wie die echten SDKs es wollen:Die Tokens werden grob gezählt, damit die Zahlen sichtbar bleiben. Den echten Wert liefert der Tokenizer des Modells. Die Anfrage ist der System-Prompt plus jede Nachricht:
# grob: 4 Tokens auf 3 Wörter (die echte Zahl liefert der Tokenizer)def tokens(text): words = len(text.split()) return (words * 4 + 2) // 3
# die Anfrage: der System-Prompt und der ganze Verlaufdef request_tokens(system, history): total = tokens(system) for message in history: total += tokens(message["content"]) return totalDas Gespräch aus dem Problem: fünf Fragen des Kunden und vier Antworten.
system = "Du bist der Assistent des Supports im Online-Shop. Antworte kurz und höflich."customer = [ "Hallo, Bestellung 1042 fehlt noch. Ich habe sie vor fünf Tagen bestellt.", "Könnt ihr sie statt zu mir in eine Filiale des Kuriers schicken?", "Ich habe außerdem einen Gutscheincode, der beim Bezahlen nicht funktioniert hat. Was nun?", "Danke. Die Jacke aus derselben Bestellung ist mir zu klein. Kann ich sie umtauschen?", "Zuletzt noch: Wann genau kommt das Paket an?",]assistant = [ "Entschuldige die Verspätung. Bestellung 1042 ist beim Kurier, morgen bis 18 Uhr bei dir.", "Ja, das Paket liegt in der Filiale des Kuriers bereit, Hauptstraße 12.", "Der Code gilt ab 50 Euro. Wir erstatten die Differenz in 5 Tagen.", "Ja, der Umtausch ist 14 Tage lang kostenlos. Schreib uns, sobald sie ankommt.",]Bei jeder Runde kommt die Frage in den Verlauf, und der ganze Verlauf wird gesendet. Der Preis ist ein Beispiel: 3 € pro Million Eingabe-Tokens.
history = []total = 0print("Runde Anfrage gesamt")for i, text in enumerate(customer): history.append({"role": "user", "content": text}) sent = request_tokens(system, history) total += sent print(f"{i + 1:3d} {sent:7d} {total:6d}") if i < len(assistant): history.append({"role": "assistant", "content": assistant[i]})price = 3.0 # € pro Million Eingabe-Tokens (ein Beispielpreis)one = total * price / 1_000_000print(f"ein Gespräch: {one:.4f} €, 20000 Gespräche: {one * 20000:.2f} €")Runde Anfrage gesamt 1 32 32 2 67 99 3 101 200 4 138 338 5 167 505ein Gespräch: 0.0015 €, 20000 Gespräche: 30.30 €Die fünfte Anfrage hat 167 Tokens, obwohl die neue Frage nur 11 hat. Das ganze Gespräch hat 167 Tokens, aber gesendet wurden 505: Die erste Nachricht wurde fünfmal gesendet. 0,0015 € pro Gespräch sind nicht viel. Aber die Kosten wachsen mit jeder Runde, nicht nur mit jedem neuen Gespräch, und 20.000 Gespräche im Monat sind schon 30,30 €.
Die Idee
Wenn das Fenster voll ist
Das Gespräch wächst, das Fenster nicht. Wenn die Anfrage nicht hineinpasst, lehnt der Anbieter sie mit einem Fehler ab oder schneidet sie selbst ab. Besser, dein Programm entscheidet, was herausfällt.
Die häufigste Regel: Behalte immer den System-Prompt und die neuesten Nachrichten, so viele wie passen. Die ältesten fallen heraus. Die Funktion geht von der neuesten Nachricht rückwärts und hört bei der ersten auf, die nicht mehr passt:
# vom Neuesten rückwärts, solange es passtdef fit_window(history, budget): kept = [] used = 0 for message in reversed(history): t = tokens(message["content"]) if used + t > budget: break kept.insert(0, message) used += t return keptDas Fenster in der Lektion hat 150 Tokens, absichtlich klein, damit es auf einen Bildschirm passt. Das Programm schneidet das Gespräch aus dem Problem zweimal ab: einmal nur mit der Regel und einmal mit einer Notiz, die das Programm getrennt neben dem System-Prompt aufbewahrt.
history = []for i, text in enumerate(customer): history.append({"role": "user", "content": text}) if i < len(assistant): history.append({"role": "assistant", "content": assistant[i]})
limit = 150print(f"die Anfrage: {request_tokens(system, history)} Tokens, das Fenster: {limit}")
window = fit_window(history, limit - tokens(system))used = request_tokens(system, window)seen = any("1042" in m["content"] for m in window)print(f"ohne Notiz: {len(window)} von {len(history)} Nachrichten, {used} Tokens")print(f" 1042 im Fenster: {'ja' if seen else 'nein'}")
note = "Notiz: Bestellung 1042."window = fit_window(history, limit - tokens(system) - tokens(note))used = request_tokens(system, window) + tokens(note)seen = "1042" in note or any("1042" in m["content"] for m in window)print(f"mit Notiz: {len(window)} von {len(history)} Nachrichten, {used} Tokens")print(f" 1042 im Fenster: {'ja' if seen else 'nein'}")die Anfrage: 167 Tokens, das Fenster: 150ohne Notiz: 7 von 9 Nachrichten, 132 Tokens 1042 im Fenster: neinmit Notiz: 7 von 9 Nachrichten, 136 Tokens 1042 im Fenster: jaOhne die Notiz fallen die beiden ältesten Nachrichten heraus, und mit ihnen die Nummer 1042. Das Modell kann keinen Text benutzen, den es nicht sieht, also fragt es noch einmal. Die Notiz hat 4 Tokens und bringt sie zurück.
Sieh es dir an
Das Fenster füllt sich
# vom Neuesten rückwärts, solange es passtdef fit_window(history, budget): kept = [] used = 0 for message in reversed(history): t = tokens(message["content"]) if used + t > budget: break kept.insert(0, message) used += t return keptSchritt für Schritt, in Worten
- Das Fenster fasst 150 Tokens. Der System-Prompt steht immer zuerst: 16 Tokens.
- Runde 1: Der Kunde nennt die Bestellnummer 1042. Die Anfrage hat 32 Tokens.
- Jede Anfrage trägt den ganzen Verlauf: 67, 101, 138 Tokens. Bisher gesendet: 338.
- Runde 5: Die Anfrage hat 167 Tokens, das Fenster 150. Sie passt nicht.
- fitWindow geht vom Neuesten rückwärts und hört bei der ersten Nachricht auf, die nicht passt. Die zwei ältesten fallen heraus: 132 Tokens bleiben.
- Die Bestellnummer 1042 stand in ihnen. Das Modell sieht sie nicht mehr und wird erneut danach fragen.
- Die Lösung: Das Programm hält eine Notiz „Bestellung 1042“ neben dem System-Prompt. 136 Tokens, und die Nummer ist sichtbar.
- Das Gespräch hat 167 Tokens, aber gesendet wurden 505: Der Verlauf wird bei jeder Runde neu bezahlt.
Ganze Nachrichten fallen heraus, von der ältesten an. Der System-Prompt und die Notiz fallen nie heraus: Das Programm setzt sie getrennt vor den Verlauf.
Probier es aus
Finde den Fehler
Ein Kollege schreibt fitWindow selbst. Wenn das Gespräch länger wird, beantwortet der Assistent Fragen von vor zehn Minuten und scheint die letzte Frage nicht zu hören. In welcher Zeile steckt der Fehler?
Wähle die Zeile mit dem Fehler.
Die Schleife beginnt bei der ältesten Nachricht. Sie füllt das Fenster mit dem Anfang des Gesprächs und hört auf, bevor sie den neuen Teil erreicht, also fällt genau die letzte Frage heraus. Sie sollte am Ende des Verlaufs beginnen und vorn einfügen.
Probier es aus
Sag die Ausgabe voraus
Vier kurze Nachrichten und ein Budget von 25 Tokens. Die Zählung ist dieselbe: 4 Tokens für 3 Wörter, aufgerundet. Was gibt das Programm aus?
history = [ {"role": "user", "content": "Ich habe letzten Freitagmorgen zwei Jacken und eine Mütze bestellt."}, {"role": "user", "content": "Eine Jacke ist angekommen."}, {"role": "assistant", "content": "Die Mütze und die zweite Jacke kommen in einem Extrapaket."}, {"role": "user", "content": "Und wann?"},]window = fit_window(history, 25)print(len(window), window[0]["content"])Vom Neuesten rückwärts: „Und wann?“ hat 3 Tokens, die Antwort über das Extrapaket hat 14 (zusammen 17), „Eine Jacke ist angekommen.“ hat 6 (zusammen 23). Die erste Nachricht hat weitere 14 und macht 37, mehr als 25. Drei Nachrichten bleiben, und die erste davon ist „Eine Jacke ist angekommen.“
Probier es aus
Ergänze
Jede Anfrage trägt den System-Prompt. Beende die Zählung.
def request_tokens(system, history):
total =
for message in history:
total += tokens(message["content"])
return totalDie Summe beginnt mit den Tokens des System-Prompts, weil er in jeder Anfrage steht. Dann kommt jede Nachricht des Verlaufs dazu.
Die Idee
Was du behältst und wann ein langer Kontext schadet
Wege, ein Gespräch in das Fenster zu bekommen:
- Die letzten n Nachrichten (ein gleitendes Fenster). Am einfachsten und am häufigsten. Der Anfang geht verloren.
- Eine Notiz mit den Fakten, die das Programm selbst führt: eine Bestellnummer, eine Adresse, was schon versprochen wurde. Günstig und genau, aber du musst wissen, welche Fakten wichtig sind.
- Eine Zusammenfassung der alten Runden, vom Modell geschrieben. Sie behält mehr, kostet aber eine weitere Anfrage und kann ein Detail auslassen.
- Suchen statt alles senden. Die Dokumente des Shops kommen nicht vollständig in die Anfrage: Das Programm findet die Abschnitte, die die Frage beantworten. Das ist die semantische Suche, die Grundlage von RAG, und sie hat später im Kurs ein eigenes Modul.
Wann ein langer Kontext schadet:
- Kosten und Langsamkeit. Jedes Token der Anfrage wird bei jeder Runde bezahlt und gelesen. Ein doppelt so langer Verlauf ist eine doppelt so teure Anfrage und eine langsamere Antwort.
- Mitten verloren. Modelle nutzen Anfang und Ende eines langen Kontexts besser als die Mitte. Eine Tatsache, die mitten in 50 Seiten vergraben ist, kann übersehen werden.
- Das Fenster ist keine Datenbank. Was auch morgen noch gewusst werden muss (Bestellungen, Vorlieben), steht in der Datenbank des Shops. In die Anfrage kommt nur, was diese Antwort braucht.
- Fremder Text im Kontext. Alles im Fenster beeinflusst die Antwort, auch Text, den du nicht geschrieben hast. Warum das gefährlich ist, steht in der Lektion über Prompt Injection im Modul über Agenten.
Genauer, für Neugierige
Warum das Gesendete so schnell wächst. Der System-Prompt habe S Tokens, und jede Runde füge m Tokens Verlauf hinzu. Die Anfrage in Runde k hat etwa S + k·m. Die Summe über n Runden ist n·S + m·(1 + 2 + … + n) = n·S + m·n·(n + 1) / 2. In Worten: Ein doppelt so langes Gespräch kostet etwa viermal so viele gesendete Tokens. Deshalb werden lange Chats gekürzt oder zusammengefasst.
Auch die Antwort steht im Fenster. Wenn das Fenster 200.000 Tokens hat (ein Beispiel) und die Anfrage 199.000, bleiben 1.000 für die Antwort. Deshalb hat die Anfrage eine Grenze für die Antwort (max_tokens, in der Lektion über den API-Aufruf), und sie muss zusammen mit dem Verlauf hineinpassen.
Das Bigramm-Modell hat ein Fenster von einem Token. Das Modell aus der Lektion Das nächste Token vorhersagen schaut nur auf das letzte Wort. Die Zahl 1042 vom Anfang des Satzes erreicht es nie. Große Modelle schauen Tausende Tokens zurück, aber immer nur bis zu einer Grenze.
Warum das Fenster begrenzt ist. Attention, die Idee aus der Lektion Embeddings: Bedeutung als Vektoren, vergleicht jedes Token mit jedem Token davor. Bei doppelt so vielen Tokens gibt es etwa viermal so viele Vergleiche. Darum ist ein langer Kontext für den Anbieter teuer und für dich langsam.
Prompt Caching. Anbieter können den wiederholten Anfang einer Anfrage (den System-Prompt, die Dokumente) behalten und dafür weniger berechnen. Wie du das nutzt, steht in der Lektion über Kosten und Latenz.
Echte Zähler. Hier gilt: Tokens = Wörter × 4 / 3. Ein echter Tokenizer liefert im Englischen etwa 4 Zeichen pro Token, und für andere Sprachen oft deutlich mehr Tokens für denselben Inhalt. Die Antwort der API nennt dir die genaue Zahl (usage.input_tokens).
Übung
Ein langes Gespräch
Löse die Aufgabe in deinem Editor.
Der Assistent führt ein Gespräch über 20 Runden. Der System-Prompt hat 200 Tokens, jede Frage des Kunden 32, jede Antwort 45. Schreibe ein Programm, das die Anfrage jeder Runde berechnet (den System-Prompt, alle früheren Fragen und Antworten und die neue Frage) und die Summe der gesendeten Tokens. Es gibt jede fünfte Runde aus und am Ende die Kosten bei 3 € pro Million Eingabe-Tokens (ein Beispiel) für ein Gespräch und für 10.000.
Runde 5: Anfrage 540, gesamt 1930Runde 10: Anfrage 925, gesamt 5785Runde 15: Anfrage 1310, gesamt 11565Runde 20: Anfrage 1695, gesamt 19270ein Gespräch: 0.0578 €, 10000 Gespräche: 578.10 €Die zwanzigste Anfrage hat 1.695 Tokens, und insgesamt werden 19.270 gesendet, mehr als das Elffache der letzten.
Weitere Aufgaben
- Ein Fenster von 1.000 Tokens. Wenn die Anfrage über 1.000 geht, wirf die älteste Runde weg (Frage und Antwort). Wie viel wird jetzt insgesamt gesendet?
- Eine Notiz statt des Verlaufs. Sende statt des ganzen Verlaufs den System-Prompt, eine Notiz von 60 Tokens und die letzten 2 Runden. Vergleiche die Kosten.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: List Comprehensions und Generatoren · dataclass, match und Muster · Text in Stücken: Tokens · Das nächste Token vorhersagen
- Geht weiter in: Die Grenzen des Modells · Ein Modell über eine API aufrufen · Der Prompt ist Code · Strukturierte Ausgabe: JSON nach einem Schema
- Aufgaben zum Thema: Wie ein Sprachmodell funktioniert