Diffuser des messages en streaming à l'aide de l'API Google Chat

Ce guide explique comment diffuser des messages de manière incrémentielle dans Google Chat à l'aide de la ressource StreamMessage dans l'API Chat. Le streaming permet à votre application Google Chat d'afficher des indicateurs de progression en direct, des étapes de raisonnement intermédiaire ou d'exécution d'outils, ainsi que des réponses textuelles incrémentielles avant de finaliser un message.

Le streaming est conçu pour les applications Chat optimisées par l'IA générative ou les workflows de backend de longue durée, où la génération d'une réponse complète prend plusieurs secondes ou minutes.

Fonctionnement du flux de messages

Un message en streaming passe par un état de streaming actif avant de devenir une ressource Message standard dans un espace :

  1. Démarrer un fil de discussion (spaces.streamMessages.create) : votre application Chat crée un StreamMessage dans un espace organisé en fils de discussion, en référençant le message d'interaction utilisateur auquel elle répond. L'API affiche un indicateur de progression en direct dans le fil de discussion et renvoie un nom de ressource StreamMessage et un nextChunkToken initial.
  2. Mettre à jour le flux (appendStreamMessageContent ou patch) : à mesure que votre application Chat génère une sortie, elle envoie des mises à jour séquentielles à l'aide du dernier nextChunkToken :
    • Appelez spaces.streamMessages.appendStreamMessageContent pour concaténer de nouveaux fragments de texte, ajouter des étapes intermédiaires, ajouter des citations intégrées ou mettre à jour progressText.
    • Appelez spaces.streamMessages.patch pour remplacer les fragments de contenu diffusés en continu ou mettre à jour progressText sur place.
  3. Finalisez ou supprimez le flux (commit ou delete) :
    • Appelez spaces.streamMessages.commit pour finaliser le flux et le convertir en ressource Message permanente. Vous pouvez également joindre des éléments de commit uniquement, tels que des fiches, des widgets d'accessoires et des sources en pied de page, lors du commit.
    • Appelez spaces.streamMessages.delete pour supprimer un flux actif sans enregistrer de message dans l'espace.

Lorsqu'un flux est actif, seul l'expéditeur du message référencé (referencedMessage) diffusé peut cliquer sur Arrêter pour annuler la génération. Chat envoie un événement de commande système d'annulation à votre application Chat afin que vous puissiez arrêter le travail du backend et valider une sortie partielle ou supprimer le flux.

Prérequis

Pour diffuser des messages en streaming avec l'API Chat, remplissez les conditions préalables suivantes :

  • Compte Google Workspace Business ou Enterprise ayant accès à Google Chat.
  • Configurez votre environnement :
  • Ajoutez votre application Chat à un espace Chat avec fils de discussion (ou à un message privé qui contient déjà au moins un message), puis obtenez le nom de ressource d'un message accessible auquel répondre (spaces/SPACE_ID/messages/MESSAGE_ID).
  • Pour les exemples Python, installez la bibliothèque cliente des API Google pour Python et google-auth, puis initialisez le client de service de l'API Chat :

    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)
    

Lancer un flux

