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

Data publikacji: 21 stycznia 2025 r.

Odpowiedź LLM strumieniowego 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ługą opowieść, używając narzędzia wiersza poleceń curl. Rozważ użycie tego wywołania interfejsu Gemini API. 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 ten (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ą bloki 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 są 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 rozdzielona znakami końca wiersza.

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 poprosisz model o coś nieco bardziej skomplikowanego? 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"

...

Co więcej, 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:**. Po zsumowaniu tych wartości otrzymamy wynik **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 za pomocą funkcji wywołania zwrotnego.

Na przykład:

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

Za pomocą interfejsu Prompt API możesz otrzymywać dane strumieniowe w kawałkach, iterując za pomocą ReadableStream.

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

Dalsze kroki

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