Jak duże modele językowe przesyłają odpowiedzi

Data publikacji: 21 stycznia 2025 r.

Strumieniowana odpowiedź LLM składa się z danych emitowanych stopniowo i w sposób ciągły. Dane przesyłane strumieniowo wyglądają inaczej po stronie serwera i po stronie klienta.

Z serwera

Aby zobaczyć, jak wygląda odpowiedź przesyłana strumieniowo, poprosiłem Gemini o opowiedzenie długiego żartu za pomocą narzędzia wiersza poleceń curl. Rozważ poniższe wywołanie interfejsu Gemini API. Jeśli chcesz go wypróbować, pamiętaj, aby zastąpić {GOOGLE_API_KEY} w adresie URL swoim kluczem interfejsu Gemini API.

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

To żądanie rejestruje te (skrócone) dane wyjściowe w formacie strumienia zdarzeń. Każdy wiersz zaczyna się od znaku data:, po którym następuje ładunek wiadomości. Konkretny format nie jest tak naprawdę ważny. Liczą się fragmenty tekstu.

//
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}}
Po wykonaniu polecenia zaczynają się przesyłać fragmenty wyniku.

Pierwszy ładunek to JSON. Przyjrzyj się bliżej wyróżnionym elementom:candidates[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
  }
}

Pierwszy wpis text to początek odpowiedzi Gemini. Gdy wyodrębnisz więcej niż text wpisów, odpowiedź będzie rozdzielona znakami nowego wiersza.

Poniższy fragment kodu zawiera kilka wpisów text, które pokazują ostateczną odpowiedź modelu.

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

...

Ale co się stanie, jeśli zamiast żartów o tyranozaurach poprosisz model o coś nieco bardziej złożonego? Możesz na przykład poprosić Gemini o utworzenie funkcji w JavaScript, która określi, czy liczba jest parzysta czy nieparzysta. Fragmenty text: wyglądają nieco inaczej.

Dane wyjściowe mają teraz format Markdown, zaczynając od bloku kodu JavaScript. Poniższy przykład zawiera te same kroki wstępnego przetwarzania co poprzednio.

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

...

Dodatkowo niektóre oznaczone elementy zaczynają się w jednym bloku, a kończą w innym. Niektóre znaczniki są zagnieżdżone. W tym przykładzie wyróżniona funkcja jest podzielona na 2 wiersze: **isEven(number) function:**. Łącznie dane wyjściowe to **isEven("number) function:**. Oznacza to, że jeśli chcesz uzyskać sformatowany kod Markdown, nie możesz po prostu przetwarzać poszczególnych fragmentów za pomocą parsera Markdown.

Od klienta

Jeśli uruchamiasz modele takie jak Gemma na kliencie za pomocą platformy takiej jak MediaPipe LLM, dane przesyłane strumieniowo są przekazywane przez funkcję wywołania zwrotnego.

Na przykład:

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

Interfejs Prompt API przesyła dane strumieniowe w postaci fragmentów przez iterację w ReadableStream.

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

Dalsze kroki

Zastanawiasz się, jak wydajnie i bezpiecznie renderować dane przesyłane strumieniowo? Zapoznaj się z naszymi sprawdzonymi metodami renderowania odpowiedzi LLM.