Lässt es sich mit cremefarbenem Papier, einer Tönung, mehr Kontrast oder größerer Schrift besser lesen? Wählen Sie Ihre Farben und Ihre Schrift, einmalig für alle Seiten.

Kostenlose Lektion

Streaming-Antworten einer Modell-API

Nach dieser Lektion: Ich kann eine Antwort lesen, die in Stücken ankommt, und sie zu einem Text zusammenfügen.

Lektion, 60 Min.4 BefehleLab für KI-Coding-Agenten: mit opencode Code erklären und reparieren lassen und die Arbeit prüfen

Eine Antwort, die Wort für Wort ankommt

Eine Streaming-Antwort ist eine Antwort, die ein Sprachmodell in kleinen Stücken sendet, während es schreibt. Sie kommt nicht als ein Block am Ende. Chat-Anwendungen nutzen das, damit Worte sofort erscheinen.

Das ist nützlich, denn eine lange Antwort kann viele Sekunden dauern. Mit einem Strom sieht die lesende Person den Anfang fast sofort. Ihr Programm muss die Stücke lesen und zusammenfügen.

Am Ende dieser Lektion

Notiz

Ich kann eine Antwort lesen, die in Stücken ankommt, und sie zu einem Text zusammenfügen.

Schlüsselwörter

BegriffWas er bedeutetBeispiel
Stromeine Antwort, die in Stücken gesendet wird, während sie entsteht
Server-Sent Eventseine Art, wie ein Server Zeilen an einen Client schickt
Datenzeileeine Stromzeile, die mit data: beginnt und JSON enthält
Deltadas neue Textstück einer Datenzeile
Endmarkedie letzte Datenzeile, die sagt, dass der Strom zu Ende istdata: [DONE]

Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.

Ein Strom ist eine Liste von Zeilen

Für einen Strom fügen Sie "stream": true zum Anfragekörper hinzu. Der Server antwortet dann mit Server-Sent Events. Das ist einfacher Text, eine Zeile nach der anderen, mit einer Leerzeile zwischen den Ereignissen.

Jede Zeile, die mit data: beginnt, trägt ein Stück JSON. Der Text steht in choices[0].delta.content. Die letzte Zeile ist data: [DONE].

Die Zeilen eines Stroms

Die Zeile beginnt mitWas sie enthält
data: {"choices":[{"delta":{"role":"assistant"}}]}Die Eröffnung: eine Rolle, noch kein Text
data: {"choices":[{"delta":{"content":"Eins"}}]}Ein Textstück zum Anfügen
data: {"choices":[{"delta":{},"finish_reason":"stop"}]}Der Grund, warum die Antwort endete
data: [DONE]Die Endmarke, die kein JSON ist
: keep-aliveEine Kommentarzeile, die ignoriert wird

Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.

Die ankommenden Byte-Pakete ansehen

Befehl
docker exec -w /home/node/project agent node -e 'const labor = require("./labor.js"); const ereignis = (text) => "data: " + JSON.stringify({ choices: [{ delta: { content: text } }] }) + "\n\n"; const ganz = ereignis("Eins") + ereignis(", zwei") + ereignis(", drei.") + "data: [DONE]\n\n"; (async () => { let pakete = []; let etikett = "Strom"; try { const antwort = await labor.senden({ stream: true, max_tokens: 30, messages: [{ role: "user", content: "Zählen Sie von eins bis fünf." }] }); if (!antwort.ok) throw new Error("HTTP " + antwort.status); const dekoder = new TextDecoder(); for await (const bytes of antwort.body) pakete.push(dekoder.decode(bytes, { stream: true })); } catch (fehler) { etikett = "Aufgezeichnetes Beispiel"; pakete = ganz.match(/[^]{1,45}/g); } console.log(etikett + ": die ersten Pakete aus Bytes"); pakete.slice(0, 4).forEach((eins, i) => console.log((i + 1) + " " + JSON.stringify(eins))); console.log("... " + pakete.length + " Pakete insgesamt."); })();'

Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.

Ein Netzwerkpaket ist keine Zeile

Die Bytes erreichen Ihr Programm in Paketen, und ein Paket kann mitten in einer Zeile enden. Führen Sie darum einen Puffer. Hängen Sie jedes Paket an, schneiden Sie jede vollständige Zeile ab und behalten Sie den Rest für das nächste Paket.

