הגדרת סטרימינג לתשובות של מודל שפה גדול (LLM) ולתנועה אחרת

במאמר הזה מוסבר איך להגדיר סטרימינג ב-API Gateway.

‫API Gateway תומך בסטרימינג. סטרימינג מאפשר לשערים להציג חיבורים לטווח ארוך ולהעביר נתונים בחלקים, גם בסטרימינג של בקשות וגם בסטרימינג של תגובות.

שימוש נפוץ בסטרימינג הוא הצגת מודל שפה גדול (LLM). המודל שולח את התשובה שלו אסימון אחד בכל פעם, כך שהלקוח יכול להציג את הטקסט בזמן שהמודל עדיין יוצר אותו. דוגמה פתורה מלאה להעברת תשובות בסטרימינג ממודל Gemma שמוגש על ידי vLLM ב-Cloud Run, מופיעה במאמר העברת תשובות בסטרימינג מ-LLM.

פרוטוקולים נתמכים לסטרימינג

כשהתכונה מופעלת, API Gateway תומך בשיטות הסטרימינג הבאות:

  • העברת תגובה מצטברת: מסגרות HTTP/2 DATA או קידוד העברה מקוטע של HTTP/1.1, בהתאם למה שהלקוח מנהל משא ומתן.
  • אירועים שנשלחים מהשרת (SSE): סטרימינג חד-כיווני מהשרת ללקוח.
  • ‫WebSockets: ערוצי תקשורת דו-כיוונית (full-duplex) דרך חיבור TCP יחיד.
  • סטרימינג דו-כיווני ב-gRPC: סטרימינג ב-full-duplex באמצעות gRPC.

דרישות מוקדמות

כדי להשתמש בסטרימינג, צריך לוודא ששירות הקצה העורפי תומך בפרוטוקול הנדרש (לדוגמה, HTTP/2 או WebSockets) ושהגדרת ה-API נכונה.

הגדרת פרוטוקול הקצה העורפי

כדי לתמוך בתנועת סטרימינג, צריך להגדיר את הפרוטוקול לקצה העורפי על סמך סוג הסטרימינג:

  • ‫gRPC: צריך להגדיר את הקצה העורפי לשימוש ב-HTTP/2‏ (h2).
  • ‫WebSockets: צריך להשתמש ב-http/1.1. לחיבורי WebSockets נדרשת לחיצת יד (handshake) של HTTP/1.1 Connection: Upgrade.
  • אירועים שנשלחים מהשרת (SSE) והעברת תגובות מצטברות: ה-backend יכול להשתמש ב-HTTP/1.1 או ב-HTTP/2 (h2). אנחנו ממליצים על HTTP/2 (h2) כדי לשפר את הביצועים.

במפרט של OpenAPI, מגדירים את פרוטוקול הקצה העורפי באופן הבא:

דוגמה (OpenAPI 3.x)

מגדירים את השדה protocol בהגדרת הקצה העורפי בעל השם באובייקט x-google-api-management.backends. צריך גם להפנות לקצה העורפי הזה באמצעות x-google-backend ברמה הבסיסית (root) או ברמת הפעולה.

x-google-api-management:
  backends:
    gemma:
      address: https://my-gemma-service.run.app
      protocol: h2 # Use 'http/1.1' for WebSockets
x-google-backend: gemma

דוגמה (OpenAPI 2.0)

מגדירים את השדה protocol בתוסף x-google-backend.

x-google-backend:
  address: https://my-gemma-service.run.app
  protocol: h2 # Use 'http/1.1' for WebSockets

הגדרת המועד האחרון לשידור

השדה deadline קובע כמה זמן בקשה (אונרית או בסטרימינג) יכולה לפעול.

בטבלה הבאה מוצג איך הזמן הקצוב לתפוגה חל על כל סוג של בקשה:

‏Method זמן קצוב לתפוגה במצב חוסר פעילות
(המרווח המקסימלי בין הודעות)
הזמן הקצוב לתפוגת הבקשה
(משך הזמן המקסימלי הכולל של הבקשה)
לא בסטרימינג לא רלוונטי: הזמן הקצוב לתפוגה כשאין פעילות חל רק על שידורים ברירת המחדל היא 15 שניות. אפשר להגדיר את deadline כדי לשנות את משך הזמן, עד 3,600 שניות לשערים שבהם מופעל סטרימינג.
סטרימינג דרך HTTP
(SSE, העברה מקוטעת)
לא רלוונטי: למעשה אין הגבלה, רק הזמן הקצוב לתפוגה של הבקשה מסיים את הסטרימינג ברירת המחדל היא 15 שניות. אפשר להגדיר את deadline כדי לשנות את משך הזמן, עד 3,600 שניות לשערים שבהם מופעל סטרימינג.
סטרימינג דרך gRPC או WebSockets ברירת המחדל היא 300 שניות. אפשר להגדיר את deadline כדי לשנות את הערך, עד 3,600 שניות לשערים שבהם מופעל סטרימינג. ב-WebSockets, המערכת מתעלמת מ-deadline של פחות מ-300 שניות, וחל מינימום של 300 שניות תמיד 3,600 שניות לשערים שמופעל בהם סטרימינג, לא ניתן להגדרה

