Trasmetti messaggi in streaming utilizzando l'API Google Chat

Questa guida spiega come trasmettere i messaggi in modo incrementale in Google Chat utilizzando la risorsa StreamMessage nell'API Chat. Lo streaming consente alla tua app Google Chat di visualizzare indicatori di avanzamento in tempo reale, passaggi di ragionamento intermedio o di esecuzione degli strumenti e risposte di testo incrementali prima di finalizzare un messaggio.

Lo streaming è progettato per le app di Chat basate sull'AI generativa o per i workflow di backend a lunga esecuzione in cui la generazione di una risposta completa richiede diversi secondi o minuti.

Come funziona lo streaming dei messaggi

Un messaggio in streaming passa attraverso uno stato di streaming attivo prima di diventare una risorsa Message standard in uno spazio:

  1. Avvia un flusso (spaces.streamMessages.create): la tua app di Chat crea un StreamMessage in uno spazio con thread, facendo riferimento al messaggio di interazione dell'utente a cui sta rispondendo. L'API visualizza un indicatore di avanzamento in tempo reale nel thread e restituisce un nome della risorsa StreamMessage e un nextChunkToken iniziale.
  2. Aggiorna lo stream (appendStreamMessageContent o patch): man mano che l'app Chat genera l'output, invia aggiornamenti sequenziali utilizzando l'ultimo nextChunkToken:
    • Chiama spaces.streamMessages.appendStreamMessageContent per concatenare nuovi blocchi di testo, aggiungere passaggi intermedi, aggiungere citazioni in linea o aggiornare progressText.
    • Chiama spaces.streamMessages.patch per sostituire i blocchi di contenuti in streaming o aggiorna progressText in loco.
  3. Finalizza o ignora lo stream (commit o delete):
    • Chiama spaces.streamMessages.commit per finalizzare lo stream e convertirlo in una risorsa Message permanente. Puoi anche allegare elementi di solo commit, come schede, widget accessori e fonti a piè di pagina, durante il commit.
    • Chiama spaces.streamMessages.delete per eliminare uno stream attivo senza salvare un messaggio nello spazio.

Mentre uno stream è attivo, solo il mittente del messaggio di riferimento (referencedMessage) su cui viene eseguito lo streaming può fare clic su Interrompi per annullare la generazione. Chat invia un evento di comando di sistema di annullamento alla tua app di Chat, in modo che tu possa interrompere il lavoro di backend ed eseguire il commit dell'output parziale o eliminare lo stream.

Prerequisiti

Per trasmettere messaggi in streaming con l'API Chat, completa i seguenti prerequisiti:

  • Un account Google Workspace Business o Enterprise con accesso a Google Chat.
  • Configura l'ambiente:
  • Aggiungi l'app di Chat a uno spazio di Chat con thread (o a un messaggio diretto 1:1 che contiene già almeno un messaggio) e ottieni il nome della risorsa di un messaggio accessibile a cui rispondere (spaces/SPACE_ID/messages/MESSAGE_ID).
  • Per gli esempi di Python, installa la libreria client delle API di Google per Python e google-auth, quindi inizializza il client del servizio 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)
    

Avvio di un flusso

