1. はじめに:AIが直接PCを操作する「Computer Use」の衝撃

Anthropicが発表したClaude 3.5 Sonnetの「Computer Use API」は、従来の「テキストやコードを生成するAI」から「OSを直接操作してタスクを実行するAIエージェント」への進化を決定づけました。

人間のように画面を目で見て、マウスを動かし、キーボードから入力し、Webブラウザの操作やファイルの編集を自律して行う。このアプローチは、従来のRPA(Robotic Process Automation)を過去のものにする可能性を秘めています。

本記事では、この「Computer Use」をPythonから呼び出し、Dockerコンテナ上の隔離されたGUI環境(Linuxデスクトップ)をAIに自律操作させるエージェントシステムの実装手順を詳しく解説します。安全かつ実用的な自律エージェントの構築方法を、実際のコードとともに見ていきましょう。


2. Anthropic Computer Useのアーキテクチャ

Computer Useの仕組みは、一種の「観察と行動のループ(ReActパターン)」に基づいています。

  1. 画面のキャプチャ(観察): ターゲット環境(Docker内など)のスクリーンショットを取得する。
  2. モデルへの入力(思考): Claude 3.5 Sonnetに現在の画面画像とユーザーの指示を渡し、次に取るべき行動を思考させる。
  3. APIによるツール呼び出し(行動): Claudeは用意された「コンピュータ操作用ツール(マウス移動、クリック、キー入力、スクリーンショット撮影など)」をJSON形式で呼び出す。
  4. 環境での実行(反映): Pythonスクリプトがツールの指示を実行し、画面を更新。再びステップ1に戻る。

この自律的なループを可能にするために、Anthropicは公式のAPIを提供しています。詳細な仕様は、公式の Anthropic Computer Use API Reference で公開されています。

また、AIに安全な操作環境を与えるためには、ホストOSから隔離されたサンドボックス環境が不可欠です。これには、かつて解説した E2B SandboxでAIエージェントにセキュアなコード実行環境を統合するPython実装ガイド のようにセキュアな実行空間を利用するか、本ガイドで解説するようにDockerコンテナ内でX11/VNCサーバーを用いた仮想デスクトップを立ち上げるのが効果的です。


3. 開発環境の準備:DockerによるGUI環境の構築

まずは、Claudeが安全に動き回れる隔離されたLinux GUI環境をDockerで用意します。Anthropicが提供している Anthropic Quickstart Repository を参考に、必要最小限の制御コンテナを組み上げます。

Dockerfileの作成

GUI環境(Xvfb + x11vnc + Openbox)とPython実行環境を同梱した Dockerfile を作成します。

FROM python:3.11-slim