דוגמה (OpenAPI 3.x)

מגדירים את השדה deadline בהגדרת הקצה העורפי שצוין.

x-google-api-management:
  backends:
    gemma:
      address: https://my-gemma-service.run.app
      protocol: h2
      deadline: 3600.0
x-google-backend: gemma

דוגמה (OpenAPI 2.0)

מגדירים את השדה deadline בתוסף x-google-backend.

x-google-backend:
  address: https://my-gemma-service.run.app
  protocol: h2
  deadline: 3600.0

בקטע מגבלות מפורטות המגבלות האחרות שחלות על חיבורי סטרימינג.

הפעלת סטרימינג בשער

הסטרימינג מוגדר בזמן יצירת השער. שימו לב להתנהגות הבאה:

  • אין השבתה מפורשת: אין דגל להשבתה מפורשת של סטרימינג. אם לא מציינים את הדגל --enable-streaming, ‏API Gateway קובע את המצב בזמן היצירה לפי הגדרת ה-API וברירת המחדל של הפלטפורמה: הגדרת API שמגדירה Model Router תמיד יוצרת שער סטרימינג. כדי לראות את המצב שבו נוצר השער, קוראים את השדה effectiveStreamingMode של השער, שהוא לפלט בלבד.
  • חוסר יכולת שינוי: מצב הסטרימינג נקבע בזמן היצירה ואי אפשר לשנות אותו אחר כך.

כדי לציין סטרימינג בשער, משתמשים בדגל --enable-streaming עם הפקודה gcloud api-gateway gateways create:

gcloud api-gateway gateways create GATEWAY_ID \
    --api=API_ID \
    --api-config=CONFIG_ID \
    --location=GCP_REGION \
    --enable-streaming

מידע נוסף על אפשרויות הפריסה של שערים זמין במאמר פריסת API לשער.

מאפייני סטרימינג של שערים

השדות הבאים במשאב Gateway שולטים בהתנהגות הסטרימינג:

שדה מאפיינים ערכים
streamingMode מחרוזת (IMMUTABLE, OPTIONAL)
  • STREAMING_MODE_UNSPECIFIED (ברירת מחדל: השירות בוחר את המצב)
  • STREAMING_MODE_ENABLED
effectiveStreamingMode מחרוזת (OUTPUT_ONLY)
  • EFFECTIVE_STREAMING_MODE_DISABLED
  • EFFECTIVE_STREAMING_MODE_ENABLED

כשמשתמשים ב-API בארכיטקטורת REST כדי ליצור שער, אפשר לציין סטרימינג בגוף הבקשה:

{
  "apiConfig": "projects/...",
  "streamingMode": "STREAMING_MODE_ENABLED"
}

מוודאים שהסטרימינג מופעל

כדי לבדוק אם הסטרימינג פעיל בשער, מתארים את השער באמצעות ה-CLI של gcloud:

gcloud api-gateway gateways describe GATEWAY_ID \
    --location=GCP_REGION

מחפשים את השדה effectiveStreamingMode בפלט. אם הסטרימינג מופעל, הפלט כולל:

effectiveStreamingMode: EFFECTIVE_STREAMING_MODE_ENABLED

קריאה לממשקי API של Google Cloud

שער סטרימינג יכול לקרוא לממשקי API של Google Cloud כמו aiplatform.googleapis.com, כדי שתוכלו להעביר תשובות ממודל Gemini ב-Agent Platform דרך השער. השער מבצע אימות לקצוות העורפיים האלה באמצעות טוקן גישה במקום טוקן של מזהה. למידע נוסף, קראו את המאמר אבטחת שירותים לקצה העורפי.

הצגת תשובות מ-LLM באופן שוטף

בדוגמה הזו, שער סטרימינג ממוקם לפני מודל Gemma ש-vLLM מציג ב-Cloud Run, ומשלים צ'אט בסטרימינג דרך השער. vLLM מציג API שתואם ל-OpenAI ומעביר תשובות בסטרימינג כאירועים שנשלחים מהשרת (SSE).

