Google Chat API を使用してメッセージをストリーミングする

このガイドでは、Chat API の StreamMessage リソースを使用して、Google Chat でメッセージを段階的にストリーミングする方法について説明します。ストリーミングを使用すると、Google Chat アプリは、メッセージを確定する前に、ライブ進行状況インジケーター、中間推論またはツール実行ステップ、増分テキスト レスポンスを表示できます。

ストリーミングは、生成 AI を活用した Chat 用アプリや、完全なレスポンスの生成に数秒または数分かかる長時間実行のバックエンド ワークフロー向けに設計されています。

メッセージ ストリーミングの仕組み

ストリーミング メッセージは、スペース内の標準の Message リソースになる前に、アクティブなストリーミング状態を通過します。

  1. スレッドを開始する(spaces.streamMessages.create): Chat アプリは、返信先のユーザー インタラクション メッセージを参照して、スレッド形式のスペースに StreamMessage を作成します。API は、スレッドにライブ進行状況インジケーターを表示し、StreamMessage リソース名と初期 nextChunkToken を返します。
  2. ストリームを更新する(appendStreamMessageContent または patch): Chat アプリは出力を生成すると、最新の nextChunkToken を使用して順次更新を送信します。
    • spaces.streamMessages.appendStreamMessageContent を呼び出して、新しいテキスト チャンクの連結、中間ステップの追加、インライン引用の追加、progressText の更新を行います。
    • spaces.streamMessages.patch を呼び出して、ストリーミングされたコンテンツ チャンクを置き換えるか、progressText をインプレースで更新します。
  3. ストリームを確定または破棄する(commit または delete):
    • spaces.streamMessages.commit を呼び出してストリームを確定し、永続的な Message リソースに変換します。commit 時に、カード、アクセサリー ウィジェット、フッターソースなどの commit 専用要素を添付することもできます。
    • spaces.streamMessages.delete を呼び出して、スペースにメッセージを保存せずにアクティブなストリームを破棄します。

ストリームがアクティブな間は、ストリーミングされている参照メッセージ(referencedMessage)の送信者のみが [停止] をクリックして生成をキャンセルできます。Chat は、キャンセル システム コマンド イベントを Chat アプリに送信します。これにより、バックエンドの作業を停止し、部分的な出力を commit するか、ストリームを削除できます。

前提条件

Chat API を使用してメッセージをストリーミングするには、次の前提条件を満たしている必要があります。

  • Google Chat へのアクセス権を持つ Business または Enterprise の Google Workspace アカウント。
  • 環境を設定します。
  • Chat 用アプリをスレッド形式の Chat スペース(または、すでに 1 つ以上のメッセージが含まれている 1 対 1 のダイレクト メッセージ)に追加し、返信するアクセス可能なメッセージのリソース名(spaces/SPACE_ID/messages/MESSAGE_ID)を取得します。
  • Python サンプルの場合は、Python 用 Google API クライアント ライブラリと google-auth をインストールし、Chat API サービス クライアントを初期化します。

    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)
    

ストリームを開始する

返信のストリーミングを開始するには、spaces.streamMessages.create を呼び出し、リクエスト本文で StreamMessage を渡します。

  • referencedMessage(必須): レスポンスをトリガーしたメッセージのリソース名。spaces/{space}/messages/{message} の形式で指定します。参照されるメッセージは同じスペースに属し、Chat アプリからアクセスできる必要があります。Chat は、ストリーミングされた返信を referencedMessage と同じスレッドに投稿します。
  • progressText(必須): 進行状況インジケーターの横に表示される、空でない書式なしテキストのステータス ラベル(最大 250 文字)。例: "Thinking..."。
  • markupSyntax(省略可): メッセージのテキスト書式設定構文(MARKUP_SYNTAX_MARKDOWN、MARKUP_SYNTAX_CHAT など)。MARKUP_SYNTAX_UNSPECIFIED の場合、デフォルトは Chat 用アプリの構成済み構文です。インライン引用を含める場合は、MARKUP_SYNTAX_MARKDOWN を設定します。
  • streamContentChunks(省略可): すぐに表示する最初の text、step、citation チャンク。

後続の呼び出しのために、レスポンスから name(spaces/{space}/streamMessages/{stream_message})と nextChunkToken の両方を保存します。

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

このメソッドは StreamMessage リソースを返します。

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

コンテンツ チャンクを追加する

Chat アプリが中間ステップまたはテキスト トークンを生成したら、spaces.streamMessages.appendStreamMessageContent を呼び出します。

  • 最新の nextChunkToken を chunkToken に渡します。呼び出しが成功するたびに、次の更新リクエストまたは commit リクエストで渡す必要がある新しい nextChunkToken が返されます。
  • text チャンクは、以前にストリーミングされたテキストに直接連結されます。必要に応じて、チャンク間にスペースまたは改行を含めます。
  • step チャンクは、メッセージに表示される中間ステップのリストに追加されます。
  • progressText は省略可能です。progressText を省略するか、"" に設定すると、Chat は以前に設定された progressText を引き続き表示します。

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

