העברת הודעות באמצעות Google Chat API

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

התכונה הזו מיועדת לאפליקציות ל-Chat שמבוססות על AI גנרטיבי או על תהליכי עבודה בקצה העורפי שנמשכים זמן רב, שבהם יצירת תשובה מלאה נמשכת כמה שניות או דקות.

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

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

  1. התחלת שיחה (spaces.streamMessages.create): האפליקציה ל-Chat יוצרת StreamMessage במרחב עם שיחות משורשרות, ומפנה להודעה של האינטראקציה עם המשתמש שעליה היא משיבה. ה-API מציג אינדיקטור של התקדמות בזמן אמת בשרשור ומחזיר שם משאב של StreamMessage ו-nextChunkToken ראשוני.
  2. עדכון הסטרימינג (appendStreamMessageContent או patch): כשאפליקציית Chat יוצרת פלט, היא שולחת עדכונים ברצף באמצעות ה-nextChunkToken האחרון:
    • קוראים ל-spaces.streamMessages.appendStreamMessageContent כדי לשרשר קטעי טקסט חדשים, להוסיף שלבי ביניים, להוסיף ציטוטים בגוף הטקסט או לעדכן את progressText.
    • קוראים ל-spaces.streamMessages.patch כדי להחליף את חלקי התוכן שמועברים בסטרימינג או לעדכן את progressText במקום.
  3. מסיימים או מבטלים את השידור (commit או delete):
    • מפעילים את spaces.streamMessages.commit כדי לסיים את השידור ולהמיר אותו למשאב Message קבוע. אתם יכולים גם לצרף רכיבים שמופיעים רק בתשובה הסופית, כמו כרטיסים, ווידג'טים נלווים ורשימת מקורות בכותרת התחתונה.
    • כדי למחוק שידור פעיל בלי לשמור הודעה במרחב, צריך להפעיל את spaces.streamMessages.delete.