לפני שמתחילים, צריך להשלים את השלבים שמפורטים בקטע הגדרת סביבת הפיתוח, כולל הגדרת חשבון השירות שמשמש ליצירת הגדרות API. השער משתמש בחשבון השירות הזה כדי לקרוא לשירות Cloud Run.

פריסת המודל

פורסים מודל Gemma לפי ההוראות במאמר פריסת מודל Gemma 4 עם קונטיינר vLLM. שימו לב לשם השירות, לכתובת ה-URL של השירות, לאזור ולשם המודל שאתם פורסים, למשל google/gemma-4-E4B-it.

מתן גישה לשער לשירות

במדריך הזה, השירות נפרס באמצעות --no-allow-unauthenticated. השער קורא לשירות באמצעות טוקן של מזהה של חשבון השירות שלו, שאתם מעבירים בתור --backend-auth-service-account כשאתם יוצרים את הגדרת ה-API. מקצים לחשבון השירות את התפקיד "הפעלת Cloud Run" (roles/run.invoker) בשירות:

gcloud run services add-iam-policy-binding SERVICE_NAME \
    --region=REGION \
    --member=serviceAccount:SERVICE_ACCOUNT_EMAIL \
    --role=roles/run.invoker

מחליפים את מה שכתוב בשדות הבאים:

  • ‫SERVICE_NAME: השם של שירות Cloud Run
  • ‫REGION: האזור שבו פרסתם את השירות
  • ‫SERVICE_ACCOUNT_EMAIL: כתובת האימייל של חשבון השירות של השער

יצירת הגדרות ה-API

שומרים את מפרט OpenAPI הבא בתור gemma-api.yaml, ומחליפים את https://my-gemma-service.run.app בכתובת ה-URL של השירות:

openapi: 3.0.3
info:
  title: Gemma API
  version: 1.0.0
x-google-api-management:
  backends:
    gemma:
      address: https://my-gemma-service.run.app
      protocol: h2
      deadline: 570.0
x-google-backend: gemma
components:
  securitySchemes:
    google_id_token:
      type: oauth2
      flows:
        implicit:
          authorizationUrl: ""
          scopes: {}
      x-google-auth:
        issuer: https://accounts.google.com
        jwksUri: https://www.googleapis.com/oauth2/v3/certs
        audiences:
          - gemma-api
security:
  - google_id_token: []
paths:
  /v1/chat/completions:
    post:
      operationId: createChatCompletion
      responses:
        '200':
          description: A chat completion, streamed as SSE when the request sets "stream" to true.

הערך deadline של 570 שניות קצר ב-30 שניות מהערך --timeout 600 שמוגדר לשירות במדריך של Gemma. כתוצאה מכך, ה-deadline של השער, ולא הזמן הקצוב לתפוגה של השירות, מסיים סטרימינג שנמשך יותר מדי זמן. x-google-backend ברמה העליונה מוגדר כברירת מחדל ל-pathTranslation: APPEND_PATH_TO_ADDRESS. השער מוסיף את נתיב הבקשה לכתובת של הקצה העורפי, כך שבקשה אל /v1/chat/completions מגיעה לנקודת הקצה של השלמת הצ'אט ב-vLLM.

הדרישה security גורמת לשער לדחות כל בקשה שלא כוללת טוקן של מזהה חתום על ידי Google עם הקהל gemma-api. אתם יכולים לבחור מחרוזת קהל אחרת, כל עוד מבקשי הקריאה מבקשים את אותה מחרוזת כשהם יוצרים אסימון. מידע נוסף זמין במאמר שימוש בטוקנים של מזהי Google לאימות משתמשים.

יוצרים את הגדרת ה-API:

gcloud api-gateway api-configs create CONFIG_ID \
    --api=API_ID \
    --openapi-spec=gemma-api.yaml \
    --backend-auth-service-account=SERVICE_ACCOUNT_EMAIL

מחליפים את מה שכתוב בשדות הבאים:

  • ‫CONFIG_ID: מזהה להגדרת ה-API
  • ‫API_ID: מזהה ה-API. אם ה-API לא קיים, הפקודה יוצרת אותו.

יצירת השער

יוצרים שער סטרימינג מהגדרות ה-API:

gcloud api-gateway gateways create GATEWAY_ID \
    --api=API_ID \
    --api-config=CONFIG_ID \
    --location=GCP_REGION \
    --enable-streaming