# 必要なパッケージのインストール
RUN apt-get update && apt-get install -y \
    xvfb \
    x11vnc \
    openbox \
    menu \
    python3-tk \
    python3-dev \
    scrot \
    curl \
    && rm -rf /var/lib/apt/lists/*

# Pythonパッケージのインストール
RUN pip install --no-cache-dir \
    anthropic \
    pillow \
    pyautogui \
    python-dotenv

WORKDIR /app
COPY agent.py /app/agent.py

# 仮想ディスプレイ環境変数の設定
ENV DISPLAY=:1

# 起動スクリプト
CMD Xvfb :1 -screen 0 1024x768x24 & \
    sleep 2 && \
    openbox-session & \
    python agent.py

4. Pythonによる「Computer Use API」の実装

それでは、Claude 3.5 Sonnetにコンピュータ操作用のツールを提示し、実行ループを回すPythonコード(agent.py)を実装します。ここでは、PyAutoGUIを使用して、Claudeから要求されたマウス・キーボード操作をシミュレートします。

import os
import time
import base64
from io import BytesIO
from anthropic import Anthropic
from PIL import Image
import pyautogui

# PyAutoGUIのフェイルセーフ設定(緊急時はマウスを画面四隅に移動して停止可能に)
pyautogui.FAILSAFE = True

client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))

# スクリーンショットを撮影してBase64文字列に変換する
def take_screenshot():
    screenshot = pyautogui.screenshot()
    # Claudeに送信しやすいようリサイズ(最大1024px推奨)
    screenshot.thumbnail((1024, 768))
    buffered = BytesIO()
    screenshot.save(buffered, format="PNG")
    return base64.b64encode(buffered.getvalue()).decode("utf-8")

# Claudeが実行できるコンピュータ操作ツールの定義
COMPUTER_TOOLS = [
    {
        "name": "computer_agent_tool",
        "description": "OS上のGUIを操作するためのツール。マウスの移動、クリック、キー入力、スクリーンショット取得が可能。",
        "input_schema": {
            "type": "object",
            "properties": {
                "action": {
                    "type": "string",
                    "enum": ["mouse_move", "left_click", "type_text", "press_key", "wait", "screenshot"]
                },
                "coordinate": {
                    "type": "array",
                    "items": {"type": "integer"},
                    "description": "[x, y] の形式。mouse_moveやleft_click時に使用。"
                },
                "text": {
                    "type": "string",
                    "description": "type_textアクションで入力する文字列。"
                },
                "key": {
                    "type": "string",
                    "description": "press_keyアクションで送信するキー(例: 'enter', 'backspace')。"
                }
            },
            "required": ["action"]
        }
    }
]

def execute_tool(action, coordinate=None, text=None, key=None):
    print(f"[Action Executing] {action} (Coord: {coordinate}, Text: {text}, Key: {key})")
    if action == "mouse_move" and coordinate:
        pyautogui.moveTo(coordinate[0], coordinate[1], duration=0.5)
    elif action == "left_click":
        if coordinate:
            pyautogui.click(coordinate[0], coordinate[1])
        else:
            pyautogui.click()
    elif action == "type_text" and text:
        pyautogui.write(text, interval=0.1)
    elif action == "press_key" and key:
        pyautogui.press(key)
    elif action == "wait":
        time.sleep(2)
    elif action == "screenshot":
        time.sleep(0.5)  # 画面描画の安定を待つ
    
    return "Action executed successfully."

def run_agent(prompt):
    messages = [
        {
            "role": "user",
            "content": f"指示に従って、画面を操作してください。指示:{prompt}"
        }
    ]
    
    # 最大ループ回数を制限(無限ループ防止)
    for step in range(15):
        print(f"\n--- Step {step + 1} ---")
        
        # 現在の画面キャプチャをシステム入力として追加
        screenshot_b64 = take_screenshot()
        messages.append({
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "現在の画面は以下の通りです。次のアクションを決定してください。"
                },
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": screenshot_b64
                    }
                }
            ]
        })
        
        # Claude 3.5 Sonnetの呼び出し(Computer Useベータ機能を有効化)
        response = client.beta.messages.create(
            model="claude-3-5-sonnet-20241022",
            max_tokens=1024, 
            tools=COMPUTER_TOOLS,
            messages=messages,
            betas=["computer-use-2024-10-22"]
        )
        
        text_response = ""
        tool_calls = []
        for block in response.content:
            if block.type == "text":
                text_response += block.text
            elif block.type == "tool_use":
                tool_calls.append(block)
        
        if text_response:
            print(f"[Claude]: {text_response}")
            messages.append({"role": "assistant", "content": text_response})
            
        if not tool_calls:
            print("タスクが完了したか、これ以上のアクションはありません。")
            break
            
        # ツールの実行
        for tool in tool_calls:
            args = tool.input
            result = execute_tool(
                action=args.get("action"),
                coordinate=args.get("coordinate"),
                text=args.get("text"),
                key=args.get("key")
            )
            # 実行結果を会話履歴に格納
            messages.append({
                "role": "assistant",
                "content": [
                    {
                        "type": "tool_result",
                        "tool_use_id": tool.id,
                        "content": result
                    }
                ]
            })

if __name__ == "__main__":
    user_instruction = "テキストエディタを開き、'Hello Claude World' と入力してデスクトップに保存してください。"
    run_agent(user_instruction)

5. 遭遇しやすいエラーとトラブルシューティング

自律OS操作エージェントの開発中、多くのエンジニアが直面する代表的な問題とその対策です。

① 画面解像度の違いによる「座標のズレ」問題

Claudeが判断して返してくるクリック座標と、実際のディスプレイ座標系が一致せず、意図しない場所をクリックしてしまうエラーです。

  • 原因: 仮想ディスプレイ(Xvfb等)に設定された解像度(例: 1024x768)と、モデル送信前に画像を過剰にリサイズ・加工してしまっていることによる比率崩れ。
  • 対策: 送信する画像のアスペクト比を、仮想ディスプレイの解像度と完全に一致させてください。また、PyAutoGUI側のスケール倍率(高DPIディスプレイ環境など)も考慮し、ピクセル単位での絶対座標ズレを防ぐため、実行前に pyautogui.size() で実機認識されている解像度を出力して確かめてください。

② ボタンの連続連打・「チャタリング」と無限ループ

ボタンを一度クリックしたにもかかわらず、画面遷移のアニメーションやレンダリングが遅れ、Claudeが「クリックが反映されなかった」と判断して再クリックを繰り返してしまう現象です。

  • 原因: 画面遷移中の「中途半端な画面」のスクリーンショットがClaudeに送信され、判断に矛盾が生じる。
  • 対策: 各アクションの実行後に強制的なウェイト(例: time.sleep(1.0))を設けるか、指示のステップ間に「wait」アクションを明示的に挟んでClaudeにロード完了を待たせるよう、システムプロンプト等で明示的に制御します。

なお、こうした「自律的な画像解析と座標特定」の精度をさらにチューニングしたい場合は、Qwen2.5-VLをPythonで動かす!オープンソース最高峰の視覚言語モデルによる画像・動画解析実践ガイド で詳しく紹介しているような、ローカルで動く視覚言語モデル(VLM)を用いたバウンディングボックスの描画手法も大いに応用可能です。


6. まとめ

本記事では、Claude 3.5 Sonnetの「Computer Use API」をPythonで扱い、Dockerコンテナ上のGUI環境を自動操作するエージェントの実装方法を紹介しました。

RPAの完全な代替、アプリケーション自動UIテスト、Webを巡回した複雑なデータ入力など、この自律操作エージェントの応用先は広大です。AIが直接OSを叩く特性上、セキュリティ上の懸念が生じるため、必ずコンテナ等の隔離環境で検証を行ってください。これまでにない「自ら考え、動くエージェント」の開発に、ぜひトライしてみてください。