בזמן שהתשובה נוצרת, רק השולח של ההודעה (referencedMessage) יכול ללחוץ על עצירה כדי לבטל את היצירה. ‫Chat שולח אירוע של פקודת מערכת לביטול לאפליקציית Chat, כדי שתוכלו להפסיק את העבודה בקצה העורפי ולשמור את הפלט החלקי או למחוק את הסטרימינג.

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

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

  • חשבון Business או Enterprise ב-Google Workspace עם גישה ל-Google Chat.
  • מגדירים את הסביבה:
  • מוסיפים את האפליקציה ל-Chat למרחב משותף עם שרשורים (או לצ'אט אישי שכבר מכיל לפחות הודעה אחת), ומקבלים את שם המשאב של הודעה נגישה שאפשר להשיב לה (spaces/SPACE_ID/messages/MESSAGE_ID).
  • בדוגמאות ב-Python, מתקינים את ספריית הלקוח של Google API ל-Python ואת google-auth, ומפעילים את לקוח השירות של Chat API:

    pip install google-api-python-client google-auth
    from google.oauth2 import service_account
    from googleapiclient.discovery import build
    
    SCOPES = ["https://www.googleapis.com/auth/chat.bot"]
    creds = service_account.Credentials.from_service_account_file(
        "credentials.json", scopes=SCOPES
    )
    chat = build("chat", "v1", credentials=creds)
    

התחלת שידור

כדי להתחיל להזרים תשובה, צריך להפעיל את spaces.streamMessages.create ולהעביר StreamMessage בגוף הבקשה:

  • ‫referencedMessage (חובה): שם המשאב של ההודעה שהפעילה את התשובה, בפורמט spaces/{space}/messages/{message}. ההודעה שאליה מפנים צריכה להיות באותו מרחב משותף, ולאפליקציה ל-Chat צריכה להיות גישה אליה. התשובה של Chat תפורסם באותו שרשור כמו referencedMessage.
  • ‫progressText (חובה): תווית סטטוס בטקסט פשוט (עד 250 תווים) שמוצגת לצד אינדיקטור ההתקדמות, למשל "Thinking...".
  • markupSyntax (אופציונלי): תחביר עיצוב הטקסט של ההודעה, כמו MARKUP_SYNTAX_MARKDOWN או MARKUP_SYNTAX_CHAT (ברירת המחדל היא התחביר שהוגדר באפליקציה ל-Chat אם MARKUP_SYNTAX_UNSPECIFIED). צריך להגדיר את MARKUP_SYNTAX_MARKDOWN אם מתכננים לכלול ציטוטים מוטבעים.
  • ‫streamContentChunks (אופציונלי): קטעי text, step או citation ראשוניים שיוצגו מיד.

שומרים את name (spaces/{space}/streamMessages/{stream_message}) וגם את nextChunkToken מהתגובה לקריאות הבאות.

HTTP

POST https://chat.googleapis.com/v1/spaces/SPACE_ID/streamMessages HTTP/1.1
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json

{
  "referencedMessage": "spaces/SPACE_ID/messages/MESSAGE_ID",
  "progressText": "Thinking...",
  "markupSyntax": "MARKUP_SYNTAX_MARKDOWN"
}

Python

stream = (
    chat.spaces()
    .streamMessages()
    .create(
        parent="spaces/SPACE_ID",
        body={
            "referencedMessage": "spaces/SPACE_ID/messages/MESSAGE_ID",
            "progressText": "Thinking...",
            "markupSyntax": "MARKUP_SYNTAX_MARKDOWN",
        },
    )
    .execute()
)

stream_name = stream["name"]
chunk_token = stream["nextChunkToken"]

השיטה מחזירה משאב StreamMessage:

{
  "name": "spaces/SPACE_ID/streamMessages/STREAM_MESSAGE_ID",
  "progressText": "Thinking...",
  "markupSyntax": "MARKUP_SYNTAX_MARKDOWN",
  "nextChunkToken": "INITIAL_CHUNK_TOKEN"
}

הוספת קטעי תוכן

כשהאפליקציה ל-Chat יוצרת שלבי ביניים או אסימוני טקסט, צריך להפעיל את spaces.streamMessages.appendStreamMessageContent:

  • עוברים את nextChunkToken האחרון בchunkToken. כל קריאה מוצלחת מחזירה nextChunkToken חדש שצריך להעביר בבקשת העדכון או השמירה הבאה.
  • החלקים של text משורשרים ישירות לטקסט שהועבר בסטרימינג קודם לכן. צריך לכלול רווחים או שורות חדשות לפי הצורך בין החלקים.
  • step קטעים נוספים לרשימת שלבי הביניים שמוצגים בהודעה.
  • progressText הוא אופציונלי. אם משמיטים את progressText או מגדירים אותו כ-"", ב-Chat ממשיך להופיע הערך של progressText שהוגדר קודם.

HTTP

POST https://chat.googleapis.com/v1/spaces/SPACE_ID/streamMessages/STREAM_MESSAGE_ID:appendStreamMessageContent HTTP/1.1
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json

{
  "chunkToken": "INITIAL_CHUNK_TOKEN",
  "progressText": "Searching documentation...",
  "streamContentChunks": [
    {
      "step": {
        "title": "Search knowledge base",
        "description": "Found 3 matching articles."
      }
    },
    {
      "text": "See <chat-citation data-id=\"c1\">guide</chat-citation>:\n"
    },
    {
      "citation": {
        "id": "c1",
        "citedSources": [
          {
            "title": "System Architecture Guide",
            "uri": "https://example.com/docs/arch"
          }
        ]
      }
    }
  ]
}

Python

stream = (
    chat.spaces()
    .streamMessages()
    .appendStreamMessageContent(
        name=stream_name,
        body={
            "chunkToken": chunk_token,
            "progressText": "Searching documentation...",
            "streamContentChunks": [
                {
                    "step": {
                        "title": "Search knowledge base",
                        "description": "Found 3 matching articles.",
                    }
                },
                {
                    "text": (
                        'See <chat-citation data-id="c1">'
                        "guide</chat-citation>:\n"
                    )
                },
                {
                    "citation": {
                        "id": "c1",
                        "citedSources": [
                            {
                                "title": "System Architecture Guide",
                                "uri": "https://example.com/docs/arch",
                            }
                        ],
                    }
                },
            ],
        },
    )
    .execute()
)

chunk_token = stream["nextChunkToken"]

החלפת תוכן בסטרימינג או טקסט של התקדמות

אם אתם צריכים לשכתב תוכן שכבר הועבר בסטרימינג (לדוגמה, אחרי הפעלת מסנן בטיחות או עיבוד לאחר מעשה) או לעדכן רק את progressText בלי להוסיף קטעים, אתם יכולים להפעיל את spaces.streamMessages.patch:

  • מגדירים את updateMask לרשימה מופרדת בפסיקים של שדות לעדכון: progressText,‏ streamContentChunks או progressText,streamContentChunks.
  • כוללים את ה-nextChunkToken האחרון בגוף הבקשה של StreamMessage, ומתעדים את ה-nextChunkToken החדש שמוחזר בתגובה.
  • כשהערך של streamContentChunks הוא updateMask, החלקים שסופקו מחליפים את התוכן ששודר בסטרימינג בעבר, במקום להתווסף אליו.
  • כשהשדה progressText נמצא ב-updateMask, השדה progressText חייב להכיל טקסט פשוט באורך של עד 250 תווים.

HTTP

PATCH https://chat.googleapis.com/v1/spaces/SPACE_ID/streamMessages/STREAM_MESSAGE_ID?updateMask=progressText,streamContentChunks HTTP/1.1
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json

{
  "nextChunkToken": "LATEST_CHUNK_TOKEN",
  "progressText": "Formatting response...",
  "streamContentChunks": [
    {
      "text": "Updated response text."
    }
  ]
}

Python

stream = (
    chat.spaces()
    .streamMessages()
    .patch(
        name=stream_name,
        updateMask="progressText,streamContentChunks",
        body={
            "nextChunkToken": chunk_token,
            "progressText": "Formatting response...",
            "streamContentChunks": [
                {"text": "Updated response text."}
            ],
        },
    )
    .execute()
)

chunk_token = stream["nextChunkToken"]

סיום השידור

בסיום היצירה, מפעילים את spaces.streamMessages.commit עם הערך האחרון של chunkToken כדי להפוך את הסטרימינג למשאב Message קבוע:

  • אפשר לכלול את streamContentChunks הסופי בבקשת השמירה, כולל סוגי מקטעים לשמירה בלבד (card,‏ accessoryWidget ו-citedSource).
  • אם כבר העברתם תוכן לא ריק בסטרימינג בשיחות קודמות, אתם יכולים להשמיט את streamContentChunks כשאתם מבצעים commit. ההודעה הסופית של ה-commit לא יכולה להיות ריקה.

HTTP

POST https://chat.googleapis.com/v1/spaces/SPACE_ID/streamMessages/STREAM_MESSAGE_ID:commit HTTP/1.1
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json

{
  "chunkToken": "LATEST_CHUNK_TOKEN",
  "streamContentChunks": [
    {
      "text": "All checks passed."
    },
    {
      "citedSource": {
        "title": "Deployment Runbook",
        "uri": "https://example.com/docs/runbook"
      }
    },
    {
      "card": {
        "cardId": "details-card",
        "card": {
          "header": {
            "title": "Build Status"
          },
          "sections": [
            {
              "widgets": [
                {
                  "textParagraph": {
                    "text": "All 42 checks succeeded."
                  }
                }
              ]
            }
          ]
        }
      }
    }
  ]
}

Python

status_card = {
    "cardId": "details-card",
    "card": {
        "header": {"title": "Build Status"},
        "sections": [
            {
                "widgets": [
                    {"textParagraph": {"text": "All 42 checks succeeded."}}
                ]
            }
        ],
    },
}

commit_response = (
    chat.spaces()
    .streamMessages()
    .commit(
        name=stream_name,
        body={
            "chunkToken": chunk_token,
            "streamContentChunks": [
                {"text": "All checks passed."},
                {
                    "citedSource": {
                        "title": "Deployment Runbook",
                        "uri": "https://example.com/docs/runbook",
                    }
                },
                {"card": status_card},
            ],
        },
    )
    .execute()
)

final_message = commit_response["message"]
print(f"Committed message: {final_message['name']}")

ביטול שידור פעיל

אם מתרחשת שגיאה שבעקבותיה אי אפשר לשחזר או שמשתמש מבטל את היצירה, ואתם לא רוצים להשאיר תשובה חלקית בשרשור, צריך להפעיל את spaces.streamMessages.delete:

HTTP

DELETE https://chat.googleapis.com/v1/spaces/SPACE_ID/streamMessages/STREAM_MESSAGE_ID HTTP/1.1
Authorization: Bearer ACCESS_TOKEN

Python

chat.spaces().streamMessages().delete(name=stream_name).execute()

אחרי שמעבירים סטרימינג ל-Message, צריך להפעיל את spaces.messages.delete כדי למחוק את ההודעה הסופית.

סוגי קטעי התוכן הנתמכים

כל רשומה ב-streamContentChunks מציינת שדה אחד באיחוד StreamContentChunk. אפשר לשלוח חלק מסוגי הנתונים לאורך כל השידור, ואחרים מתקבלים רק ב-spaces.streamMessages.commit:

שדה המקטע אמצעי התשלום הנתמכים השדה הסופי Message הערות
text create, appendStreamMessageContent, patch, commit text טקסט מצטבר. השדה שורשר בתאריך appendStreamMessageContent והוחלף בתאריך patch. הטקסט הסופי שנשלח לא יכול להיות ארוך מ-32,000 בייטים.
step create, appendStreamMessageContent, patch, commit elements.steps.steps[] שלב עיבוד ביניים עם title בטקסט פשוט (חובה) ו-description (אופציונלי).
citation create, appendStreamMessageContent, patch, commit elements.citations[] ‫Citation מוטבע עם id אלפאנומרי (פחות מ-63 תווים, מותר להשתמש בקווים תחתונים) ו-citedSources. צריך להוסיף תגי markupSyntax: "MARKUP_SYNTAX_MARKDOWN" ותגי <chat-citation data-id="ID">...</chat-citation> תואמים ב-text. שליחת citation עם id קיים מחליפה את הציטוט הקודם.
card commit בלבד cardsV2[] כרטיס אינטראקטיבי או כרטיס מידע CardWithId.
accessoryWidget commit בלבד accessoryWidgets[] AccessoryWidget כפתורים שמוצגים מתחת להודעה, כמו כפתורים לשליחת משוב.
citedSource commit בלבד elements.citedSources[] קישור לכותרת תחתונה של CitedSource שמוצג מתחת להודעה בלי תגי ציטוט מוטמעים.

טיפול בביטול שידור ביוזמת המשתמש

בזמן שהשידור פעיל, רק השולח של ההודעה שאליה מתייחסים (referencedMessage) יכול ללחוץ על עצירה בהודעה שמשודרת. כשהמשתמש מפסיק את הסטרימינג, אפליקציית Chat שולחת אירוע אינטראקציה של פקודת מערכת שמכיל את cancelStream.streamMessage (שם המשאב של spaces/{space}/streamMessages/{stream_message} של הסטרימינג שהופסק) לנקודת הקצה של האפליקציה ל-Chat.

המבנה של המטען הייעודי (payload) של ה-JSON הנכנס תלוי באופן שבו אתם בונים את האפליקציה ל-Chat – כתוסף ל-Google Workspace או באמצעות אירועי אינטראקציה של Chat API:

תוספים ל-Google Workspace

{
  "commonEventObject": {
    "hostApp": "CHAT"
  },
  "chat": {
    "user": {
      "name": "users/USER_ID"
    },
    "space": {
      "name": "spaces/SPACE_ID"
    },
    "systemCommandPayload": {
      "systemCommandMetadata": {
        "cancelStream": {
          "streamMessage": "spaces/SPACE_ID/streamMessages/STREAM_ID"
        }
      }
    }
  }
}

Google Chat API

{
  "type": "SYSTEM_COMMAND",
  "space": {
    "name": "spaces/SPACE_ID"
  },
  "user": {
    "name": "users/USER_ID"
  },
  "systemCommandMetadata": {
    "cancelStream": {
      "streamMessage": "spaces/SPACE_ID/streamMessages/STREAM_ID"
    }
  }
}

כשאפליקציית Chat מקבלת אירוע מסוג cancelStream:

  1. צריך לאותת ל-background worker להפסיק ליצור טוקנים נוספים או להריץ כלים במורד הזרם עבור streamMessage.
  2. כדי לפתור את הבעיה, אתם יכולים לבחור באחת מהאפשרויות הבאות:
    • לשמור פלט חלקי: התקשרו ל-spaces.streamMessages.commit עם ה-chunkToken האחרון (אופציונלי, הוספת הערה כגון "\n\n_Generation stopped._").
    • מחיקת התשובה: מפעילים את spaces.streamMessages.delete כדי ששום דבר לא יישמר בשרשור.
  3. מחזירים תגובת HTTP 200 OK JSON ריקה ({}) מהגורם המטפל באירועי אינטראקציה.

מגבלות

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

  • זמינות בפלטפורמות השונות: אפשר לראות את ערוץ ההודעות רק ב-Chat במחשב. בקרוב תהיה תמיכה ב-Android וב-iOS.
  • זמן קצוב לתפוגה של סטרימינג: StreamMessage פעיל מסתיים 10 דקות אחרי שהוא נוצר. האפליקציה ל-Chat צריכה לקרוא ל-spaces.streamMessages.commit או ל-spaces.streamMessages.delete תוך 10 דקות מתחילת הסטרימינג.