מחליפים את מה שכתוב בשדות הבאים:

  • ‫GATEWAY_ID: מזהה לשער
  • ‫GCP_REGION: האזור של השער, שיכול להיות שונה מ-REGION. הערכים המותרים מפורטים במאמר פריסת API בשער.

כשהשער מוכן, מקבלים את שם המארח שלו:

gcloud api-gateway gateways describe GATEWAY_ID \
    --location=GCP_REGION \
    --format="value(defaultHostname)"

קבלת טוקן של מזהה לגורם המבקש

בחשבון משתמש אי אפשר לבחור את הקהל של טוקן המזהה, ולכן בדוגמה הזו הטוקן נוצר בשביל חשבון שירות שאתם מתחזים אליו. למבצע הקריאה החוזרת, משתמשים בחשבון שירות קיים או יוצרים חשבון חדש. מידע נוסף זמין במאמר יצירת חשבונות שירות. מקצים לעצמכם את התפקיד "יצירת אסימונים בחשבון שירות" (roles/iam.serviceAccountTokenCreator) בחשבון השירות, שה-CLI של gcloud צריך כדי להתחזות אליו:

gcloud iam service-accounts add-iam-policy-binding CALLER_SERVICE_ACCOUNT_EMAIL \
    --member=user:USER_EMAIL \
    --role=roles/iam.serviceAccountTokenCreator

מחליפים את מה שכתוב בשדות הבאים:

  • ‫CALLER_SERVICE_ACCOUNT_EMAIL: כתובת האימייל של חשבון השירות ששולח קריאה לשער
  • USER_EMAIL: כתובת האימייל שלכם

שליחת בקשה לסטרימינג

שולחים בקשה להשלמת צ'אט שמגדירה את "stream": true, עם טוקן של מזהה לחשבון השירות של מבצע הקריאה החוזרת בכותרת Authorization. הדגל -N משבית את אגירת הפלט ב-curl, כך שכל אירוע מודפס כשהוא מגיע:

curl -N https://DEFAULT_HOSTNAME/v1/chat/completions \
    -H "Authorization: Bearer $(gcloud auth print-identity-token \
        --impersonate-service-account=CALLER_SERVICE_ACCOUNT_EMAIL \
        --audiences=gemma-api)" \
    -H "Content-Type: application/json" \
    -d '{
      "model": "MODEL_NAME",
      "messages": [{"role": "user", "content": "Why is the sky blue?"}],
      "stream": true
    }'

מחליפים את מה שכתוב בשדות הבאים:

  • ‫DEFAULT_HOSTNAME: שם המארח של השער
  • ‫CALLER_SERVICE_ACCOUNT_EMAIL: חשבון השירות מהשלב הקודם
  • ‫MODEL_NAME: המודל שפרסתם, למשל google/gemma-4-E4B-it

התגובה היא זרם SSE. האירוע הראשון כולל את התפקיד assistant, כל אירוע אחריו כולל את החלק הבא של התשובה, והאירוע האחרון לפני data: [DONE] מגדיר את finish_reason. הפלט אמור להיראות כך:

data: {"id":"chatcmpl-7bcd57ad-1c3c-4779-91be-2973b875e517","object":"chat.completion.chunk","created":1790099706,"model":"google/gemma-4-E4B-it","choices":[{"index":0,"delta":{"role":"assistant","content":""},"logprobs":null,"finish_reason":null}],"prompt_token_ids":null}

data: {"id":"chatcmpl-7bcd57ad-1c3c-4779-91be-2973b875e517","object":"chat.completion.chunk","created":1790099706,"model":"google/gemma-4-E4B-it","choices":[{"index":0,"delta":{"content":"The"},"logprobs":null,"finish_reason":null,"token_ids":null}]}

...

data: {"id":"chatcmpl-7bcd57ad-1c3c-4779-91be-2973b875e517","object":"chat.completion.chunk","created":1790099706,"model":"google/gemma-4-E4B-it","choices":[{"index":0,"delta":{"content":""},"logprobs":null,"finish_reason":"stop","stop_reason":106,"token_ids":null}]}

data: [DONE]

הסרת המשאבים

כדי להימנע מחיובים בחשבון Google Cloud על המשאבים שבהם השתמשתם בדוגמה הזו, אתם צריכים למחוק את השער ואת הגדרת ה-API:

gcloud api-gateway gateways delete GATEWAY_ID \
    --location=GCP_REGION
gcloud api-gateway api-configs delete CONFIG_ID \
    --api=API_ID