ストリーミングされたコンテンツまたは進行状況テキストを置き換える

以前にストリーミングされたコンテンツを書き換える必要がある場合(後処理や安全フィルタの実行後など)、またはチャンクを追加せずに progressText のみを更新する場合は、spaces.streamMessages.patch を呼び出します。

  • updateMask を、更新するフィールド(progressText、streamContentChunks、progressText,streamContentChunks)のカンマ区切りリストに設定します。
  • StreamMessage リクエスト本文に最新の nextChunkToken を含め、レスポンスで返された新しい nextChunkToken を記録します。
  • streamContentChunks が updateMask の場合、提供されたチャンクは、以前にストリーミングされたコンテンツに追加されるのではなく、置き換えられます。
  • progressText が updateMask の場合、progressText は 250 文字以下の空でない書式なしテキストにする必要があります。

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

ストリームを確定する

生成が完了したら、最新の chunkToken を指定して spaces.streamMessages.commit を呼び出し、ストリームを永続的な Message リソースに確定します。

  • 必要に応じて、commit-only チャンクタイプ(card、accessoryWidget、citedSource)を含む最終的な streamContentChunks を commit リクエストに含めることができます。
  • 以前の呼び出しで空でないコンテンツをすでにストリーミングしている場合は、commit 時に streamContentChunks を省略できます。最終的に commit されたメッセージを空にすることはできません。

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

アクティブなストリームを破棄する

修復不可能なエラーが発生した場合や、ユーザーが生成をキャンセルした場合に、スレッドに部分的な返信を残したくない場合は、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()

ストリームが Message に commit された後、確定されたメッセージを削除する必要がある場合は、代わりに spaces.messages.delete を呼び出します。

サポートされているコンテンツ チャンクタイプ

streamContentChunks の各エントリは、StreamContentChunk 共用体の 1 つのフィールドを指定します。一部のチャンクタイプはストリーム全体で送信できますが、他のチャンクタイプは spaces.streamMessages.commit でのみ受け入れられます。

チャンク フィールド サポートされるメソッド 最終的な Message フィールド メモ
text create、appendStreamMessageContent、patch、commit text 増分テキスト。appendStreamMessageContent で連結。patch で置き換え。最終的に commit されるテキストは 32,000 バイト以下にする必要があります。
step create、appendStreamMessageContent、patch、commit elements.steps.steps[] 書式なしテキスト title(必須)とオプションの description を使用した中間処理ステップ。
citation create、appendStreamMessageContent、patch、commit elements.citations[] インライン Citation。英数字の id(63 文字未満、アンダースコアを使用可能)と citedSources を使用します。text に markupSyntax: "MARKUP_SYNTAX_MARKDOWN" と一致する <chat-citation data-id="ID">...</chat-citation> タグが必要です。既存の id を含む citation を送信すると、以前の引用が上書きされます。
card commit のみ cardsV2[] インタラクティブまたは情報提供用の CardWithId カード。
accessoryWidget commit のみ accessoryWidgets[] メッセージの下に表示される AccessoryWidget ボタン(フィードバック ボタンなど)。
citedSource commit のみ elements.citedSources[] CitedSource フッターリンク。インライン引用タグなしでメッセージの下に表示されます。

ユーザーが開始したストリームのキャンセルを処理する

ストリーミングがアクティブな間は、ストリーミング中の参照メッセージ(referencedMessage)の送信者のみが、ストリーミング メッセージの [停止] をクリックできます。ユーザーがストリームを停止すると、Chat は cancelStream.streamMessage(停止したストリームの spaces/{space}/streamMessages/{stream_message} リソース名)を含むシステム コマンド インタラクション イベントを Chat アプリのエンドポイントに送信します。

受信 JSON ペイロードの構造は、Chat アプリを Google Workspace アドオンとして構築するか、Chat API のインタラクション イベントを使用して構築するかによって異なります。

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

Google Chat API

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

Chat 用アプリが cancelStream イベントを受信した場合:

  1. バックグラウンド ワーカーに、streamMessage のトークンの生成やダウンストリーム ツールの実行を停止するよう指示します。
  2. 次のいずれかの方法でアクティブなストリームを解決します。
    • 部分的な出力を保持する: 最新の chunkToken を指定して spaces.streamMessages.commit を呼び出します(必要に応じて、"\n\n_Generation stopped._" などのメモを追加します)。
    • 返信を破棄する: spaces.streamMessages.delete を呼び出して、スレッドに何も保存されないようにします。
  3. インタラクション イベント ハンドラから空の HTTP 200 OK JSON レスポンス({})を返します。

制限事項

メッセージ ストリーミングには次の制限があります。

  • プラットフォームでの提供状況: メッセージ ストリーミングは、ウェブ版の Chat でのみ表示されます。Android と iOS のサポートは近日公開予定です。
  • ストリーム タイムアウト: アクティブな StreamMessage は、作成から 10 分後にタイムアウトします。Chat 用アプリは、ストリームの開始から 10 分以内に spaces.streamMessages.commit または spaces.streamMessages.delete を呼び出す必要があります。