ブラウザの操作を Gemini に任せて、手元の画面で見ていた夜のことを想像してみてください。検索ページが開き、入力欄に文字が入り、あと一手でボタンが押されるはずのところで、画面が止まります。エラーは出ていません。スクリプトは正常終了の表示を返しています。
2026 年 10 月 30 日、Gemini API の changelog に「Computer Use generally available (GA)」の項目が加わりました。Interactions API と GenerateContent API の両方で、Computer Use ツールが GA になったという内容です。プレビューの頃から試していた方も、これから試す方も、同じ「途中で終わる」場面に出会いやすくなる時期だと思います。
最初にお伝えしたいのは、「止まった」のではなく「ループがそこで終わるように書かれていた」ことがとても多い、という点です。原因の切り分けは、モデルの精度を疑う前に、ループの出口を数えるところから始められます。
公式のループは、どこで終わるように書かれているか
公式ドキュメントの Computer Use の手順は、おおよそ次の形です。
- プロンプトと最初のスクリーンショット、ツール設定を
client.interactions.createに送る - 返ってきた
function_callを読み、手元の環境(例では Playwright)でその操作を実行する - 新しいスクリーンショットを撮り、
function_resultとしてprevious_interaction_idを付けて送り返す - モデルが
function_callを返さなくなるまで繰り返す
ツールの有効化は、tools=[{"type": "computer_use", "environment": "browser"}] のように書きます。environment には browser・mobile・desktop が選べます。
ここで一つ、見落としやすい点があります。終わりの合図は「エラー」ではなく、「function_call が来なかった」という事実だけです。つまり正常終了と中断が、どちらも同じ形で現れます。さらに公式の例では、ループを turn_limit = 5 で打ち切っています。この数字を写したまま使うと、五手目で何事もなく終わります。
四つの出口を、ログに残して見分ける
ループが終わる理由は、私が公式の記述から数えたかぎり、次の四つに整理できます。
| 出口 | 何が起きているか | ログで見る手がかり |
|---|---|---|
| 1. モデルが終えた | function_call が無い応答が返った。モデル自身は完了、または続行不能と判断している | 最後の応答のテキストに、完了報告か、できなかった理由が書かれている |
| 2. 手数の上限 | 自分で書いた turn_limit に達した | ループ回数が上限と一致している |
| 3. 確認待ちで止めた | safety_decision の require_confirmation に対し、確認を取れず、またはコード側が中断した | 最後の function_call に safety_decision があり、decision が require_confirmation |
| 4. 返し方の不備 | function_result を返せなかった、previous_interaction_id がつながっていない、スクリーンショットが空または形式違い | 例外のログ、または次の create 呼び出しの直前で途切れている |
この四つは、ループの末尾で理由を一行書き出すだけで見分けられます。次のコードは、公式の構造を借りた骨組みです。capture_screenshot と execute_action は、ご自身の環境(Playwright など)に合わせて用意していただく部分で、API の呼び出しの細部は公式のサンプルに合わせてください。
# 骨組み: 出口の理由を必ず 1 行で残す
TURN_LIMIT = 20 # 公式の例は 5。試作の上限をそのまま本番に持ち込まない
exit_reason = None
prev_id = None
turn = 0
while True:
turn += 1
if turn > TURN_LIMIT:
exit_reason = f"turn_limit({TURN_LIMIT})"
break
try:
interaction = send_to_model(prev_id) # create を包んだ自作関数
except Exception as e:
exit_reason = f"api_error:{type(e).__name__}"
break
prev_id = interaction.id
calls = [s for s in interaction.steps if s.type == "function_call"]
if not calls:
exit_reason = "model_finished_no_function_call"
break
results = []
for call in calls:
decision = getattr(call, "safety_decision", None)
if decision and decision.decision == "require_confirmation":
if not ask_user(decision.explanation):
exit_reason = "safety_confirmation_declined"
break
results.append(execute_action(call)) # スクリーンショットと URL を含めて返す
if exit_reason:
break
print(f"[computer_use] exit={exit_reason} turns={turn}")このコードの要点は、exit_reason が空のまま抜ける道を作らないことです。どの break にも名前を付けておくと、翌朝ログを開いたときに「どれだった」かだけで、次の調べ物が決まります。
四番の「返し方」は、止まったように見えて実は続けられている
出口の中でもっとも見分けにくいのが四番です。画面は動かなくなり、エラーは出ません。公式の記述からは、返し方で押さえる点が三つ読み取れました。
function_resultには、現在の URL と PNG 画像を入れて返す(スクリーンショットは base64 で、mime_typeはimage/png)- 続きの呼び出しには
previous_interaction_idを付ける - 最初のリクエストにも、スクリーンショットを一枚付ける
なお座標については、公式の説明が少し揺れています。0〜999 の範囲で返る座標をピクセルへ変換する、という手順の例がある一方で、別の箇所には画面サイズに合わせたピクセル予測という書き方も見えます。私の確認できた範囲では、どちらが正しいのかをこの一ページだけでは決め切れませんでした。クリックの位置がいつも少しずれて、結果として何も起きないまま終わる場合は、変換の有無を疑う価値があります。
また、例のキー入力の処理には Meta+A が使われており、これは macOS 向けの書き方です。Windows や Linux のブラウザでは全選択のキーが違うため、入力欄に前の文字が残ったまま進み、モデル側が「入力できなかった」と判断して終える、という経路もあり得ます。これは公式が書いている結果ではなく、私の推測です。
三番の「確認待ち」は、設計として正しい止まり方です
safety_decision に require_confirmation が付いたときは、利用者に確認を取り、了承されたら結果に safety_acknowledgement を設定して続けます。公式は、安全設定の上書きは「好み」を伝えるだけのものであり、safety_decision の処理は必ず実装するよう書いています。
安全ポリシーの分類には、金銭取引・機密データの変更・連絡ツール・アカウント作成・データ変更・同意管理・法的な同意などが並んでいます。ストアのコンソールで何かを送信する画面に触れる操作は、この分類に当たりやすいはずです。私自身、アプリの運用画面をブラウザで触る作業を任せたいと考える立場なので、ここで止まるのは「不便」ではなく、むしろ残しておきたい挙動だと感じます。
止まった理由が三番だったときは、ループを直すのではなく、確認の画面を出す側を整えます。確認を出せない環境(夜間の無人実行など)で動かしているなら、その操作はそもそも任せない、という線引きが先です。
画面操作を始める前の、小さな準備
公式は、Computer Use には誤りやセキュリティ上の脆弱性が含まれる可能性があるとして、注意深い監督、重要な判断・機密データ・取り消せない操作への不使用、サンドボックス化した VM やコンテナでの実行を勧めています。プロンプトインジェクションの検出(enable_prompt_injection_detection)は既定で無効で、使うには Gemini 3.5 Flash-Lite 以降が必要とのことでした。公式の例のモデル名は gemini-3.8-flash です。
最初に外部へ送信する操作が無い、読み取り中心の作業から試して、出口の理由が一行でも読めるようになってから、範囲を広げていくのがよさそうです。同じ Computer Use のうち、実行中に古い画面を見てしまう問題は Gemini Computer Use が「古い画面」を操作して静かに事故るとき に、失敗からの立て直しの設計は Gemini Computer Use の自己修復アーキテクチャ にまとめられています。今回の「そもそもなぜ終わったか」の前段として、あわせて読んでいただけます。
私が線を引くとしたら
ここまで書いて思うのは、自動化の失敗は「動かなかった」ことより、「理由が読めなかった」ことのほうが後を引く、ということです。試作のうちは五手で終わっても気になりません。ところが上限を忘れたまま運用に回すと、止まった理由を探すだけで夜が終わってしまいます。
終わった理由が一行で読めないループは、まだ任せません。 この線引きを、私は今回の確認で改めて自分に課すことにしました。いま思えば、動くかどうかばかりを見て、終わり方を見ていなかったのかもしれません。
まずは、お手元のループの末尾に exit_reason を一行足して、次に止まったとき出口の名前が分かる状態から始めていただければと思います。