طريقة عرض الردود تدريجيًا باستخدام النماذج اللغوية الكبيرة

تاريخ النشر: 21 يناير 2025

تتألف استجابة النموذج اللغوي الكبير التي يتم بثها من بيانات يتم إصدارها بشكل تدريجي ومستمر. تختلف طريقة عرض البيانات المتدفقة على الخادم والعميل.

من الخادم

لفهم شكل الردود التي يتم بثها، طلبت من Gemini أن يخبرني نكتة طويلة باستخدام أداة سطر الأوامر curl. لنأخذ بعين الاعتبار طلب البيانات التالي من Gemini API. إذا جرّبت ذلك، احرص على استبدال {GOOGLE_API_KEY} في عنوان URL بمفتاح 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."}]}]}'

يسجّل هذا الطلب الناتج التالي (المقتطع) بتنسيق دفق الأحداث. يبدأ كل سطر بـ data: متبوعًا بحِمل الرسالة. لا يهم التنسيق المحدّد، بل المهم هو أجزاء النص.

//
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}}
بعد تنفيذ الأمر، يتم بث أجزاء النتائج.

الحِمل الأول هو JSON. إليك نظرة عن كثب على 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
  }
}

يمثّل الإدخال الأول text بداية ردّ Gemini. عند استخراج المزيد من إدخالات text، يكون الردّ مفصولاً بأحرف سطر جديد.

تعرض المقتطفة التالية إدخالات text متعددة، ما يشير إلى الرد النهائي من النموذج.

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

...

ولكن ماذا يحدث إذا طلبت من النموذج شيئًا أكثر تعقيدًا بدلاً من نكات عن الديناصورات؟ على سبيل المثال، اطلب من Gemini إنشاء دالة JavaScript لتحديد ما إذا كان الرقم زوجيًا أو فرديًا. تبدو أجزاء text: مختلفة قليلاً.

يحتوي الناتج الآن على تنسيق Markdown، بدءًا من جزء رمز JavaScript. يتضمّن المثال التالي خطوات المعالجة المسبقة نفسها كما في السابق.

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

...

ولزيادة صعوبة الأمر، تبدأ بعض العناصر المحدّدة في جزء وتنتهي في جزء آخر. بعض الترميز متداخل. في المثال التالي، تم تقسيم الدالة المميّزة بين سطرين: **isEven( وnumber) function:**. يبلغ الناتج المجمّع **isEven("number) function:**. وهذا يعني أنّه إذا أردت إخراج Markdown منسَّق، لا يمكنك معالجة كل جزء على حدة باستخدام محلّل Markdown.

من العميل

إذا كنت تستخدم نماذج مثل Gemma على العميل باستخدام إطار عمل مثل MediaPipe LLM، تصل بيانات البث من خلال دالة رد اتصال.

على سبيل المثال:

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

باستخدام Prompt API، يمكنك الحصول على بيانات البث كأجزاء من خلال تكرار ReadableStream.

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

الخطوات التالية

هل تتساءل عن كيفية عرض البيانات المتدفقة بشكل فعّال وآمن؟ يمكنك الاطّلاع على أفضل الممارسات لعرض ردود النماذج اللغوية الكبيرة.