Transmitir mensagens usando a API Google Chat

Neste guia, explicamos como transmitir mensagens de forma incremental no Google Chat usando o recurso StreamMessage na API Chat. Com o streaming, seu app do Google Chat pode mostrar indicadores de progresso em tempo real, etapas intermediárias de raciocínio ou execução de ferramentas e respostas de texto incrementais antes de finalizar uma mensagem.

O streaming foi projetado para apps do Chat com tecnologia de IA generativa ou fluxos de trabalho de back-end de longa duração, em que a geração de uma resposta completa leva vários segundos ou minutos.

Como funciona o streaming de mensagens

Uma mensagem de streaming passa por um estado de streaming ativo antes de se tornar um recurso padrão Message em um espaço:

  1. Iniciar uma conversa (spaces.streamMessages.create): o app do Chat cria um StreamMessage em um espaço com conversas agrupadas, referenciando a mensagem de interação do usuário que está sendo respondida. A API mostra um indicador de progresso em tempo real na conversa e retorna um nome de recurso StreamMessage e um nextChunkToken inicial.
  2. Atualizar o stream (appendStreamMessageContent ou patch): à medida que o app do Chat gera uma resposta, ele envia atualizações sequenciais usando o nextChunkToken mais recente:
    • Chame spaces.streamMessages.appendStreamMessageContent para concatenar novos blocos de texto, adicionar ao final etapas intermediárias, adicionar citações inline ou atualizar progressText.
    • Chame spaces.streamMessages.patch para substituir os blocos de conteúdo transmitidos ou atualizar progressText no local.
  3. Finalize ou descarte o stream (commit ou delete):
    • Chame spaces.streamMessages.commit para finalizar o stream e convertê-lo em um recurso Message permanente. Também é possível anexar elementos somente de commit, como fichas de informações, widgets de acessórios e fontes do rodapé, ao fazer o commit.
    • Chame spaces.streamMessages.delete para descartar um stream ativo sem salvar uma mensagem no espaço.

Enquanto uma transmissão está ativa, apenas o remetente da mensagem referenciada (referencedMessage) que está sendo transmitida pode clicar em Parar para cancelar a geração. O Chat envia um evento de comando do sistema de cancelamento para seu app do Chat para que você possa interromper o trabalho de back-end e confirmar a saída parcial ou excluir o stream.

Pré-requisitos

Para transmitir mensagens com a API Chat, conclua os seguintes pré-requisitos:

  • Uma conta do Google Workspace Business ou Enterprise com acesso ao Google Chat.
  • Configure o ambiente:
  • Adicione o app do Chat a um espaço do Chat com conversas agrupadas (ou uma mensagem direta individual que já contenha pelo menos uma mensagem) e receba o nome do recurso de uma mensagem acessível para responder (spaces/SPACE_ID/messages/MESSAGE_ID).
  • Para amostras em Python, instale a biblioteca de cliente da API do Google para Python e google-auth e inicialize o cliente de serviço da 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)
    

Iniciar um stream

Para iniciar o streaming de uma resposta, chame spaces.streamMessages.create e transmita um StreamMessage no corpo da solicitação:

  • referencedMessage (obrigatório): nome do recurso da mensagem que acionou a resposta, formatado como spaces/{space}/messages/{message}. A mensagem referenciada precisa pertencer ao mesmo espaço e ser acessível ao seu app do Chat. O Chat posta a resposta transmitida na mesma conversa que referencedMessage.
  • progressText (obrigatório): rótulo de status de texto simples não vazio (até 250 caracteres) mostrado ao lado do indicador de progresso, como "Thinking...".
  • markupSyntax (opcional): sintaxe de formatação de texto para a mensagem, como MARKUP_SYNTAX_MARKDOWN ou MARKUP_SYNTAX_CHAT (o padrão é a sintaxe configurada do app do Chat se MARKUP_SYNTAX_UNSPECIFIED). Defina MARKUP_SYNTAX_MARKDOWN se você planeja incluir citações inline.
  • streamContentChunks (opcional): blocos iniciais de text, step ou citation para exibição imediata.

Salve name (spaces/{space}/streamMessages/{stream_message}) e nextChunkToken da resposta para chamadas subsequentes.

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

O método retorna um recurso StreamMessage:

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

Anexar blocos de conteúdo

À medida que o app do Chat produz etapas intermediárias ou tokens de texto, chame spaces.streamMessages.appendStreamMessageContent:

  • Transmita o nextChunkToken mais recente em chunkToken. Cada chamada bem-sucedida retorna um novo nextChunkToken que você precisa transmitir na próxima solicitação de atualização ou commit.
  • Os blocos text são concatenados diretamente no texto transmitido anteriormente. Inclua espaços ou novas linhas onde for necessário entre os blocos.
  • Os blocos step são anexados à lista de etapas intermediárias mostradas na mensagem.
  • progressText é opcional. Se você omitir progressText ou definir como "", o Chat vai continuar mostrando o progressText definido anteriormente.

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

