OpenHands × Ollama ローカルLLM実践記 — Mac Studio M3 Ultra で動かすまでの全記録

TL;DR: OpenHands(旧OpenDevin)をMac Studio M3 Ultra(96GB)+ Ollama + Qwen3-Coder 30B で動かそうとした。Docker-in-Docker のビルド問題、Playwright依存、ランタイムイメージ手動構築を経てUI起動まで到達したが、30Bモデルのtool calling精度不足で実用には至らなかった。


1. OpenHands とは

OpenHands(旧 OpenDevin)は、オープンソースのAIコーディングエージェントプラットフォーム。75以上のLLMプロバイダーに対応し、SWE-bench で Qwen3-Coder 使用時に 69.6% のスコアを記録している。

公式リポジトリ: https://github.com/All-Hands-AI/OpenHands

特徴:

  • Web UI でブラウザから操作
  • Docker サンドボックスで安全にコード実行
  • CodeActAgent による自律的なタスク遂行
  • Playwright 統合によるブラウザ操作

2. 動機 — なぜ OpenHands を試したか

前回の実験で Qwen Code(CLI エージェント)を Ollama + Qwen3-Coder 30B で動かしたが、複雑な multi-step タスク(GitHub PR レビューなど)で tool calling が破綻する問題に直面した。

OpenHands は SWE-bench で高スコアを出しており、エージェントスキャフォールディングの力で同じ 30B モデルでも改善されるのでは?という仮説を検証するために試した。

3. 環境

項目スペック
マシンMac Studio M3 Ultra
メモリ96GB 統合メモリ
OSmacOS (Darwin 25.3.0)
DockerDocker Desktop 28.5.2
Ollamaローカル稼働中
モデルqwen3-coder:30b (18GB)

4. セットアップ手順と遭遇した問題

4.1 Docker イメージの選定

1
2
3
# 最初に main タグを試した
docker pull ghcr.io/all-hands-ai/openhands:main
docker pull ghcr.io/all-hands-ai/runtime:main

問題1: runtime:main と openhands:main のバージョン不一致

openhands:main は /openhands/micromamba/bin/micromamba を期待するが、runtime:main は miniforge3 に移行済みだった。

exec: "/openhands/micromamba/bin/micromamba": stat: no such file or directory

解決: openhands:0.40(安定版タグ)に切り替え。runtime は自前でビルドする方針に変更。

4.2 Docker-in-Docker ビルド失敗

OpenHands は初回起動時にサンドボックス用のランタイムイメージを コンテナ内から Docker buildx で自動ビルドする設計。しかし macOS Docker Desktop 環境ではこのビルドがゾンビプロセスになり停止。

[docker] <defunct>

--privileged フラグを付けても解決しなかった。

解決: ホスト側で手動ビルドし、環境変数 SANDBOX_RUNTIME_CONTAINER_IMAGE で指定する方式に変更。

4.3 ランタイムイメージの手動ビルド

OpenHands 0.40 のソースからテンプレート(Dockerfile.j2)を抽出し、手動で Dockerfile を作成。

1
2
3
4
5
6
7
8
9
# ソースコード抽出
CID=$(docker create ghcr.io/all-hands-ai/openhands:0.40)
docker cp "$CID:/app/openhands" ./code_openhands
docker cp "$CID:/app/pyproject.toml" ./pyproject.toml
docker cp "$CID:/app/poetry.lock" ./poetry.lock
docker rm "$CID"

# ビルド
docker build -t ghcr.io/all-hands-ai/runtime:oh_v0.40.0_local .

問題2: Debian Trixie パッケージ名変更

nikolaik/python-nodejs ベースイメージが Debian Trixie に更新されており、パッケージ名が変わっていた。

E: Package 'libgl1-mesa-glx' has no installation candidate
E: Package 'ttf-unifont' has no installation candidate

解決: libgl1-mesa-glxlibgl1、Playwright の --with-deps を手動インストールに変更。

