Google Chat の設定
目次
- 全体像
- ステップ 1: GCP のプロジェクトを作る、または選ぶ
- ステップ 2: 二つの API を有効にする
- ステップ 3: サービスアカウントを作る
- ステップ 4: Pub/Sub のトピックと購読を作る
- ステップ 5: トピックへの IAM の設定(ここが要です)
- ステップ 6: 購読への IAM の設定
- ステップ 7: Chat のアプリを設定する
- ステップ 8: 試す space にボットを入れる
- ステップ 9: Hermes を設定する
- 考え中の目印を変える
- 表示のしかたと、できること
- 聞き返しを対話できるカードで出す
- ステップ 10: Chat そのもののファイル添付(任意)
- なぜ別の手続きが要るのか
- 一度だけの設定(プロファイルごと)
- 利用者ごとの認可(チャットの中で行います)
- スコープ
- 複数の利用者がいるとき
- 困ったときは
- 安全のための覚え書き
Hermes Agent を Google Chat のボットとしてつなぎます。受信には Cloud Pub/Sub の プル型の購読を、送信には Chat の REST API を使います。使い勝手は Slack の Socket Mode や Telegram のロングポーリングと同じで、Hermes を動かしている側に外から届く URL も、 トンネルも、TLS の証明書も要りません。接続して認証し、購読を待ち受けるだけです。 Telegram のボットがトークンで待ち受けるのと同じ考え方です。
> hermes gateway setup を動かして Google Chat を選ぶと、手順に沿って設定できます。
全体像
| 構成要素 | 内容 |
|---|---|
| ライブラリ | google-cloud-pubsub、google-api-python-client、google-auth |
| 受信の経路 | Cloud Pub/Sub のプル型の購読(外から届く受け口は不要) |
| 送信の経路 | Chat の REST API(chat.googleapis.com) |
| 認証 | 購読に対して roles/pubsub.subscriber を持つサービスアカウントの JSON |
| 相手の見分け方 | Chat のリソース名(users/{id})とメールアドレス |
ステップ 1: GCP のプロジェクトを作る、または選ぶ
Pub/Sub のトピックを置くために、Google Cloud のプロジェクトが必要です。まだなければ console.cloud.google.com で作ります。 個人のアカウントにも無料枠があり、ボットの通信量なら十分まかなえます。
プロジェクト ID(例: my-chat-bot-123)を控えておきます。このあとの手順でずっと 使います。
ステップ 2: 二つの API を有効にする
コンソールで APIとサービス → ライブラリ を開き、次を有効にします。
- Google Chat API
- Cloud Pub/Sub API
個人のボットが出すくらいの量なら、どちらも無料の範囲に収まります。
ステップ 3: サービスアカウントを作る
IAM と管理 → サービス アカウント → サービス アカウントを作成 と進みます。
- 名前:
hermes-chat-bot - 「このサービス アカウントにプロジェクトへのアクセスを許可する」の手順は飛ばします。
必要なのは特定の購読に対する IAM の権限だけです。プロジェクト全体に Pub/Sub の役割を 与えては いけません。
作成できたらそのサービスアカウントを開き、キー → 鍵を追加 → 新しい鍵を作成 → JSON からファイルをダウンロードします。Hermes だけが読める場所に保存してください(例: ~/.hermes/google-chat-sa.json に置き、chmod 600 を設定)。
ステップ 4: Pub/Sub のトピックと購読を作る
Pub/Sub → トピック → トピックを作成 と進みます。
- トピック ID:
hermes-chat-events - ほかはすべて初期値のままにします。
作成すると、トピックの詳細ページに サブスクリプション のタブが出ます。ここで一つ作ります。
- サブスクリプション ID:
hermes-chat-events-sub - 配信タイプ: プル
- メッセージの保持期間: 7 日(Hermes を再起動しても、たまった分が残ります)
- ほかは初期値のままにします。
ステップ 5: トピックへの IAM の設定(ここが要です)
購読ではなく トピック のほうに、IAM のプリンシパルを追加します。
- プリンシパル:
chat-api-push@system.gserviceaccount.com - ロール:
Pub/Sub Publisher
これがないと、Google Chat はトピックにイベントを流せず、ボットには何も届きません。
ステップ 6: 購読への IAM の設定
購読 のほうに、自分のサービスアカウントをプリンシパルとして追加します。
- プリンシパル:
hermes-chat-bot@<your-project>.iam.gserviceaccount.com - ロール:
Pub/Sub Subscriber
同じ購読に Pub/Sub Viewer も与えてください。Hermes は起動時に subscription.get() を呼んで、そこへ届くかを確かめます。
ステップ 7: Chat のアプリを設定する
APIとサービス → Google Chat API → 構成 を開きます。
- アプリ名: 相手に見せたい名前を入れます(「Hermes」あたりが無難です)。
- アバターの URL: 公開されている PNG なら何でもかまいません(Google が用意したものもあります)。
- 説明: アプリの一覧に出る短い一文です。
- 機能: 1:1 のメッセージを受信する と スペースとグループの会話に参加する を有効にします。
- 接続設定: Cloud Pub/Sub を選び、トピック名
projects/<your-project>/topics/hermes-chat-events を入れます。
- 公開設定: 自分の Workspace(または特定の相手)に限定します。試している最中に
全員へ公開しないでください。
保存します。
ステップ 8: 試す space にボットを入れる
ブラウザで Google Chat を開きます。+ 新しいチャット のメニューでアプリの名前を 検索し、個別のやり取りを始めます。最初にメッセージを送ると、Google から ADDED_TO_SPACE というイベントが届きます。Hermes はこれを使ってボット自身の users/{id} を覚え、自分の発言を取り除くのに使います。
ステップ 9: Hermes を設定する
~/.hermes/.env に Google Chat の項目を書き足します。
# Required
GOOGLE_CHAT_PROJECT_ID=my-chat-bot-123
GOOGLE_CHAT_SUBSCRIPTION_NAME=projects/my-chat-bot-123/subscriptions/hermes-chat-events-sub
GOOGLE_CHAT_SERVICE_ACCOUNT_JSON=/home/you/.hermes/google-chat-sa.json
# Authorization — paste the emails of people allowed to talk to the bot
GOOGLE_CHAT_ALLOWED_USERS=you@yourdomain.com,coworker@yourdomain.com
# Optional
GOOGLE_CHAT_HOME_CHANNEL=spaces/AAAA... # default delivery destination for cron jobs
GOOGLE_CHAT_MAX_MESSAGES=1 # Pub/Sub FlowControl; 1 serializes commands per session
GOOGLE_CHAT_MAX_BYTES=16777216 # 16 MiB — cap on in-flight message bytesプロジェクト ID は GOOGLE_CLOUD_PROJECT からも読み取れますし、サービスアカウントの ファイルの場所は GOOGLE_APPLICATION_CREDENTIALS からも読み取れます。使い慣れたほうを 選んでください。
Google Chat のアダプターが必要とするものは、専用のインストーラーから入れます。 実行時の確認と同じく、安全のために決めたバージョンの下限がそのまま適用されます。
python -m plugins.platforms.google_chat.oauth --install-depsゲートウェイを動かします。
hermes gateway次のような記録が出るはずです。
[GoogleChat] Connected; project=my-chat-bot-123, subscription=<redacted>,
bot_user_id=users/XXXX, flow_control(msgs=1, bytes=16777216)試しているやり取りで「hola」と送ってみてください。ボットはまず 「Hermes is thinking…」という目印を出し、そのメッセージ自体を本当の返事に 書き換えます。「メッセージが削除されました」の跡は残りません。
考え中の目印を変える
目印の文章は、~/.hermes/config.yaml の typing_status_text で変えられます。 たとえば Ada という名前の子猫のアシスタントなら、こうなります。
platforms:
google_chat:
# Custom working-state marker text (default: "Hermes is thinking…").
typing_status_text: "is pouncing… 🐾"Slack のようにその人にだけ見える一行ではなく、これは 実際に投稿されるメッセージ で、 あとから返事に書き換えられます。ここに書いた文章は、ふつうのメッセージとして 少しのあいだチャットに現れます。目印そのものをやめたいときは typing_indicator: false にします。
表示のしかたと、できること
Google Chat が表示できる Markdown は限られています。
| 使えるもの | 使えないもの |
|---|---|
*bold*, _italic_, ~strike~, ` code ` |
見出し、箇条書き |
| URL による画像の埋め込み | 対話できる Card v2 のボタン(このゲートウェイの v1 では未対応) |
Chat そのもののファイル添付(/setup-files のあと。ステップ 10 を参照) |
音声メモや丸い動画メモ |
エージェントのシステムプロンプトには Google Chat 向けの案内が入っているので、 この制限を踏まえ、表示できない書き方を避けるようになっています。
一通あたりの長さの上限は 4000 文字です。長い返事は自動で複数のメッセージに分けて 送られます。
スレッドにも対応しています。スレッドの中で返信すると、Hermes は thread.name を 見て同じスレッドに返事を投稿します。スレッドごとに別の Hermes のセッションになります。
聞き返しを対話できるカードで出す
エージェントが選択肢つきで聞き返すとき、アダプターはそれを番号つきの文字の一覧では なく、Chat の Card v2 として表示します。選択肢ごとのボタンに加えて 「Other / type answer」 のボタンも並びます。ボタンを押せばそのまま答えになります (CARD_CLICKED のイベントが、待っているセッションへ選んだ内容を返します)。 カードを送れなかったときや、決まった選択肢がない質問のときは、これまでどおり文字での 聞き返しに戻ります。設定は要りません。
ステップ 10: Chat そのもののファイル添付(任意)
そのままでもボットは、文章、URL による画像の埋め込み、音声・動画・書類のダウンロード カードを投稿できます。人がファイルをドラッグして落としたときと同じ Chat そのものの 添付として届けたい場合は、利用者ごとに一度だけ OAuth の認可をしてもらいます。
なぜ別の手続きが要るのか
Google Chat の media.upload は、サービスアカウントでの認証をはっきり断ります。
> This method doesn't support app authentication with a service account. > Authenticate with a user account.
これを回避できる IAM の役割やスコープはありません。この受け口は利用者本人の資格情報 しか受け付けないのです。そのためボットは、ファイルをアップロードするときだけ *利用者として* ふるまう必要があります。具体的には、そのファイルを頼んだ本人としてです。
一度だけの設定(プロファイルごと)
- 同じ GCP のプロジェクトで APIとサービス → 認証情報 を開きます。
- 認証情報を作成 → OAuth クライアント ID → デスクトップ アプリ と進みます。
- JSON をダウンロードし、Hermes を動かしているホストへ移します。
- そのクライアントを Hermes に登録します(登録したいプロファイルで動かします)。
# Default profile:
python -m plugins.platforms.google_chat.oauth \
--client-secret /path/to/client_secret.json
# A named profile gets its own separate registration:
hermes -p <profile> python -m plugins.platforms.google_chat.oauth \
--client-secret /path/to/client_secret.jsonこれで、いま使っているプロファイルの Hermes のホームにクライアントの秘密の値が 書き込まれます(初期のプロファイルなら ~/.hermes/google_chat_user_client_secret.json です)。この値は プロファイルごとに分かれていて、共有されません。プロファイルごとに 登録します。これは意図した作りで、プロファイルは認証の境界として切り離されており、 二つのプロファイルが別々の Google の OAuth アプリやアカウントを向けます。Google Chat の ファイル添付を使うプロファイルごとに、一度ずつ登録してください。
利用者ごとの認可(チャットの中で行います)
それぞれの利用者が、ボットとの個別のやり取りの中で一度だけ手続きをします。
- ボットに
/setup-filesと送ります。いまの状態と次にすることが返ってきます。 /setup-files startと送ります。ボットが OAuth の URL を返します。- その URL を開いて 許可 を押すと、ブラウザが
http://localhost:1/?...&code=... を開こうとして失敗します。これは想定どおりで、 認可コードはアドレス欄に入っています。
- 失敗した URL(あるいは
code=...の値だけ)をコピーして、
/setup-files <PASTED_URL> の形でチャットに貼り付けます。ボットがそれを 引き換えてリフレッシュトークンを受け取ります。
トークンは ~/.hermes/google_chat_user_tokens/<sanitized_email>.json に保存されます。 以降、その人との個別のやり取りでファイルを頼まれたときは *その人の* トークンを使うので、 ボットはその人としてアップロードし、ファイルはその人の space に届きます。
あとで取り消すには /setup-files revoke と送ります。消えるのはその人のトークンだけで、 ほかの人のものはそのまま残ります。
スコープ
この手続きで求めるスコープは chat.messages.create の一つだけです。これで media.upload と、アップロードした attachmentDataRef を指す messages.create の 両方をまかなえます。Drive も、Chat の広いスコープも使いません。必要最小限にとどめる ための作りです。
複数の利用者がいるとき
頼んだ人のトークンがまだない場合、ボットは以前の作りで使っていた一人ぶんのトークン ~/.hermes/google_chat_user_token.json に戻ります(複数の利用者に対応する前から 入っていた場合です)。どちらもないときは、/setup-files を実行するよう伝える はっきりした文章を投稿します。
誰かが取り消しても、消えるのはその人のぶんだけです。ある人のトークンで 401 や 403 が 返っても、消えるのはその人ぶんの保持だけです。利用者どうしが邪魔をすることはありません。
困ったときは
「hola」と送ってもボットが黙ったままです。
- コンソールで、Pub/Sub の購読に未配信のメッセージがたまっていないか確かめます。
たまっているなら Hermes の認証が通っていません。GOOGLE_CHAT_SERVICE_ACCOUNT_JSON と、そのサービスアカウントが購読の Pub/Sub Subscriber に入っているかを見直します。
- 購読にメッセージが一件もないなら、Google Chat 側が流せていません。
トピック の IAM をもう一度確かめてください。 chat-api-push@system.gserviceaccount.com に Pub/Sub Publisher が要ります。
hermes gatewayの記録に[GoogleChat] Connectedが出ているかを見ます。
[GoogleChat] Config validation failed と出ていれば、直すべき環境変数が そのメッセージに書かれています。
返事は来るのに、エージェントの答えではなくエラーが出ます。
記録に [GoogleChat] Pub/Sub stream died がないか確かめます。これが繰り返し出るなら、 サービスアカウントの資格情報が入れ替わったか、購読が消えている可能性があります。 10 回試してだめだと、アダプターは自分を停止と見なします。
送るたびに「403 Forbidden」になります。
ボットが space から外されたか、Chat API のコンソールで取り消されています。もう一度 space に入れてください(次の ADDED_TO_SPACE のイベントで、自動的にまた送れるようになります)。
「Rate limit hit」の警告が多すぎます。
Chat API の初期の上限は、space ごとに一分あたり 60 通です。エージェントが長い返事を 流し続けてこれを超えると、アダプターは間隔を空けながらやり直しますが、その分だけ 相手を待たせることになります。返事を短くするか、GCP のコンソールで上限を上げることを 検討してください。
ファイルではなく「/setup-files」の案内ばかり返ってきます。
頼んだ人の OAuth のトークンがなく、以前の作りのトークンもありません。その人との 個別のやり取りで /setup-files を実行し、ステップ 10 に従ってもらってください。 引き換えが終われば、次に頼まれたときからゲートウェイを再起動せずに添付できます。
/setup-files start が「No client credentials stored.」と返します。
一度だけの設定が *このプロファイルでは* 済んでいません(クライアントの秘密の値は プロファイルごとなので、別のプロファイルで登録しても見えません)。端末から、 ゲートウェイが使っているプロファイルで動かします。
# Default profile:
python -m plugins.platforms.google_chat.oauth \
--client-secret /path/to/client_secret.json
# Named profile:
hermes -p <profile> python -m plugins.platforms.google_chat.oauth \
--client-secret /path/to/client_secret.jsonそのうえで、もう一度 /setup-files start と送ります。
/setup-files <PASTED_URL> が「Token exchange failed.」と返します。
認可コードは一度きりで、有効な時間も短めです(ふつうは数分)。/setup-files start で 新しい URL を出し直して、やり直してください。
安全のための覚え書き
- サービスアカウントのスコープ: アダプターは
chat.botとpubsubのスコープを
求めます。実際に効かせるのは IAM のほうにしてください。サービスアカウントには 最小限(購読に対する roles/pubsub.subscriber と roles/pubsub.viewer)だけを 与え、プロジェクト全体や組織全体の Pub/Sub の役割は与えないでください。
- 添付をダウンロードするときの守り: Hermes がサービスアカウントのトークンを付けて
取りにいくのは、Google が持つ短い許可の一覧に載ったホストだけです (googleapis.com、drive.google.com、lh[3-6].googleusercontent.com など)。 それ以外のホストは HTTP の要求を出す前にはねます。細工したイベントでトークンを GCE のメタデータの受け口へ向けさせる、といった攻撃を防ぐためです。
- 伏せ字: サービスアカウントのメールアドレス、購読のパス、トピックのパスは
agent/redact.py が記録から取り除きます。中身をそのまま出す確認用の表示 (GOOGLE_CHAT_DEBUG_RAW=1)も同じ伏せ字の仕組みを通り、DEBUG の水準で記録されます。
- 社内規程: 決まりのある Workspace(データの置き場所や AI の扱いに方針がある職場)に
このボットをつなぐつもりなら、最初に入れる前に承認を取ってください。
- 利用者ごとの OAuth のスコープ: 添付のための手続きが求めるのは
chat.messages.create *だけ* です。media.upload とそれに続く messages.create を まかなう最小限です。トークンはそのままの JSON として ~/.hermes/google_chat_user_tokens/<sanitized_email>.json に保存されます (守りはファイルの権限で、サービスアカウントの鍵ファイルと同じ考え方です)。 それぞれのトークンの持ち主はただ一人で、取り消しもその人だけに効きます。