🤖

LangGraph computer use agent(cua)をわかりやすく解説

に公開

背景

computer use(OpenAIでいうとOperator)はagentが人間のようにコンピュータを操作できるように設計されており、デスクトップ環境(主に仮想環境)でのタスク自動化を可能にする技術です。
agentが人間のようにコンピュータを操作する新たな可能性を示し、​今後さらなる精度向上や安全性の強化が期待されています。ビジネスプロセスの自動化や新しいユーザーインターフェースの開発など、多岐にわたる応用が考えられ、様々の業務を自動化することができる重要な技術として注目されています。
LangGraph-cua-pyはOSSとしてcomputer useをLangGraphで構築できるリポジトリが公開されたので、本ブログでLangGraph cuaを解説をしていこうと思います。

computer useとは

  • 主な特徴

    • WEB画面の認識: 画面のスクリーンショット画像から、画面上の情報を解析することができます。ユーザーのクエリーに基づいて、agentが画面を認識し次に何をして、どのような情報を取得すべきかなどを判断することができます。
    • 汎用的なアクション: WEB画面を操作するときに、人間のようにマウスカーソルを動かし、対象のボタンをクリック、入力項目へタイピング、画面のスクロール、などなどが行えます。これらのアクションはツールとして個別に開発者が実装する必要があります。他のAPI連携など多彩なアクションが行えるので、agentに振る舞ってほしいアクションがあれば、柔軟にツールを実装しバインドできます。
  • 利用上の注意

    • ​agentに広範なアクセスを与えると、不適切な操作を防ぐため厳格に管理された環境下でagentを稼働させることが必要です。
    • Claudeがユーザーのコンピュータ環境にアクセスするため、適切なセキュリティ対策(agentの権限管理など)を講じることが重要です。基本は、仮想マシンを使用して仮想マシン上でcomputer useを行います。機密データへのアクセス制限、インターネットアクセスの制御、人間による監視などを行うためローカル環境で直接computer useを行わずに仮想マシン上で稼働させている例が多いです。

LangGraph computer use agenのグラフ

cuaのgraphは下記の内容になっています。
各ノードの処理内容をこれから説明していきます。

call_modelノード

call_modelノードはagentとの対話を処理するコンポーネントです。
ざっくりいうとLLMにシステムプロンプト+クエリーを提供し、LLMがそのインプットを基に継続でcomputer useを行うか、最終回答でgraphを終了するかを判定します。
初回はユーザークエリーをLLMに提供しますが、2回目以降はcomputer useの結果であるスクリーンショット画像がLLMのインプットとなります。

  1. ZDR (Zero Data Retention)の有効化判定。
  • ユーザーのクエリーを取得し、Zero Data Retention(ZDR)ポリシー判定を行います。ユーザーのデータプライバシーとセキュリティを強化するための取り組みです。​このポリシーの下では、特定の条件を満たすユーザーが、APIの入力および出力データが処理後に即座に削除され、OpenAIのシステム上に保存されないようリクエストすることが可能な仕組み。
  • 有効であれば、全メッセージ履歴を使用してリクエスト。OpenAI側がステートレス状態となるので履歴情報を全てOpenAI側に提供する必要がある。
  • 無効であれば、直近のメッセージと前回のレスポンスIDを使用してリクエスト。OpenAI側がステートフル状態となるので履歴情報をクライアント側で管理する必要がなくなる。
  1. OpenAI computer-use-previewの初期化
  • OpenAIのcomputer use専用モデル。汎用モデルとは異なり、computer use用にトレーニングされたモデル
  1. agentにツールをバインド
  • agentが利用可能なツールをバインドする。用途にあったツールのみをバインドさせagentのアクション範囲を制限する
  1. システムプロンプトの設定有無
  • 設定ありの場合、システムプロンプト+ユーザーのクエリーをインプットにLLMへリクエスト
  • 設定なしの場合、ユーザーのクエリーをインプットにLLMへリクエスト
  • ※初回はユーザークエリーのみであるが、2回目以降はcomputer useで取得した仮想環境のWEB画面のスクリーンショットがインプット(過去の履歴も含む)となる。
  1. レスポンスを取得(AIメッセージの取得)
  • レスポンスにtool_outputが含まれる場合: computer useを実行するためのフローに進む
  • レスポンスにtool_outputが含まれない場合: computer useを実行する必要がない、最終回答を生成することができたという意味なので、graphを終了
  1. computer useを起動ならば、create_vm_instanceノードに進む

