Transmite mensajes con la API de Google Chat

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:

  1. Iniciar una transmisión (spaces.streamMessages.create): Tu app de Chat crea un StreamMessage en 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 recurso StreamMessage y un nextChunkToken inicial.
  2. Actualiza el flujo (appendStreamMessageContent o patch): A medida que tu app de Chat genera resultados, envía actualizaciones secuenciales con el nextChunkToken más reciente:
    • Llama a spaces.streamMessages.appendStreamMessageContent para concatenar nuevos fragmentos de texto, agregar pasos intermedios, agregar citas intercaladas o actualizar progressText.
    • Llama a spaces.streamMessages.patch para reemplazar los fragmentos de contenido transmitidos o actualizar progressText en su lugar.
  3. Finaliza o descarta la transmisión (commit o delete):
    • Llama a spaces.streamMessages.commit para finalizar la transmisión y convertirla en un recurso Message permanente. 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.delete para descartar una transmisión activa sin guardar un mensaje en el espacio.

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:
  • 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-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 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 formato spaces/{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 que referencedMessage.
  • 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, como MARKUP_SYNTAX_MARKDOWN o MARKUP_SYNTAX_CHAT (el valor predeterminado es la sintaxis configurada de la app de Chat si es MARKUP_SYNTAX_UNSPECIFIED). Establece MARKUP_SYNTAX_MARKDOWN si planeas incluir citas intercaladas.
  • streamContentChunks (opcional): Son los fragmentos iniciales de text, step o citation que 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 nextChunkToken más reciente en chunkToken. Cada llamada exitosa devuelve un nextChunkToken nuevo que debes pasar en tu próxima solicitud de actualización o confirmación.
  • Los fragmentos de text se concatenan directamente en el texto transmitido anteriormente. Incluye espacios o saltos de línea donde sea necesario entre los fragmentos.
  • Los fragmentos de step se agregan a la lista de pasos intermedios que se muestran en el mensaje.
  • progressText es opcional. Si omites progressText o lo estableces en "", Chat seguirá mostrando el progressText establecido 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 updateMask en una lista separada por comas de los campos que se actualizarán: progressText, streamContentChunks o progressText,streamContentChunks.
  • Incluye la nextChunkToken más reciente dentro del cuerpo de la solicitud StreamMessage y registra la nueva nextChunkToken que se muestra en la respuesta.
  • Cuando streamContentChunks está en updateMask, los fragmentos proporcionados reemplazan el contenido transmitido anteriormente en lugar de agregarlo.
  • Cuando progressText está en updateMask, progressText debe 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 streamContentChunks final en la solicitud de confirmación, incluidos los tipos de fragmentos de solo confirmación (card, accessoryWidget y citedSource).
  • Si ya transmitiste contenido no vacío en llamadas anteriores, puedes omitir streamContentChunks cuando 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:

  1. Indica a tu trabajador en segundo plano que deje de generar más tokens o de ejecutar herramientas downstream para streamMessage.
  2. Resuelve la transmisión activa con uno de los siguientes enfoques:
    • Mantén el resultado parcial: Llama a spaces.streamMessages.commit con el chunkToken más reciente (de manera opcional, agrega una nota como "\n\n_Generation stopped._").
    • Descartar la respuesta: Llama a spaces.streamMessages.delete para que no se guarde nada en la conversación.
  3. Devuelve una respuesta JSON HTTP 200 OK vací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 StreamMessage activo agota el tiempo de espera 10 minutos después de su creación. Tu app de Chat debe llamar a spaces.streamMessages.commit o spaces.streamMessages.delete en un plazo de 10 minutos después de iniciar la transmisión.