Come gli LLM generano risposte dinamiche

Data di pubblicazione: 21 gennaio 2025

Una risposta LLM in streaming è costituita da dati emessi in modo incrementale e continuo. I dati in streaming hanno un aspetto diverso dal server e dal client.

Dal server

Per capire come funziona una risposta in streaming, ho chiesto a Gemini di raccontarmi una barzelletta lunga utilizzando lo strumento a riga di comando curl. Prendi in considerazione la seguente chiamata all'API Gemini. Se provi, assicurati di sostituire{GOOGLE_API_KEY} nell'URL con la tua chiave API Gemini.

$ 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."}]}]}'

Questa richiesta registra il seguente output (troncato) in formato stream di eventi. Ogni riga inizia con data: seguita dal payload del messaggio. Il formato specifico non è importante, ciò che conta sono i blocchi di testo.

//
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}}
Dopo aver eseguito il comando, i chunk dei risultati vengono inviati in streaming.

Il primo payload è JSON. Esamina più da vicino il testo evidenziatocandidates[0].content.parts[0].text:

{
  "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
  }
}

La prima voce text è l'inizio della risposta di Gemini. Quando estrai più voci text, la risposta è delimitata da una nuova riga.

Lo snippet seguente mostra più voci text, che mostrano la risposta finale del modello.

"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?\""

...

Ma cosa succede se, invece di chiedere barzellette sui T-Rex, chiedi al modello qualcosa di leggermente più complesso? Ad esempio, puoi chiedere a Gemini di creare una funzione JavaScript per determinare se un numero è pari o dispari. I chunk text: hanno un aspetto leggermente diverso.

L'output ora contiene il formato Markdown, a partire dal blocco di codice JavaScript. Il seguente esempio include gli stessi passaggi di pre-elaborazione precedenti.

"```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"

...

Per complicare ulteriormente la situazione, alcuni degli elementi con markup iniziano in un chunk e terminano in un altro. Parte del markup è nidificata. Nell'esempio seguente, la funzione evidenziata è suddivisa in due righe:**isEven( e number) function:**. Combinati, l'output è **isEven("number) function:**. Ciò significa che, se vuoi generare output Markdown formattato, non puoi semplicemente elaborare ogni chunk singolarmente con un parser Markdown.

Dal cliente

Se esegui modelli come Gemma sul client con un framework come MediaPipe LLM, i dati in streaming vengono inviati tramite una funzione di callback.

Ad esempio:

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

Con l'API Prompt, puoi ottenere i dati in streaming sotto forma di chunk iterando su un ReadableStream.

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

Passaggi successivi

Ti stai chiedendo come eseguire il rendering dei dati in streaming in modo efficiente e sicuro? Leggi le nostre best practice per il rendering delle risposte LLM.