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

Data publikacji: 21 stycznia 2025 r.

Odpowiedź LLM przesyłana strumieniowo składa się z danych emitowanych stopniowo i ciągle. Dane strumieniowe wyglądają inaczej na serwerze i na kliencie.

Z serwera

Aby zrozumieć, jak wygląda odpowiedź strumieniowa, poprosiłam Gemini, aby opowiedział mi długi żart, używając narzędzia wiersza poleceń curl. Rozważ wywołanie interfejsu Gemini API w ten sposób: Jeśli chcesz to zrobić, pamiętaj, aby zastąpić w adresie URL ciąg znaków {GOOGLE_API_KEY} 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 następujący (obcięty) wynik w formacie strumienia zdarzeń. Każdy wiersz zaczyna się od data:, a następnie następuje ładunek wiadomości. Konkretny format nie jest tak naprawdę ważny, ważne są 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 fragmenty wyników będą przesyłane strumieniem.

Pierwszy ładunek to JSON. Przyjrzyj się 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 pozycji text, odpowiedź będzie ograniczona znakami nowej linii.

Ten fragment kodu zawiera wiele 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?\""

...

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

Dane wyjściowe zawierają teraz format Markdown, począwszy od bloku kodu JavaScript. Ten 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"

...

Aby utrudnić zadanie, niektóre z oznaczonych elementów zaczynają się w jednym fragmencie, a kończą w drugim. Część znaczników jest zagnieżdżona. W tym przykładzie wyróżniona funkcja jest podzielona na 2 wiersze: **isEven(number) function:**. Łączna suma wyników to:**isEven("number) function:**. Oznacza to, że jeśli chcesz wygenerować sformatowany tekst Markdown, nie możesz po prostu przetworzyć każdego fragmentu osobno za pomocą parsowania Markdown.

Od klienta

Jeśli na kliencie uruchamiasz modele takie jak Gemma z wykorzystaniem frameworku takiego jak MediaPipe LLM, dane strumieniowe są przekazywane przez funkcję wywołania zwrotnego.

Na przykład:

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

Dzięki interfejsowi Prompt API możesz otrzymywać dane strumieniowe w kawałkach, iterując po ReadableStream.

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

Dalsze kroki

Chcesz wiedzieć, jak wydajnie i bezpiecznie renderować dane strumieniowe? Zapoznaj się ze sprawdzonymi metodami renderowania odpowiedzi LLM.