Pour commencer à diffuser une réponse, appelez spaces.streamMessages.create et transmettez un StreamMessage dans le corps de la requête :

  • referencedMessage (obligatoire) : nom de ressource du message qui a déclenché la réponse, au format spaces/{space}/messages/{message}. Le message référencé doit appartenir au même espace et être accessible à votre application Chat. Chat publie la réponse diffusée dans le même fil de discussion que referencedMessage.
  • progressText (obligatoire) : libellé d'état en texte brut non vide (jusqu'à 250 caractères) affiché à côté de l'indicateur de progression, par exemple "Thinking...".
  • markupSyntax (facultatif) : syntaxe de mise en forme du texte pour le message, par exemple MARKUP_SYNTAX_MARKDOWN ou MARKUP_SYNTAX_CHAT (par défaut, la syntaxe configurée de l'application Chat si MARKUP_SYNTAX_UNSPECIFIED). Définissez MARKUP_SYNTAX_MARKDOWN si vous prévoyez d'inclure des citations intégrées.
  • streamContentChunks (facultatif) : fragments text, step ou citation initiaux à afficher immédiatement.

Enregistrez name (spaces/{space}/streamMessages/{stream_message}) et nextChunkToken à partir de la réponse pour les appels ultérieurs.

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

La méthode renvoie une ressource StreamMessage :

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

Ajouter des fragments de contenu

Lorsque votre application Chat produit des étapes intermédiaires ou des jetons de texte, appelez spaces.streamMessages.appendStreamMessageContent :

  • Transmettez la dernière version de nextChunkToken dans chunkToken. Chaque appel réussi renvoie un nouveau nextChunkToken que vous devez transmettre lors de votre prochaine requête de mise à jour ou de commit.
  • Les fragments text sont concaténés directement sur le texte précédemment diffusé. Incluez des espaces ou des sauts de ligne si nécessaire entre les fragments.
  • Les fragments step sont ajoutés à la liste des étapes intermédiaires affichées dans le message.
  • progressText est facultatif. Si vous omettez progressText ou si vous le définissez sur "", Chat continue d'afficher le progressText précédemment défini.

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

Remplacer le contenu diffusé en streaming ou le texte de progression

Si vous devez réécrire du contenu précédemment diffusé (par exemple, après avoir exécuté un filtre de post-traitement ou de sécurité) ou ne mettre à jour que progressText sans ajouter de fragments, appelez spaces.streamMessages.patch :

  • Définissez updateMask sur une liste de champs à mettre à jour, séparés par une virgule : progressText, streamContentChunks ou progressText,streamContentChunks.
  • Incluez le dernier nextChunkToken dans le corps de la requête StreamMessage et enregistrez le nouveau nextChunkToken renvoyé dans la réponse.
  • Lorsque streamContentChunks est défini sur updateMask, les fragments fournis remplacent le contenu précédemment diffusé au lieu de s'y ajouter.
  • Lorsque progressText est dans updateMask, progressText doit être un texte brut non vide de 250 caractères maximum.

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

Finaliser un flux

Une fois la génération terminée, appelez spaces.streamMessages.commit avec le dernier chunkToken pour finaliser le flux dans une ressource Message permanente :

  • Vous pouvez éventuellement inclure des streamContentChunks finaux dans la requête de commit, y compris des types de fragments réservés au commit (card, accessoryWidget et citedSource).
  • Si vous avez déjà diffusé du contenu non vide lors d'appels précédents, vous pouvez omettre streamContentChunks lors de la validation. Le message de commit final ne peut pas être vide.

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']}")

Supprimer un flux actif

Si une erreur irrécupérable se produit ou si un utilisateur annule la génération et que vous ne souhaitez pas laisser de réponse partielle dans le fil de discussion, appelez 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()

Une fois qu'un flux est validé dans un Message, appelez plutôt spaces.messages.delete si vous devez supprimer le message finalisé.

Types de fragments de contenu compatibles

Chaque entrée de streamContentChunks spécifie un champ de l'union StreamContentChunk. Certains types de fragments peuvent être envoyés tout au long du flux, tandis que d'autres ne sont acceptés que dans spaces.streamMessages.commit :

Champ de fragment Méthodes acceptées Champ Message final Remarques
text create, appendStreamMessageContent, patch, commit text Texte incrémental. Concaténation le appendStreamMessageContent ; remplacement le patch. Le texte final validé ne peut pas dépasser 32 000 octets.
step create, appendStreamMessageContent, patch, commit elements.steps.steps[] Étape de traitement intermédiaire avec title en texte brut (obligatoire) et description (facultatif).
citation create, appendStreamMessageContent, patch, commit elements.citations[] Remplacez Citation par un id alphanumérique (moins de 63 caractères, traits de soulignement autorisés) et citedSources. Nécessite des balises markupSyntax: "MARKUP_SYNTAX_MARKDOWN" et <chat-citation data-id="ID">...</chat-citation> correspondantes dans text. L'envoi d'un citation avec un id existant écrase la citation précédente.
card commit uniquement cardsV2[] Carte interactive ou d'information CardWithId.
accessoryWidget commit uniquement accessoryWidgets[] AccessoryWidget : boutons affichés sous le message, tels que les boutons de commentaires.
citedSource commit uniquement elements.citedSources[] CitedSource : lien de pied de page affiché sous le message sans balises de citation intégrées.

Gérer l'annulation du flux initiée par l'utilisateur

Lorsqu'un flux est actif, seul l'expéditeur du message référencé (referencedMessage) sur lequel le flux est diffusé peut cliquer sur Arrêter sur le message de diffusion. Lorsque l'utilisateur arrête un flux, Chat envoie un événement d'interaction de commande système contenant cancelStream.streamMessage (le nom de ressource spaces/{space}/streamMessages/{stream_message} du flux arrêté) au point de terminaison de votre application Chat.

La structure de la charge utile JSON entrante dépend de la façon dont vous créez votre application Chat : en tant que module complémentaire Google Workspace ou à l'aide d'événements d'interaction de l'API Chat.

Modules complémentaires 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"
        }
      }
    }
  }
}

API Google Chat

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

Lorsque votre application Chat reçoit un événement cancelStream :

  1. Signalez à votre nœud de calcul en arrière-plan d'arrêter de générer d'autres jetons ou d'exécuter des outils en aval pour streamMessage.
  2. Résolvez le flux actif en utilisant l'une des approches suivantes :
    • Conserver la sortie partielle : appelez spaces.streamMessages.commit avec le dernier chunkToken (en ajoutant éventuellement une note telle que "\n\n_Generation stopped._").
    • Supprimer la réponse : appelez spaces.streamMessages.delete pour que rien ne soit enregistré dans le fil de discussion.
  3. Renvoyez une réponse JSON HTTP 200 OK vide ({}) à partir de votre gestionnaire d'événements d'interaction.

Limites

Le streaming de messages présente les limites suivantes :

  • Disponibilité de la plate-forme : le flux de messages n'est visible que dans Chat sur le Web. La compatibilité avec Android et iOS sera bientôt disponible.
  • Délai d'expiration du flux : un StreamMessage actif expire 10 minutes après sa création. Votre application Chat doit appeler spaces.streamMessages.commit ou spaces.streamMessages.delete dans les 10 minutes suivant le début du flux.