Zum Inhalt springen
EN DE

Boss Fight: CLI Chat

Du baust einen interaktiven CLI-Chat — ein Terminal-Programm, das wie ein Mini-ChatGPT funktioniert. Der Chat kombiniert alle sechs Bausteine aus Level 1: SDK Setup, Modell-Auswahl, System Prompt, Streaming und Structured Output.

Dein Chat soll sich so anfuehlen:

> Hallo! Wer bist Du?
Ich bin Dein AI-Assistent. Ich kann Dir bei Fragen helfen...
> /json Was ist TypeScript?
{
"topic": "TypeScript",
"summary": "TypeScript ist eine typisierte Erweiterung von JavaScript...",
"keyPoints": ["Statische Typen", "Bessere IDE-Unterstuetzung", ...],
"difficulty": "beginner"
}
> Tokens bisher: 342

Erwartete Dauer: 30-45 Minuten. Erstelle eine Datei chat.ts und starte mit npx tsx chat.ts.

Wichtig: Jeder generateText/streamText Call ist zustandslos — das LLM erinnert sich nicht an vorherige Nachrichten. Wenn Du einen Chat bauen willst, musst Du den gesamten Nachrichtenverlauf bei jedem Call als messages Array mitschicken. Ohne das antwortet das LLM auf jede Nachricht, als wäre es die erste.

Dieses Projekt verbindet alle sechs Bausteine:

User Input fliesst in selectModel zum Modell, zusammen mit System Prompt in streamText/Output.object zum CLI Output
  1. Interaktive Eingabe — Der User kann wiederholt Nachrichten eingeben (readline oder process.stdin). Das Programm laeuft bis der User “exit” tippt.
  2. System Prompt (Challenge 1.6) — Der Chat hat eine definierte Rolle mit Regeln und Stil. Der System Prompt wird bei jedem Call mitgegeben.
  3. streamText für normale Antworten (Challenge 1.4) — Normale Nachrichten werden mit streamText beantwortet. Text erscheint Token für Token im Terminal.
  4. Output.object + Zod Schema bei “/json” (Challenge 1.5) — Wenn der User eine Nachricht mit /json beginnt, wird die Antwort als strukturiertes JSON-Objekt generiert (generateText + Output.object).
  5. selectModel basierend auf Nachrichtenlaenge (Challenge 1.2) — Kurze Nachrichten (unter 50 Zeichen) nutzen ein guenstiges Flash-Modell. Lange Nachrichten nutzen ein Pro-Modell.
  6. Usage-Tracking (Challenge 1.3) — Nach jeder Antwort werden die verbrauchten Tokens angezeigt. Ein Gesamtzaehler trackt die Session.
import * as readline from 'readline';
// TODO: Importiere AI SDK Funktionen (generateText, streamText, Output)
// TODO: Importiere Provider (anthropic aus '@ai-sdk/anthropic')
// TODO: Importiere zod (z aus 'zod')
// Tipp: Wenn Du nur einen Provider hast, nutze verschiedene Modelle
// desselben Providers -- z.B. anthropic('claude-haiku-3-5-20241022')
// für kurze und anthropic('claude-sonnet-4-5-20250514') für lange Nachrichten.
// TODO: Definiere einen System Prompt mit Rolle und Regeln
// TODO: Definiere ein Zod Schema für den /json Modus
// TODO: Implementiere selectModel(message: string)
let totalTokens = 0;
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
function askQuestion(): void {
rl.question('\n> ', async (input) => {
const message = input.trim();
if (message === 'exit') {
console.log(`\nSession beendet. Tokens gesamt: ${totalTokens}`);
rl.close();
return;
}
if (message === '') {
askQuestion();
return;
}
// TODO: Pruefe ob die Nachricht mit /json beginnt
// TODO: Waehle das Modell basierend auf der Nachrichtenlaenge
// TODO: Bei /json → generateText + Output.object
// TODO: Sonst → streamText + textStream
// TODO: Aktualisiere totalTokens
askQuestion();
});
}
console.log('CLI Chat gestartet. Tippe "exit" zum Beenden.');
console.log('Starte eine Nachricht mit /json für strukturierte Ausgabe.\n');
askQuestion();

Dein Boss Fight ist bestanden, wenn:

  • Der Chat laeuft interaktiv im Terminal und reagiert auf User-Eingaben
  • Ein System Prompt definiert Rolle und Verhalten des Assistenten
  • Normale Nachrichten werden mit streamText Token für Token ausgegeben
  • Nachrichten mit /json erzeugen strukturierten Output mit Output.object und Zod Schema
  • selectModel waehlt das Modell basierend auf der Nachrichtenlaenge
  • Nach jeder Antwort wird der Token-Verbrauch angezeigt
  • Ein Gesamtzaehler trackt die Tokens der gesamten Session
  • “exit” beendet das Programm sauber mit Gesamtzaehler
Hinweis 1: readline und async/await

Das readline-Callback ist nicht direkt async. Du kannst den Callback aber als async markieren — das funktioniert, weil readline den Rueckgabewert des Callbacks ignoriert. Alternativ kannst Du eine separate async function handleMessage(message: string) erstellen und sie aus dem Callback aufrufen.

Hinweis 2: Stream-Output ins Terminal schreiben

Für streamText nutze process.stdout.write(chunk) statt console.log(chunk), damit der Text ohne Zeilenumbrueche Wort für Wort erscheint. Nach dem Stream einen console.log() für den finalen Zeilenumbruch. Den Token-Verbrauch bekommst Du aus fullStream (Event-Typ finish) oder über result.usage (ein Promise, das Du nach dem Stream awaiten kannst).

Hinweis 3: /json-Erkennung und Prompt-Extraktion

Pruefe mit message.startsWith('/json') ob der JSON-Modus aktiviert werden soll. Der eigentliche Prompt ist dann message.slice(5).trim() — alles nach “/json”. Für den JSON-Modus nutze generateText (nicht streamText), weil Output.object ein vollstaendiges Objekt braucht, kein Stream.

Part of AI Learning — free courses from prompt to production. Jan on LinkedIn