Cara LLM melakukan streaming respons

Dipublikasikan: 21 Januari 2025

Respons LLM yang di-streaming terdiri dari data yang dikeluarkan secara bertahap dan terus-menerus. Data streaming terlihat berbeda dari server dan klien.

Dari server

Untuk memahami tampilan respons yang di-streaming, saya meminta Gemini untuk menceritakan lelucon panjang menggunakan alat command line curl. Pertimbangkan panggilan berikut ke Gemini API. Jika Anda mencobanya, pastikan untuk mengganti {GOOGLE_API_KEY} di URL dengan kunci Gemini API Anda.

$ curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:streamGenerateContent?alt=sse&key={GOOGLE_API_KEY}" \
      -H 'Content-Type: application/json' \
      --no-buffer \
      -d '{ "contents":[{"parts":[{"text": "Tell me a long T-rex joke, please."}]}]}'

Permintaan ini mencatat output (terpotong) berikut ke dalam log, dalam format aliran peristiwa. Setiap baris dimulai dengan data:, diikuti dengan payload pesan. Format konkret sebenarnya tidak penting, yang penting adalah bagian teks.

//
data: {"candidates":[{"content": {"parts": [{"text": "A T-Rex"}],"role": "model"},
  "finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],
  "usageMetadata": {"promptTokenCount": 11,"candidatesTokenCount": 4,"totalTokenCount": 15}}

data: {"candidates": [{"content": {"parts": [{ "text": " walks into a bar and orders a drink. As he sits there, he notices a" }], "role": "model"},
  "finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],
  "usageMetadata": {"promptTokenCount": 11,"candidatesTokenCount": 21,"totalTokenCount": 32}}
Setelah menjalankan perintah, potongan hasil akan di-streaming.

Payload pertama adalah JSON. Pelajari lebih lanjut candidates[0].content.parts[0].text yang ditandai:

{
  "candidates": [
    {
      "content": {
        "parts": [
          {
            "text": "A T-Rex"
          }
        ],
        "role": "model"
      },
      "finishReason": "STOP",
      "index": 0,
      "safetyRatings": [
        {
          "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_HATE_SPEECH",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_HARASSMENT",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
          "probability": "NEGLIGIBLE"
        }
      ]
    }
  ],
  "usageMetadata": {
    "promptTokenCount": 11,
    "candidatesTokenCount": 4,
    "totalTokenCount": 15
  }
}

Entri text pertama tersebut adalah awal respons Gemini. Saat Anda mengekstrak lebih banyak entri text, respons akan dibatasi baris baru.

Cuplikan berikut menunjukkan beberapa entri text, yang menunjukkan respons akhir dari model.

"A T-Rex"

" was walking through the prehistoric jungle when he came across a group of Triceratops. "

"\n\n\"Hey, Triceratops!\" the T-Rex roared. \"What are"

" you guys doing?\"\n\nThe Triceratops, a bit nervous, mumbled,
\"Just... just hanging out, you know? Relaxing.\"\n\n\"Well, you"

" guys look pretty relaxed,\" the T-Rex said, eyeing them with a sly grin.
\"Maybe you could give me a hand with something.\"\n\n\"A hand?\""

...

Namun, apa yang terjadi jika Anda meminta model untuk membuat sesuatu yang sedikit lebih kompleks, bukan lelucon T-rex? Misalnya, minta Gemini untuk membuat fungsi JavaScript untuk menentukan apakah angka tersebut genap atau ganjil. Potongan text: terlihat sedikit berbeda.

Output kini berisi format Markdown, yang dimulai dengan blok kode JavaScript. Contoh berikut menyertakan langkah prapemrosesan yang sama seperti sebelumnya.

"```javascript\nfunction"

" isEven(number) {\n  // Check if the number is an integer.\n"

"  if (Number.isInteger(number)) {\n  // Use the modulo operator"

" (%) to check if the remainder after dividing by 2 is 0.\n  return number % 2 === 0; \n  } else {\n  "
"// Return false if the number is not an integer.\n    return false;\n }\n}\n\n// Example usage:\nconsole.log(isEven("

"4)); // Output: true\nconsole.log(isEven(7)); // Output: false\nconsole.log(isEven(3.5)); // Output: false\n```\n\n**Explanation:**\n\n1. **`isEven("

"number)` function:**\n   - Takes a single argument `number` representing the number to be checked.\n   - Checks if the `number` is an integer using `Number.isInteger()`.\n   - If it's an"

...

Untuk mempersulit masalah, beberapa item yang diberi markup dimulai di satu bagian dan diakhiri di bagian lain. Beberapa markup bertingkat. Dalam contoh berikut, fungsi yang ditandai dibagi menjadi dua baris: **isEven( dan number) function:**. Jika digabungkan, outputnya adalah **isEven("number) function:**. Artinya, jika ingin menghasilkan output Markdown yang diformat, Anda tidak dapat memproses setiap bagian satu per satu dengan parser Markdown.

Dari klien

Jika Anda menjalankan model seperti Gemma di klien dengan framework seperti MediaPipe LLM, data streaming akan berasal dari fungsi callback.

Contoh:

llmInference.generateResponse(
  inputPrompt,
  (chunk, done) => {
     console.log(chunk);
});

Dengan Prompt API, Anda mendapatkan data streaming sebagai bagian dengan melakukan iterasi pada ReadableStream.

const languageModel = await self.ai.languageModel.create();
const stream = languageModel.promptStreaming(inputPrompt);
for await (const chunk of stream) {
  console.log(chunk);
}

Langkah berikutnya

Ingin tahu cara merender data streaming dengan performa tinggi dan aman? Baca praktik terbaik untuk merender respons LLM.