Substituir o conteúdo transmitido ou o texto de progresso

Se você precisar reescrever conteúdo transmitido anteriormente, por exemplo, depois de executar um filtro de pós-processamento ou de segurança, ou atualizar apenas progressText sem anexar blocos, chame spaces.streamMessages.patch:

  • Defina updateMask como uma lista separada por vírgulas de campos a serem atualizados: progressText, streamContentChunks ou progressText,streamContentChunks.
  • Inclua o nextChunkToken mais recente no corpo da solicitação StreamMessage e registre o novo nextChunkToken retornado na resposta.
  • Quando streamContentChunks está em updateMask, os blocos fornecidos substituem o conteúdo transmitido anteriormente em vez de serem anexados a ele.
  • Quando progressText está em updateMask, progressText precisa ser um texto simples não vazio de até 250 caracteres.

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

Finalizar um stream

Quando a geração terminar, chame spaces.streamMessages.commit com o chunkToken mais recente para finalizar o stream em um recurso permanente Message:

  • Se quiser, inclua o streamContentChunks final na solicitação de commit, incluindo tipos de blocos somente de commit (card, accessoryWidget e citedSource).
  • Se você já tiver transmitido conteúdo não vazio em chamadas anteriores, poderá omitir streamContentChunks ao confirmar. A mensagem final confirmada não pode estar vazia.

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

Descartar um stream ativo

Se ocorrer um erro irrecuperável ou se um usuário cancelar a geração e você não quiser deixar uma resposta parcial na conversa, chame 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()

Depois que um stream for confirmado em um Message, chame spaces.messages.delete se precisar excluir a mensagem finalizada.

Tipos de blocos de conteúdo compatíveis

Cada entrada em streamContentChunks especifica um campo na união StreamContentChunk. Alguns tipos de bloco podem ser enviados durante todo o stream, enquanto outros são aceitos apenas em spaces.streamMessages.commit:

Campo de fragmento Métodos aceitos Campo Message final Observações
text create, appendStreamMessageContent, patch, commit text Texto incremental. Concatenado em appendStreamMessageContent; substituído em patch. O texto final confirmado não pode exceder 32.000 bytes.
step create, appendStreamMessageContent, patch, commit elements.steps.steps[] Etapa de processamento intermediária com title de texto simples (obrigatório) e description opcional.
citation create, appendStreamMessageContent, patch, commit elements.citations[] Citation inline com um id alfanumérico (menos de 63 caracteres, sublinhados permitidos) e citedSources. Requer tags markupSyntax: "MARKUP_SYNTAX_MARKDOWN" e <chat-citation data-id="ID">...</chat-citation> correspondentes em text. O envio de um citation com um id atual substitui a citação anterior.
card Somente commit cardsV2[] Card interativo ou informativo CardWithId.
accessoryWidget Somente commit accessoryWidgets[] AccessoryWidget botões exibidos abaixo da mensagem, como botões de feedback.
citedSource Somente commit elements.citedSources[] CitedSource link de rodapé exibido abaixo da mensagem sem tags de citação inline.

Processar o cancelamento de stream iniciado pelo usuário

Enquanto uma transmissão está ativa, apenas o remetente da mensagem referenciada (referencedMessage) que está sendo transmitida pode clicar em Parar na mensagem de transmissão. Quando o usuário interrompe um stream, o Chat envia um evento de interação de comando do sistema contendo cancelStream.streamMessage (o nome do recurso spaces/{space}/streamMessages/{stream_message} do stream interrompido) para o endpoint do app Chat.

A estrutura do payload JSON recebido depende de como você cria o app do Chat: como um complemento do Google Workspace ou usando eventos de interação da API Chat:

Complementos do 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"
    }
  }
}

Quando o app do Chat recebe um evento cancelStream:

  1. Sinalize para o worker em segundo plano parar de gerar mais tokens ou executar ferramentas downstream para streamMessage.
  2. Resolva o stream ativo usando uma das seguintes abordagens:
    • Manter saída parcial: chame spaces.streamMessages.commit com o chunkToken mais recente (opcionalmente, adicionando uma nota como "\n\n_Generation stopped._").
    • Descartar a resposta: chame spaces.streamMessages.delete para que nada seja salvo na conversa.
  3. Retorne uma resposta JSON HTTP 200 OK vazia ({}) do manipulador de eventos de interação.

Limitações

O streaming de mensagens tem as seguintes limitações:

  • Disponibilidade da plataforma: o streaming de mensagens só aparece no Chat na Web. O suporte para Android e iOS será lançado em breve.
  • Tempo limite do stream: um StreamMessage ativo expira 10 minutos após a criação. O app do Chat precisa chamar spaces.streamMessages.commit ou spaces.streamMessages.delete em até 10 minutos após o início do stream.