Per avviare lo streaming di una risposta, chiama spaces.streamMessages.create e passa un StreamMessage nel corpo della richiesta:

  • referencedMessage (obbligatorio): nome della risorsa del messaggio che ha attivato la risposta, formattato come spaces/{space}/messages/{message}. Il messaggio a cui viene fatto riferimento deve appartenere allo stesso spazio ed essere accessibile alla tua app di Chat. Chat pubblica la risposta in streaming nello stesso thread di referencedMessage.
  • progressText (obbligatorio): etichetta di stato in testo normale non vuota (fino a 250 caratteri) mostrata accanto all'indicatore di avanzamento, ad esempio "Thinking...".
  • markupSyntax (Facoltativo): sintassi di formattazione del testo per il messaggio, ad esempio MARKUP_SYNTAX_MARKDOWN o MARKUP_SYNTAX_CHAT (per impostazione predefinita, la sintassi configurata dell'app di Chat se MARKUP_SYNTAX_UNSPECIFIED). Imposta MARKUP_SYNTAX_MARKDOWN se prevedi di includere citazioni in linea.
  • streamContentChunks (facoltativo): blocchi iniziali text, step o citation da visualizzare immediatamente.

Salva sia name (spaces/{space}/streamMessages/{stream_message}) sia nextChunkToken dalla risposta per le chiamate successive.

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

Il metodo restituisce una risorsa StreamMessage:

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

Aggiungi blocchi di contenuti

Man mano che l'app di Chat produce passaggi intermedi o token di testo, chiama spaces.streamMessages.appendStreamMessageContent:

  • Passa l'ultimo nextChunkToken in chunkToken. Ogni chiamata riuscita restituisce un nuovo nextChunkToken che devi passare nella richiesta di aggiornamento o commit successiva.
  • I chunk text vengono concatenati direttamente al testo precedentemente trasmesso in streaming. Includi spazi o ritorni a capo dove necessario tra i chunk.
  • I chunk step vengono aggiunti all'elenco dei passaggi intermedi mostrati nel messaggio.
  • progressText è facoltativo. Se ometti progressText o lo imposti su "", Chat continua a visualizzare il valore progressText impostato in precedenza.

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

Sostituisci i contenuti in streaming o il testo di avanzamento

Se devi riscrivere contenuti precedentemente trasmessi in streaming, ad esempio dopo aver eseguito un filtro di post-elaborazione o di sicurezza, o aggiornare solo progressText senza aggiungere chunk, chiama spaces.streamMessages.patch:

  • Imposta updateMask su un elenco separato da virgole di campi da aggiornare: progressText, streamContentChunks o progressText,streamContentChunks.
  • Includi l'ultimo nextChunkToken nel corpo della richiesta StreamMessage e registra il nuovo nextChunkToken restituito nella risposta.
  • Quando streamContentChunks è in updateMask, i chunk forniti sostituiscono i contenuti precedentemente trasmessi in streaming anziché aggiungersi.
  • Quando progressText è in updateMask, progressText deve essere un testo normale non vuoto di massimo 250 caratteri.

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

Finalizza uno stream

Al termine della generazione, chiama spaces.streamMessages.commit con l'ultimo chunkToken per finalizzare lo stream in una risorsa Message permanente:

  • Se vuoi, puoi includere i streamContentChunks finali nella richiesta di commit, inclusi i tipi di chunk di solo commit (card, accessoryWidget e citedSource).
  • Se hai già eseguito lo streaming di contenuti non vuoti in chiamate precedenti, puoi omettere streamContentChunks durante il commit. Il messaggio finale di commit non può essere vuoto.

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

Ignora uno stream attivo

Se si verifica un errore irreversibile o un utente annulla la generazione e non vuoi lasciare una risposta parziale nel thread, chiama 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()

Una volta eseguito il commit di uno stream in un Message, chiama spaces.messages.delete se devi eliminare il messaggio finalizzato.

Tipi di segmenti di contenuti supportati

Ogni voce in streamContentChunks specifica un campo nell'unione StreamContentChunk. Alcuni tipi di chunk possono essere inviati durante lo stream, mentre altri sono accettati solo in spaces.streamMessages.commit:

Campo Chunk Metodi supportati Campo Message finale Note
text create, appendStreamMessageContent, patch, commit text Testo incrementale. Concatenato il giorno appendStreamMessageContent; sostituito il giorno patch. Il testo finale confermato non può superare i 32.000 byte.
step create, appendStreamMessageContent, patch, commit elements.steps.steps[] Passaggio di elaborazione intermedio con testo normale title (obbligatorio) e description facoltativo.
citation create, appendStreamMessageContent, patch, commit elements.citations[] Citation in linea con un id alfanumerico (meno di 63 caratteri, trattini bassi consentiti) e citedSources. Richiede i tag markupSyntax: "MARKUP_SYNTAX_MARKDOWN" e <chat-citation data-id="ID">...</chat-citation> corrispondenti in text. L'invio di un citation con un id esistente sovrascrive la citazione precedente.
card Solo commit cardsV2[] Scheda interattiva o informativa CardWithId.
accessoryWidget Solo commit accessoryWidgets[] Pulsanti AccessoryWidget visualizzati sotto il messaggio, ad esempio i pulsanti di feedback.
citedSource Solo commit elements.citedSources[] Link nel piè di pagina CitedSource visualizzato sotto il messaggio senza tag di citazione in linea.

Gestisci l'annullamento dello stream avviato dall'utente

Mentre uno stream è attivo, solo il mittente del messaggio di riferimento (referencedMessage) su cui viene eseguito lo streaming può fare clic su Interrompi nel messaggio di streaming. Quando l'utente interrompe uno stream, Chat invia un evento di interazione con un comando di sistema contenente cancelStream.streamMessage (il nome della risorsa spaces/{space}/streamMessages/{stream_message} dello stream interrotto) all'endpoint dell'app Chat.

La struttura del payload JSON in entrata dipende dal fatto che tu crei l'app di Chat come componente aggiuntivo di Google Workspace o utilizzando gli eventi di interazione dell'API Chat:

Componenti aggiuntivi di 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 l'app di Chat riceve un evento cancelStream:

  1. Invia un segnale al worker in background per interrompere la generazione di altri token o l'esecuzione di strumenti downstream per streamMessage.
  2. Risolvi lo stream attivo utilizzando uno dei seguenti approcci:
    • Mantieni l'output parziale: chiama spaces.streamMessages.commit con l'ultimo chunkToken (facoltativamente aggiungendo una nota come "\n\n_Generation stopped._").
    • Ignora la risposta: chiama spaces.streamMessages.delete in modo che non venga salvato nulla nel thread.
  3. Restituisci una risposta JSON HTTP 200 OK vuota ({}) dal gestore di eventi di interazione.

Limitazioni

Il flusso di messaggi presenta le seguenti limitazioni:

  • Disponibilità della piattaforma: il flusso di messaggi è visibile solo in Chat sul web. Il supporto per Android e iOS sarà disponibile a breve.
  • Timeout dello stream: un StreamMessage attivo scade 10 minuti dopo la creazione. L'app di Chat deve chiamare spaces.streamMessages.commit o spaces.streamMessages.delete entro 10 minuti dall'avvio dello stream.