create_vm_instanceノード

call_modelノードは仮想環境の準備を担当します。
このノードはユーザーがCUAに初めてアクセスしたとき、またはインスタンスIDが不足している場合に
呼び出され、AIエージェントが操作できる仮想環境のプロビジョニングを行います。

ここでのポイントはScrapybara API経由でVM環境にアクセスしているということです。

Scrapybaraは、AIエージェント向けの仮想デスクトップインフラストラクチャを提供するプラットフォームです。​これにより、開発者はリモートデスクトップインスタンスを迅速に起動し、AIエージェントがコンピュータ操作を行うための環境を構築できます。

Scrapybaraの主な特徴

  1. マネージド仮想環境
  • 分離された仮想マシン/コンテナインスタンスをオンデマンドでプロビジョニング
  • Linux (Ubuntu)、Windows、または専用ブラウザ環境のホスティング
  • インスタンスのライフサイクル管理(作成、停止、監視)
  1. ブラウザ自動化インターフェース
  • 高レベル API による Chrome/Chromium ブラウザの操作
  • LinuxまたはWindowsを介したエミュレーション
  • 画面キャプチャとストリーミング
  1. WebRTCストリーミング:
  • 仮想環境からブラウザへのリアルタイム画面ストリーミング

下記はScrapybaraを導入したことによる、技術的課題の解消

  • 簡易的にVM環境の提供。ブラウザインスタンスのプロビジョニング
  • ブラウザの低レベル操作(クリック、タイプ、スクロール、などなど)。Scrapybaraを利用することにより、Selenium等の低レベルブラウザ自動化ツールと比較して、これらの操作の実装が格段に簡易化されている
  • 画面キャプチャとフィードバックループのためのスクリーンショット画像の取得
  • 操作の視覚的モニタリングのためのストリーミング
  1. instance_idの存在確認
  • 初回はinstanceをまだ生成していないため、APIキーを使ってVM作成フローに進む
  • 2回目以降は、既に生成したinstanceを利用するため、create_vm_instanceノードでは何もせずに後続のノードに流れる
  1. APIの取得有無
  • 正常にAPIを取得できれば、Scrapybaraクライアントを初期化
  • 正常にAPIを取得できなければ、異常終了
  1. 仮想環境タイプの決定
  • ubuntu、win、webブラウザのどれかをパラメータとして設定する。リポジトリの設定ではwebブラウザなので仮想ブラウザが起動する
  1. インスタンスを生成しストリームURLを生成
  2. VM環境を生成したので、create_vm_instanceノードを終了し、take_computer_actionノードへ遷移

take_computer_actionノード

take_computer_actionノードは、起動しているVM上でのブラウザ操作を実行します。
LangGraph CUAの中核となる「行動」部分を担当し、AIの意思決定を物理的な操作(クリック、タイプ、スクロールなど)に変換します。

主な役割

  • AIモデルが生成したツール指示の解析と検証
  • Scrapybaraを通じたVM上での実際の操作実行
  • 各種操作タイプ(click、type、scroll、keypress等)の処理
  • 操作結果のスクリーンショット取得と加工
  • AIへのフィードバック用ツールメッセージの生成
  1. 最新メッセージの取得。新しいメッセージを取得し、直近のアクション内容を確認
  2. tool_output抽出。本リポジトリではcomputer useのみのツールなので、computer useのツールが呼び出されているかを確認
  3. VMのインスタンスIDを取得。前ノードで起動したインスタンスIDを取得
  • ※ここまでの工程で前提となるデータが無ければ異常終了となる
  1. VMへ認証
  2. VMへ認証ができれば、VM上でアクションを実施
  • 初回はユーザークエリーに基づいて、アクションを決定する
  • 2回目以降は、VMの画面をスクリーンショットし画像を推論して次のアクションを決定する
  1. アクションを実行したら、アクション結果としてスクリーンショット画像を取得
  2. 画像を後続のインプットとするためにメッセージを作成
  3. 結果のオブジェクトを作成
  4. call_modelノードへアクション結果(スクリーンショット画像)のメッセージを連携

2回目call_modelノード

take_computer_actionノードの結果を履歴と共にLLMへリクエスト。
LLMが最終回答を生成することができなければ、再度take_computer_actionノードへ遷移し処理を行う。
※初回でVM起動をしているので、2回目はcreate_vm_instanceノードで処理は行わない

