
はじめに
SmolAgents(v1.12.0)でDockerコンテナをサンドボックスとして使用した時に出力が段階的にならない問題に直面しました。昨日なんとか解決方法を見つけたのですが、その後Gradioを使えばもっと簡単に解決することがわかりました。touch-sp.hatenablog.com
SmolAgentsライブラリではGradioでAgentを実行するための「GradioUI」というのが用意されています。
Dockerfile
FROM python:3.12-bullseye
# ビルド依存関係のインストール
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
python3-dev \
# ポートのリスニング状態を確認するため(Gradioを使う時)
net-tools && \
pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir 'smolagents[openai,mcp,gradio]' && \
# Node.jsの公式リポジトリを追加
curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \
apt-get install -y nodejs && \
npm install -g npm@latest && \
# npmグローバルパッケージのインストール
npm i -g @modelcontextprotocol/server-filesystem && \
# クリーンアップ
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# 作業ディレクトリの設定
WORKDIR /app
# デフォルトコマンド
CMD ["python", "-c", "print('Container ready')"]docker build --force-rm=true -t agent-sandbox .
sandbox.py
import docker import time class DockerSandbox: def __init__(self, image_name="agent-sandbox"): self.client = docker.from_env() self.container = None self.image_name = image_name def create_container(self): try: # コンテナを作成(ポートマッピングを追加) self.container = self.client.containers.run( self.image_name, command="tail -f /dev/null", # コンテナを実行状態に保つ detach=True, tty=True, extra_hosts={"host.docker.internal": "host-gateway"}, network_mode="bridge", ports={'7860/tcp': 7860}, # Gradioのデフォルトポート volumes={ "/home/hoge/data": {"bind": "/app/data", "mode": "rw"} } ) print(f"コンテナを作成しました (ID: {self.container.id[:8]}...)") except Exception as e: raise Exception(f"コンテナ作成エラー: {e}") def gradio_run(self, code: str) -> None: if not self.container: self.create_container() # バックグラウンドで実行 self.container.exec_run( cmd=["python", "-c", code], detach=True ) # ポート待機確認 print("Gradioサーバーを起動中...", end="", flush=True) max_attempts = 10 for attempt in range(max_attempts): time.sleep(1) print(".", end="", flush=True) # netstatを使用してポートのリスニング状態を確認 netstat_result = self.container.exec_run( cmd=["bash", "-c", "netstat -tulpn 2>/dev/null | grep 7860 || echo ''"] ) if netstat_result.output.strip(): print(" 完了!") print("\n✅ Gradioアプリが起動しました") print("📊 http://localhost:7860 でアクセスできます") return None print("\n❌ サーバー起動に失敗しました") return None def cleanup(self): if self.container: try: self.container.stop() self.container.remove() print("Container stopped and removed successfully") except Exception as e: print(f"エラー: {e}") finally: self.container = None def get_logs(self): """コンテナ内のプロセス状態を取得""" if not self.container: return "コンテナが起動していません" # プロセス確認 ps_cmd = "ps aux | grep python | grep -v grep" ps_result = self.container.exec_run(cmd=["bash", "-c", ps_cmd]) ps_output = ps_result.output.decode('utf-8').strip() # ポート確認 port_cmd = "netstat -tulpn 2>/dev/null | grep 7860 || echo 'ポートが開いていません'" port_result = self.container.exec_run(cmd=["bash", "-c", port_cmd]) port_output = port_result.output.decode('utf-8').strip() return f"プロセス状態:\n{ps_output}\n\nポート状態:\n{port_output}"
Agent実行ファイル
from sandbox import DockerSandbox # DockerSandboxのインスタンスを作成 sandbox = DockerSandbox() agent_code = """ from smolagents import CodeAgent, OpenAIServerModel, ToolCollection, GradioUI from mcp import StdioServerParameters model=OpenAIServerModel( model_id="gemma-3-12b-it-4bit", api_base="http://host.docker.internal:8080", api_key="EMPTY" ) server_parameters = StdioServerParameters( command="npx", args=["-y", "@modelcontextprotocol/server-filesystem","/app/data"] ) with ToolCollection.from_mcp(server_parameters) as tool_collection: agent = CodeAgent( model=model, tools=[*tool_collection.tools] ) # エージェントの実行 GradioUI(agent).launch(server_name='0.0.0.0', server_port=7860, share=False) """ try: # 最小限の出力でGradioアプリを起動 sandbox.gradio_run(agent_code) # ユーザーが終了するまで待機 print("\nアプリ実行中... Ctrl+C で終了します") while True: try: cmd = input("\n> ") if cmd.lower() == "exit" or cmd.lower() == "quit": break elif cmd.lower() == "status": print("\n" + sandbox.get_logs()) elif cmd.lower() == "help": print("\nコマンド一覧:") print(" status - サーバー状態を確認") print(" exit - アプリを終了") print(" help - このヘルプを表示") elif cmd.strip() == "": pass else: print(f"不明なコマンド: {cmd}. 'help'と入力してコマンド一覧を表示") except KeyboardInterrupt: print("\n終了します...") break except Exception as e: print(f"エラーが発生しました: {e}") finally: # 終了処理 sandbox.cleanup()
言語モデル
言語モデルはllama.cppで実行している「gemma-3-12b-it-Q4_K_M.gguf」です。./llama-server -m ~/models/gemma-3-12b-it-Q4_K_M.gguf -c 8192 -ngl 30 --host 0.0.0.0
Dockerコンテナからアクセスする場合には「--host 0.0.0.0」が必須です。