אם יצרתם את ה-API בשביל הדוגמה הזו, מחקו אותו:

gcloud api-gateway apis delete API_ID

מוחקים את השירות של Cloud Run:

gcloud run services delete SERVICE_NAME \
    --region=REGION

תמחור

במהלך גרסת טרום ההשקה הציבורית של הסטרימינג, הלקוחות לא מחויבים על תעבורת נתונים יוצאת ברשת בשערים שבהם הסטרימינג מופעל. עם זאת, החיוב על Service Control עדיין חל ברמת ה-API, בלי קשר לשלב ההשקה.

מגבלות

במהלך Public Preview, המגבלות הבאות חלות על סטרימינג ב-API Gateway:

  • חוסר יכולת שינוי: אי אפשר לעדכן שער קיים כדי להפעיל או להשבית סטרימינג. צריך ליצור שער חדש. חשוב לזכור ששער שמאפשר סטרימינג מקבל צורה שונה של שם מארח, ולכן צריך לעדכן את הלקוחות או את רשומות ה-DNS. אם אתם רוצים שנעדכן את רשומת השער שלכם כדי להשתמש בפורמט החדש, פנו לתמיכה. ב-API Gateway משתמשים בתבניות הבאות של שמות מארחים:

    • לא בסטרימינג: {gateway_id}-{base36_project_number}.{shard_hash}.gateway.dev, לדוגמה test-gateway-4jcaz8x.uc.gateway.dev
    • סטרימינג: {gateway_id}-{project_number}.{region}.gateway.dev, לדוגמה test-gateway-9876654321.us-central1.gateway.dev
    • סטרימינג (גרסה קודמת): {service}-{tenant_project_number}.{region}.run.app, לדוגמה test-gateway-834512064953.us-central1.run.app. שערי כניסה שנוצרו לפני ששמות המארחים האזוריים של *.gateway.dev היו זמינים, שומרים על שם המארח הזה באופן קבוע ולא מועברים לתבנית החדשה.

    שער חדש שמופעל בו סטרימינג מקבל את התבנית סטרימינג. שתי הדוגמאות הראשונות הן של אותו שער באותו פרויקט: בתבנית Streaming מספר הפרויקט מופיע בפורמט עשרוני ולא ב-base36, ולכן בתווית הראשונה יש פחות מקום מאשר בשער שאינו בסטרימינג. התווית הראשונה היא המחרוזת המשולבת {gateway_id}-{project_number}, שצריכה להתאים למגבלת התווים של תווית DNS, שהיא 63 תווים. מגבלת 49 התווים של מזהה השער שומרת על המגבלה הזו למספרי פרויקטים של עד 13 ספרות. למספר פרויקט ארוך יותר נדרש מזהה שער קצר יותר.

  • ‫Terraform: אי אפשר להפעיל סטרימינג באמצעות Terraform (מתוכנן לגרסה עתידית).

  • איזון עומסים ודומיינים בהתאמה אישית: שערים עם effectiveStreamingMode של EFFECTIVE_STREAMING_MODE_ENABLED לא תואמים לאיזון עומסים ב-HTTP(S)‎ עבור API Gateway או קבוצות של נקודות קצה ללא שרת (serverless). אי אפשר להציב שער כזה מאחורי קבוצת נקודות קצה ללא שרת (serverless) או מאזן עומסים חיצוני של אפליקציות (ALB). לכן, דומיינים מותאמים אישית (שמסתמכים על איזון עומסים) לא נתמכים בשערים האלה במהלך גרסת טרום-השקה (Public Preview).

  • התנהגות המועד האחרון: הפעלת סטרימינג בשער לא משנה את ההתנהגות של השדה deadline בנתיב SSE או בנתיב של העברה מקוטעת. המועד האחרון נשאר מגבלה של זמן שעון על התגובה המלאה, כך שהסטרימינג נחתך ברגע שהמועד האחרון חולף, בלי קשר לכמות הנתונים שהוא שולח. ברירת המחדל היא 15 שניות והמקסימום הוא 3,600 שניות. לעומת זאת, ב-WebSocket, ‏deadline מגביל את הפער בין ההודעות, והחיבור מסתיים אחרי 3,600 שניות. הגדרת המועד האחרון להעברת נתונים

  • ‫Model Context Protocol (MCP): יצירת השער באמצעות --enable-streaming לא יוצרת סטרימינג של נקודת קצה (endpoint) של MCP. התשובות של MCP נשארות גוף application/json יחיד, בלי קשר למצב הסטרימינג של השער. פרטים נוספים זמינים בקטע מגבלות של MCP.