En esta guía, se explica cómo transmitir mensajes de forma incremental en Google Chat con
el recurso StreamMessage de la API de Chat. La transmisión permite que tu
app de Google Chat muestre indicadores de progreso en vivo, pasos de razonamiento
intermedio o ejecución de herramientas, y respuestas de texto incrementales antes
de finalizar un mensaje.
La transmisión está diseñada para apps de Chat potenciadas por IA generativa o flujos de trabajo de backend de larga duración en los que generar una respuesta completa lleva varios segundos o minutos.
Cómo funciona la transmisión de mensajes
Un mensaje de transmisión progresa a través de un estado de transmisión activo antes
de convertirse en un recurso
Message
estándar en un espacio:
- Iniciar una transmisión (
spaces.streamMessages.create): Tu app de Chat crea unStreamMessageen un espacio en cadena y hace referencia al mensaje de interacción del usuario al que responde. La API muestra un indicador de progreso en vivo en el hilo y devuelve un nombre de recursoStreamMessagey unnextChunkTokeninicial. - Actualiza el flujo (
appendStreamMessageContentopatch): A medida que tu app de Chat genera resultados, envía actualizaciones secuenciales con elnextChunkTokenmás reciente:- Llama a
spaces.streamMessages.appendStreamMessageContentpara concatenar nuevos fragmentos de texto, agregar pasos intermedios, agregar citas intercaladas o actualizarprogressText. - Llama a
spaces.streamMessages.patchpara reemplazar los fragmentos de contenido transmitidos o actualizarprogressTexten su lugar.
- Llama a
- Finaliza o descarta la transmisión (
commitodelete):- Llama a
spaces.streamMessages.commitpara finalizar la transmisión y convertirla en un recursoMessagepermanente. También puedes adjuntar elementos de solo confirmación, como tarjetas, widgets de accesorios y fuentes al pie de página, cuando confirmes. - Llama a
spaces.streamMessages.deletepara descartar una transmisión activa sin guardar un mensaje en el espacio.
- Llama a
Mientras una transmisión está activa, solo el remitente del mensaje al que se hace referencia
(referencedMessage) en el que se transmite puede hacer clic en Detener para cancelar
la generación. Chat envía un evento de comando del sistema de cancelación a
tu app de Chat para que puedas detener el trabajo de backend y confirmar
el resultado parcial o borrar la transmisión.
Requisitos previos
Para transmitir mensajes con la API de Chat, completa los siguientes requisitos previos:
- Una cuenta de Google Workspace para empresas con acceso a Google Chat
- Configura tu entorno:
- Crea un proyecto de Google Cloud
- Habilita y configura la API de Google Chat con un nombre, un ícono y una descripción para tu app de Chat.
- Crea credenciales de cuenta de servicio y guárdalas como
credentials.json. La transmisión de mensajes requiere la autenticación de la app con el permiso de autorizaciónhttps://www.googleapis.com/auth/chat.bot. No se admite la autenticación de usuarios.
- Agrega tu app de Chat a un espacio de Chat
en forma de hilos (o un mensaje directo 1:1 que ya contenga al
menos un mensaje) y obtén el nombre del recurso de un mensaje accesible
para responder (
spaces/SPACE_ID/messages/MESSAGE_ID). Para las muestras de Python, instala la biblioteca cliente de la API de Google para Python y
google-auth, y, luego, inicializa el cliente del servicio de la API de Chat:pip install google-api-python-client google-authfrom 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 una transmisión
Para comenzar a transmitir una respuesta, llama a spaces.streamMessages.create y pasa un
StreamMessage en el cuerpo de la solicitud:
referencedMessage(obligatorio): Es el nombre del recurso del mensaje que activó la respuesta, con el formatospaces/{space}/messages/{message}. El mensaje al que se hace referencia debe pertenecer al mismo espacio y ser accesible para tu app de Chat. Chat publica la respuesta transmitida en el mismo hilo quereferencedMessage.progressText(obligatorio): Es una etiqueta de estado de texto sin formato no vacía (hasta 250 caracteres) que se muestra junto al indicador de progreso, como"Thinking...".markupSyntax(opcional): Es la sintaxis de formato de texto para el mensaje, comoMARKUP_SYNTAX_MARKDOWNoMARKUP_SYNTAX_CHAT(el valor predeterminado es la sintaxis configurada de la app de Chat si esMARKUP_SYNTAX_UNSPECIFIED). EstableceMARKUP_SYNTAX_MARKDOWNsi planeas incluir citas intercaladas.streamContentChunks(opcional): Son los fragmentos iniciales detext,stepocitationque se mostrarán de inmediato.
Guarda name (spaces/{space}/streamMessages/{stream_message}) y
nextChunkToken de la respuesta para las llamadas posteriores.
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"]
El método devuelve un recurso StreamMessage:
{
"name": "spaces/SPACE_ID/streamMessages/STREAM_MESSAGE_ID",
"progressText": "Thinking...",
"markupSyntax": "MARKUP_SYNTAX_MARKDOWN",
"nextChunkToken": "INITIAL_CHUNK_TOKEN"
}
Agrega fragmentos de contenido
A medida que tu app de Chat produce pasos intermedios o tokens de texto, llama a spaces.streamMessages.appendStreamMessageContent:
- Pasa el
nextChunkTokenmás reciente enchunkToken. Cada llamada exitosa devuelve unnextChunkTokennuevo que debes pasar en tu próxima solicitud de actualización o confirmación. - Los fragmentos de
textse concatenan directamente en el texto transmitido anteriormente. Incluye espacios o saltos de línea donde sea necesario entre los fragmentos. - Los fragmentos de
stepse agregan a la lista de pasos intermedios que se muestran en el mensaje. progressTextes opcional. Si omitesprogressTexto lo estableces en"", Chat seguirá mostrando elprogressTextestablecido 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"]
Reemplaza el contenido transmitido o el texto de progreso
Si necesitas volver a escribir contenido transmitido anteriormente, por ejemplo, después de ejecutar un
filtro de procesamiento posterior o de seguridad, o actualizar solo progressText sin
agregar fragmentos, llama a spaces.streamMessages.patch:
- Establece
updateMasken una lista separada por comas de los campos que se actualizarán:progressText,streamContentChunksoprogressText,streamContentChunks. - Incluye la
nextChunkTokenmás reciente dentro del cuerpo de la solicitudStreamMessagey registra la nuevanextChunkTokenque se muestra en la respuesta. - Cuando
streamContentChunksestá enupdateMask, los fragmentos proporcionados reemplazan el contenido transmitido anteriormente en lugar de agregarlo. Cuando
progressTextestá enupdateMask,progressTextdebe ser un texto sin formato no vacío de hasta 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"]
Finaliza una transmisión
Cuando finalice la generación, llama a spaces.streamMessages.commit con el chunkToken más reciente para finalizar la transmisión en un recurso Message permanente:
- De manera opcional, puedes incluir el
streamContentChunksfinal en la solicitud de confirmación, incluidos los tipos de fragmentos de solo confirmación (card,accessoryWidgetycitedSource). Si ya transmitiste contenido no vacío en llamadas anteriores, puedes omitir
streamContentChunkscuando confirmes. El mensaje de confirmación final no puede estar vacío.
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']}")
Descarta una transmisión activa
Si se produce un error irrecuperable o un usuario cancela la generación y no quieres
dejar una respuesta parcial en la conversación, llama a
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 vez que se confirma una transmisión en un Message, llama a spaces.messages.delete en su lugar si necesitas borrar el mensaje finalizado.
Tipos de fragmentos de contenido admitidos
Cada entrada en streamContentChunks especifica un campo en la
unión StreamContentChunk. Algunos tipos de fragmentos se pueden enviar durante la transmisión,
mientras que otros solo se aceptan en spaces.streamMessages.commit:
| Campo de fragmento | Métodos admitidos | Campo Message final |
Notas |
|---|---|---|---|
text |
create, appendStreamMessageContent, patch, commit |
text |
Texto incremental. Se concatenó el
appendStreamMessageContent y se reemplazó el patch.
El texto confirmado final no puede superar los 32,000 bytes. |
step |
create, appendStreamMessageContent, patch, commit |
elements.steps.steps[] |
Es el paso de procesamiento intermedio con title de texto sin formato
(obligatorio) y description opcional. |
citation |
create, appendStreamMessageContent, patch, commit |
elements.citations[] |
Citation intercalado con un id alfanumérico (menos de 63 caracteres, se permiten guiones bajos) y citedSources. Requiere etiquetas markupSyntax: "MARKUP_SYNTAX_MARKDOWN" y <chat-citation data-id="ID">...</chat-citation> coincidentes en text. Si envías un citation con un id existente, se reemplazará la cita anterior. |
card |
commit únicamente |
cardsV2[] |
Tarjeta interactiva o informativa de
CardWithId. |
accessoryWidget |
commit únicamente |
accessoryWidgets[] |
Botones AccessoryWidget que se muestran debajo del mensaje, como los botones de comentarios |
citedSource |
commit únicamente |
elements.citedSources[] |
Vínculo de pie de página CitedSource que se muestra debajo del mensaje
sin etiquetas de cita intercaladas. |
Cómo controlar la cancelación de la transmisión iniciada por el usuario
Mientras una transmisión está activa, solo el remitente del mensaje al que se hace referencia
(referencedMessage) en el que se transmite puede hacer clic en Detener en el mensaje de
transmisión. Cuando el usuario detiene una transmisión, Chat envía un evento de interacción de comando del sistema que contiene cancelStream.streamMessage (el nombre del recurso spaces/{space}/streamMessages/{stream_message} de la transmisión detenida) al extremo de tu app de Chat.
La estructura de la carga útil de JSON entrante depende de si compilas tu app de Chat como un complemento de Google Workspace o con eventos de interacción de la API de Chat:
Complementos de 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 de Google Chat
{
"type": "SYSTEM_COMMAND",
"space": {
"name": "spaces/SPACE_ID"
},
"user": {
"name": "users/USER_ID"
},
"systemCommandMetadata": {
"cancelStream": {
"streamMessage": "spaces/SPACE_ID/streamMessages/STREAM_ID"
}
}
}
Cuando tu app de Chat recibe un evento cancelStream, haz lo siguiente:
- Indica a tu trabajador en segundo plano que deje de generar más tokens o de ejecutar
herramientas downstream para
streamMessage. - Resuelve la transmisión activa con uno de los siguientes enfoques:
- Mantén el resultado parcial: Llama a
spaces.streamMessages.commitcon elchunkTokenmás reciente (de manera opcional, agrega una nota como"\n\n_Generation stopped._"). - Descartar la respuesta: Llama a
spaces.streamMessages.deletepara que no se guarde nada en la conversación.
- Mantén el resultado parcial: Llama a
- Devuelve una respuesta JSON HTTP
200 OKvacía ({}) desde tu controlador de eventos de interacción.
Limitaciones
La transmisión de mensajes tiene las siguientes limitaciones:
- Disponibilidad de la plataforma: El flujo de mensajes solo es visible en Chat en la Web. Próximamente se admitirán iOS y Android.
- Tiempo de espera de la transmisión: Un
StreamMessageactivo agota el tiempo de espera 10 minutos después de su creación. Tu app de Chat debe llamar aspaces.streamMessages.commitospaces.streamMessages.deleteen un plazo de 10 minutos después de iniciar la transmisión.