サンプル実行

price-finder.pyのシステムプロンプトとクエリーを修正して、動かしてみました。

async def main():
    """Run the Price Finder workflow with a sample tire price query."""
    # Define the initial conversation messages
    messages = [
        {
            "role": "system",
            "content": (
                "あなたは高度な AI コンピュータ使用アシスタントです。使用しているブラウザはすでに初期化されており、google.com にアクセスしています。常にコンピュータ ツールを使用して情報を検索し、ブラウザの機能をデモンストレーションしてください。"
            ),
        },
        {
            "role": "user",
            "content": (
                "2025年4月時点で、プロ野球選手の「佐々木朗希」はMLBでどこのチームに所属していますか教えて下さい。"
            ),
        },
    ]

    # Stream the graph execution with updates visible
    stream = graph.astream({"messages": messages}, subgraphs=True, stream_mode="updates")
    print("Stream started")

    # Process and display the stream updates
    async for update in stream:
        print(f"\n----\nUPDATE: {update}\n----\n")

    print("Done")

price-finder.pyの結果

スクリーンショット画像をエンコードしており、エンコードした文字列がログに出力(大量に文字列が表示される)されるので、下記のログは一部抜粋して記載しています。

「2025年4月時点で、プロ野球選手の「佐々木朗希」はMLBでどこのチームに所属していますか教えて下さい。」というクエリーに対して、最終的に「2025年4月時点で、佐々木朗希選手はロサンゼルス・ドジャースに所属しています。」という回答が得られました。

(pyenv_3_12_9) ryuseitakahashi@RyuseinoMacBook-Air examples % python price-finder.py
アクティブなインスタンス数: 0
クリーンアップ完了
Stream started

----
UPDATE: ((), {'process_input': {'route': 'computer_use_agent'}})
----


----
UPDATE: (('computer_use_agent:937b81c5-5d55-6c1e-b59b-b19ea2cf96ba',), {'call_model': {'messages': AIMessage(content=[], additional_kwargs={'tool_outputs': [{'id': 'cu_67f4b2e30b108192ba607f72294bd47108a99371ab99941c', 'action': {'type': 'screenshot'}, 'call_id': 'call_PETMNAOERFtDRy0wVM6mW5QW', 'pending_safety_checks': [], 'status': 'completed', 'type': 'computer_call'}]}, response_metadata={'id': 'resp_67f4b2e1a92881929d3091b62c47f3bb08a99371ab99941c', 'created_at': 1744089825.0, 'metadata': {}, 'model': 'computer-use-preview-2025-03-11', 'object': 'response', 'status': 'completed', 'model_name': 'computer-use-preview-2025-03-11'}, id='run-aa192ef3-0a27-421b-a272-23822c3ae282-0', usage_metadata={'input_tokens': 604, 'output_tokens': 7, 'total_tokens': 611, 'output_token_details': {}})}})
----

----
UPDATE: (('computer_use_agent:937b81c5-5d55-6c1e-b59b-b19ea2cf96ba',), {'create_vm_instance': {'instance_id': 's-ea724397', 'stream_url': 'https://api.proxy.scrapybara.com/v1/instance/s-ea724397/stream'}})
----
・・・・
・・・・
・・・・
 AIMessage(content=[{'type': 'text', 'text': '2025年4月時点で、佐々木朗希選手はロサンゼルス・ドジャースに所属しています。', 'annotations': []}], additional_kwargs={'reasoning': {'id': 'rs_67f4b36202d48192b892ec1dc3fe689108a99371ab99941c', 'summary': [], 'type': 'reasoning'}}, response_metadata={'id': 'resp_67f4b35925508192a80c04bafe06926b08a99371ab99941c', 'created_at': 1744089945.0, 'metadata': {}, 'model': 'computer-use-preview-2025-03-11', 'object': 'response', 'status': 'completed', 'model_name': 'computer-use-preview-2025-03-11'}, id='msg_67f4b368735081928b3ab82d1628d2e708a99371ab99941c', usage_metadata={'input_tokens': 13775, 'output_tokens': 188, 'total_tokens': 13963, 'output_token_details': {}})], 'instance_id': 's-ea724397', 'stream_url': 'https://api.proxy.scrapybara.com/v1/instance/s-ea724397/stream', 'authenticated_id': None}})
----

Done

引用

DXC Lab

Discussion