1
2
3
4
RUN apt-get install -y --no-install-recommends \
    libnss3 libnspr4 libatk1.0-0 libatspi2.0-0 \
    libxcomposite1 libxdamage1 libxrandr2 libxkbcommon0 \
    libgbm1 libpango-1.0-0 libcairo2 libasound2 fonts-unifont

4.4 ランタイムサーバーが起動しない

最初のビルドでは Playwright の依存ライブラリが不足しており、ブラウザ環境の初期化でクラッシュ → メインの Action Execution Server が起動しなかった。

Host system is missing dependencies to run browsers.

Playwright 依存ライブラリを追加して再ビルドし解決。

4.5 最終的な起動コマンド

1
2
3
4
5
6
7
8
docker run -d \
  --name openhands \
  --privileged \
  -p 3000:3000 \
  -e SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:oh_v0.40.0_local \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v ~/.openhands:/root/.openhands \
  ghcr.io/all-hands-ai/openhands:0.40

4.6 LLM 設定

UIのドロップダウンにローカルモデルが表示されないため、API 経由で設定を注入。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
curl -s -X POST http://localhost:3000/api/settings \
  -H "Content-Type: application/json" \
  -d '{
    "llm_model": "ollama/qwen3-coder:30b",
    "llm_api_key": "EMPTY",
    "llm_base_url": "http://host.docker.internal:11434",
    "agent": "CodeActAgent",
    "max_iterations": 50,
    "sandbox_runtime_container_image": "ghcr.io/all-hands-ai/runtime:oh_v0.40.0_local"
  }'

ポイント:

  • モデル名は ollama/qwen3-coder:30b(litellm のプレフィックス規則)
  • Base URL は host.docker.internal(Docker → ホストのOllama接続)
  • API Key は空文字不可のため EMPTY を設定

5. 結果 — UI起動成功、しかし…

Web UI(http://localhost:3000)の起動、ランタイムサンドボックスの起動まで到達。チャット入力も可能になった。

しかし、実際にタスクを実行すると:

Agent encountered an error.
Parameter 'is_input' is expected to be one of ['true', 'false'].

原因: OpenHands の execute_bash ツールは is_input パラメータに文字列の "true" / "false" を期待する。30B モデルが以下のいずれかの問題を起こしている:

  1. パラメータを省略する
  2. boolean 型(true/false)で返す(文字列の "true" ではなく)
  3. 不正な値を渡す

これは前回 Qwen Code で確認した「30B モデルは API レベルの tool calling はできるが、複雑なエージェントワークフローでのツールスキーマ遵守が不安定」という制限と同根の問題。

6. 教訓とまとめ

セットアップで学んだこと

問題原因解決策
runtime:main 互換性micromamba → miniforge3 移行バージョンタグを揃える
Docker-in-Docker ビルド失敗macOS Docker Desktop の制限ホスト側で手動ビルド
Debian パッケージ名変更Trixie 移行パッケージ名を修正
Playwright クラッシュ依存ライブラリ不足手動インストール
UI でモデル選択不可litellm リスト固定API 経由で設定注入

30B ローカルモデルの限界(再確認)

ツール結果失敗パターン
Qwen Code複雑タスクで失敗XML テキスト出力(tool_calls API 未使用)
OpenHandsエージェントエラーis_input パラメータのスキーマ違反

結論: OpenHands のエージェントスキャフォールディングは優秀だが、モデルの tool calling 精度が十分でなければスキャフォールディングでは救えない。30B ローカルモデルでは:

  • 簡単な単発タスク → Qwen Code で実用可能
  • 複雑なマルチステップタスク → Claude Code(クラウド API)が必要
  • OpenHands → クラウド API(Claude / GPT-4 / Qwen OAuth)と組み合わせれば真価を発揮

推奨構成

┌──────────────────────┐
│   タスクの複雑さ     │
├──────────────────────┤
│ 簡単(ファイル編集) │ → Qwen Code + Ollama ローカル
│ 中程度(実装)       │ → Claude Code
│ 複雑(PR レビュー)  │ → Claude Code / OpenHands + クラウドAPI
└──────────────────────┘

7. 参考リンク