Nur eine vollständige Zeile lässt sich sicher als JSON lesen. Fügen Sie dann jedes Textstück der Antwort hinzu.

strom.js schreiben, einen Leser für ganze Zeilen

Befehl
docker exec -w /home/node/project agent node -e 'require("fs").writeFileSync("strom.js", ["// strom.js: liest eine Stromantwort als vollständige Zeilen, wie auch immer die Pakete geschnitten sind.", "async function* zeilenVon(antwort) {", "  let behalten = \"\";", "  const dekoder = new TextDecoder();", "  for await (const bytes of antwort.body) {", "    behalten += dekoder.decode(bytes, { stream: true });", "    const teile = behalten.split(\"\\n\");", "    behalten = teile.pop();", "    yield* teile;", "  }", "  if (behalten !== \"\") yield behalten;", "}", "", "module.exports = { zeilenVon };"].join("\n") + "\n")'

Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.

stueckeVon ergänzen, das den Text liefert

Befehl
docker exec -w /home/node/project agent node -e 'require("fs").appendFileSync("strom.js", ["", "// Teil 2: sendet einen Aufruf im Strom und liefert die Textstücke, eins nach dem anderen.", "async function* stueckeVon(anfrage) {", "  const antwort = await require(\"./labor.js\").senden({ ...anfrage, stream: true });", "  if (!antwort.ok) throw new Error(\"HTTP \" + antwort.status);", "  for await (const zeile of zeilenVon(antwort)) {", "    if (!zeile.startsWith(\"data: \") || zeile === \"data: [DONE]\") continue;", "    const stueck = JSON.parse(zeile.slice(6)).choices[0].delta.content;", "    if (stueck) yield stueck;", "  }", "}", "", "module.exports = { zeilenVon, stueckeVon };"].join("\n") + "\n")'

Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.

Ein häufiger Fehler

Warnung

Viele glauben, jedes Paket sei ein Ereignis. Das ist falsch. Ein Paket kann drei Ereignisse enthalten oder die Hälfte von einem. Ihr Programm ist richtig, wenn es denselben Text liefert, wie auch immer die Bytes geschnitten sind. Erst puffern, dann Zeilen lesen.

Die Stücke ausgeben und zusammenfügen

Befehl
docker exec -w /home/node/project agent node -e 'const { stueckeVon } = require("./strom.js"); const beispiel = ["Eins", ", zwei", ", drei", ", vier", ", fünf."]; (async () => { const start = Date.now(); let erstes = null, anzahl = 0, text = "", etikett = "Antwort des Modells"; try { const frage = { max_tokens: 40, messages: [{ role: "user", content: "Zählen Sie von eins bis fünf." }] }; for await (const stueck of stueckeVon(frage)) { if (erstes === null) erstes = Date.now() - start; text += stueck; anzahl++; process.stdout.write(stueck); } } catch (fehler) { etikett = "Aufgezeichnetes Beispiel"; text = beispiel.join(""); anzahl = beispiel.length; erstes = null; process.stdout.write(text); } console.log("\n" + etikett + ": " + anzahl + " Stücke, " + text.length + " Zeichen."); if (erstes !== null) console.log("Das erste Stück kam nach " + erstes + " ms, alles nach " + (Date.now() - start) + " ms."); })();'

Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.

Beispiel: von den Zeilen zum Text

Datenzeilen der Reihe nachAngefügtes StückText bisher
Eröffnung, nur die RolleKeinesLeer
content "Eins"EinsEins
content ", zwei", zweiEins, zwei
finish_reason "stop"KeinesEins, zwei
data: [DONE]KeinesEins, zwei

Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.

Zum Weiterdenken: das erste Stück messen

Führen Sie das Zusammensetzen-Skript dreimal aus und notieren Sie die Zeit bis zum ersten Stück und bis zur ganzen Antwort.

Sagen Sie dann, welche der beiden Zeiten die lesende Person stärker bemerkt, und warum ein Strom bei langen Antworten am meisten hilft.

Über dieses Lab und seinen Quellcode: Lab für KI-Coding-Agenten: mit opencode Code erklären und reparieren lassen und die Arbeit prüfen

Sie unterrichten eine Gruppe? Weisen Sie dieses Modul Ihren Studierenden zu, mit Handbuch für Lehrende, automatisch geprüften Aufgaben und einem Lab für jede Person.