ゆっくりしていってね!
本書リポジトリ: https://github.com/hiromichinomata/yukkuri-smolagents
第0章 プロローグ — エージェントってなに?
本章のゴール: エージェントと smolagents の位置づけを掴み、環境を用意して 最初の
CodeAgentを動かす。
0.1 最近よく聞く「エージェント」
霊夢: 最近、ニュースでも SNS でも「AI エージェント」って言葉、よく聞くのよね。ChatGPT に何かやらせる、みたいな話?
魔理沙: だいたい合ってるぜ。ただ、本書で扱う「エージェント」はもう一段踏み込んだものだ。
霊夢: 一段?
魔理沙: LLM に ツール を持たせて、考える → 行動する → 結果を見る を繰り返しながら、タスクを最後まで運ぶ仕組みだ。いわば「頭脳(LLM)」に「手足(ツール)」をつけたロボットだな。
霊夢: 手足……検索とか計算とか?
魔理沙: その通り。Web 検索、Python 実行、DB 問い合わせ、Hub 上の画像生成 Space 呼び出し……なんでもツールにできる。エージェントはその中から選んで使うんだ。
0.2 3 つの部品 — LLM・ツール・ループ
霊夢: 構成要素、整理して教えて。
魔理沙: 最小構成は次の 3 つだ。
| 部品 | 役割 |
|---|---|
| LLM(モデル) | 次に何をするか考える |
| ツール | 外界とやり取りする(検索・実行・API) |
| エージェントループ | タスクが終わるまで「思考 → 実行 → 観察」を回す |
flowchart LR User[ユーザー: タスク] --> Agent[エージェント] Agent --> LLM[LLM] LLM -->|方針・コード・ツール呼び出し| Agent Agent --> Tools[ツール群] Tools -->|結果| Agent Agent -->|完了| Answer[最終回答]
霊夢: ユーザーが「パリの天気は?」って聞いたら?
魔理沙: 例えばこうなる。
- LLM が「検索ツールを使おう」と判断
- ツールが Web を検索
- 結果を LLM に渡す
- LLM が要約して
final_answerで返す
霊夢: 1 回のチャットじゃなくて、ループなのね。
魔理沙: 当たり。だから本書では agent.run("...") の 中で何ステップ起きたか をログで追う練習もするぜ。
0.3 普通の LLM 呼び出しとの違い
霊夢: chat.completions で聞くだけと、何が違うの?
魔理沙: コードで比べるのが早い。まず「ただ聞く」パターン。
# パターン A: 単発の LLM 呼び出し(エージェントではない) question = "1 から 10 までの合計は?" # 疑似コード — 実際は OpenAI / HF などの API を 1 回叩く # response = model.generate(question) # print(response.text)
霊夢: 答えを 言うだけ だわ。
魔理沙: 次がエージェント。中で Python を実行 して確認できる。
# パターン B: CodeAgent(本書の主役) from smolagents import CodeAgent, InferenceClientModel model = InferenceClientModel() agent = CodeAgent(tools=[], model=model) result = agent.run("1 から 10 までの合計を計算して") print(result)
霊夢: tools=[] なのに動くの?
魔理沙: CodeAgent は 自分で Python コードを書いて実行 できるから、ツールが空でも計算はできる。ツールは「外の世界」に出るための追加装備だと思えばいい。
0.4 smolagents って何?
霊夢: じゃあ smolagents は、そのエージェントを作るライブラリ?
魔理沙: その通り。Hugging Face が公開している 軽量な Python ライブラリ だ。名前の "smol" は "small" の意、コアがコンパクトなのが売りだぜ。
霊夢: 他にもエージェント用フレームワークあるんでしょ?
魔理沙: ある。smolagents の特徴をざっくり表にするとこうだ。
| 特徴 | 内容 |
|---|---|
| シンプル | エージェントのコアが ~1000 行級。抽象化が薄い |
| CodeAgent 第一級 | 行動を Python コード で書く(JSON ツール呼び出しだけじゃない) |
| ToolCallingAgent も可 | 従来型の JSON ツール呼び出しにも対応 |
| モデル非依存 | HF Inference、LiteLLM、Transformers、Ollama など |
| Hub 連携 | ツール・エージェントの共有 |
| 安全な実行 | ローカル制限付き実行、E2B / Docker 等のサンドボックス |
霊夢: CodeAgent がメインなのね。
魔理沙: 本書も CodeAgent 中心 で進める。複雑な処理はコードの方が書きやすいし、ベンチマークでも有利なことが多いからな。
公式の入口はこちら。
0.5 transformers.agents との関係
霊夢: transformers パッケージにもエージェントっぽいの、なかった?
魔理沙: あった。transformers.agents は smolagents に置き換わっていく 方向だ。新規プロジェクトは smolagents を使うのがおすすめだぜ。
霊夢: 移行する人向けの話は?
魔理沙: 概念は同じ(model + tools + run)。API 名が変わっているだけのことが多い。例えばモデルは今は InferenceClientModel が主流だ(旧 HfApiModel など)。
# 旧イメージ(参考・バージョンにより異なる) # from smolagents import HfApiModel # model = HfApiModel() # 現在の推奨(2025 年時点のドキュメント準拠) from smolagents import InferenceClientModel model = InferenceClientModel()
霊夢: 第 0 章では深追いしなくていいわね。
魔理沙: ああ。とにかく 「これからは smolagents」 と覚えておけば十分だ。
🖥️ ハンズオン 0-1 — 環境を用意する
霊夢: 話は分かったわ。動かしてみたい!
魔理沙: ここから手を動かすぜ。Python 3.10 以上 を推奨する。
リポジトリを手元に置く
本書のサンプルは yukkuri-smolagents リポジトリに載せていく想定だ。
git clone https://github.com/hiromichinomata/yukkuri-smolagents.git cd yukkuri-smolagents
霊夢: すでにクローン済みなら cd だけでいいのね。
仮想環境を作る
python3 -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate python -V pip install -U pip
smolagents を入れる
本書ではまず 標準ツール付き のインストールを使う。
pip install 'smolagents[toolkit]'
霊夢: [toolkit] って何?
魔理沙: extras ってやつだ。[toolkit] を付けると Web 検索などの追加依存が入る。第 2 章で他の extras もまとめて説明するぜ。
インストール確認
python -c "import smolagents; print(smolagents.__version__)"
バージョンが表示されれば OK。
python -c "from smolagents import CodeAgent, InferenceClientModel; print('import OK')"
Hugging Face トークン(推論 API 用)⚠️
クラウド上のモデルを使うハンズオンでは HF トークン が必要になる。
- Hugging Face → Settings → Access Tokens でトークンを作成
- 環境変数に設定
export HF_TOKEN="hf_xxxxxxxxxxxxxxxxxxxxxxxx"
霊夢: .env に書いてもいい?
魔理沙: いいぜ。ただし Git にコミットするな よ。
# .env(リポジトリには含めない) HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxx
# Python から読む例(python-dotenv を使う場合) # pip install python-dotenv from dotenv import load_dotenv load_dotenv()
.gitignore に .env があるか確認しておく。
.env .venv/ __pycache__/ *.pyc
🖥️ ハンズオン 0-2 — 10 行で最初の CodeAgent
魔理沙: いよいよ本番だ。次のファイルを作るぜ。
examples/ch00/first_agent.py:
"""第0章: 最初の CodeAgent""" from smolagents import CodeAgent, InferenceClientModel def main() -> None: model = InferenceClientModel() agent = CodeAgent(tools=[], model=model) task = "1 から 10 までの整数の合計を計算し、答えだけを返して" result = agent.run(task) print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
ディレクトリを作って保存する。
mkdir -p examples/ch00 # 上記を examples/ch00/first_agent.py に保存 python examples/ch00/first_agent.py
# examples/ch00/first_agent.py(リポジトリ同梱・全文) """第0章: 最初の CodeAgent""" from smolagents import CodeAgent, InferenceClientModel def main() -> None: model = InferenceClientModel() agent = CodeAgent(tools=[], model=model) task = "1 から 10 までの整数の合計を計算し、答えだけを返して" result = agent.run(task) print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
霊夢: 実行したら、ログがいっぱい出てきたわ……
魔理沙: 正常だ。ざっくり読み方はこうだ。
╭──────────────── New run ────────────────╮ │ 1 から 10 までの整数の合計を…… │ ╰─────────────────────────────────────────╯ ━━━━━━━━━━━━━━━━━━━━ Step 0 ━━━━━━━━━━━━━━━ ╭─ Executing this code: ─────────────────╮ │ total = sum(range(1, 11)) │ │ print(total) │ ╰────────────────────────────────────────╯ ... ╭─ Executing this code: ─────────────────╮ │ final_answer(55) │ ╰────────────────────────────────────────╯ Out - Final answer: 55
| ログの部分 | 意味 |
|---|---|
New run |
今回のタスク |
Step N |
N 回目の思考・実行サイクル |
Executing this code |
LLM が書いた Python |
final_answer(...) |
エージェントが完了を宣言 |
Input tokens / Output tokens |
コストの目安 |
霊夢: 答えは 55 ね。合ってるわ!
0.6 もっと短い一発スクリプト
魔理沙: ファイルを作らず REPL で試すなら、これだけでもいい。
from smolagents import CodeAgent, InferenceClientModel agent = CodeAgent(tools=[], model=InferenceClientModel()) print(agent.run("フィボナッチ数列の第 10 項を求めて"))
霊夢: 本当に数行ね……
魔理沙: これが smolagents の「smol」だぜ。あとは ツールを足す・モデルを替える・マルチエージェントにする と広がっていく。
0.7 よくあるエラーと対処 ⚠️
霊夢: うち、エラー出たんだけど……
魔理沙: 第 0 章で多いのはこのあたりだ。
認証エラー(401 / 403)
Unauthorized ... HF_TOKEN ...
対処:
echo $HF_TOKEN # 空なら設定し直す export HF_TOKEN="hf_..."
モデル・レート制限
Rate limit exceeded ...
対処: しばらく待つ、HF Pro の検討、または第 4 章で触る 別モデル(Ollama ローカルなど)に切り替える。
ModuleNotFoundError: smolagents
pip install 'smolagents[toolkit]' # 仮想環境を activate し忘れていないか確認 which python
コード実行エラー(Step 内の Python)
Code execution failed ...
対処: タスクを具体化する(「答えだけ」「整数で」など)。第 9 章以降で additional_authorized_imports も学ぶ。
0.8 本章のまとめ
霊夢: 整理するわ。
- エージェント = LLM + ツール + ループでタスクを完遂する仕組み
- smolagents = それを少ないコードで書ける HF 製ライブラリ
- CodeAgent = 行動を Python コードで書く(本書の主役)
- ハンズオン =
pip install 'smolagents[toolkit]'→CodeAgentを動かした
魔理沙: 次の第 1 章では、本書の読み方・examples/ の構成・ログの詳しい見方を説明するぜ。
✅ 章末チェックリスト
手を動かしたら、次を確認しよう。
- [ ] Python 3.10+ と仮想環境が使える
- [ ]
pip install 'smolagents[toolkit]'が成功した - [ ]
from smolagents import CodeAgent, InferenceClientModelができる - [ ]
HF_TOKENを設定した(クラウド推論を使う場合) - [ ]
examples/ch00/first_agent.pyまたは REPL でagent.run(...)が動いた - [ ] ログに
Step 0とfinal_answerが出た - [ ]
.envを Git にコミットしていない
次章へ
霊夢: 第 0 章、クリアよ!
魔理沙: 次は本格的に環境とモデルを整えるぜ。ゆっくりしていこうな。
第1章 本書の舞台裏 — ゆっくり解説のルールと道具
本章のゴール: 本書の読み方・
examples/の対応・HF_TOKENとログの見方を押さえ、第 0 章以降のハンズオンを迷わず進められる。
1.1 霊夢と魔理沙の役割
霊夢: 第 0 章でエージェントは動かしたわ。でもこの本、ずっと私と魔理沙が喋ってるのよね。読者の私たち、どっちの声を聞けばいいの?
魔理沙: 役割分担は固定だ。
| 役 | 担当 | 例 |
|---|---|---|
| 霊夢 | 素朴な疑問・要約・「やってみる」 | 「ログの Step って何?」 |
| 魔理沙 | 実装・コマンド・突っ込み | 「だから agent.logs を見ろだぜ」 |
霊夢: つまり私が 読者の代弁、魔理沙が 先生兼デモ係 ね。
魔理沙: その通り。会話のあとには 必ずコードか表 が来る。会話だけで終わる章はないと思え。
1.2 章の流れ — 導入 → 概念 → ハンズオン → 振り返り
霊夢: 毎章、同じリズムなの?
魔理沙: 目次 のとおり、だいたい次の順だ。
1. ゴール(引用ブロック 1 文) 2. 概念(会話 + 表 / mermaid + コード) 3. 🖥️ ハンズオン(手を動かす) 4. ⚠️ よくあるエラー 5. まとめ + ✅ チェックリスト + 次章へ
| 記号 | 意味 |
|---|---|
| 🖥️ | ハンズオン(実行する) |
| ⚠️ | API キー・コスト・セキュリティ |
| 🔗 | 公式ドキュメント |
| 📦 | リポジトリ同梱ファイル |
1.3 examples/ と章番号の対応
霊夢: サンプルコード、どこに置いてあるの?
魔理沙: 章ごとに examples/chNN/ だ。NN は 2 桁の章番号。
| 章 | ディレクトリ | 主なスクリプト |
|---|---|---|
| 0 | examples/ch00/ |
first_agent.py |
| 1 | examples/ch01/ |
check_env.py, inspect_run_logs.py |
| 2 | examples/ch02/ |
import_check.py, hf_connection.py |
| 3 | examples/ch03/ |
fibonacci_no_tools.py, inspect_agent_logs.py |
| … | … | … |
# リポジトリルートで ls examples/ ls examples/ch01/
霊夢: 本文に examples/ch03/foo.py と書いてあったら、そのファイルが本当にあるってことね。
魔理沙: 当たり。📦 と書いてあるパスは そのまま実行できる 前提だぜ。
1.4 HF_TOKEN と .env ⚠️
霊夢: 第 0 章で HF_TOKEN って言われたけど、もう一度整理して。
魔理沙: Hugging Face の アクセストークン だ。クラウド上の Inference Providers 経由でモデルを叩くときに使う。
- Settings → Access Tokens で作成(Read 権限で足りることが多い)
- シェルまたは
.envに設定
export HF_TOKEN="hf_xxxxxxxxxxxxxxxxxxxxxxxx"
# リポジトリルートにテンプレがある cp .env.example .env # .env を編集(Git にコミットするな)
# .env の中身の例 HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxx
# python-dotenv を使う場合(requirements.txt に含まれる) from dotenv import load_dotenv load_dotenv()
霊夢: 本書、実際のキーを書いてないわね。
魔理沙: 絶対にコミットするな。ドキュメントでは hf_... のプレースホルダだけ使う。HUGGINGFACEHUB_API_TOKEN という名前でも読まれることがある。
# 設定確認(値そのものは表示しない方が安全) test -n "$HF_TOKEN" && echo "HF_TOKEN is set" || echo "HF_TOKEN is empty"
.gitignore の例:
.env .venv/ __pycache__/
1.5 ログの読み方(おさらい)
霊夢: agent.run() すると画面いっぱいログが出るの。何を見ればいい?
魔理沙: 最低限、次の 4 つを追えばいい。
| 表示 | 意味 |
|---|---|
New run |
今回のタスク開始 |
Step N |
N 回目の思考・実行サイクル |
Executing this code |
CodeAgent が実行した Python |
final_answer(...) |
タスク完了の宣言 |
╭──────────────── New run ────────────────╮ │ 1 から 5 までの合計を…… │ ╰─────────────────────────────────────────╯ ━━━━━━━━━━━━━━━━━━━━ Step 0 ━━━━━━━━━━━━━━━ ╭─ Executing this code: ─────────────────╮ │ total = sum(range(1, 6)) │ │ final_answer(total) │ ╰────────────────────────────────────────╯ Out - Final answer: 15
霊夢: トークン数も出るわ。
魔理沙: Input tokens / Output tokens は コストの目安 だ。無料枠やレート制限に当たったら、第 4 章で別モデルに切り替える話をするぜ。
プログラムからログを見るには agent.logs と write_memory_to_messages() がある(第 3 章で深掘り)。
# run のあと len(agent.logs) messages = agent.write_memory_to_messages()
1.6 推奨モデル・無料枠・コスト ⚠️
霊夢: お金かかるの? 無料でやれる?
魔理沙: 状況次第 だ。Inference Providers はアカウント・モデル・混雑で変わる。
| 注意点 | 内容 |
|---|---|
| 無料枠 | あるが上限・レート制限あり |
| モデル ID | 重いモデルほど遅く・高くなりがち |
| ループ | agent.run 1 回で 複数 Step → 複数回 LLM 呼び出し |
| 本番 | トークン数 × 単価を監視(第 21 章) |
霊夢: じゃあ本書のデフォルトは?
魔理沙: CodeAgent + InferenceClientModel()(モデル ID 省略時はライブラリのデフォルト)。ローカルで無料に寄せたいなら第 4 章の Ollama か TransformersModel だ。
from smolagents import CodeAgent, InferenceClientModel # 本書のデフォルト構成 model = InferenceClientModel() agent = CodeAgent(tools=[], model=model)
🖥️ ハンズオン 1-1 — 環境とトークンを確認する
霊夢: 舞台裏の話は分かった。自分の PC が準備できてるか試したい!
魔理沙: 📦 examples/ch01/check_env.py を実行するぜ。
git clone https://github.com/hiromichinomata/yukkuri-smolagents.git cd yukkuri-smolagents source .venv/bin/activate python examples/ch01/check_env.py
# examples/ch01/check_env.py(リポジトリ同梱・全文) """第1章: HF_TOKEN と import の事前確認""" from __future__ import annotations import os import sys def mask_token(token: str) -> str: if len(token) <= 8: return "hf_***" return f"{token[:4]}...{token[-4:]}" def main() -> None: print("=== 第1章: 環境チェック ===") print(f"Python: {sys.version.split()[0]}") try: import smolagents print(f"smolagents: {smolagents.__version__}") except ImportError: print("smolagents が未インストールです。") print(" pip install 'smolagents[toolkit]'") sys.exit(1) from smolagents import CodeAgent, InferenceClientModel print("import OK: CodeAgent, InferenceClientModel") token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACEHUB_API_TOKEN") if token: print(f"HF_TOKEN: 設定済み ({mask_token(token)})") else: print("HF_TOKEN: 未設定(クラウド推論のハンズオンには必要)") print(" export HF_TOKEN='hf_...'") print(" または .env に HF_TOKEN=... を書いて python-dotenv を使う") # モデルは作るだけ(API は叩かない) model = InferenceClientModel() agent = CodeAgent(tools=[], model=model) del agent print("CodeAgent / InferenceClientModel の初期化: OK") if __name__ == "__main__": main()
期待される出力の例:
=== 第1章: 環境チェック === Python: 3.12.x smolagents: 1.x.x import OK: CodeAgent, InferenceClientModel HF_TOKEN: 設定済み (hf_...xxxx) CodeAgent / InferenceClientModel の初期化: OK
霊夢: HF_TOKEN: 未設定 だったら?
魔理沙: クラウド推論のハンズオンはスキップか失敗する。先に export するか .env を用意しろ。
🖥️ ハンズオン 1-2 — ログをプログラムから要約する
魔理沙: 第 0 章と同じく API を叩くが、今度は ログの中身 を数える。
python examples/ch01/inspect_run_logs.py
# examples/ch01/inspect_run_logs.py(リポジトリ同梱・全文) """第1章: agent.run 後のログを要約表示""" from __future__ import annotations import os import sys from smolagents import CodeAgent, InferenceClientModel def summarize_logs(logs: list) -> None: print(f"\n=== agent.logs: {len(logs)} エントリ ===") for i, entry in enumerate(logs): keys = ", ".join(sorted(entry.keys())) if isinstance(entry, dict) else type(entry).__name__ print(f" [{i}] keys: {keys}") def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") print(" export HF_TOKEN='hf_...' のあと再実行してください。") sys.exit(0) model = InferenceClientModel() agent = CodeAgent(tools=[], model=model, verbosity_level=1) task = "1 から 5 までの合計を計算し、答えだけを返して" print(f"タスク: {task}\n") result = agent.run(task) print("\n=== 最終回答 ===") print(result) summarize_logs(agent.logs) messages = agent.write_memory_to_messages() print(f"\n=== write_memory_to_messages(): {len(messages)} 件 ===") for j, msg in enumerate(messages[:6]): role = getattr(msg, "role", type(msg).__name__) content = getattr(msg, "content", str(msg)) preview = (content[:80] + "…") if len(str(content)) > 80 else content print(f" [{j}] {role}: {preview}") if len(messages) > 6: print(f" ... 他 {len(messages) - 6} 件") if __name__ == "__main__": main()
スクリプトの要点:
"""第1章: agent.run 後のログを要約表示""" from smolagents import CodeAgent, InferenceClientModel agent = CodeAgent(tools=[], model=InferenceClientModel(), verbosity_level=1) result = agent.run("1 から 5 までの合計を計算し、答えだけを返して") print(result) print(len(agent.logs)) agent.write_memory_to_messages()
霊夢: logs の各要素に keys: って出たわ。中身は章によって違うのね。
魔理沙: バージョンでキー名は多少変わる。件数と Step の流れ が分かれば十分だ。細部は第 3 章でまた触る。
1.7 よくあるエラーと対処 ⚠️
トークンを Git に載せてしまった
# 1. Hub でトークンを revoke # 2. 新トークンを発行 # 3. git history から消す必要があるなら filter-repo 等(上級)
HF_TOKEN はあるのに 401
Unauthorized ...
echo "${HF_TOKEN:+set}" # set なら変数は存在
# 先頭・末尾の空白、引用符の付け間違いを確認
別の Python を見ている
which python pip show smolagents | grep Location
1.8 本章のまとめ
霊夢: 整理するわ。
- 霊夢=疑問、魔理沙=実装 の掛け合いで読む
- サンプルは
examples/chNN/に章対応で置いてある HF_TOKENはクラウド推論用。.envはコミットしない- ログは
Step/ 実行コード /final_answer/ トークン数 を見る - コストは 1 run = 複数 Step になりうる
魔理沙: 次章はインストールと extras の大作戦だ。
✅ 章末チェックリスト
- [ ] 第0章 の
first_agent.pyを実行した - [ ]
examples/ch01/check_env.pyが import OK を表示した - [ ]
HF_TOKENを設定した(またはローカルモデル方針を決めた) - [ ]
.envを.gitignoreした - [ ] ログの
Step 0とfinal_answerの意味が説明できる - [ ]
examples/の章番号ルールが分かった
次章へ
霊夢: ルール、把握したわ!
魔理沙: 次は依存関係をきっちり入れるぜ。ゆっくりしていこうな。
第2章 インストール大作戦 — extras と仮想環境
本章のゴール:
smolagentsを extras 付きで正しく入れ、仮想環境で import 確認 とHF_TOKEN経由の Inference API 接続まで完了する。
2.1 extras って何?
霊夢: 第 0 章で pip install 'smolagents[toolkit]' って言われたけど、[toolkit] って何なの?
魔理沙: extras(追加依存のまとまり)だ。コアだけ入れるか、検索ツール付きか、LiteLLM 付きか……用途で選ぶ。
| extra | 主な用途 |
|---|---|
| (なし) | 最小の smolagents 本体 |
toolkit |
Web 検索など標準ツール(本書の基本) |
litellm |
OpenAI / Anthropic / Ollama 等 100+ プロバイダ |
transformers |
ローカル TransformersModel |
mlx-lm |
Apple Silicon の MLXModel |
openai |
AzureOpenAIModel など |
bedrock |
AmazonBedrockModel |
mcp |
MCP サーバー連携 |
all |
全部(重い・開発用) |
# 本書の推奨(第 0〜6 章) pip install 'smolagents[toolkit]' # 第 4 章で Ollama を試すとき pip install 'smolagents[toolkit,litellm]'
霊夢: [toolkit] と [litellm] の違いは?
魔理沙: toolkit = smolagents 公式の検索・音声ツール群。litellm = 外部 API をまとめて叩くアダプタだ。役割が違うから、両方必要ならカンマで並べる。
pip install "smolagents[toolkit,litellm]"
2.2 仮想環境 — venv と uv
霊夢: システムの Python に直接入れちゃダメなの?
魔理沙: 他プロジェクトと バージョン衝突 するから、venv が鉄則だ。
python3 -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate python -V pip install -U pip
uv を使う場合:
uv venv .venv source .venv/bin/activate uv pip install 'smolagents[toolkit]'
| 確認コマンド | 期待 |
|---|---|
which python |
プロジェクトの .venv 配下 |
pip list | grep smolagents |
パッケージが表示される |
2.3 依存関係トラブルシュート
霊夢: pip install で赤い文字が……
魔理沙: よくあるのはこの3つだ。
ERROR: Could not find a version that satisfies the requirement ...
→ Python が 3.10 未満 の可能性。
python3.12 -m venv .venv
ModuleNotFoundError: No module named 'smolagents'
→ venv 未 activate。
ImportError: cannot import name 'WebSearchTool'
→ [toolkit] なしで入れた。
pip install 'smolagents[toolkit]' --upgrade
2.4 HF_TOKEN と Inference API
霊夢: インストールできたら、つながるか試したい!
魔理沙: InferenceClientModel は内部で huggingface_hub.InferenceClient を使う。トークンは環境変数 HF_TOKEN が読まれる。
from smolagents import InferenceClientModel # token 引数を省略 → 環境変数 HF_TOKEN を使用 model = InferenceClientModel()
明示的に渡す例(本番は環境変数推奨):
import os from smolagents import InferenceClientModel model = InferenceClientModel(token=os.environ["HF_TOKEN"])
⚠️ コードに token="hf_..." と 直書きしない。
🖥️ ハンズオン 2-1 — import 確認
霊夢: 入ったかどうか、コマンド一発で見たいわ。
魔理沙: 📦 examples/ch02/import_check.py だ。
pip install 'smolagents[toolkit]' python examples/ch02/import_check.py
# examples/ch02/import_check.py(リポジトリ同梱・全文) """第2章: extras 導入後の import 確認""" from __future__ import annotations import sys def main() -> None: print("=== 第2章: import チェック ===") print(f"Python: {sys.version.split()[0]}\n") try: import smolagents print(f" OK smolagents {smolagents.__version__}") from smolagents import CodeAgent, InferenceClientModel print(" OK CodeAgent, InferenceClientModel") except ImportError as exc: print(f" NG smolagents: {exc}") print("\npip install 'smolagents[toolkit]' を実行してください") sys.exit(1) try: from smolagents import WebSearchTool print(" OK WebSearchTool ([toolkit])") except ImportError: print(" NG WebSearchTool — pip install 'smolagents[toolkit]'") try: import huggingface_hub print(f" OK huggingface_hub {huggingface_hub.__version__}") except ImportError: print(" NG huggingface_hub(toolkit インストールで入ることが多い)") if __name__ == "__main__": main()
期待出力:
=== 第2章: import チェック === Python: 3.12.x OK smolagents 1.x.x OK CodeAgent, InferenceClientModel OK WebSearchTool ([toolkit]) OK huggingface_hub x.x.x
ワンライナーでも可:
python -c "from smolagents import CodeAgent, InferenceClientModel, WebSearchTool; print('OK')"
🖥️ ハンズオン 2-2 — Inference API に接続
魔理沙: 短いタスクで 実際に 1 run する。
export HF_TOKEN="hf_..." # プレースホルダを実トークンに python examples/ch02/hf_connection.py
# examples/ch02/hf_connection.py(リポジトリ同梱・全文) """第2章: HF Inference API への接続テスト(短いタスク)""" from __future__ import annotations import os import sys from smolagents import CodeAgent, InferenceClientModel def main() -> None: token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACEHUB_API_TOKEN") if not token: print("HF_TOKEN が未設定です。") print(" export HF_TOKEN='hf_...'") sys.exit(1) print("=== 第2章: Inference API 接続テスト ===") model = InferenceClientModel() agent = CodeAgent(tools=[], model=model, verbosity_level=1) result = agent.run("2 + 2 は? 答えの数字だけ返して。") print("\n=== 最終回答 ===") print(result) if __name__ == "__main__": main()
霊夢: 4 って返ってきた! つながってるわね。
魔理沙: ここまでできれば第 3 章の本番ハンズオンに進める。
2.5 よくあるエラー集 ⚠️
401 Unauthorized
Unauthorized ... HF_TOKEN ...
export HF_TOKEN="hf_..." python examples/ch02/hf_connection.py
# examples/ch02/hf_connection.py(リポジトリ同梱・全文) """第2章: HF Inference API への接続テスト(短いタスク)""" from __future__ import annotations import os import sys from smolagents import CodeAgent, InferenceClientModel def main() -> None: token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACEHUB_API_TOKEN") if not token: print("HF_TOKEN が未設定です。") print(" export HF_TOKEN='hf_...'") sys.exit(1) print("=== 第2章: Inference API 接続テスト ===") model = InferenceClientModel() agent = CodeAgent(tools=[], model=model, verbosity_level=1) result = agent.run("2 + 2 は? 答えの数字だけ返して。") print("\n=== 最終回答 ===") print(result) if __name__ == "__main__": main()
Rate limit
Rate limit exceeded ...
対処: 待つ / 別モデル / HF アカウントの枠を確認(第 4 章)。
モデル ID が無効
Model ... not supported ...
# 別 ID を試す(例) InferenceClientModel(model_id="Qwen/Qwen2.5-Coder-32B-Instruct")
ネットワーク
curl -I https://huggingface.co
2.6 本章のまとめ
霊夢: まとめるわ。
[toolkit]で標準ツール付きインストールが本書の基本- venv でプロジェクトごとに隔離
InferenceClientModelはHF_TOKENで認証- ハンズオンで import と 短い run を確認した
✅ 章末チェックリスト
- [ ] Python 3.10+ の venv を使っている
- [ ]
pip install 'smolagents[toolkit]'成功 - [ ]
examples/ch02/import_check.pyが OK - [ ]
HF_TOKENを設定した - [ ]
examples/ch02/hf_connection.pyでagent.runが完走した
次章へ
霊夢: インストール編、クリアよ!
魔理沙: 次はエージェントの最小構成だぜ。
第3章 エージェントの最小構成 — model と tools
本章のゴール:
model+toolsの 2 要素でCodeAgentを組み立て、run()の流れとagent.logs/write_memory_to_messages()で実行の中身を読める。
3.1 動かす 2 要素
霊夢: エージェントって、結局何があれば動くの?
魔理沙: 最小は モデル と ツールリスト だ。
from smolagents import CodeAgent, InferenceClientModel model = InferenceClientModel() # 頭脳 tools = [] # 手足(空でも可) agent = CodeAgent(tools=tools, model=model)
| 引数 | 役割 |
|---|---|
model |
LLM を呼び出すアダプタ |
tools |
エージェントが使える Tool のリスト |
add_base_tools |
True で標準ツールを追加(第 6 章) |
霊夢: tools=[] なのに第 0 章は動いたわ。
魔理沙: CodeAgent は内蔵の Python 実行 があるから、計算だけならツール不要だ。
3.2 CodeAgent.run() の流れ
霊夢: run("...") の中で何が起きてるの?
魔理沙: ざっくり ループ だ。
flowchart TD
A[タスク受付] --> B[LLM: 次の行動を決める]
B --> C[Python コード生成・実行]
C --> D{final_answer?}
D -->|No| B
D -->|Yes| E[最終回答を返す]
- タスクをシステムプロンプトに載せる
- LLM がコード(やツール呼び出し)を出す
- 実行結果を LLM に返す
final_answer(...)が出るまで繰り返す
result = agent.run("フィボナッチ数列の第 10 項を求めて") # result には最終回答の値が入る
ログ上の目印:
━━━━━━━━━━━━━━━━━━━━ Step 0 ━━━━━━━━━━━━━━━ ╭─ Executing this code: ─────────────────╮ │ ... │ ╰────────────────────────────────────────╯ ... ╭─ Executing this code: ─────────────────╮ │ final_answer(55) │ ╰────────────────────────────────────────╯
3.3 ログの読み方(Step・コード・トークン)
霊夢: Step が増えるたびにお金っぽいのよね……
魔理沙: その認識でいい。1 Step ≒ 1 回の LLM 往復 に近いと思え。
| ログ要素 | 見るポイント |
|---|---|
Step N |
何回考え直したか |
Executing this code |
実際に走った Python |
Observation / Out |
実行結果 |
Input tokens |
プロンプト長の目安 |
Output tokens |
生成量の目安 |
verbosity を下げる例:
agent = CodeAgent(tools=[], model=model, verbosity_level=0)
3.4 agent.logs と write_memory_to_messages()
魔理沙: 画面ログの裏側には 構造化された履歴 がある。
agent.logs: 各 Step の詳細が dict などで リストに追加 されるwrite_memory_to_messages(): モデルに再投入する形式の チャットメッセージ列 に変換(全ログは含まない)
agent.run("3 の階乗を計算して") print(len(agent.logs)) for i, entry in enumerate(agent.logs): print(i, entry.keys() if isinstance(entry, dict) else type(entry)) messages = agent.write_memory_to_messages() print(len(messages))
霊夢: 全部は messages に入らないのね。
魔理沙: デバッグの 高レベル要約 用だと思え。細部は logs を見ろ。
🖥️ ハンズオン 3-1 — ツールなしでフィボナッチ
霊夢: 自分で数列、出してもらいたい!
魔理沙: 📦 examples/ch03/fibonacci_no_tools.py
export HF_TOKEN="hf_..." python examples/ch03/fibonacci_no_tools.py
# examples/ch03/fibonacci_no_tools.py(リポジトリ同梱・全文) """第3章: ツールなし CodeAgent でフィボナッチ""" from __future__ import annotations import os import sys from smolagents import CodeAgent, InferenceClientModel def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) model = InferenceClientModel() agent = CodeAgent(tools=[], model=model) task = ( "フィボナッチ数列の第 10 項を Python で計算し、" "整数の答えだけを final_answer で返して。" ) print(f"タスク: {task}\n") result = agent.run(task) print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
期待: 第 10 項は 55(0-indexed で F(10)=55 の定義に注意。モデルが 1-indexed で答える場合はタスク文で指定を厳密に)。
Out - Final answer: 55
霊夢: ログに def fib みたいなコードが出てたわ。
魔理沙: CodeAgent の典型だ。ツールなしでもアルゴリズムを書いて実行 する。
🖥️ ハンズオン 3-2 — logs と messages を覗く
python examples/ch03/inspect_agent_logs.py
# examples/ch03/inspect_agent_logs.py(リポジトリ同梱・全文) """第3章: agent.logs と write_memory_to_messages を覗く""" from __future__ import annotations import json import os import sys from smolagents import CodeAgent, InferenceClientModel def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) model = InferenceClientModel() agent = CodeAgent(tools=[], model=model, verbosity_level=1) agent.run("3 の階乗を計算し、答えだけ返して。") print(f"\n=== logs 件数: {len(agent.logs)} ===") for i, step in enumerate(agent.logs): if not isinstance(step, dict): print(f" [{i}] {step!r}") continue step_type = step.get("step_type") or step.get("type") or "?" print(f" [{i}] step_type={step_type!r}, keys={list(step.keys())}") print("\n=== 最後の log エントリ(整形) ===") if agent.logs: last = agent.logs[-1] if isinstance(last, dict): print(json.dumps(last, ensure_ascii=False, indent=2, default=str)[:2000]) else: print(last) messages = agent.write_memory_to_messages() print(f"\n=== messages: {len(messages)} 件 ===") for j, msg in enumerate(messages): role = getattr(msg, "role", "?") content = str(getattr(msg, "content", msg)) print(f"--- [{j}] {role} ({len(content)} chars) ---") if __name__ == "__main__": main()
霊夢: messages の方が短いわ。会話用に間引いてるのね。
魔理沙: 当たり。UI や再プロンプト用の 別ビュー だ。
3.5 よくあるエラー ⚠️
final_answer しないまま max steps
Max steps reached ...
agent = CodeAgent(tools=[], model=model, max_steps=10) # タスクを具体化: 「必ず final_answer で整数のみ返す」
Step 内の Python 失敗
Code execution failed ...
対処: 入力を小さくする・「標準ライブラリのみ」など制約を足す。
トークン不足っぽい途中切れ
対処: タスクを短くする / 第 4 章で max_tokens を調整。
3.6 本章のまとめ
霊夢: 整理!
- model + tools がエージェントの最小構成
run()は Step ループでfinal_answerまで進む- CodeAgent はツールが空でも Python 実行できる
agent.logsが詳細、write_memory_to_messages()が要約ビュー
✅ 章末チェックリスト
- [ ]
CodeAgent(tools=[], model=InferenceClientModel())を説明できる - [ ]
examples/ch03/fibonacci_no_tools.pyが完走した - [ ] ログで
Stepとfinal_answerを指せる - [ ]
examples/ch03/inspect_agent_logs.pyでlogs件数を確認した - [ ]
write_memory_to_messages()の役割を一言で言える
次章へ
霊夢: 最小構成、マスターしたわ!
魔理沙: 次は頭脳を差し替えるぜ。
第4章 モデルを選ぶ — 頭脳の付け替え
本章のゴール: 複数の
*Modelクラスの違いを把握し、Inference API または Ollama のどちらか一方に接続して同じタスクを実行できる。
4.1 ローカル? クラウド? 無料?
霊夢: モデルって選択肢が多すぎて困るのよね。
魔理沙: smolagents は 同じ CodeAgent に、違う model= を刺すだけだ。
| 方式 | クラス | 向き |
|---|---|---|
| HF Inference Providers | InferenceClientModel |
本書デフォルト・無料枠あり |
| 100+ API | LiteLLMModel |
OpenAI / Anthropic / Ollama 等 |
| ローカル transformers | TransformersModel |
GPU/CPU で完結 |
| Apple Silicon | MLXModel |
Mac 向け高速 |
| Azure | AzureOpenAIModel |
企業 Azure OpenAI |
| AWS | AmazonBedrockModel |
Bedrock ネイティブ |
flowchart LR Agent[CodeAgent] Agent --> ICM[InferenceClientModel] Agent --> LLM[LiteLLMModel] Agent --> TR[TransformersModel] Agent --> MLX[MLXModel]
4.2 InferenceClientModel(推奨)
魔理沙: huggingface_hub.InferenceClient 経由で Hub 上の多数プロバイダを叩く。
from smolagents import CodeAgent, InferenceClientModel model = InferenceClientModel() # または model_id を明示 model = InferenceClientModel(model_id="Qwen/Qwen2.5-Coder-32B-Instruct") agent = CodeAgent(tools=[], model=model)
認証:
export HF_TOKEN="hf_..."
import os from smolagents import InferenceClientModel model = InferenceClientModel(token=os.environ.get("HF_TOKEN"))
4.3 LiteLLMModel — OpenAI / Anthropic / Ollama
pip install 'smolagents[litellm]'
from smolagents import CodeAgent, LiteLLMModel model = LiteLLMModel( model_id="anthropic/claude-3-5-sonnet-latest", api_key="YOUR_ANTHROPIC_API_KEY", # 本番は環境変数推奨 ) agent = CodeAgent(tools=[], model=model)
Ollama(ローカル)の例:
from smolagents import CodeAgent, LiteLLMModel model = LiteLLMModel( model_id="ollama_chat/llama3.2", api_base="http://localhost:11434", api_key="ollama", num_ctx=8192, ) agent = CodeAgent(tools=[], model=model)
⚠️ Ollama は別途 ollama serve とモデルの ollama pull が必要。
4.4 TransformersModel と MLXModel
pip install 'smolagents[transformers]'
from smolagents import CodeAgent, TransformersModel model = TransformersModel(model_id="meta-llama/Llama-3.2-3B-Instruct") agent = CodeAgent(tools=[], model=model)
Apple Silicon:
pip install 'smolagents[mlx-lm]'
from smolagents import CodeAgent, MLXModel model = MLXModel(model_id="mlx-community/Llama-3.2-3B-Instruct-4bit") agent = CodeAgent(tools=[], model=model)
霊夢: GPU メモリと相談ね……
魔理沙: ああ。VRAM 計算機などでモデルサイズを選べ。
4.5 Azure OpenAI と Amazon Bedrock
pip install 'smolagents[openai]'
from smolagents import CodeAgent, AzureOpenAIModel model = AzureOpenAIModel(model_id="gpt-4o-mini") agent = CodeAgent(tools=[], model=model)
環境変数の例:
export AZURE_OPENAI_ENDPOINT="https://xxxx.openai.azure.com" export AZURE_OPENAI_API_KEY="..." export OPENAI_API_VERSION="2024-10-01-preview"
Bedrock:
pip install 'smolagents[bedrock]'
from smolagents import AmazonBedrockModel, CodeAgent model = AmazonBedrockModel(model_id="anthropic.claude-3-sonnet-20240229-v1:0") agent = CodeAgent(tools=[], model=model)
LiteLLM 経由の Bedrock:
from smolagents import LiteLLMModel model = LiteLLMModel(model_id="bedrock/anthropic.claude-3-sonnet-20240229-v1:0")
4.6 モデルパラメータ — temperature / max_tokens / REMOVE_PARAMETER
魔理沙: 各 Model は生成パラメータを受け取れる。サポート外のキーは REMOVE_PARAMETER で送らないようにできる。
from smolagents import InferenceClientModel, REMOVE_PARAMETER model = InferenceClientModel( temperature=0.2, max_tokens=512, # プロバイダが未対応の引数を落とす例 # some_vendor_specific_arg=REMOVE_PARAMETER, )
| パラメータ | 効果 |
|---|---|
temperature |
低いほど決定的 |
max_tokens |
出力上限 |
model_id |
チェックポイント / デプロイ名 |
4.7 モデル選びの指針表
| 状況 | おすすめ |
|---|---|
| 本書を最短で | InferenceClientModel + HF_TOKEN |
| オフライン / 課金回避 | TransformersModel / Ollama |
| 既存 OpenAI 契約 | LiteLLMModel / AzureOpenAIModel |
| AWS 統一 | AmazonBedrockModel |
| Mac のローカル | MLXModel |
| エージェント向きコード生成 | Coder 系 model_id |
🖥️ ハンズオン 4-1 — 同じタスクを 2 モデルで比較
霊夢: 違い、体感したい!
魔理沙: 📦 examples/ch04/compare_models.py
export HF_TOKEN="hf_..." python examples/ch04/compare_models.py # 2 つ目を比較する場合 export SMOLAGENTS_MODEL_ID_SECOND="Qwen/Qwen2.5-Coder-32B-Instruct" python examples/ch04/compare_models.py
# examples/ch04/compare_models.py(リポジトリ同梱・全文) """第4章: 同じタスクを2つのモデル設定で比較(HF + 任意の2つ目)""" from __future__ import annotations import os import sys import time from smolagents import CodeAgent, InferenceClientModel TASK = "1 から 20 までの整数の合計を計算し、答えだけ返して。" def run_once(label: str, model_id: str | None) -> None: print(f"\n{'=' * 50}") print(f"モデル: {label}") if model_id: model = InferenceClientModel(model_id=model_id) else: model = InferenceClientModel() agent = CodeAgent(tools=[], model=model, verbosity_level=0) started = time.perf_counter() result = agent.run(TASK) elapsed = time.perf_counter() - started print(f"回答: {result}") print(f"所要時間: {elapsed:.1f}s / logs: {len(agent.logs)} 件") def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) # デフォルトモデル run_once("InferenceClientModel(デフォルト)", None) # 2 つ目は環境変数で上書き可能 second = os.environ.get("SMOLAGENTS_MODEL_ID_SECOND") if second: run_once(f"InferenceClientModel({second})", second) else: print("\n2 つ目の比較をするには:") print(" export SMOLAGENTS_MODEL_ID_SECOND='Qwen/Qwen2.5-Coder-32B-Instruct'") print(" python examples/ch04/compare_models.py") if __name__ == "__main__": main()
================================================== モデル: InferenceClientModel(デフォルト) 回答: 210 所要時間: 12.3s / logs: 3 件
霊夢: 所要時間と logs 件数、メモったわ。
🖥️ ハンズオン 4-2 — Inference か Ollama か一方に接続
📦 examples/ch04/connect_backend.py
# A: Hugging Face(デフォルト) export HF_TOKEN="hf_..." python examples/ch04/connect_backend.py # B: Ollama export SMOLAGENTS_BACKEND=ollama export OLLAMA_MODEL_ID="ollama_chat/llama3.2" python examples/ch04/connect_backend.py
# examples/ch04/connect_backend.py(リポジトリ同梱・全文) """第4章: Inference API または Ollama のどちらか一方に接続""" from __future__ import annotations import os import sys from smolagents import CodeAgent, InferenceClientModel TASK = "7 の平方根を概算し、小数第2位まで返して。" def run_hf() -> None: print("=== バックエンド: Hugging Face InferenceClientModel ===") model = InferenceClientModel() agent = CodeAgent(tools=[], model=model, verbosity_level=1) print(agent.run(TASK)) def run_ollama() -> None: print("=== バックエンド: Ollama (LiteLLMModel) ===") try: from smolagents import LiteLLMModel except ImportError: print("pip install 'smolagents[litellm]' が必要です") sys.exit(1) model = LiteLLMModel( model_id=os.environ.get("OLLAMA_MODEL_ID", "ollama_chat/llama3.2"), api_base=os.environ.get("OLLAMA_API_BASE", "http://localhost:11434"), api_key=os.environ.get("OLLAMA_API_KEY", "ollama"), num_ctx=int(os.environ.get("OLLAMA_NUM_CTX", "8192")), ) agent = CodeAgent(tools=[], model=model, verbosity_level=1) print(agent.run(TASK)) def main() -> None: backend = os.environ.get("SMOLAGENTS_BACKEND", "hf").lower() if backend == "ollama": run_ollama() elif backend == "hf": if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定です。Ollama を使う場合:") print(" export SMOLAGENTS_BACKEND=ollama") sys.exit(1) run_hf() else: print("SMOLAGENTS_BACKEND は 'hf' または 'ollama' を指定してください") sys.exit(1) if __name__ == "__main__": main()
# Ollama 側の準備例 ollama pull llama3.2 ollama serve
4.8 よくあるエラー ⚠️
Ollama connection refused
Connection refused ... 11434
→ ollama serve が起動しているか確認。
Azure / Bedrock の認証
→ 環境変数とリージョン・デプロイ名を再確認。キーはコードに直書きしない。
モデルが tool use に弱い
→ Step が増える・final_answer しない。Coder 系 model_id に変更。
4.9 本章のまとめ
霊夢: 頭脳は差し替え可能、ってことね。
InferenceClientModelが本書のデフォルトLiteLLMModelで Ollama / OpenAI 等- ローカルは Transformers / MLX
- 企業向けは Azure / Bedrock
- パラメータで 温度・長さ を調整
✅ 章末チェックリスト
- [ ] 5 種類以上の Model クラス名を挙げられる
- [ ]
examples/ch04/compare_models.pyを実行した - [ ] HF または Ollama のどちらかで
connect_backend.pyが動いた - [ ]
temperature/max_tokensの意味を説明できる
次章へ
魔理沙: 次はエージェントの「手」だぜ。
第5章 ツールの基本 — エージェントの「手」
本章のゴール:
@toolとToolサブクラスで自作ツールを定義し、Hub ダウンロード数トップのモデル名を返すエージェントを動かし、agent.toolsで差し替えできる。
5.1 ツールとは何か
霊夢: CodeAgent は Python を書けるのに、わざわざツールが要るの?
魔理沙: 外の世界 に出るときはツールだ。Hub API、検索、社内 DB……は 名前・説明・入出力スキーマ が要る。
| 要素 | 役割 |
|---|---|
name |
LLM が呼び分ける識別子 |
description |
システムプロンプトに載る説明 |
inputs |
引数の型と説明 |
output_type |
戻り値の型 |
forward |
実際の処理 |
flowchart LR LLM[LLM] -->|ツール呼び出し| Tool[Tool.forward] Tool -->|結果| LLM
初期化時に、全ツールの説明が エージェントのシステムプロンプト に焼き込まれる。
5.2 @tool デコレータ
魔理沙: 関数を書いて、docstring で説明すれば最短だ。
from smolagents import tool @tool def model_download_tool(task: str) -> str: """ 指定タスクで Hugging Face Hub のダウンロード数が最も多いモデル ID を返す。 Args: task: タスク名(例: text-classification) """ from huggingface_hub import list_models model = next(iter(list_models(filter=task, sort="downloads", direction=-1))) return model.id
要件:
- 型ヒント(引数・戻り値)
- docstring に
Args:で各引数の説明 - 関数名がそのままツール名になる
from smolagents import CodeAgent, InferenceClientModel agent = CodeAgent(tools=[model_download_tool], model=InferenceClientModel())
5.3 Tool サブクラス
霊夢: もっと複雑な道具は?
魔理沙: Tool を継承する。Hub 公式例と同じ形だ。
from smolagents import Tool class ModelDownloadTool(Tool): name = "model_download_tool" description = ( "指定タスクで Hugging Face Hub のダウンロード数が最も多いモデル ID を返す。" ) inputs = { "task": { "type": "string", "description": "タスク名(例: summarization)", } } output_type = "string" def forward(self, task: str) -> str: from huggingface_hub import list_models model = next(iter(list_models(filter=task, sort="downloads", direction=-1))) return model.id
| 方式 | 向き |
|---|---|
@tool |
短い関数 1 本 |
Tool サブクラス |
状態を持つ・メソッド複数 |
5.4 システムプロンプトへの自動埋め込み
霊夢: エージェントはツールの存在、どう知るの?
魔理沙: 初期化時に ツール一覧がプロンプトに注入 される。だから description と Args は丁寧に書け、とさっき言ったんだ。
agent = CodeAgent(tools=[model_download_tool], model=InferenceClientModel()) # agent.tools は dict: name -> Tool インスタンス print(list(agent.tools.keys()))
手動でツールだけ試す(エージェントなし):
from huggingface_hub import list_models task = "text-classification" print(next(iter(list_models(filter=task, sort="downloads", direction=-1))).id)
🖥️ ハンズオン 5-1 — Hub ダウンロード数トップのモデル名
霊夢: エージェントに Hub を調べさせたい!
魔理沙: 📦 examples/ch05/hub_top_model_tool.py
export HF_TOKEN="hf_..." python examples/ch05/hub_top_model_tool.py
# examples/ch05/hub_top_model_tool.py(リポジトリ同梱・全文) """第5章: Hub ダウンロード数トップのモデル名を返す自作ツール""" from __future__ import annotations import os import sys from huggingface_hub import list_models from smolagents import CodeAgent, InferenceClientModel, tool @tool def model_download_tool(task: str) -> str: """ 指定タスクで Hugging Face Hub のダウンロード数が最も多いモデル ID を返す。 Args: task: タスク名(例: text-classification, text-to-video) """ model = next(iter(list_models(filter=task, sort="downloads", direction=-1))) return model.id def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) model = InferenceClientModel() agent = CodeAgent(tools=[model_download_tool], model=model) task = ( "text-classification タスクで Hub のダウンロード数が最も多い " "モデル名を model_download_tool で調べ、名前だけ返して。" ) print(agent.run(task)) if __name__ == "__main__": main()
霊夢: ログに model_download_tool の呼び出しが出てた!
魔理沙: CodeAgent は Python からツールを呼ぶコード を書く。JSON 専用の ToolCallingAgent とは違う見え方だな。
🖥️ ハンズオン 5-2 — agent.tools で差し替え
📦 examples/ch05/swap_tools.py
python examples/ch05/swap_tools.py
# examples/ch05/swap_tools.py(リポジトリ同梱・全文) """第5章: agent.tools 辞書で実行中にツールを差し替え""" from __future__ import annotations import os import sys from huggingface_hub import list_models from smolagents import CodeAgent, InferenceClientModel, Tool class ModelDownloadTool(Tool): name = "model_download_tool" description = ( "指定タスクで Hugging Face Hub のダウンロード数が最も多いモデル ID を返す。" ) inputs = { "task": { "type": "string", "description": "タスク名(例: summarization)", } } output_type = "string" def forward(self, task: str) -> str: model = next(iter(list_models(filter=task, sort="downloads", direction=-1))) return model.id def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) model = InferenceClientModel() agent = CodeAgent(tools=[], model=model, add_base_tools=False) print("=== 初期 tools ===") print(list(agent.tools.keys())) hub_tool = ModelDownloadTool() agent.tools[hub_tool.name] = hub_tool print("=== 差し替え後 tools ===") print(list(agent.tools.keys())) result = agent.run( "summarization タスクで最もダウンロードされている Hub モデル名を返して。" ) print("\n=== 最終回答 ===") print(result) if __name__ == "__main__": main()
霊夢: 空で作ってから辞書に足すのね。
魔理沙: agent.tools は普通の dict だ。実行中の差し替えも可能だが、走ってる run の途中 は基本いじらない方がいい。
5.5 ツールを増やしすぎない ⚠️
魔理沙: 弱いモデルほど、ツールが多いと 迷子 になる。
| 対策 | 内容 |
|---|---|
| ツール数 | タスクに必要な分だけ |
| 説明 | 1 ツール 1 責務 |
| 統合 | 似た API は 1 ツールにまとめる(第 14 章) |
5.6 よくあるエラー ⚠️
Hub API / ネットワーク
ConnectionError ... huggingface.co
→ ネットワークと HF_TOKEN(プライベートモデルでない限り Read は不要なことも多いが、レート制限あり)
docstring に Args がない
→ @tool の説明が貧弱になり、LLM が引数を間違える。
forward の import
Hub に push する場合は forward 内で import するルールあり(第 7 章)。
5.7 本章のまとめ
霊夢: 手の作り方、分かったわ。
- ツール = LLM 向け API +
forward実装 @toolが手軽、Tool継承 が本格- 説明はシステムプロンプトに載る
agent.tools[name] = toolで追加・差し替え- 多すぎるツールは弱モデルを壊す
✅ 章末チェックリスト
- [ ]
@toolで関数をツール化した - [ ]
Toolサブクラスの 4 属性を説明できる - [ ]
examples/ch05/hub_top_model_tool.pyが完走した - [ ]
examples/ch05/swap_tools.pyでagent.toolsを更新した - [ ] ツール説明が LLM 向けである理由を説明できる
次章へ
霊夢: 自作ツール、楽しいわね!
魔理沙: 次は公式が用意した道具箱だぜ。
第6章 標準ツールボックス — すぐ使える道具
本章のゴール:
add_base_toolsとWebSearchToolを理解し、Web 検索タスクをエージェントに実行させ、ツール単体の手動テストもできる。
6.1 add_base_tools=True の意味
霊夢: 毎回ツールを自作しなくても、最初から入ってるの?
魔理沙: [toolkit] extra を入れていれば、標準ツールボックス を足せる。
from smolagents import CodeAgent, InferenceClientModel agent = CodeAgent( tools=[], model=InferenceClientModel(), add_base_tools=True, ) print(list(agent.tools.keys()))
| 標準ツール(概要) | 用途 |
|---|---|
| DuckDuckGo 検索系 | Web 検索 |
| Python interpreter | ToolCallingAgent 向け(CodeAgent は自前実行) |
| Transcriber | 音声→テキスト(Whisper 系) |
霊夢: CodeAgent なのに Python インタプリタツール?
魔理沙: CodeAgent は もともとコード実行できる から、重複を避ける設計だ。検索だけ欲しいときは WebSearchTool を明示 する方が分かりやすい。
6.2 DuckDuckGoSearchTool と WebSearchTool
魔理沙: ドキュメントでは WebSearchTool が推奨されることが多い。
from smolagents import DuckDuckGoSearchTool, WebSearchTool # 低レベル(DuckDuckGo 直) ddg = DuckDuckGoSearchTool() # 本書のハンズオンで使うラッパ(推奨) search_tool = WebSearchTool()
add_base_tools 内部では DuckDuckGo 系が使われるが、ハンズオンでは WebSearchTool を直接渡す 形に統一する。
from smolagents import CodeAgent, InferenceClientModel, WebSearchTool agent = CodeAgent( tools=[WebSearchTool()], model=InferenceClientModel(), add_base_tools=False, )
6.3 PythonInterpreterTool(ToolCallingAgent 向け)
霊夢: CodeAgent じゃないエージェント用?
魔理沙: ああ。ToolCallingAgent は JSON でツールを呼ぶから、Python 実行用のツール が別途要る。CodeAgent には不要(第 10 章)。
from smolagents import ToolCallingAgent, InferenceClientModel agent = ToolCallingAgent( tools=[], model=InferenceClientModel(), add_base_tools=True, )
6.4 音声 — Transcriber(概要)
pip install 'smolagents[toolkit,audio]'
音声ファイル URL を additional_args で渡すパターンは第 19 章。本章では 存在を知っている 程度で十分だ。
(第19章で詳述)Transcriber + マルチモーダル model
🖥️ ハンズオン 6-1 — Web 検索でニュースを調べる
霊夢: 今日のニュース、エージェントに調べさせたい!
魔理沙: 📦 examples/ch06/web_search_agent.py
pip install 'smolagents[toolkit]' export HF_TOKEN="hf_..." python examples/ch06/web_search_agent.py
# examples/ch06/web_search_agent.py(リポジトリ同梱・全文) """第6章: Web 検索でニュースを調べる CodeAgent""" from __future__ import annotations import os import sys from smolagents import CodeAgent, InferenceClientModel, WebSearchTool def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) model = InferenceClientModel() agent = CodeAgent( tools=[WebSearchTool()], model=model, add_base_tools=False, ) task = ( "Web 検索ツールを使い、日本の主要ニュースの見出しを 3 つ挙げ、" "出典 URL も含めて要約して返して。" ) print(agent.run(task)) if __name__ == "__main__": main()
霊夢: Step が増えたわ。検索 → 読む → まとめ、って感じね。
魔理沙: 外部 API + LLM の 往復が増える から、トークンと時間に注意だ。
🖥️ ハンズオン 6-2 — ツールを手動で試す
霊夢: エージェント抜きで検索だけ試せない?
魔理沙: できる。デバッグの定石だ。
python examples/ch06/manual_search_tool.py
# examples/ch06/manual_search_tool.py(リポジトリ同梱・全文) """第6章: WebSearchTool をエージェントなしで手動実行""" from __future__ import annotations import sys from smolagents import WebSearchTool def main() -> None: search_tool = WebSearchTool() query = "smolagents Hugging Face" print(f"クエリ: {query}\n") try: result = search_tool(query) except Exception as exc: # noqa: BLE001 — ネットワーク・依存の差を表示 print(f"検索に失敗しました: {exc}") print("pip install 'smolagents[toolkit]' とネット接続を確認してください。") sys.exit(1) print("=== 検索結果(先頭 1500 文字) ===") text = str(result) print(text[:1500]) if len(text) > 1500: print(f"\n... (全 {len(text)} 文字)") if __name__ == "__main__": main()
期待: 検索結果のテキスト(長いので先頭だけ表示)。
=== 検索結果(先頭 1500 文字) === ...
霊夢: エージェントが変なとき、ここで切り分けられるのね。
魔理沙: ツールが壊れてるのか LLM が壊れてるのか を分ける。
6.5 ツールを増やしすぎない理由 ⚠️
| 問題 | 原因 |
|---|---|
| 間違ったツール選択 | 説明が似ているツールが多い |
| 遅い | 検索 × 多 Step |
| 高い | 長い検索結果がプロンプトに入る |
対策:
# 検索だけ渡す(余計な base を足さない) CodeAgent(tools=[WebSearchTool()], model=model, add_base_tools=False)
タスク文で 出典・件数・言語 を指定する(第 14・17 章)。
6.6 よくあるエラー ⚠️
ImportError: WebSearchTool
pip install 'smolagents[toolkit]'
検索が空 / タイムアウト
→ ネットワーク、DuckDuckGo のレート制限。クエリを短くする。
エージェントが検索せず hallucination
→ タスクに「必ず WebSearchTool を使え」と明記。
agent.run("必ず WebSearchTool で検索してから答えて: ...")
6.7 本章のまとめ
霊夢: 公式の道具箱、使えるようになったわ。
add_base_tools=Trueで標準セットを追加できる- 本書の CodeAgent 例は
WebSearchTool()を明示 PythonInterpreterToolは主に ToolCallingAgent 用- 手動
search_tool(query)で切り分けデバッグ - 検索ツールは コストと Step が増えやすい
✅ 章末チェックリスト
- [ ]
add_base_toolsの意味を説明できる - [ ]
WebSearchToolと CodeAgent を組み合わせた - [ ]
examples/ch06/web_search_agent.pyを実行した - [ ]
examples/ch06/manual_search_tool.pyで単体テストした - [ ] ツール過多のリスクを一言で言える
次章へ
霊夢: 第 1 部の土台、だいぶ固まったわね!
魔理沙: 次は Hub にツールを載せる話だ。ゆっくりしていこうな。
第7章 ツールを共有する — Hugging Face Hub
本章のゴール: 自作ツールを Hub に公開する流れと、
load_tool/Tool.from_hub/ToolCollection.from_hubで他人のツールを安全に取り込む手順を理解する。
7.1 なぜ Hub に載せるの?
霊夢: 第 5 章で作ったツール、自分の PC にしかないのよね。チームで使いたいときは?
魔理沙: そのときは Hugging Face Hub に載せる。ツールは Space リポジトリとして公開でき、Gradio UI 付きで試せる。load_tool("username/my-tool") 一行でエージェントに足せるんだ。
霊夢: エージェント本体も Hub に出せるの?
魔理沙: できる(第 20 章)。今章は ツール に絞るぜ。
| API | 用途 |
|---|---|
tool.push_to_hub(repo_id) |
自作ツールを Space にアップロード |
load_tool(repo_id) |
Hub からツールを復元 |
Tool.from_hub(repo_id) |
同上(クラスメソッド) |
ToolCollection.from_hub(slug) |
コレクション内のツールをまとめて取得 |
🔗 Tools tutorial — Share your tool
7.2 push できるツールのルール
霊夢: なんでも push できるの?
魔理沙: いいえ。Hub 上で tool.py として再現できる形が必要だ。
- import は
forwardの中 — モジュール先頭のimport osは push 時に壊れることがある __init__に余計な引数を足さない — インスタンス固有の状態は Hub 共有と相性が悪い- メソッドは自己完結 — グローバル変数に依存しない
# ❌ push 向きでない例 import requests # トップレベル import class BadTool(Tool): def __init__(self, api_key: str): self.api_key = api_key # Hub 共有で追跡しづらい
# ✅ push 向きの例(import は forward 内) class GoodTool(Tool): name = "my_tool" description = "..." inputs = {"q": {"type": "string", "description": "query"}} output_type = "string" def forward(self, q: str) -> str: import requests return requests.get(f"https://httpbin.org/get?q={q}").status_code
📦 本書の実装: examples/ch07/custom_downloads_tool.py
# examples/ch07/custom_downloads_tool.py(リポジトリ同梱・全文) """第7章: Hub に push する自作ツール(Tool サブクラス)""" from smolagents import Tool class HubDownloadsTool(Tool): name = "model_download_counter" description = ( "Returns the Hugging Face Hub model id with the most downloads " "for a given task category (e.g. text-classification)." ) inputs = { "task": { "type": "string", "description": "Task category on the Hub, such as text-classification", } } output_type = "string" def forward(self, task: str) -> str: from huggingface_hub import list_models model = next(iter(list_models(filter=task, sort="downloads", direction=-1))) return model.id def main() -> None: tool = HubDownloadsTool() print(tool.forward("text-classification")) if __name__ == "__main__": main()
7.3 push_to_hub の流れ
霊夢: 手順、順番に教えて。
魔理沙: こうだ。
flowchart LR A[Tool クラスを書く] --> B[Hub で空の Space 作成] B --> C[push_to_hub] C --> D[Gradio で試す] D --> E[load_tool で再利用]
- Hugging Face で SDK: Gradio の Space を作成
- リポジトリ名を決める(例:
yourname/hub-downloads-tool) HF_TOKENを write 権限付きで設定 ⚠️- Python から
push_to_hub
"""push の最小イメージ(トークンは環境変数から)""" import os from smolagents import Tool # ... HubDownloadsTool を定義 ... tool = HubDownloadsTool() tool.push_to_hub("yourname/hub-downloads-tool", token=os.environ["HF_TOKEN"])
📦 任意ハンズオン用: examples/ch07/push_to_hub_optional.py
export HF_TOKEN="hf_..." # 実値を Git にコミットしない export HUB_TOOL_REPO="yourname/hub-downloads-tool" python examples/ch07/push_to_hub_optional.py
# examples/ch07/push_to_hub_optional.py(リポジトリ同梱・全文) """第7章: 自作ツールを Hub に push(任意・HF_TOKEN 必須)""" import os import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) from custom_downloads_tool import HubDownloadsTool def main() -> None: token = os.environ.get("HF_TOKEN") if not token: print("⚠️ HF_TOKEN が未設定のため push はスキップします。") print(" export HF_TOKEN='hf_...'") print(" Hub で YOUR_USER/hub-downloads-tool リポジトリを先に作成してください。") return repo_id = os.environ.get("HUB_TOOL_REPO", "YOUR_USER/hub-downloads-tool") if "YOUR_USER" in repo_id: print("⚠️ HUB_TOOL_REPO を自分の Hugging Face ユーザー名に置き換えてください。") print(" export HUB_TOOL_REPO='yourname/hub-downloads-tool'") return tool = HubDownloadsTool() tool.push_to_hub(repo_id, token=token) print(f"✅ pushed: https://huggingface.co/spaces/{repo_id}") if __name__ == "__main__": main()
霊夢: トークンないと?
魔理沙: スクリプトは スキップしてメッセージだけ出す ようにしてある。load のハンズオンは公開済みツールで試せるぜ。
7.4 trust_remote_code とは ⚠️
霊夢: load_tool の例で trust_remote_code=True って出てきたわ。怖いの?
魔理沙: Hub から取得した Python をそのまま実行する から、悪意あるリポジトリならマシン上で何でもされうる。だから:
- 信頼できる作者・組織のツールだけ 使う
- 中身は Space の
tool.pyを 必ず目視 する - 本番ではサンドボックス(第 12 章)を検討
from smolagents import load_tool # 明示的に信頼を宣言しないと load できない tool = load_tool("m-ric/hf-model-downloads", trust_remote_code=True)
| 選択 | 意味 |
|---|---|
trust_remote_code=False(既定) |
未検証コードは実行しない |
trust_remote_code=True |
「この repo のコードを実行していい」とユーザーが同意 |
霊夢: MCP でも同じ話?
魔理沙: 第 8 章で ToolCollection.from_mcp(..., trust_remote_code=True) も出てくる。思想は同じ 信頼境界 だ。
7.5 load_tool と Tool.from_hub
魔理沙: どちらも同じツールを返す。好みで選べ。
from smolagents import load_tool, Tool t1 = load_tool("m-ric/hf-model-downloads", trust_remote_code=True) t2 = Tool.from_hub("m-ric/hf-model-downloads", trust_remote_code=True)
エージェントに載せる例:
from smolagents import CodeAgent, InferenceClientModel, load_tool hub_tool = load_tool("m-ric/hf-model-downloads", trust_remote_code=True) model = InferenceClientModel() agent = CodeAgent(tools=[hub_tool], model=model) print(agent.run("depth-estimation で最も DL 数の多いモデル id は?"))
Space には Gradio UI も付く。ブラウザで入出力を試してからエージェントに渡すと安全だ。
7.6 ToolCollection.from_hub
霊夢: ツールがたくさんあるコレクションは?
魔理沙: Tool Collection として Hub にまとめられているものを、slug で一括ロードできる。
from smolagents import ToolCollection, CodeAgent, InferenceClientModel import os collection = ToolCollection.from_hub( collection_slug="huggingface-tools/diffusion-tools-6630bb19a942c2306a2cdb6f", token=os.environ["HF_TOKEN"], ) agent = CodeAgent( tools=[*collection.tools], model=InferenceClientModel(), add_base_tools=False, )
霊夢: 全部いっぺんにエージェントに?
魔理沙: できるが、遅延ロード され、エージェントが呼んだツールだけ初期化される。それでもツール過多は LLM を混乱させる(第 6 章)ので注意だ。
🖥️ ハンズオン 7-1 — 自作ツールを Hub に push(任意)
霊夢: 自分のツール、載せてみたい!
魔理沙: トークンと空 Space があればいける。無ければスキップで OK だ。
準備
pip install 'smolagents[toolkit]' huggingface_hub export HF_TOKEN="hf_..." # write 権限
ツール定義の確認
python examples/ch07/custom_downloads_tool.py
# examples/ch07/custom_downloads_tool.py(リポジトリ同梱・全文) """第7章: Hub に push する自作ツール(Tool サブクラス)""" from smolagents import Tool class HubDownloadsTool(Tool): name = "model_download_counter" description = ( "Returns the Hugging Face Hub model id with the most downloads " "for a given task category (e.g. text-classification)." ) inputs = { "task": { "type": "string", "description": "Task category on the Hub, such as text-classification", } } output_type = "string" def forward(self, task: str) -> str: from huggingface_hub import list_models model = next(iter(list_models(filter=task, sort="downloads", direction=-1))) return model.id def main() -> None: tool = HubDownloadsTool() print(tool.forward("text-classification")) if __name__ == "__main__": main()
(text-classification で DL 数トップの model id が表示される)
push(任意)
export HUB_TOOL_REPO="yourname/hub-downloads-tool" python examples/ch07/push_to_hub_optional.py
# examples/ch07/push_to_hub_optional.py(リポジトリ同梱・全文) """第7章: 自作ツールを Hub に push(任意・HF_TOKEN 必須)""" import os import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) from custom_downloads_tool import HubDownloadsTool def main() -> None: token = os.environ.get("HF_TOKEN") if not token: print("⚠️ HF_TOKEN が未設定のため push はスキップします。") print(" export HF_TOKEN='hf_...'") print(" Hub で YOUR_USER/hub-downloads-tool リポジトリを先に作成してください。") return repo_id = os.environ.get("HUB_TOOL_REPO", "YOUR_USER/hub-downloads-tool") if "YOUR_USER" in repo_id: print("⚠️ HUB_TOOL_REPO を自分の Hugging Face ユーザー名に置き換えてください。") print(" export HUB_TOOL_REPO='yourname/hub-downloads-tool'") return tool = HubDownloadsTool() tool.push_to_hub(repo_id, token=token) print(f"✅ pushed: https://huggingface.co/spaces/{repo_id}") if __name__ == "__main__": main()
霊夢: YOUR_USER のままだと止まったわ。
魔理沙: 意図的だ。Hub の Space は 自分の Hugging Face ユーザー名 で作る。誤 push を防ぐため HUB_TOOL_REPO を差し替えろ。本書の Git リポジトリだけが hiromichinomata/yukkuri-smolagents だぜ。
🖥️ ハンズオン 7-2 — 公開ツールを load_tool で使う
霊夢: push しなくても、他人のツールは試せるのね。
魔理沙: こちらが本命だ。📦 examples/ch07/load_hub_tool.py
python examples/ch07/load_hub_tool.py
# examples/ch07/load_hub_tool.py(リポジトリ同梱・全文) """第7章: Hub 上の他人ツールを load_tool で読み込む""" import os from smolagents import CodeAgent, InferenceClientModel, load_tool def main() -> None: # 公式チュートリアルで公開されているデモ Space repo_id = "m-ric/hf-model-downloads" print(f"Loading tool from {repo_id} (trust_remote_code=True) ...") hub_tool = load_tool(repo_id, trust_remote_code=True) if not os.environ.get("HF_TOKEN"): print("⚠️ エージェント実行には HF_TOKEN が必要です。load のみ確認して終了します。") print(f" tool name: {hub_tool.name}") return model = InferenceClientModel() agent = CodeAgent(tools=[hub_tool], model=model) task = ( "text-classification タスクで Hub 上もっともダウンロードされている " "モデル id を教えて。答えはモデル id だけ。" ) result = agent.run(task) print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
HF_TOKEN が無い場合は load だけ して終了する。
Loading tool from m-ric/hf-model-downloads (trust_remote_code=True) ...
⚠️ エージェント実行には HF_TOKEN が必要です。load のみ確認して終了します。
tool name: model_download_counter
トークンありなら CodeAgent が Hub ツールを呼ぶ。
━━━━━━━━━━━━━━━━━━━━ Step 0 ━━━━━━━━━━━━━━━
...
final_answer("bert-base-uncased") # 例・時期により異なる
| ログ | 意味 |
|---|---|
model_download_counter(...) |
Hub ツールの forward 実行 |
final_answer |
タスク完了 |
コレクション(任意)
export HF_TOKEN="hf_..." python examples/ch07/tool_collection_from_hub.py # エージェントまで: RUN_AGENT=1 python examples/ch07/tool_collection_from_hub.py
# examples/ch07/tool_collection_from_hub.py(リポジトリ同梱・全文) """第7章: ToolCollection.from_hub でコレクション一括読み込み(任意)""" import os from smolagents import CodeAgent, InferenceClientModel, ToolCollection def main() -> None: token = os.environ.get("HF_TOKEN") if not token: print("⚠️ HF_TOKEN 未設定。from_hub はトークンが必要なことが多いです。") return # 公式ドキュメントの例(slug は Hub 上の Tool Collection) collection_slug = "huggingface-tools/diffusion-tools-6630bb19a942c2306a2cdb6f" print(f"Loading collection: {collection_slug}") collection = ToolCollection.from_hub(collection_slug=collection_slug, token=token) print(f"Loaded {len(collection.tools)} tool(s): {[t.name for t in collection.tools]}") if os.environ.get("RUN_AGENT") != "1": print("エージェントまで動かす場合: RUN_AGENT=1 python ... (画像生成で課金の可能性)") return model = InferenceClientModel() agent = CodeAgent(tools=[*collection.tools], model=model, add_base_tools=False) result = agent.run("Draw a simple icon of a book.") print(result) if __name__ == "__main__": main()
7.7 よくあるエラー ⚠️
霊夢: RepositoryNotFound って……
魔理沙: よくあるのはこのへん。
401 / 403 on push
Unauthorized ...
対処: HF_TOKEN に write 権限があるか、repo 名が Hub 上の Space と一致するか確認。
trust_remote_code 未指定
Loading ... requires you to execute remote code ...
対処: trust_remote_code=True を付ける。中身を読んだうえで。
push 時の import エラー
Error when saving tool: imports should be inside forward
対処: トップレベル import を forward 内へ移動。
7.8 本章のまとめ
霊夢: 整理するわ。
push_to_hub— 自作Toolを Space として共有(import 規則あり)load_tool/from_hub— 他人ツールを再利用、trust_remote_code=Trueは同意ToolCollection.from_hub— コレクション slug で複数ツール- セキュリティ — 信頼できない repo は載せない・読まない
魔理沙: 次章は LangChain・Gradio Space・MCP で既存エコシステムとつなぐぜ。
✅ 章末チェックリスト
- [ ]
HubDownloadsToolをローカルでforward実行した - [ ] (任意)
push_to_hubで自分の Space に上げた - [ ]
load_tool(..., trust_remote_code=True)の意味を説明できる - [ ]
m-ric/hf-model-downloadsをエージェントに載せた(または load のみ確認) - [ ]
ToolCollection.from_hubの用途を理解した - [ ] 秘匿トークンを Git にコミットしていない
次章へ
霊夢: Hub、便利だけど信頼が大事ね。
魔理沙: その通り。次は MCP で社内 API ともつなぐぜ。ゆっくりしていこうな。
第8章 既存エコシステムとつなぐ — LangChain・Space・MCP
本章のゴール: LangChain ツール・Gradio Space・MCP サーバーを smolagents に橋渡しし、信頼境界と
structured_outputを理解する。
8.1 わざわざ作らなくていい?
霊夢: 第 5 章で @tool を書いたばかりなのに、また別の作り方?
魔理沙: 車輪の再発明を避ける 章だ。すでに LangChain 用ツールや Hub の Gradio Space、社内の MCP サーバーがあるなら、ラッパーで載せるだけでいい。
| 経路 | 典型用途 |
|---|---|
Tool.from_langchain() |
既存 LC ツールの再利用 |
Tool.from_space() |
Hub 上の画像生成・ASR Space |
MCPClient / ToolCollection.from_mcp() |
標準化された外部ツールサーバー |
8.2 Tool.from_langchain
霊夢: LangChain 入れてないプロジェクトでも使える?
魔理沙: 使うときだけ langchain-community などを入れる。ラッパーは LC ツールの名前・説明・実行 を smolagents の Tool API に写す。
pip install 'smolagents[toolkit]' langchain-community
from langchain_community.tools import DuckDuckGoSearchRun from smolagents import Tool, CodeAgent, InferenceClientModel lc_tool = DuckDuckGoSearchRun() search_tool = Tool.from_langchain(lc_tool) agent = CodeAgent(tools=[search_tool], model=InferenceClientModel()) agent.run("Attention is All You Need の著者数は?")
📦 examples/ch08/langchain_search_tool.py
# examples/ch08/langchain_search_tool.py(リポジトリ同梱・全文) """第8章: LangChain ツールを Tool.from_langchain で再利用""" import os from smolagents import CodeAgent, InferenceClientModel, Tool def main() -> None: try: from langchain_community.tools import DuckDuckGoSearchRun except ImportError: print("⚠️ pip install 'langchain-community' が必要です。") return lc_tool = DuckDuckGoSearchRun() search_tool = Tool.from_langchain(lc_tool) if not os.environ.get("HF_TOKEN"): print("⚠️ HF_TOKEN 未設定。ツールの手動呼び出しのみ。") print(search_tool("smolagents huggingface")) return model = InferenceClientModel() agent = CodeAgent(tools=[search_tool], model=model) result = agent.run( "smolagents とは何か、1 文で説明して。出典 URL があれば末尾に。" ) print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
霊夢: SerpAPI みたいな有料キーは?
魔理沙: LangChain 側の設定のままだ。キーは .env で、Git には載せない ⚠️
8.3 Tool.from_space — Gradio Space をツール化
魔理沙: Space ID を渡すと、裏で gradio-client が API を叩く。
from smolagents import Tool image_tool = Tool.from_space( "black-forest-labs/FLUX.1-schnell", name="image_generator", description="Generate an image from a prompt", ) # 単体テスト # result = image_tool("A sunny beach")
エージェントに additional_args でコンテキストを渡す例(公式パターン):
from smolagents import CodeAgent, InferenceClientModel agent = CodeAgent(tools=[image_tool], model=InferenceClientModel()) agent.run( "Improve this prompt, then generate an image.", additional_args={"user_prompt": "A rabbit wearing a space suit"}, )
📦 スケッチ: examples/ch08/from_space_sketch.py(RUN_SPACE=1 で実行)
# examples/ch08/from_space_sketch.py(リポジトリ同梱・全文) """第8章: Gradio Space を Tool.from_space で使う(スケッチ・任意実行)""" import os from smolagents import CodeAgent, InferenceClientModel, Tool def main() -> None: # 画像生成 Space — 実行は GPU/課金の可能性あり space_id = "black-forest-labs/FLUX.1-schnell" image_tool = Tool.from_space( space_id, name="image_generator", description="Generate an image from a text prompt", ) print(f"Tool ready: {image_tool.name}") print("Space 直呼び出しは RUN_SPACE=1 のときのみ実行します。") if os.environ.get("RUN_SPACE") != "1": return if not os.environ.get("HF_TOKEN"): print("⚠️ HF_TOKEN が必要です。") return model = InferenceClientModel() agent = CodeAgent(tools=[image_tool], model=model) result = agent.run( "Improve the prompt then generate an image.", additional_args={"user_prompt": "A rabbit in a space suit on the moon"}, ) print(result) if __name__ == "__main__": main()
霊夢: GPU と課金が怖いわ……
魔理沙: デフォルトは ツール定義だけ確認 で止めるようにしてある。
8.4 MCP とは
霊夢: MCP、略語ばっかり……
魔理沙: Model Context Protocol。ツールを サーバー として提供し、エージェントはクライアントで接続する。stdio(子プロセス)か Streamable HTTP が多い。
flowchart LR Agent[CodeAgent] --> MCPClient MCPClient -->|stdio or HTTP| Server[MCP Server] Server --> API[DB / API / ファイル]
smolagents では主に:
MCPClient— 接続を管理し、ツール一覧をエージェントに渡すToolCollection.from_mcp— 同様にコレクションとして取得
8.5 MCPClient — stdio と HTTP
stdio(ローカルプロセス)
from mcp import StdioServerParameters from smolagents import MCPClient, CodeAgent, InferenceClientModel import os server_parameters = StdioServerParameters( command="uvx", args=["--quiet", "pubmedmcp@0.1.3"], env={"UV_PYTHON": "3.12", **os.environ}, ) with MCPClient(server_parameters) as tools: agent = CodeAgent(tools=tools, model=InferenceClientModel(), add_base_tools=True) agent.run("COVID-19 治療の最近の研究を要約して")
Streamable HTTP
from smolagents import MCPClient, CodeAgent, InferenceClientModel with MCPClient({"url": "http://127.0.0.1:8000/mcp", "transport": "streamable-http"}) as tools: agent = CodeAgent(tools=tools, model=InferenceClientModel()) agent.run("二日酔いの対処法は?")
手動で接続を閉じる
mcp_client = MCPClient(server_parameters) try: tools = mcp_client.get_tools() agent = CodeAgent(tools=tools, model=model) print(agent.run("...")) finally: mcp_client.disconnect()
8.6 複数 MCP サーバー
魔理沙: リストを渡せば 複数サーバーのツールをマージ する。
from smolagents import MCPClient server_params1 = StdioServerParameters(command="uvx", args=["--quiet", "pubmedmcp@0.1.3"]) server_params2 = {"url": "http://127.0.0.1:8000/mcp", "transport": "streamable-http"} with MCPClient([server_params1, server_params2]) as tools: agent = CodeAgent(tools=tools, model=model) agent.run("論文を調べ、頭痛の対処も教えて")
📦 スケッチ: examples/ch08/mcp_multi_sketch.py
# examples/ch08/mcp_multi_sketch.py(リポジトリ同梱・全文) """第8章: 複数 MCP サーバー接続のスケッチ(コメント中心)""" import os from mcp import StdioServerParameters from smolagents import MCPClient # 例: stdio MCP + Streamable HTTP MCP を同時に # server_params1 = StdioServerParameters(command="uvx", args=["--quiet", "pubmedmcp@0.1.3"], ...) # server_params2 = {"url": "http://127.0.0.1:8000/mcp", "transport": "streamable-http"} # # with MCPClient([server_params1, server_params2], structured_output=True) as tools: # agent = CodeAgent(tools=tools, model=model) # agent.run("...") def main() -> None: print("複数 MCP は MCPClient([param1, param2]) で接続します。") print("本番では各サーバーの信頼性を個別に検証してください ⚠️") if not os.environ.get("HF_TOKEN"): print("(エージェント実行には HF_TOKEN が必要)") if __name__ == "__main__": main()
霊夢: ツール名が被ったら?
魔理沙: 衝突に注意。本番ではプレフィックス付きサーバー設計が安全だ。
8.7 structured_output=True
霊夢: JSON スキーマって、MCP の新機能?
魔理沙: ツールが 構造化データ を返すとき、LLM がスキーマをシステムプロンプトで見られる。structured_output=True で有効化する(将来デフォルト True 予定のため、明示指定を推奨)。
with MCPClient(server_parameters, structured_output=True) as tools: agent = CodeAgent(tools=tools, model=model) agent.run("東京の気温を華氏でも教えて")
ローカルデモサーバー(Pydantic モデル付き):
📦 サーバー: examples/ch08/mcp_weather_server.py
📦 クライアント: examples/ch08/mcp_weather_agent.py
pip install 'smolagents[toolkit]' mcp python examples/ch08/mcp_weather_agent.py
# examples/ch08/mcp_weather_agent.py(リポジトリ同梱・全文) """第8章: MCPClient でローカル天気 MCP に接続""" import os from mcp import StdioServerParameters from smolagents import CodeAgent, InferenceClientModel, MCPClient def main() -> None: if not os.environ.get("HF_TOKEN"): print("⚠️ HF_TOKEN が未設定です。エージェントは Inference API を使います。") server_parameters = StdioServerParameters( command="python", args=["examples/ch08/mcp_weather_server.py"], ) model = InferenceClientModel() with MCPClient(server_parameters, structured_output=True) as tools: print(f"MCP tools: {[t.name for t in tools]}") agent = CodeAgent(tools=tools, model=model) result = agent.run( "東京の気温を摂氏で教えて。湿度も一言で。" ) print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
# examples/ch08/mcp_weather_server.py(リポジトリ同梱・全文) """第8章: 構造化出力付きのローカル MCP 天気デモサーバー""" from mcp.server.fastmcp import FastMCP from pydantic import BaseModel, Field mcp = FastMCP("Weather Service") class WeatherInfo(BaseModel): location: str = Field(description="Location name") temperature: float = Field(description="Temperature in Celsius") conditions: str = Field(description="Weather conditions") humidity: int = Field(description="Humidity percentage", ge=0, le=100) @mcp.tool( name="get_weather_info", description="Get weather information for a location as structured data.", ) def get_weather_info(city: str) -> WeatherInfo: """Demo weather — not real API data.""" return WeatherInfo( location=city, temperature=22.5, conditions="partly cloudy", humidity=65, ) if __name__ == "__main__": mcp.run()
8.8 セキュリティ — MCP の信頼境界 ⚠️
霊夢: Hub の trust_remote_code と同じ?
魔理沙: 同族だ。
| 種類 | リスク |
|---|---|
| stdio MCP | サーバー起動 = ローカルでコード実行 |
| HTTP MCP | リモートだが、悪意ある指示でデータ流出の可能性 |
| 複数サーバー | 攻撃面が増える |
チェックリスト(本番):
- [ ] サーバー作者・ソースを検証した
- [ ] 最小権限の API キーだけ渡した
- [ ] 社外サーバーに社内シークレットを渡していない
- [ ] 可能なら第 12 章のサンドボックスと併用
🖥️ ハンズオン 8-1 — LangChain 検索ツール
霊夢: 手を動かすわ。
pip install langchain-community export HF_TOKEN="hf_..." python examples/ch08/langchain_search_tool.py
# examples/ch08/langchain_search_tool.py(リポジトリ同梱・全文) """第8章: LangChain ツールを Tool.from_langchain で再利用""" import os from smolagents import CodeAgent, InferenceClientModel, Tool def main() -> None: try: from langchain_community.tools import DuckDuckGoSearchRun except ImportError: print("⚠️ pip install 'langchain-community' が必要です。") return lc_tool = DuckDuckGoSearchRun() search_tool = Tool.from_langchain(lc_tool) if not os.environ.get("HF_TOKEN"): print("⚠️ HF_TOKEN 未設定。ツールの手動呼び出しのみ。") print(search_tool("smolagents huggingface")) return model = InferenceClientModel() agent = CodeAgent(tools=[search_tool], model=model) result = agent.run( "smolagents とは何か、1 文で説明して。出典 URL があれば末尾に。" ) print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
トークンなし → DuckDuckGo を ツール単体 で 1 回実行。
🖥️ ハンズオン 8-2 — ローカル MCP 天気デモ
魔理沙: 別ターミナル不要。MCPClient が子プロセスでサーバーを起動する。
pip install mcp python examples/ch08/mcp_weather_agent.py
# examples/ch08/mcp_weather_agent.py(リポジトリ同梱・全文) """第8章: MCPClient でローカル天気 MCP に接続""" import os from mcp import StdioServerParameters from smolagents import CodeAgent, InferenceClientModel, MCPClient def main() -> None: if not os.environ.get("HF_TOKEN"): print("⚠️ HF_TOKEN が未設定です。エージェントは Inference API を使います。") server_parameters = StdioServerParameters( command="python", args=["examples/ch08/mcp_weather_server.py"], ) model = InferenceClientModel() with MCPClient(server_parameters, structured_output=True) as tools: print(f"MCP tools: {[t.name for t in tools]}") agent = CodeAgent(tools=tools, model=model) result = agent.run( "東京の気温を摂氏で教えて。湿度も一言で。" ) print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
MCP tools: ['get_weather_info'] ... (最終回答に気温・湿度)
| ポイント | 説明 |
|---|---|
structured_output=True |
WeatherInfo スキーマを LLM が参照 |
| デモデータ | 実 API ではなく固定値 |
8.9 ToolCollection.from_mcp
魔理沙: MCPClient と同様の接続だが、with ToolCollection.from_mcp(...) as col: で col.tools を展開するスタイル。
from smolagents import ToolCollection, CodeAgent with ToolCollection.from_mcp(server_parameters, trust_remote_code=True, structured_output=True) as col: agent = CodeAgent(tools=[*col.tools], model=model, add_base_tools=True) agent.run("...")
8.10 よくあるエラー ⚠️
ModuleNotFoundError: mcp
pip install mcp
MCP サーバー起動失敗
Connection closed / Failed to initialize MCP session
対処: command / args のパス、uvx が入っているか、ファイアウォール(HTTP 時)を確認。
LangChain ツールの import エラー
pip install langchain-community
8.11 本章のまとめ
霊夢: まとめるわ。
from_langchain— 既存ツールをラップfrom_space— Hub Gradio Space を 1 ツール化MCPClient— stdio / HTTP、複数サーバー可structured_output— 構造化ツール出力を LLM が理解しやすく- 信頼 — stdio = ローカル実行と同等の危険度
✅ 章末チェックリスト
- [ ]
Tool.from_langchainで LC ツールを載せた(または import 確認) - [ ]
Tool.from_spaceの引数(space_id,name,description)を説明できる - [ ] ローカル MCP 天気デモを実行した
- [ ]
structured_output=Trueの意味を理解した - [ ] MCP 利用時のセキュリティチェックリストを読んだ
次章へ
霊夢: エコシステム、広いわね……
魔理沙: 次はいよいよ CodeAgent の芯 を解剖するぜ。
第9章 CodeAgent — コードで動くエージェント
本章のゴール: CodeAgent の実行モデルを理解し、
additional_authorized_imports・final_answer_checks・additional_argsを使ったハンズオンを完了する。
9.1 JSON じゃなくて Python?
霊夢: 第 0 章から CodeAgent ばかり見てきたけど、なんでコードなの?
魔理沙: ツール呼び出しを Python スニペット として書けるから、ループ・分岐・中間変数・複数ツールの合成が楽なんだ。研究ベンチマークでもコードエージェントが有利なことが多い。
# CodeAgent が書きやすいパターン docs = search("smolagents secure execution") summary = summarize(docs[0]) final_answer(summary)
霊夢: ToolCallingAgent は?
魔理沙: JSON で 1 ツールずつ。第 10 章で並べて比べるぜ。
9.2 エージェントループの中身
魔理沙: 1 ステップはざっくり次の流れだ。
flowchart TD
T[タスク + システムプロンプト] --> LLM[LLM: Python を生成]
LLM --> EX[LocalPythonExecutor 等で実行]
EX --> OBS[stdout / エラー / ツール結果]
OBS --> LLM
LLM --> FA{final_answer?}
FA -->|Yes| Done[終了]
FA -->|No| LLM
| 関数・概念 | 役割 |
|---|---|
| 生成コード | ツール呼び出しや計算 |
print(...) |
次ステップ用の観察ログ |
final_answer(x) |
正常終了の宣言 |
max_steps |
無限ループ防止 |
9.3 LocalPythonExecutor の概要
霊夢: 普通の python じゃないの?
魔理沙: デフォルトは AST を解釈する専用エグゼキュータ。許可されていない import や危険な属性アクセスは弾かれる。詳細は第 12 章。
# エージェント内部イメージ(利用者は通常意識しない) # from smolagents.local_python_executor import LocalPythonExecutor # executor = LocalPythonExecutor(additional_authorized_imports=[...])
リモート実行に切り替えるときは executor_type="e2b" など(第 12 章)。
9.4 additional_authorized_imports
霊夢: Web のタイトル取りたいだけなのに、import requests が弾かれたわ……
魔理沙: 既定では 安全な標準ライブラリだけ。外のパッケージは 明示許可リスト に足す。
from smolagents import CodeAgent, InferenceClientModel agent = CodeAgent( tools=[], model=InferenceClientModel(), additional_authorized_imports=["requests", "bs4"], ) agent.run("https://huggingface.co/blog の <title> を取得して")
サブモジュール制限
魔理沙: numpy だけ許可しても numpy.random は別途 必要。またはワイルドカード:
additional_authorized_imports=["numpy", "numpy.random"] # または additional_authorized_imports=["numpy.*"]
⚠️ 危険な import を足すな — LLM が生成したコードがそのまま動く。
pip install requests beautifulsoup4
📦 examples/ch09/web_title_agent.py
# examples/ch09/web_title_agent.py(リポジトリ同梱・全文) """第9章: additional_authorized_imports で Web ページタイトル取得""" from smolagents import CodeAgent, InferenceClientModel def main() -> None: model = InferenceClientModel() agent = CodeAgent( tools=[], model=model, additional_authorized_imports=["requests", "bs4"], ) url = "https://huggingface.co/docs/smolagents" task = ( f"URL '{url}' の HTML から <title> テキストだけを取得して返して。" "requests と BeautifulSoup (bs4) を使ってよい。" ) result = agent.run(task) print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
9.5 final_answer と final_answer_checks
霊夢: final_answer(42) って出たけど、文字列 "42" だったときは?
魔理沙: 検証関数で 弾いて続行 できる。
def is_integer(final_answer, agent_memory=None) -> bool: try: int(str(final_answer).strip()) return True except ValueError: return False agent = CodeAgent( tools=[], model=InferenceClientModel(), final_answer_checks=[is_integer], )
| 戻り値 | 動作 |
|---|---|
True |
その final_answer で終了 |
False |
エラーをログに残し、エージェント継続 |
用途: 数値問題・JSON 形式・社内 ID 形式など。
📦 examples/ch09/final_answer_checks.py
# examples/ch09/final_answer_checks.py(リポジトリ同梱・全文) """第9章: final_answer_checks で整数回答を強制""" from smolagents import CodeAgent, InferenceClientModel def is_integer(final_answer, agent_memory=None) -> bool: try: int(str(final_answer).strip()) return True except ValueError: return False def main() -> None: model = InferenceClientModel() agent = CodeAgent( tools=[], model=model, final_answer_checks=[is_integer], max_steps=8, ) result = agent.run("3 と 7 の最小公倍数を計算し、答えは整数だけ返して") print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
9.6 additional_args
霊夢: タスク文に URL を書くの、長くて嫌なのよね。
魔理沙: agent.run(task, additional_args={...}) で、実行時の Python から見える変数を渡せる。画像 URL・DataFrame・設定 dict など。
agent.run(
"user_prompt を改善してから image_generator を呼んで",
additional_args={"user_prompt": "月面のうさぎ"},
)
本書のシンプル例:
agent.run(
"base_url と path を連結した完全 URL を返して",
additional_args={
"base_url": "https://huggingface.co",
"path": "/blog/smolagents",
},
)
📦 examples/ch09/additional_args_demo.py
# examples/ch09/additional_args_demo.py(リポジトリ同梱・全文) """第9章: additional_args でタスク外の変数をエージェントに渡す""" from smolagents import CodeAgent, InferenceClientModel def main() -> None: model = InferenceClientModel() agent = CodeAgent(tools=[], model=model) base_url = "https://huggingface.co" path = "/blog/smolagents" result = agent.run( "base_url と path を連結した URL のパス部分(ドメイン除く)だけを返して。", additional_args={ "base_url": base_url, "path": path, }, ) print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
霊夢: プロンプトに埋め込まれるの?
魔理沙: state にマージされ、システム側から 利用可能な変数 として告知される。機密情報を渡すときはログ漏れに注意 ⚠️
🖥️ ハンズオン 9-1 — Web ページタイトル取得
霊夢: requests と BeautifulSoup、動かしてみる!
pip install requests beautifulsoup4 export HF_TOKEN="hf_..." python examples/ch09/web_title_agent.py
# examples/ch09/web_title_agent.py(リポジトリ同梱・全文) """第9章: additional_authorized_imports で Web ページタイトル取得""" from smolagents import CodeAgent, InferenceClientModel def main() -> None: model = InferenceClientModel() agent = CodeAgent( tools=[], model=model, additional_authorized_imports=["requests", "bs4"], ) url = "https://huggingface.co/docs/smolagents" task = ( f"URL '{url}' の HTML から <title> テキストだけを取得して返して。" "requests と BeautifulSoup (bs4) を使ってよい。" ) result = agent.run(task) print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
━━━━━━━━━━━━━━━━━━━━ Step 0 ━━━━━━━━━━━━━━━
╭─ Executing this code: ─────────────────╮
│ import requests │
│ from bs4 import BeautifulSoup │
│ ... │
│ final_answer("...") │
╰────────────────────────────────────────╯
| エラー | 対処 |
|---|---|
Import of requests is not allowed |
additional_authorized_imports に "requests" を追加 |
bs4 not found |
pip install beautifulsoup4 |
🖥️ ハンズオン 9-2 — final_answer_checks
python examples/ch09/final_answer_checks.py
# examples/ch09/final_answer_checks.py(リポジトリ同梱・全文) """第9章: final_answer_checks で整数回答を強制""" from smolagents import CodeAgent, InferenceClientModel def is_integer(final_answer, agent_memory=None) -> bool: try: int(str(final_answer).strip()) return True except ValueError: return False def main() -> None: model = InferenceClientModel() agent = CodeAgent( tools=[], model=model, final_answer_checks=[is_integer], max_steps=8, ) result = agent.run("3 と 7 の最小公倍数を計算し、答えは整数だけ返して") print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
最初に final_answer("21") のように文字列で返すと、チェックが失敗して 追加ステップ が走ることがある。
Final answer check failed: is_integer returned False ... final_answer(21)
霊夢: 自分で検証してくれるの、地味に便利ね。
9.7 CodeAgent の強み(再掲)
# 複数ツール + 制御フロー(概念例) results = [] for q in ["smolagents", "CodeAgent"]: results.append(web_search(q)) final_answer(max(results, key=len))
| 強み | 弱み |
|---|---|
| 表現力・合成 | 構文エラー・予測しづらさ |
| 動的ロジック | 実行環境のセキュリティ設計が必須 |
9.8 よくあるエラー ⚠️
InterpreterError: Import of X is not allowed
additional_authorized_imports=["X"]
Forbidden access to module: os(サブモジュール経由)
random._os など — 許可パッケージでも内部経路はブロックされる(第 12 章)。
final_answer しないまま max_steps
タスクを具体化するか max_steps を増やす。第 13 章でデバッグ設定も学ぶ。
9.9 本章のまとめ
霊夢: チェックリスト用に言うわ。
- CodeAgent — Python 生成 → 実行 →
final_answer additional_authorized_imports— 外部パッケージの許可final_answer_checks— 回答形式の検証additional_args— タスク外コンテキストの注入- 実行の実体は LocalPythonExecutor(第 12 章で深掘り)
✅ 章末チェックリスト
- [ ] CodeAgent ループ(生成 → 実行 → 観察)を説明できる
- [ ]
requests/bs4でタイトル取得ハンズオンを完了した - [ ]
final_answer_checksで整数チェックを試した - [ ]
additional_argsを 1 回以上使った - [ ] 危険な import を安易に許可していない
次章へ
霊夢: CodeAgent、だいぶ腹落ちしたわ。
魔理沙: 次は 対になる ToolCallingAgent と比べるぜ。ゆっくりしていこうな。
第10章 ToolCallingAgent — 構造化ツール呼び出し
本章のゴール: ToolCallingAgent と CodeAgent の違いを表で整理し、同じ Web タイトル取得タスクを両方で実行してログを比較する。
10.1 2 つのエージェント哲学
霊夢: ずっと CodeAgent だったけど、もう一種類あるんでしょ?
魔理沙: ToolCallingAgent だ。行動を JSON 形式のツール呼び出し で表す。OpenAI API の function calling に近い。
flowchart TB
subgraph CodeAgent
C1[LLM] --> C2[Python コード]
C2 --> C3[Executor]
end
subgraph ToolCallingAgent
T1[LLM] --> T2[JSON tool call]
T2 --> T3[Tool.forward]
end
10.2 比較表
| 観点 | CodeAgent | ToolCallingAgent |
|---|---|---|
| 行動の表現 | Python スニペット | JSON(name + arguments) |
| コード実行 | あり(ローカル or サンドボックス) | 基本なし(ツール内のみ) |
| 表現力 | ループ・合成・変数 | ツール定義内に限定 |
| 予測可能性 | 低め(構文・ロジックエラー) | 高め(スキーマ検証) |
| 安全性 | import 制御が必要 | 任意コードを書かない |
| 向き用途 | 計算・パイプライン・複合 | API ラッパー・検索・定型操作 |
霊夢: どっちが「強い」の?
魔理沙: 難しいタスク・合成 は CodeAgent、信頼性・監査 は ToolCallingAgent、と割り切るとよい。
同じタスクの CodeAgent 側(対比用)
from smolagents import CodeAgent, InferenceClientModel code_agent = CodeAgent( tools=[], model=InferenceClientModel(), additional_authorized_imports=["requests", "bs4"], ) code_agent.run("https://example.com の <title> だけ返して")
霊夢: 片方は Python、片方は JSON ツール呼び出し、って違いね。
10.3 ToolCallingAgent の最小例
from smolagents import ToolCallingAgent, InferenceClientModel, WebSearchTool model = InferenceClientModel() agent = ToolCallingAgent(tools=[WebSearchTool()], model=model) agent.run("フランスの首都は?")
ログではおおよそ次の形(モデルにより異なる):
{ "tool_call": { "name": "web_search", "arguments": { "query": "capital of France" } } }
VisitWebpageTool を手動で試す
魔理沙: エージェント抜きでツールだけ叩くと、ToolCalling の中身が分かりやすい。
from smolagents import VisitWebpageTool page = VisitWebpageTool()("https://huggingface.co/docs/smolagents") print(str(page)[:800])
10.4 PythonInterpreterTool との関係
魔理沙: add_base_tools=True のとき、ToolCallingAgent には PythonInterpreterTool が付くことがある。CodeAgent は もともとコード実行できる ので重複を避ける設計だ。
from smolagents import ToolCallingAgent, InferenceClientModel agent = ToolCallingAgent( tools=[], model=InferenceClientModel(), add_base_tools=True, # WebSearch + PythonInterpreter 等 )
10.5 いつ ToolCallingAgent を選ぶか
| 選ぶ | 避ける |
|---|---|
| ツールが独立した API 呼び出し | 複雑なデータ変換を毎回ツールに書く |
| OpenAI 互換エンドポイントに載せたい | ループ・条件分岐がタスクの中心 |
| 監査ログでツール名を固定したい | 1 ステップで大量の Python 合成が必要 |
10.6 OpenAI 互換 API 向け設定
魔理沙: LiteLLMModel + ToolCallingAgent が定番だ。
# pip install 'smolagents[litellm]' import os from smolagents import LiteLLMModel, ToolCallingAgent, VisitWebpageTool model = LiteLLMModel( model_id="gpt-4o-mini", api_key=os.environ["OPENAI_API_KEY"], ) agent = ToolCallingAgent(tools=[VisitWebpageTool()], model=model) agent.run("https://example.com の title は?")
📦 examples/ch10/tool_calling_litellm_sketch.py
# examples/ch10/tool_calling_litellm_sketch.py(リポジトリ同梱・全文) """第10章: OpenAI 互換 API 向け ToolCallingAgent(スケッチ)""" import os from smolagents import ToolCallingAgent, VisitWebpageTool def main() -> None: api_key = os.environ.get("OPENAI_API_KEY") if not api_key: print("⚠️ OPENAI_API_KEY 未設定。以下は設定後の例です。") print(""" # pip install 'smolagents[litellm]' from smolagents import LiteLLMModel, ToolCallingAgent, VisitWebpageTool model = LiteLLMModel(model_id="gpt-4o-mini", api_key=os.environ["OPENAI_API_KEY"]) agent = ToolCallingAgent(tools=[VisitWebpageTool()], model=model) print(agent.run("https://example.com の title テキストは?")) """) return from smolagents import LiteLLMModel model = LiteLLMModel(model_id="gpt-4o-mini") agent = ToolCallingAgent(tools=[VisitWebpageTool()], model=model) result = agent.run("https://example.com のページタイトルだけ教えて") print(result) if __name__ == "__main__": main()
🖥️ ハンズオン 10-1 — Web タイトル取得の比較
霊夢: 同じ質問、両方に投げてみるわ!
魔理沙: 📦 examples/ch10/compare_web_title.py
pip install requests beautifulsoup4 export HF_TOKEN="hf_..." python examples/ch10/compare_web_title.py
# examples/ch10/compare_web_title.py(リポジトリ同梱・全文) """第10章: 同じ URL タイトル取得を CodeAgent と ToolCallingAgent で比較""" import time from smolagents import CodeAgent, InferenceClientModel, ToolCallingAgent, VisitWebpageTool URL = "https://huggingface.co/docs/smolagents" TASK = f"次の URL のページタイトル(<title>)だけを返して: {URL}" def run_code_agent(model) -> tuple[str, float]: agent = CodeAgent( tools=[], model=model, additional_authorized_imports=["requests", "bs4"], verbosity_level=1, ) start = time.perf_counter() result = agent.run(TASK) return str(result), time.perf_counter() - start def run_tool_calling_agent(model) -> tuple[str, float]: agent = ToolCallingAgent( tools=[VisitWebpageTool()], model=model, verbosity_level=1, ) start = time.perf_counter() result = agent.run(TASK) return str(result), time.perf_counter() - start def main() -> None: model = InferenceClientModel() print("=== CodeAgent ===") code_answer, code_sec = run_code_agent(model) print(code_answer) print(f"({code_sec:.1f}s)") print("\n=== ToolCallingAgent ===") tc_answer, tc_sec = run_tool_calling_agent(model) print(tc_answer) print(f"({tc_sec:.1f}s)") print("\n--- メモ ---") print("CodeAgent: Python 生成 + ローカル実行") print("ToolCallingAgent: VisitWebpageTool 等の構造化ツール呼び出し") if __name__ == "__main__": main()
スクリプトの骨格:
from smolagents import CodeAgent, ToolCallingAgent, InferenceClientModel, VisitWebpageTool model = InferenceClientModel() url = "https://huggingface.co/docs/smolagents" task = f"次の URL のページタイトル(<title>)だけを返して: {url}" code_agent = CodeAgent( tools=[], model=model, additional_authorized_imports=["requests", "bs4"], ) tool_agent = ToolCallingAgent(tools=[VisitWebpageTool()], model=model) print(code_agent.run(task)) print(tool_agent.run(task))
| エージェント | 典型ステップ |
|---|---|
| CodeAgent | requests + BeautifulSoup を生成 |
| ToolCallingAgent | visit_webpage(VisitWebpageTool)を JSON 呼び出し |
=== CodeAgent === Agents - Hugging Face smolagents documentation (12.3s) === ToolCallingAgent === Agents - Hugging Face smolagents documentation (8.1s)
(時間・文言はモデルとネットワークで変動)
霊夢: ToolCalling の方がステップ、読みやすかったわ。
魔理沙: ツール名がログにそのまま出るから、本番監査には向くな。
🖥️ ハンズオン 10-2 — OpenAI 互換 ToolCallingAgent
export OPENAI_API_KEY="sk-..." pip install 'smolagents[litellm]' python examples/ch10/tool_calling_litellm_sketch.py
# examples/ch10/tool_calling_litellm_sketch.py(リポジトリ同梱・全文) """第10章: OpenAI 互換 API 向け ToolCallingAgent(スケッチ)""" import os from smolagents import ToolCallingAgent, VisitWebpageTool def main() -> None: api_key = os.environ.get("OPENAI_API_KEY") if not api_key: print("⚠️ OPENAI_API_KEY 未設定。以下は設定後の例です。") print(""" # pip install 'smolagents[litellm]' from smolagents import LiteLLMModel, ToolCallingAgent, VisitWebpageTool model = LiteLLMModel(model_id="gpt-4o-mini", api_key=os.environ["OPENAI_API_KEY"]) agent = ToolCallingAgent(tools=[VisitWebpageTool()], model=model) print(agent.run("https://example.com の title テキストは?")) """) return from smolagents import LiteLLMModel model = LiteLLMModel(model_id="gpt-4o-mini") agent = ToolCallingAgent(tools=[VisitWebpageTool()], model=model) result = agent.run("https://example.com のページタイトルだけ教えて") print(result) if __name__ == "__main__": main()
キーなし → スクリプトが 貼り付け用サンプル を表示して終了。
10.7 ベンチマーク上の CodeAgent(概要)
魔理沙: 論文・ブログでは、コード行動 がツール合成タスクで有利な報告が多い。一方、単純 API 呼び出しだけなら ToolCalling の方がステップ数・失敗率で勝つこともある。
霊夢: 深追いは?
魔理沙: 公式 Examples と GAIA 系の第 16 章へ。本章は 選び方 までだ。
10.8 よくあるエラー ⚠️
ToolCallingAgent で Web 取得できない
VisitWebpageTool を tools= に含める。または add_base_tools=True。
CodeAgent 側だけ import エラー
additional_authorized_imports は CodeAgent 専用。ToolCalling には効かない。
JSON パースエラー
モデルが tool call 形式に弱い → モデル変更、または CodeAgent へ。
10.9 本章のまとめ
霊夢: まとめ。
- CodeAgent — Python 行動、高い表現力
- ToolCallingAgent — JSON ツール呼び出し、高い構造化
- 同タスク比較 — ログの読みやすさ・ステップ数が違う
- OpenAI 系 —
LiteLLMModel+ ToolCallingAgent
✅ 章末チェックリスト
- [ ] 比較表を自分の言葉で説明できる
- [ ]
compare_web_title.pyを実行し、両方のログを見た - [ ] ToolCallingAgent に
VisitWebpageToolを渡した - [ ] (任意)OpenAI API スケッチを読んだ
次章へ
霊夢: 使い分け、だいぶ見えてきたわ。
魔理沙: 次は CLI でさっと試す 章だ。ゆっくりしていこうな。
第11章 CLI でさっと試す — smolagent と webagent
本章のゴール:
smolagent/webagentCLI の使い方と、Python API との使い分けを理解する。
11.1 なぜ CLI?
霊夢: 毎回 .py 書くの、面倒なときあるのよね。
魔理沙: インストール時に smolagent と webagent コマンドが付く。プロンプト・モデル・ツール・import を フラグで渡して即実行 できる。
pip install 'smolagents[toolkit]' smolagent --help
11.2 smolagent — ワンショット実行
魔理沙: 引数にタスク文字列、その後オプションだ。
smolagent "Plan a trip to Tokyo, Kyoto and Osaka between Mar 28 and Apr 7." \ --model-type InferenceClientModel \ --model-id Qwen/Qwen2.5-Coder-32B-Instruct \ --imports pandas numpy \ --tools web_search
| フラグ | 意味 |
|---|---|
--model-type |
InferenceClientModel, LiteLLMModel など |
--model-id |
モデル ID |
--imports |
additional_authorized_imports(空白区切り) |
--tools |
web_search 等、または user/space-id |
--provider |
Inference Providers 指定(任意) |
--api-base / --api-key |
LiteLLM 系(任意) |
Space をツールとして指定
smolagent "Generate a cat icon." \ --tools black-forest-labs/FLUX.1-schnell \ --model-type InferenceClientModel
/ を含む名前は Tool.from_space として読み込まれる(CLI 実装)。
11.3 インタラクティブモード
霊夢: 引数なしだと?
魔理沙: ウィザード が起動する。
smolagent
案内される項目(バージョンにより多少異なる):
- エージェント種別 — CodeAgent / ToolCallingAgent
- ツール選択 — 利用可能ツールボックスから
- モデル — タイプ・ID・API 設定
- 追加 import
- タスクプロンプト
霊夢: 試行錯誤にちょうどいいわね。
11.4 action_type — code vs tool_calling
魔理沙: 内部では --action-type で切り替え(対話モードでも選択可)。
smolagent "What is 2+2?" \ --model-type InferenceClientModel \ --action-type tool_calling \ --tools web_search
| 値 | エージェント |
|---|---|
code(既定) |
CodeAgent |
tool_calling |
ToolCallingAgent |
11.5 Python API との対応
📦 examples/ch11/cli_equivalent_agent.py
# examples/ch11/cli_equivalent_agent.py(リポジトリ同梱・全文) """第11章: smolagent CLI と同等の Python API 構成""" from smolagents import CodeAgent, InferenceClientModel, WebSearchTool def main() -> None: # CLI 例: # smolagent "Plan a trip to Tokyo..." \\ # --model-type InferenceClientModel \\ # --model-id Qwen/Qwen2.5-Coder-32B-Instruct \\ # --imports pandas numpy \\ # --tools web_search model = InferenceClientModel() agent = CodeAgent( tools=[WebSearchTool()], model=model, additional_authorized_imports=["pandas", "numpy"], stream_outputs=True, ) prompt = ( "3月28日から4月7日まで、東京・京都・大阪を巡る旅行の" "大まかな日別プランを箇条書きで。各日 1 行程度。" ) result = agent.run(prompt) print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
| 場面 | おすすめ |
|---|---|
| ちょっと試す・デモ | CLI |
| 本番・テスト・CI | Python API |
| Hub push / カスタムツール | Python API |
11.6 webagent — ブラウザ自動化の概要
霊夢: webagent は普通の smolagent と何が違うの?
魔理沙: Helium ベースで、実ブラウザを操作する Web 閲覧特化 エージェントだ。クリック・フォーム・商品ページ取得など。
# pip install 'smolagents[toolkit]' # helium 等が入る構成を確認 webagent "go to example.com, open the docs page, return the main heading." \ --model-type LiteLLMModel \ --model-id gpt-4o-mini
⚠️ 注意:
- ブラウザは ローカルで動く — ログイン状態・Cookie に注意
- モデルは 視覚+DOM 理解が必要なことが多く、強いモデル推奨
- 本番スクレイピングより 調査・デモ 向き
flowchart LR User[プロンプト] --> webagent webagent --> Helium[Helium / ブラウザ] Helium --> Site[Web サイト] Site --> Answer[最終回答]
🖥️ ハンズオン 11-1 — CLI で旅行プラン
霊夢: ターミナルだけでやってみる!
A. CLI 直叩き(推奨)
export HF_TOKEN="hf_..." smolagent "3月28日から4月7日、東京・京都・大阪の大まかな旅行プランを箇条書きで" \ --model-type InferenceClientModel \ --tools web_search
B. Python 同等スクリプト
python examples/ch11/cli_equivalent_agent.py
# examples/ch11/cli_equivalent_agent.py(リポジトリ同梱・全文) """第11章: smolagent CLI と同等の Python API 構成""" from smolagents import CodeAgent, InferenceClientModel, WebSearchTool def main() -> None: # CLI 例: # smolagent "Plan a trip to Tokyo..." \\ # --model-type InferenceClientModel \\ # --model-id Qwen/Qwen2.5-Coder-32B-Instruct \\ # --imports pandas numpy \\ # --tools web_search model = InferenceClientModel() agent = CodeAgent( tools=[WebSearchTool()], model=model, additional_authorized_imports=["pandas", "numpy"], stream_outputs=True, ) prompt = ( "3月28日から4月7日まで、東京・京都・大阪を巡る旅行の" "大まかな日別プランを箇条書きで。各日 1 行程度。" ) result = agent.run(prompt) print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
魔理沙: web_search が Inference + 検索 API に依存する。エラー時は第 2 章のトークン・ネットワークを確認だ。
╭──────────────── New run ────────────────╮
...
╭─ Executing tool 'web_search' ───────────╮
...
final_answer("Day 1: Tokyo ...")
11.7 環境変数と .env
魔理沙: CLI は python-dotenv で .env を読む実装になっていることが多い。
# .env(Git にコミットしない) HF_TOKEN=hf_... OPENAI_API_KEY=sk-...
smolagent "Hello" --model-type InferenceClientModel
11.8 よくあるエラー ⚠️
smolagent: command not found
pip install 'smolagents[toolkit]' which smolagent # venv activate 確認
Tool X is not recognized
--tools は 登録済み名(web_search)か Space ID(org/space)のみ。
webagent でブラウザが起動しない
Helium / ChromeDriver の依存。OS ごとの Helium ドキュメント を参照。
11.9 本章のまとめ
霊夢: 整理。
smolagent— 汎用 CodeAgent / ToolCallingAgent を CLI から- インタラクティブ — 引数なしでウィザード
webagent— ブラウザ操作特化(Helium)- 本番は Python API — 再現性・テストのため
✅ 章末チェックリスト
- [ ]
smolagent --helpを実行した - [ ] ワンショットまたはインタラクティブで 1 タスク動かした
- [ ]
--tools web_searchと--importsの意味を説明できる - [ ]
cli_equivalent_agent.pyと CLI の対応を理解した - [ ]
webagentがブラウザ連携であることを説明できる
次章へ
霊夢: CLI、試験用に便利ね。
魔理沙: 次は いよいよ安全 の本番編だ。ゆっくりしていこうな。
第12章 コード実行のセキュリティ — サンドボックスと executor
本章のゴール: LocalPythonExecutor の制限を体験し、
executor_type(e2b / docker 等) の位置づけと マルチエージェント制約 を理解する。
12.1 LLM が書いたコード、怖くない?
霊夢: CodeAgent、便利だけど……私の PC で import os されたら?
魔理沙: 正当な不安だ。脅威はざっくり 4 つ。
| 脅威 | 例 |
|---|---|
| LLM のミス | 意図しない rm -rf 風操作 |
| プロンプトインジェクション | 悪意ある Web ページが「このコードを実行せよ」 |
| 悪意ある Hub ツール | trust_remote_code の乱用 |
| 公開エージェントへの攻撃 | 外部から有害タスクを投入 |
霊夢: 100% 安全な方法は?
魔理沙: ない。レイヤーを重ねる(許可 import → 専用インタプリタ → リモートサンドボックス)のが現実的だ。
12.2 2 つのサンドボックス戦略
flowchart TB
subgraph A[スニペットのみリモート]
L1[ローカル: LLM + エージェント] --> R1[リモート: 生成コード実行]
end
subgraph B[システム全体リモート]
R2[リモート: エージェント + モデル + ツール]
end
| 方式 | executor_type |
特徴 |
|---|---|---|
| A. スニペットのみ | e2b, docker, modal, blaxel |
セットアップ比較的楽。マネージドエージェントは制約あり |
| B. 全体 | E2B 内で agent 全体を起動など | 隔離は強いが API キー運搬が難しい |
12.3 LocalPythonExecutor の仕組み
魔理沙: 通常の CPython ではなく、AST を歩いて安全な操作だけ 実行する。
- 許可リスト外 import 禁止
- サブモジュール も個別許可(
numpy.randomなど) - 演算回数上限 で無限ループ抑制
- 未定義操作は 即エラー
from smolagents.local_python_executor import LocalPythonExecutor executor = LocalPythonExecutor(additional_authorized_imports=["numpy"]) try: executor("import os; os.system('echo pwned')") except Exception as e: print(e)
📦 examples/ch12/local_executor_sandbox.py
python examples/ch12/local_executor_sandbox.py
# examples/ch12/local_executor_sandbox.py(リポジトリ同梱・全文) """第12章: LocalPythonExecutor のガードレールを直接試す""" from smolagents.local_python_executor import LocalPythonExecutor def run_capture(executor: LocalPythonExecutor, code: str) -> None: print(f"\n>>> {code.strip()[:60]}...") try: print(executor(code)) except Exception as exc: print("ERROR:", exc) def main() -> None: executor = LocalPythonExecutor(additional_authorized_imports=["numpy"]) run_capture(executor, "import os\nos.system('echo bad')") run_capture(executor, "import random\nrandom._os.system('echo bad')") run_capture(executor, "while True:\n pass") if __name__ == "__main__": main()
ERROR: Import of os is not allowed ... ERROR: Forbidden access to module: os ERROR: Maximum number of ... iterations in While loop exceeded
⚠️ ローカル完封は不可能 — Pillow で巨大画像を量産するなど、許可パッケージの悪用は理論上あり得る。
12.4 CodeAgent と許可 import
霊夢: 第 9 章の additional_authorized_imports は、この executor への渡し込み?
魔理沙: その通り。エージェントは内部で LocalPythonExecutor(またはリモート executor)を使う。
from smolagents import CodeAgent, InferenceClientModel agent = CodeAgent( tools=[], model=InferenceClientModel(), additional_authorized_imports=["requests"], )
許可 しない と os は弾かれる — 次のハンズオン。
12.5 executor_type — E2B / Docker / Modal / Blaxel
魔理沙: エージェント初期化時に指定。生成コードだけがリモートで走る。
E2B
pip install 'smolagents[e2b]' export E2B_API_KEY="..."
from smolagents import CodeAgent, InferenceClientModel with CodeAgent( model=InferenceClientModel(), tools=[], executor_type="e2b", ) as agent: agent.run("フィボナッチ数列の第 100 項を求めて")
Docker
pip install 'smolagents[docker]' # Docker デーモンが起動していること
with CodeAgent( model=InferenceClientModel(), tools=[], executor_type="docker", ) as agent: agent.run("100 番目のフィボナッチ数は?")
📦 任意: examples/ch12/docker_executor_optional.py
Modal / Blaxel
# examples/ch12/docker_executor_optional.py(リポジトリ同梱・全文) """第12章: executor_type=docker(任意・Docker 必須)""" import os import shutil from smolagents import CodeAgent, InferenceClientModel def main() -> None: if not shutil.which("docker"): print("⚠️ docker コマンドが見つかりません。スキップします。") return if not os.environ.get("HF_TOKEN"): print("⚠️ HF_TOKEN 未設定。Inference API が必要です。") return model = InferenceClientModel() with CodeAgent(model=model, tools=[], executor_type="docker") as agent: result = agent.run("フィボナッチ数列の第 15 項を計算して整数で返して") print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
| キー | 用途 |
|---|---|
E2B_API_KEY |
E2B サンドボックス |
BL_API_KEY / BL_WORKSPACE |
Blaxel |
| Modal | Modal アカウント設定 |
霊夢: with で囲むのはなぜ?
魔理沙: コンテナ / VM を 確実に破棄 するため。agent.cleanup() でも可。
12.6 マルチエージェントと E2B の制約
魔理沙: スニペットのみリモート のとき、モデル呼び出しはローカルに残る。マネージドエージェントをリモートで再帰呼び出しすると シークレットを渡せない 問題があり、公式も 複雑なマルチエージェントは未対応 としている。
対処: E2B コンテナ内で エージェント全体 を起動する(第 12 章公式の長いサンプル参照)。
🖥️ ハンズオン 12-1 — 失敗する import
霊夢: わざと os を使わせてみる!
📦 examples/ch12/blocked_import_agent.py
export HF_TOKEN="hf_..." python examples/ch12/blocked_import_agent.py
# examples/ch12/blocked_import_agent.py(リポジトリ同梱・全文) """第12章: 許可されていない import で CodeAgent が失敗する例""" from smolagents import CodeAgent, InferenceClientModel def main() -> None: model = InferenceClientModel() agent = CodeAgent( tools=[], model=model, additional_authorized_imports=[], # os は許可しない max_steps=4, verbosity_level=1, ) task = ( "import os を使ってカレントディレクトリのファイル一覧を " "final_answer で返して。os 以外は使わないで。" ) try: result = agent.run(task) print("=== 最終回答 ===") print(result) except Exception as exc: print("=== 例外 ===") print(exc) if __name__ == "__main__": main()
Code execution failed at line 'import os' due to: InterpreterError: Import of os is not allowed. Authorized imports are: [...]
| 観察 | 学び |
|---|---|
| ステップは継続しうる | LLM が別手段を試す場合あり |
os 未許可 |
ホワイトリストが効いている |
🖥️ ハンズオン 12-2 — (任意)Docker executor
pip install 'smolagents[docker]' export HF_TOKEN="hf_..." python examples/ch12/docker_executor_optional.py
# examples/ch12/docker_executor_optional.py(リポジトリ同梱・全文) """第12章: executor_type=docker(任意・Docker 必須)""" import os import shutil from smolagents import CodeAgent, InferenceClientModel def main() -> None: if not shutil.which("docker"): print("⚠️ docker コマンドが見つかりません。スキップします。") return if not os.environ.get("HF_TOKEN"): print("⚠️ HF_TOKEN 未設定。Inference API が必要です。") return model = InferenceClientModel() with CodeAgent(model=model, tools=[], executor_type="docker") as agent: result = agent.run("フィボナッチ数列の第 15 項を計算して整数で返して") print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
Docker 未インストール時は スキップメッセージ のみ。
E2B を試す場合:
pip install 'smolagents[e2b]' export E2B_API_KEY="..."
with CodeAgent(model=InferenceClientModel(), tools=[], executor_type="e2b") as agent: print(agent.run("第 20 フィボナッチ数は?"))
12.7 セキュリティチェックリスト(本番)⚠️
- [ ]
additional_authorized_importsを最小限にした - [ ] Hub / MCP は信頼ソースのみ
- [ ] 公開エージェントにレート制限・入力検証
- [ ] 本番では
executor_typeリモートを検討 - [ ] ログに API キーを出していない
- [ ] マルチエージェント時は E2B の制約を読んだ
12.8 よくあるエラー ⚠️
Import of X is not allowed
許可リストに追加するか、本当に必要か 再検討。
E2B / Docker 接続失敗
E2B_API_KEY not set Cannot connect to the Docker daemon
環境変数とデーモン起動を確認。
ローカルでは動くがサンドボックスで失敗
リモート環境に パッケージ未インストール。カスタム Docker イメージで pip install する。
12.9 本章のまとめ
霊夢: まとめるわ。
- LocalPythonExecutor — import・サブモジュール・ループ上限
- 完全安全は無理 — リモート executor で層を足す
executor_type—e2b/docker/modal/blaxelwith/cleanup()— リソース解放- マルチエージェント — リモートスニペット方式に制約
✅ 章末チェックリスト
- [ ]
local_executor_sandbox.pyで 3 種のエラーを確認した - [ ]
blocked_import_agent.pyでos拒否を見た - [ ] 2 つのサンドボックス戦略を説明できる
- [ ]
executor_type="docker"またはe2bの手順を読んだ(任意実行) - [ ] 本番セキュリティチェックリストを眺めた
次章へ
霊夢: 怖かったけど、対策はあるのね。
魔理沙: 次は max_steps や Gradio でエージェントを育てる章だ。ゆっくりしていこうな。
第13章 エージェントを育てる — 設定とデバッグ
本章のゴール:
max_steps・verbosity_level・interrupt・reset=False・ストリーミング を使い分け、GradioUI で対話デバッグできる。
13.1 動くけど、中身が見えない
霊夢: 第 3 章でログは見たけど……本番に近づくと、ステップが多すぎたり、途中で止めたくなったりするのよね。
魔理沙: そのときは エージェントのノブ を回す。max_steps で暴走を防ぎ、verbosity_level でログ量を調整、interrupt() で人間が割り込む。会話アプリなら reset=False で記憶をつなぐ。
| パラメータ / API | 役割 |
|---|---|
max_steps |
1 タスクあたりの最大ステップ数 |
verbosity_level |
コンソールログの詳しさ(0〜2 程度) |
agent.interrupt() |
現在ステップの終了後に停止 |
run(..., reset=False) |
メモリを消さず次タスクへ |
run(..., stream=True) |
ステップをジェネレータで逐次取得 |
stream_outputs=True |
モデル出力のトークンストリーム(対応モデルのみ) |
13.2 max_steps — 無限ループのブレーキ
魔理沙: final_answer しないまま回り続けると、コストも時間も溶ける。既定値はあるが、タスクに合わせて下げたり上げたりする。
from smolagents import CodeAgent, InferenceClientModel agent = CodeAgent( tools=[], model=InferenceClientModel(), max_steps=10, )
打ち切り時の例:
Max steps reached ...
霊夢: 増やせばいい?
魔理沙: 根本は タスク文を具体化 すること。「必ず final_answer で整数のみ」など。max_steps は保険だ。
📦 examples/ch13/verbosity_max_steps.py
export HF_TOKEN="hf_..." python examples/ch13/verbosity_max_steps.py
# examples/ch13/verbosity_max_steps.py(リポジトリ同梱・全文) """第13章: verbosity_level と max_steps の違いを確認""" from __future__ import annotations import os import sys from smolagents import CodeAgent, InferenceClientModel def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) model = InferenceClientModel() task = "1 から 5 までの合計を計算し、整数だけを final_answer で返して。" print("=== verbosity_level=0(静か) ===") quiet = CodeAgent(tools=[], model=model, verbosity_level=0, max_steps=5) print(quiet.run(task)) print("\n=== verbosity_level=2(詳細) ===") verbose = CodeAgent(tools=[], model=model, verbosity_level=2, max_steps=5) print(verbose.run(task)) print("\n=== max_steps=1(打ち切りの例) ===") tight = CodeAgent(tools=[], model=model, verbosity_level=1, max_steps=1) try: print(tight.run("100 個の素数を列挙して final_answer で返して。")) except Exception as exc: print(f"例外または打ち切り: {exc}") if __name__ == "__main__": main()
13.3 verbosity_level — ログの濃さ
| レベル | 目安 |
|---|---|
0 |
静か(最終回答中心) |
1 |
既定。Step・コード・トークン数 |
2 |
より詳細なデバッグ向け |
agent = CodeAgent(tools=[], model=InferenceClientModel(), verbosity_level=0) result = agent.run("2+2 は?")
本番ログ集約では 0、開発中は 1〜2 が多い。
13.4 agent.interrupt() — 人間の停止ボタン
霊夢: Gradio で長いタスクを走らせたら、止めたいわ。
魔理沙: interrupt() は いまのステップが終わったあと に止まる。Gradio の停止ボタンから呼ぶ想定だ。
import threading import time from smolagents import CodeAgent, InferenceClientModel agent = CodeAgent(tools=[], model=InferenceClientModel(), max_steps=20) def stop_later(): time.sleep(8) agent.interrupt() threading.Thread(target=stop_later, daemon=True).start() try: agent.run("非常に長い計算タスク ...") except Exception as e: print("中断:", e)
📦 examples/ch13/interrupt_demo.py
# examples/ch13/interrupt_demo.py(リポジトリ同梱・全文) """第13章: agent.interrupt() で実行を止めるデモ""" from __future__ import annotations import os import sys import threading import time from smolagents import CodeAgent, InferenceClientModel def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) model = InferenceClientModel() agent = CodeAgent(tools=[], model=model, verbosity_level=1, max_steps=12) def interrupt_after_delay() -> None: time.sleep(8) print("\n>>> interrupt() を呼び出します <<<") agent.interrupt() watcher = threading.Thread(target=interrupt_after_delay, daemon=True) watcher.start() task = ( "1 から 200 までの素数をすべて列挙し、" "見つかった個数も含めて final_answer で返して。" "時間がかかってもよい。" ) try: result = agent.run(task) print("=== 最終回答 ===") print(result) except Exception as exc: print("=== 中断またはエラー ===") print(type(exc).__name__, exc) if __name__ == "__main__": main()
13.5 reset=False — 会話をつなぐ
魔理沙: run(task, reset=True)(既定)だと、前のタスクのメモリが消える。チャット UI では reset=False で文脈を継ぐ。
agent.run("私の名前は霊夢。記憶したと返して。", reset=True) agent.run("さっきの私の名前は?", reset=False) # 「霊夢」と答えやすい agent.run("さっきの私の名前は?", reset=True) # 記憶クリア
GradioUI も内部で reset=False を使う(公式 Guided tour)。
📦 examples/ch13/reset_false_demo.py
python examples/ch13/reset_false_demo.py
# examples/ch13/reset_false_demo.py(リポジトリ同梱・全文) """第13章: reset=False で会話メモリを継続""" from __future__ import annotations import os import sys from smolagents import CodeAgent, InferenceClientModel def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) model = InferenceClientModel() agent = CodeAgent(tools=[], model=model, verbosity_level=1, max_steps=6) first = agent.run( "私の名前はゆっくり霊夢。これを覚えて、final_answer で「記憶した」とだけ返して。", reset=True, ) print("--- 1 ターン目 ---") print(first) second = agent.run( "さっき教えた私の名前は? final_answer で名前だけ返して。", reset=False, ) print("--- 2 ターン目(reset=False) ---") print(second) third = agent.run( "さっき教えた私の名前は?", reset=True, ) print("--- 3 ターン目(reset=True で記憶クリア) ---") print(third) if __name__ == "__main__": main()
13.6 ストリーミング出力の概要
霊夢: トークンが少しずつ出るやつ?
魔理沙: 2 層ある。
run(stream=True) — ステップ単位
for step in agent.run("12 * 13 を計算して", stream=True): print(type(step).__name__)
各イテレーションで ActionStep などが返る。UI で「Step ごとに更新」したいとき向き。
stream_outputs=True — LLM トークン単位
agent = CodeAgent(
tools=[],
model=InferenceClientModel(),
stream_outputs=True,
)
モデルが generate_stream を実装している必要がある。未対応なら初期化時に警告される。
📦 examples/ch13/stream_run_demo.py
# examples/ch13/stream_run_demo.py(リポジトリ同梱・全文) """第13章: agent.run(stream=True) でステップを逐次取得""" from __future__ import annotations import os import sys from smolagents import CodeAgent, InferenceClientModel def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) model = InferenceClientModel() agent = CodeAgent(tools=[], model=model, verbosity_level=0, max_steps=5) task = "12 × 13 を計算し、整数を final_answer で返して。" print("=== stream=True: 各ステップを表示 ===") step_gen = agent.run(task, stream=True) for i, step in enumerate(step_gen): print(f"[step {i}] {type(step).__name__}") print("\n=== stream_outputs=True(モデルが generate_stream 対応時) ===") streaming_agent = CodeAgent( tools=[], model=model, verbosity_level=1, max_steps=4, stream_outputs=True, ) print(streaming_agent.run("7 の階乗を計算して final_answer で返して。")) if __name__ == "__main__": main()
🖥️ ハンズオン 13-1 — GradioUI でチャット UI
霊夢: ブラウザで試したい!
魔理沙: pip install 'smolagents[gradio]' が要る。
pip install 'smolagents[gradio]' export HF_TOKEN="hf_..." python examples/ch13/gradio_ui_agent.py
# examples/ch13/gradio_ui_agent.py(リポジトリ同梱・全文) """第13章: GradioUI でチャット形式のエージェント UI""" from __future__ import annotations import os import sys from smolagents import CodeAgent, GradioUI, InferenceClientModel, WebSearchTool def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) try: import gradio # noqa: F401 except ImportError: print("Gradio が未インストールです: pip install 'smolagents[gradio]'") sys.exit(1) model = InferenceClientModel() agent = CodeAgent( tools=[WebSearchTool()], model=model, add_base_tools=False, verbosity_level=1, max_steps=10, ) print("ブラウザで Gradio UI を開きます。終了は Ctrl+C。") print("内部では各送信ごとに agent.run(message, reset=False) が呼ばれます。") GradioUI(agent).launch(share=False) if __name__ == "__main__": main()
- 各メッセージ送信 →
agent.run(ユーザー入力, reset=False) - 思考・コード・ツール結果がチャット UI に流れる
- 停止ボタン →
agent.interrupt()を接続できる(カスタム UI 時)
⚠️ share=True は一時的な公開 URL が作られる。デモ以外は False 推奨。
🖥️ ハンズオン 13-2 — reset=False を 2 ターンで体験
python examples/ch13/reset_false_demo.py
# examples/ch13/reset_false_demo.py(リポジトリ同梱・全文) """第13章: reset=False で会話メモリを継続""" from __future__ import annotations import os import sys from smolagents import CodeAgent, InferenceClientModel def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) model = InferenceClientModel() agent = CodeAgent(tools=[], model=model, verbosity_level=1, max_steps=6) first = agent.run( "私の名前はゆっくり霊夢。これを覚えて、final_answer で「記憶した」とだけ返して。", reset=True, ) print("--- 1 ターン目 ---") print(first) second = agent.run( "さっき教えた私の名前は? final_answer で名前だけ返して。", reset=False, ) print("--- 2 ターン目(reset=False) ---") print(second) third = agent.run( "さっき教えた私の名前は?", reset=True, ) print("--- 3 ターン目(reset=True で記憶クリア) ---") print(third) if __name__ == "__main__": main()
2 ターン目で名前を覚えていれば成功。3 ターン目 reset=True では忘れるのが正常。
13.7 よくあるエラー ⚠️
Max steps reached
タスクを短くする・final_answer を明示・max_steps を少し増やす。
stream_outputs の警告
別モデルにするか stream_outputs=False に戻す。
Gradio が import できない
pip install 'smolagents[gradio]'
13.8 本章のまとめ
霊夢: まとめるわ。
max_steps— 暴走とコストの上限verbosity_level— 開発時は上げ、本番は下げるinterrupt()— ステップ境界での安全な停止reset=False— チャット継続・Gradio 既定- ストリーム —
stream=True(ステップ)とstream_outputs(トークン)
✅ 章末チェックリスト
- [ ]
verbosity_max_steps.pyでログの濃さの差を見た - [ ]
reset_false_demo.pyで 2 ターン目が文脈を引き継ぐことを確認した - [ ] (任意)
interrupt_demo.pyを実行した - [ ] (任意)
gradio_ui_agent.pyで UI を開いた - [ ]
stream=Trueとstream_outputsの違いを説明できる
次章へ
霊夢: ノブが増えたわ。次は「しょぼいエージェント」の改善?
魔理沙: 第 14 章で ツール設計と API 統合 だ。ゆっくりしていこうな。
第14章 うまいエージェントの作り方 — シンプルさとツール設計
本章のゴール: LLM 呼び出しを減らす設計と 読みやすいツールを身につけ、天気 API を 悪い例→良い例→統合ツール でリファクタする。
14.1 動くけど、しょぼい……
霊夢: エージェント、動くんだけどステップばっかりで遅いのよね。
魔理沙: 多くは 設計の問題 だ。LLM は部屋の中に閉じ込められ、ツール結果だけが窓から渡される。情報が薄いと、何度も試行錯誤する。
14.2 最優先: ワークフローを単純に
魔理沙: ベストなマルチエージェントより、単純な単一エージェント + 良いツール の方が勝つことが多い。
| 原則 | 具体策 |
|---|---|
| LLM 呼び出しを減らす | 2 つの API を 1 ツール にまとめる |
| 決定論に寄せる | 計算・整形は Python / ツール内で完結 |
| タスクを明確に | 曖昧な「調べて」より入出力形式を指定 |
flowchart LR
subgraph bad[悪い例]
A1[LLM] --> T1[天気 API]
A1 --> T2[距離 API]
end
subgraph good[良い例]
A2[LLM] --> M[return_spot_information]
M --> T1
M --> T2
end
14.3 ツール設計 — 天気 API の悪い例
霊夢: 何が「悪い」の?
魔理沙: 公式チュートリアルの Poor version ベースだ。
date_timeの形式が不明locationの書き方が不明- エラー時の説明がない
- 戻り値が
str([28.0, 0.35, 0.85])で読みにくい forward内に ログがない
@tool def get_weather_api(location: str, date_time: str) -> str: """ Returns the weather report. Args: location: the name of the place ... date_time: the date and time ... """ lon, lat = convert_location_to_coordinates(location) parsed = datetime.datetime.strptime(date_time, "%m/%d/%y %H:%M:%S") return str(get_weather_report_at_coordinates((lon, lat), parsed))
📦 examples/ch14/bad_weather_tool.py
export HF_TOKEN="hf_..." python examples/ch14/bad_weather_tool.py
# examples/ch14/bad_weather_tool.py(リポジトリ同梱・全文) """第14章: 悪い例 — ログ・形式・エラー説明が不足した天気ツール""" from __future__ import annotations import datetime import os import sys from smolagents import CodeAgent, InferenceClientModel, tool def _coords_from_location(location: str) -> tuple[float, float]: # デモ用ダミー座標 return 3.3, -42.0 def _weather_at_coords(coords: tuple[float, float], date_time: datetime.datetime) -> list[float]: # [気温°C, 降水リスク 0-1, 波の高さ m] return [28.0, 0.35, 0.85] @tool def get_weather_api(location: str, date_time: str) -> str: """ Returns the weather report. Args: location: the name of the place that you want the weather for. date_time: the date and time for which you want the report. """ lon, lat = _coords_from_location(location) parsed = datetime.datetime.strptime(date_time, "%m/%d/%y %H:%M:%S") return str(_weather_at_coords((lon, lat), parsed)) def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) agent = CodeAgent( tools=[get_weather_api], model=InferenceClientModel(), verbosity_level=1, max_steps=8, ) task = ( "2026/05/23 12:00:00 の時点で、" "モロッコのタガズートのサーフスポットの天気を教えて。" ) print(agent.run(task)) if __name__ == "__main__": main()
日付形式を間違えると、LLM が何度もリトライしがちだ。
14.4 ツール設計 — 良い例へリファクタ
魔理沙: 自問:「初めてこのツールを使う自分が、エラーを直せるか?」
改善ポイント:
- Args に具体例(国名まで、日時フォーマット)
printでログ(ツール名・引数・失敗理由)ValueErrorに修正ヒント を載せる- 人間が読める 1 文 で返す
DATE_FMT = "%m/%d/%y %H:%M:%S" @tool def get_weather_api(location: str, date_time: str) -> str: """ 指定した場所・日時のサーフ向け天気レポートを返す。 Args: location: 例: "Anchor Point, Taghazout, Morocco" date_time: 例: '05/23/26 12:00:00'(形式 '%m/%d/%y %H:%M:%S') """ lon, lat = _coords_from_location(location) try: parsed = datetime.datetime.strptime(date_time, DATE_FMT) except ValueError as exc: raise ValueError( f"date_time は '{DATE_FMT}' 形式で渡してください。詳細: {exc}" ) from exc temp_c, rain_risk, wave_m = _weather_at_coords((lon, lat), parsed) return ( f"Weather report for {location}, {parsed.strftime(DATE_FMT)}: " f"Temperature {temp_c}°C, rain risk {rain_risk * 100:.0f}%, " f"wave height {wave_m}m." )
📦 examples/ch14/good_weather_tool.py
python examples/ch14/good_weather_tool.py
# examples/ch14/good_weather_tool.py(リポジトリ同梱・全文) """第14章: 良い例 — 形式・ログ・読みやすい出力の天気ツール""" from __future__ import annotations import datetime import os import sys from smolagents import CodeAgent, InferenceClientModel, tool DATE_FMT = "%m/%d/%y %H:%M:%S" def _coords_from_location(location: str) -> tuple[float, float]: print(f"[get_weather_api] 座標変換: location={location!r}") if not location.strip(): raise ValueError("location が空です。例: 'Anchor Point, Taghazout, Morocco'") return 3.3, -42.0 def _weather_at_coords(coords: tuple[float, float], date_time: datetime.datetime) -> tuple[float, float, float]: print(f"[get_weather_api] 天気取得: coords={coords}, at={date_time.isoformat()}") return 28.0, 0.35, 0.85 @tool def get_weather_api(location: str, date_time: str) -> str: """ 指定した場所・日時のサーフ向け天気レポートを返す。 Args: location: 場所名。例: "Anchor Point, Taghazout, Morocco"(国名まで含めるとよい)。 date_time: 日時。必ず '%m/%d/%y %H:%M:%S' 形式の文字列(例: '05/23/26 12:00:00')。 """ lon, lat = _coords_from_location(location) try: parsed = datetime.datetime.strptime(date_time, DATE_FMT) except ValueError as exc: raise ValueError( "date_time の変換に失敗しました。" f"形式は '{DATE_FMT}' です。例: '05/23/26 12:00:00'。" f"詳細: {exc}" ) from exc temp_c, rain_risk, wave_m = _weather_at_coords((lon, lat), parsed) return ( f"Weather report for {location}, {parsed.strftime(DATE_FMT)}: " f"Temperature {temp_c}°C, rain risk {rain_risk * 100:.0f}%, wave height {wave_m}m." ) def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) agent = CodeAgent( tools=[get_weather_api], model=InferenceClientModel(), verbosity_level=1, max_steps=6, ) task = ( "2026年5月23日12時(現地)の、モロッコ・タガズートの天気を " "get_weather_api で取得し、要約して final_answer で返して。" f"date_time は必ず {DATE_FMT!r} 形式で渡すこと。" ) print(agent.run(task)) if __name__ == "__main__": main()
霊夢: ログに [get_weather_api] が出ると、デバッグしやすそうね。
14.5 タスク文の書き方
魔理沙: ツールだけじゃない。タスク もプロンプトの一部だ。
| 悪い例 | 良い例 |
|---|---|
| 天気を教えて | date_time は '%m/%d/%y %H:%M:%S' で get_weather_api を 1 回使え |
| 調べて | 公式ドキュメント URL を ## 出典 に列挙せよ |
| レポートを書いて | Markdown で 3 節、各節 3 行以内 |
エージェント全体への恒久指示は instructions=(システムプロンプトに追記)。
agent = CodeAgent(
tools=[...],
model=model,
instructions="最終回答は日本語。出典 URL を必ず含める。",
)
14.6 2 つの API を 1 ツールに統合
霊夢: 天気と距離、別々に呼ばせるのが悪いの?
魔理沙: サーフ旅行の例では 1 回のツール呼び出し で両方返す方が、ステップ数・レイテンシ・失敗率が下がる。
@tool def return_spot_information( origin_city: str, surf_spot: str, date_time: str, ) -> str: """ 移動時間とサーフスポットの天気をまとめて返す。 ... """ hours = _travel_hours(origin_city, surf_spot) weather = _weather_report(surf_spot, parsed) return f"Travel ...\nWeather: {weather}"
📦 examples/ch14/merged_spot_info_agent.py
python examples/ch14/merged_spot_info_agent.py
# examples/ch14/merged_spot_info_agent.py(リポジトリ同梱・全文) """第14章: 天気 API と距離 API を 1 ツールに統合して LLM 呼び出しを減らす""" from __future__ import annotations import datetime import os import sys from smolagents import CodeAgent, InferenceClientModel, tool DATE_FMT = "%m/%d/%y %H:%M:%S" def _travel_hours(origin: str, destination: str) -> float: print(f"[return_spot_information] 距離 API: {origin!r} -> {destination!r}") return 2.5 def _weather_report(location: str, date_time: datetime.datetime) -> str: print(f"[return_spot_information] 天気 API: {location!r} @ {date_time.isoformat()}") return ( f"Temperature 26°C, rain risk 20%, wave height 1.2m " f"at {location} on {date_time.strftime(DATE_FMT)}." ) @tool def return_spot_information( origin_city: str, surf_spot: str, date_time: str, ) -> str: """ サーフスポットへの移動時間と、そのスポットの天気をまとめて返す。 Args: origin_city: 出発都市(例: 'Marrakech, Morocco')。 surf_spot: サーフスポット名(例: 'Taghazout, Morocco')。 date_time: 現地の日時。'%m/%d/%y %H:%M:%S' 形式(例: '05/23/26 12:00:00')。 """ try: parsed = datetime.datetime.strptime(date_time, DATE_FMT) except ValueError as exc: raise ValueError( f"date_time は '{DATE_FMT}' 形式で渡してください。詳細: {exc}" ) from exc hours = _travel_hours(origin_city, surf_spot) weather = _weather_report(surf_spot, parsed) return ( f"Travel from {origin_city} to {surf_spot}: about {hours:.1f} hours.\n" f"Weather: {weather}" ) def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) agent = CodeAgent( tools=[return_spot_information], model=InferenceClientModel(), verbosity_level=1, max_steps=5, ) task = ( "マラケシュからタガズートへ行くサーフ旅行について、" "移動時間と 2026/05/23 12:00:00 時点の天気をまとめて教えて。" "ツールは return_spot_information を 1 回だけ使うこと。" ) print(agent.run(task)) if __name__ == "__main__": main()
タスク側で「ツールは 1 回だけ」と書くと、さらに安定する。
🖥️ ハンズオン 14-1 — 悪い天気ツール → 良いツール
霊夢: 比較したいわ。
魔理沙: 同じタスク文で bad と good を実行し、ステップ数とログ を比べろ。
python examples/ch14/bad_weather_tool.py
# examples/ch14/bad_weather_tool.py(リポジトリ同梱・全文) """第14章: 悪い例 — ログ・形式・エラー説明が不足した天気ツール""" from __future__ import annotations import datetime import os import sys from smolagents import CodeAgent, InferenceClientModel, tool def _coords_from_location(location: str) -> tuple[float, float]: # デモ用ダミー座標 return 3.3, -42.0 def _weather_at_coords(coords: tuple[float, float], date_time: datetime.datetime) -> list[float]: # [気温°C, 降水リスク 0-1, 波の高さ m] return [28.0, 0.35, 0.85] @tool def get_weather_api(location: str, date_time: str) -> str: """ Returns the weather report. Args: location: the name of the place that you want the weather for. date_time: the date and time for which you want the report. """ lon, lat = _coords_from_location(location) parsed = datetime.datetime.strptime(date_time, "%m/%d/%y %H:%M:%S") return str(_weather_at_coords((lon, lat), parsed)) def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) agent = CodeAgent( tools=[get_weather_api], model=InferenceClientModel(), verbosity_level=1, max_steps=8, ) task = ( "2026/05/23 12:00:00 の時点で、" "モロッコのタガズートのサーフスポットの天気を教えて。" ) print(agent.run(task)) if __name__ == "__main__": main()
python examples/ch14/good_weather_tool.py
# examples/ch14/good_weather_tool.py(リポジトリ同梱・全文) """第14章: 良い例 — 形式・ログ・読みやすい出力の天気ツール""" from __future__ import annotations import datetime import os import sys from smolagents import CodeAgent, InferenceClientModel, tool DATE_FMT = "%m/%d/%y %H:%M:%S" def _coords_from_location(location: str) -> tuple[float, float]: print(f"[get_weather_api] 座標変換: location={location!r}") if not location.strip(): raise ValueError("location が空です。例: 'Anchor Point, Taghazout, Morocco'") return 3.3, -42.0 def _weather_at_coords(coords: tuple[float, float], date_time: datetime.datetime) -> tuple[float, float, float]: print(f"[get_weather_api] 天気取得: coords={coords}, at={date_time.isoformat()}") return 28.0, 0.35, 0.85 @tool def get_weather_api(location: str, date_time: str) -> str: """ 指定した場所・日時のサーフ向け天気レポートを返す。 Args: location: 場所名。例: "Anchor Point, Taghazout, Morocco"(国名まで含めるとよい)。 date_time: 日時。必ず '%m/%d/%y %H:%M:%S' 形式の文字列(例: '05/23/26 12:00:00')。 """ lon, lat = _coords_from_location(location) try: parsed = datetime.datetime.strptime(date_time, DATE_FMT) except ValueError as exc: raise ValueError( "date_time の変換に失敗しました。" f"形式は '{DATE_FMT}' です。例: '05/23/26 12:00:00'。" f"詳細: {exc}" ) from exc temp_c, rain_risk, wave_m = _weather_at_coords((lon, lat), parsed) return ( f"Weather report for {location}, {parsed.strftime(DATE_FMT)}: " f"Temperature {temp_c}°C, rain risk {rain_risk * 100:.0f}%, wave height {wave_m}m." ) def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) agent = CodeAgent( tools=[get_weather_api], model=InferenceClientModel(), verbosity_level=1, max_steps=6, ) task = ( "2026年5月23日12時(現地)の、モロッコ・タガズートの天気を " "get_weather_api で取得し、要約して final_answer で返して。" f"date_time は必ず {DATE_FMT!r} 形式で渡すこと。" ) print(agent.run(task)) if __name__ == "__main__": main()
意図的に date_time を曖昧にしたタスクを試すと差が出やすい。
🖥️ ハンズオン 14-2 — API 統合でステップ削減
python examples/ch14/merged_spot_info_agent.py
# examples/ch14/merged_spot_info_agent.py(リポジトリ同梱・全文) """第14章: 天気 API と距離 API を 1 ツールに統合して LLM 呼び出しを減らす""" from __future__ import annotations import datetime import os import sys from smolagents import CodeAgent, InferenceClientModel, tool DATE_FMT = "%m/%d/%y %H:%M:%S" def _travel_hours(origin: str, destination: str) -> float: print(f"[return_spot_information] 距離 API: {origin!r} -> {destination!r}") return 2.5 def _weather_report(location: str, date_time: datetime.datetime) -> str: print(f"[return_spot_information] 天気 API: {location!r} @ {date_time.isoformat()}") return ( f"Temperature 26°C, rain risk 20%, wave height 1.2m " f"at {location} on {date_time.strftime(DATE_FMT)}." ) @tool def return_spot_information( origin_city: str, surf_spot: str, date_time: str, ) -> str: """ サーフスポットへの移動時間と、そのスポットの天気をまとめて返す。 Args: origin_city: 出発都市(例: 'Marrakech, Morocco')。 surf_spot: サーフスポット名(例: 'Taghazout, Morocco')。 date_time: 現地の日時。'%m/%d/%y %H:%M:%S' 形式(例: '05/23/26 12:00:00')。 """ try: parsed = datetime.datetime.strptime(date_time, DATE_FMT) except ValueError as exc: raise ValueError( f"date_time は '{DATE_FMT}' 形式で渡してください。詳細: {exc}" ) from exc hours = _travel_hours(origin_city, surf_spot) weather = _weather_report(surf_spot, parsed) return ( f"Travel from {origin_city} to {surf_spot}: about {hours:.1f} hours.\n" f"Weather: {weather}" ) def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) agent = CodeAgent( tools=[return_spot_information], model=InferenceClientModel(), verbosity_level=1, max_steps=5, ) task = ( "マラケシュからタガズートへ行くサーフ旅行について、" "移動時間と 2026/05/23 12:00:00 時点の天気をまとめて教えて。" "ツールは return_spot_information を 1 回だけ使うこと。" ) print(agent.run(task)) if __name__ == "__main__": main()
ログで return_spot_information が 1 回 呼ばれていれば成功に近い。
14.7 計画(planning)と stream=True(概要)
魔理沙: 複雑タスクではエージェントが 計画ステップ を挟むことがある。run(stream=True) で PlanningStep も逐次見られる(第 13 章)。
本番では:
- 計画を許すタスク →
max_stepsに余裕 - 単純タスク → ツール統合で計画自体不要にする
14.8 デバッグの順序
- より強いモデル に切り替え(コストとトレードオフ)
- タスク・ツール説明 を具体化
verbosity_levelを上げてログ確認- (最後の手段)プロンプトテンプレート変更 — 公式は非推奨気味
14.9 よくあるエラー ⚠️
ツールを何度も呼ぶ
→ タスクに「1 回だけ」と書く、または API を統合。
strptime 失敗のループ
→ ツールの docstring と ValueError メッセージに 正しい例 を書く。
14.10 本章のまとめ
霊夢: まとめ。
- シンプルなワークフロー が最強に近い
- ツール — 形式・ログ・読みやすい出力・エラーメッセージ
- タスク文 — 入出力と回数制限を明示
- API 統合 — LLM ステップとコストを削る
✅ 章末チェックリスト
- [ ] 悪い例と良い例の差を 3 点説明できる
- [ ]
good_weather_tool.pyを実行した - [ ]
merged_spot_info_agent.pyで統合ツールを試した - [ ] 公式 Building good agents を一読した
次章へ
霊夢: ツールの説明文、地味だけど大事ね。
魔理沙: 次は 複数エージェント で専門化するぜ。ゆっくりしていこうな。
第15章 マネージャーと専門家 — managed_agents
本章のゴール:
name/description/managed_agentsでマネージャーと検索専門エージェントを組み立て、ログから どのエージェントが呼ばれたか 追跡できる。
15.1 なぜマルチエージェントか
霊夢: ツールを増やすんじゃなくて、エージェントを増やすの?
魔理沙: 専門化 と メモリ分離 だ。Web 検索エージェントの履歴に、コード生成の失敗ログを詰め込まなくていい。ベンチマークでは、役割分担した方が成績が上がることも多い。
flowchart TB User[ユーザー] --> M[マネージャー CodeAgent] M -->|task 文字列| W[web_search_agent] W -->|要約結果| M M --> Answer[final_answer]
15.2 name と description — ツールと同じ重要性
魔理沙: マネージドエージェントも ツールと同様、初期化時にマネージャーのシステムプロンプトへ埋め込まれる。
| 属性 | 役割 |
|---|---|
name |
呼び出し名(Python 関数名のように使う) |
description |
いつ・何を渡すかの説明 |
web_agent = CodeAgent(
tools=[WebSearchTool()],
model=model,
name="web_search_agent",
description=(
"Runs web searches for you. Give it your query as an argument."
),
)
霊夢: description が曖昧だと?
魔理沙: マネージャーが 間違った専門家 を呼ぶか、タスク文が薄くて失敗する。第 14 章のツール設計と同じ思想だ。
15.3 managed_agents — マネージャーの組み立て
魔理沙: マネージャー側は tools=[] でも、チームメンバー呼び出し がプロンプトに載る。
from smolagents import CodeAgent, InferenceClientModel, WebSearchTool model = InferenceClientModel() web_agent = CodeAgent( tools=[WebSearchTool()], model=model, name="web_search_agent", description="Runs web searches for you. Give it your query as an argument.", add_base_tools=False, ) manager_agent = CodeAgent( tools=[], model=model, managed_agents=[web_agent], ) manager_agent.run("Who is the CEO of Hugging Face?")
マネージャーのコード生成では、だいたい次のように見える:
result = web_search_agent(task="Hugging Face CEO 2026 ...") print(result)
15.4 メモリとツールセットの切り分け
| 専門家 | ツール例 | メモリ |
|---|---|---|
web_search_agent |
WebSearchTool |
検索クエリ・URL |
code_agent |
(空 + Python 実行) | 計算・整形 |
| マネージャー | なし(委譲のみ) | 最終統合 |
霊夢: 全部 1 人のエージェントにツールを載せるよりいい?
魔理沙: 単純タスクなら 1 人の方が安い(第 14 章)。複雑・長文・役割がぶつかるときにマルチを検討する。
🖥️ ハンズオン 15-1 — web_search 専門 + マネージャー
霊夢: 動かしてみるわ。
📦 examples/ch15/manager_web_search.py
export HF_TOKEN="hf_..." python examples/ch15/manager_web_search.py
# examples/ch15/manager_web_search.py(リポジトリ同梱・全文) """第15章: マネージャー CodeAgent + Web 検索専門エージェント""" from __future__ import annotations import os import sys from smolagents import CodeAgent, InferenceClientModel, WebSearchTool def build_agents(model: InferenceClientModel) -> CodeAgent: web_agent = CodeAgent( tools=[WebSearchTool()], model=model, name="web_search_agent", description=( "Runs web searches for you. Give it your query as a detailed task string. " "Returns summarized search results with URLs when possible." ), add_base_tools=False, verbosity_level=1, max_steps=8, ) manager = CodeAgent( tools=[], model=model, managed_agents=[web_agent], verbosity_level=1, max_steps=10, ) return manager def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) model = InferenceClientModel() manager = build_agents(model) task = "Hugging Face の CEO は誰?根拠 URL を含めて final_answer で答えて。" print(manager.run(task)) if __name__ == "__main__": main()
Step 0: web_search_agent(task=...) ... Out - Final answer: ...
🖥️ ハンズオン 15-2 — ログで呼び出しを追跡
📦 examples/ch15/trace_managed_agent_logs.py
python examples/ch15/trace_managed_agent_logs.py
# examples/ch15/trace_managed_agent_logs.py(リポジトリ同梱・全文) """第15章: ログからマネージドエージェント呼び出しを追跡""" from __future__ import annotations import json import os import sys from smolagents import CodeAgent, InferenceClientModel, WebSearchTool def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) model = InferenceClientModel() web_agent = CodeAgent( tools=[WebSearchTool()], model=model, name="web_search_agent", description="Runs web searches. Pass a clear search task.", add_base_tools=False, verbosity_level=1, max_steps=6, ) manager = CodeAgent( tools=[], model=model, managed_agents=[web_agent], verbosity_level=1, max_steps=8, ) manager.run("smolagents とは何? 2 文で要約して。") print("=== agent.logs の件数 ===", len(manager.logs)) for i, entry in enumerate(manager.logs): text = json.dumps(entry, ensure_ascii=False, default=str) if "web_search_agent" in text or "managed" in text.lower(): print(f"\n--- log[{i}] (managed 関連) ---") print(text[:1500]) if __name__ == "__main__": main()
霊夢: web_search_agent という文字列が log に出てた!
魔理沙: 本番ではこの文字列や Step の tool_calls 相当をメトリクスに載せると、どの専門家が高コストか 分かる。
15.5 additional_args で専門家にコンテキストを渡す
魔理沙: マネージドエージェント呼び出しも、ツールと同様 additional_args で画像や DataFrame を渡せる(公式プロンプトテンプレート参照)。
# マネージャーのタスク例 manager.run( "code_agent にこの CSV を集計させて", additional_args={"dataframe": df}, )
15.6 コストと E2B ⚠️
霊夢: 第 12 章のサンドボックスと両立する?
魔理沙: マネージドエージェント + リモート executor(E2B スニペット方式) には制約がある。本番構成では公式 Secure code execution を再確認しろ。
15.7 よくあるエラー ⚠️
マネージャーが自分で検索しようとする
description を「検索が必要なら 必ず web_search_agent を呼べ」と具体化。
専門家が final_answer しない
専門家タスクに「結果を テキストで返す 。final_answer はマネージャーが行う」と書くか、専門家にも max_steps を適切に設定。
名前の衝突
name は Python 識別子として有効な文字(スネークケース推奨)。
15.8 本章のまとめ
霊夢: まとめ。
- マルチエージェント — 専門化とメモリ分離
name/description— ツールと同レベルで丁寧にmanaged_agents=[...]— マネージャーにチームを登録- ログ —
web_search_agent等で呼び出しを追跡
✅ 章末チェックリスト
- [ ]
manager_web_search.pyを実行した - [ ]
trace_managed_agent_logs.pyで専門家名をログから見つけた - [ ] マネージャーと専門家の役割分担を図で説明できる
- [ ] 単一エージェントの方がよいケースを 1 つ挙げられる
次章へ
霊夢: チーム編成、楽しいわね。
魔理沙: 次は GAIA 級の 3 役構成 だ。ゆっくりしていこうな。
第16章 実践マルチエージェント — GAIA スタイルの構成
本章のゴール: 検索・コード・マネージャー の 3 役にツールを切り分け、調査レポート タスクを GAIA ブログの要点に沿って実行する。
16.1 GAIA と「役割分担」
霊夢: GAIA って何?
魔理沙: 現実世界の質問に答える エージェント・ベンチマーク だ。HF チームは smolagents でマルチエージェント構成を組み、リーダーボード上位を狙った。
要点(本書用に圧縮):
| 要点 | 内容 |
|---|---|
| 専門化 | 検索・閲覧・計算でエージェントを分ける |
| ツールの最小化 | 各専門家に 必要なツールだけ |
| マネージャー | 計画と最終統合。重い閲覧履歴を抱えない |
| 検証 | 可能なら別ステップで事実確認(本章はマネージャー指示で簡略化) |
16.2 3 役の設計パターン
flowchart TB User[調査レポート依頼] --> M[report_manager] M --> S[search_agent<br/>WebSearch + VisitWebpage] M --> C[code_agent<br/>Python 計算] S --> M C --> M M --> Report[Markdown レポート + 出典]
| 役 | name |
ツール | 責務 |
|---|---|---|---|
| 検索 | search_agent |
WebSearchTool, VisitWebpageTool |
検索・ページ読取・要点抽出 |
| コード | code_agent |
(なし) | 数値検算・表整形 |
| マネージャー | report_manager |
なし | 委譲・レポート形式・出典セクション |
霊夢: 検証役は?
魔理沙: 本番 GAIA 構成では ファクトチェック 用のエージェントを足すこともある。本章は 3 役 + マネージャー指示で「出典必須」に留める(第 17 章で単一エージェントのリサーチも学ぶ)。
16.3 ツールセットの切り分け
魔理沙: code_agent に WebSearchTool を渡すと、検索ログがコード専門家のメモリを汚す。渡さない。
search_agent = CodeAgent(
tools=[WebSearchTool(), VisitWebpageTool()],
name="search_agent",
description="Web 検索とページ閲覧の専門家。要点と URL を返す。",
add_base_tools=False,
)
code_agent = CodeAgent(
tools=[],
name="code_agent",
description="数値計算・データ整形の専門家。",
)
16.4 コストとレイテンシのトレードオフ
| 構成 | LLM 呼び出し | 向き |
|---|---|---|
| 単一 + 全ツール | 少なめ | 簡単タスク |
| 3 役マルチ | 多め | 長い調査・役割衝突を避けたい |
| 統合ツール(第 14 章) | 中程度 | API 呼び出しの重複を削る |
霊夢: 常にマルチが正解?
魔理沙: 違う。まず単一 + 良いツール。足りなければ専門家を増やす。
🖥️ ハンズオン 16-1 — 3 役で調査レポート
霊夢: レポート、書いてもらうのよ!
📦 examples/ch16/gaia_three_agent_report.py
export HF_TOKEN="hf_..." python examples/ch16/gaia_three_agent_report.py
# examples/ch16/gaia_three_agent_report.py(リポジトリ同梱・全文) """第16章: 3 役(検索・コード・マネージャー)で調査レポートを生成""" from __future__ import annotations import os import sys from smolagents import CodeAgent, InferenceClientModel, VisitWebpageTool, WebSearchTool def build_research_system(model: InferenceClientModel) -> CodeAgent: search_agent = CodeAgent( tools=[WebSearchTool(), VisitWebpageTool()], model=model, name="search_agent", description=( "Web 検索とページ閲覧の専門家。" "調査タスクを受け取り、要点と出典 URL を箇条書きで返す。" ), add_base_tools=False, verbosity_level=1, max_steps=10, ) code_agent = CodeAgent( tools=[], model=model, name="code_agent", description=( "数値計算・データ整形の専門家。" "検索結果の数値を検算したり、表形式にまとめたりする。" ), verbosity_level=1, max_steps=8, ) manager = CodeAgent( tools=[], model=model, name="report_manager", description="調査レポートを統合するマネージャー。", managed_agents=[search_agent, code_agent], instructions=( "最終回答は Markdown 形式の短い調査レポートにすること。" "必ず ## 出典 セクションに URL を列挙すること。" "search_agent に調査、必要なら code_agent に計算を任せる。" ), verbosity_level=1, max_steps=12, ) return manager def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) model = InferenceClientModel() manager = build_research_system(model) topic = "smolagents" task = ( f"トピック「{topic}」について調査レポートを書いて。" "概要、主な特徴 3 点、公式ドキュメント URL を含めること。" "数値の比較があれば code_agent で検算してよい。" ) print(manager.run(task)) if __name__ == "__main__": main()
期待する流れ:
- マネージャーが
search_agent(task=...)を計画 - 必要なら
code_agentで数値整理 - マネージャーが
## 概要## 特徴## 出典形式でfinal_answer
Step 0: search_agent(task=...) Step 1: ... Out - Final answer: ## 概要 ...
16.5 ブログ要点の整理
魔理沙: Beating GAIA から持ち帰るべきこと:
- エージェントは薄く、ツールは厚く(ただしツール数は増やしすぎない)
- マネージャーは委譲に徹する — 自分で Web を読み始めないよう
descriptionを書く - 出典と検算 — ハルシネーション対策はプロセスで入れる
- モデル選定 — 難問ほど強いモデル(第 4 章)
16.6 よくあるエラー ⚠️
レポートに URL がない
マネージャーの instructions とタスク文の両方に「## 出典」を要求。
search_agent が final_answer して終わる
専門家の description に「マネージャー向けに中間結果を返す」と明記。
ステップ過多
max_steps を役割ごとに絞る、またはトピックを狭める(「smolagents のインストール方法のみ」など)。
16.7 本章のまとめ
霊夢: まとめ。
- GAIA — 現実的な質問でエージェントを評価するベンチマーク
- 3 役 — 検索・コード・マネージャー
- ツール切り分け — 専門家に必要最小限
- トレードオフ — コスト増 ↔ 品質・安定性
✅ 章末チェックリスト
- [ ]
gaia_three_agent_report.pyを実行した - [ ] ログで
search_agent/code_agentの呼び出しを確認した - [ ] Beating GAIA blog の見出しを 3 つ言える
- [ ] 単一エージェントの方がよいケースを説明できる
次章へ
霊夢: チーム戦、なんか本格的ね。
魔理沙: 次章は 1 体の Web リサーチャー に絞る。VisitWebpage と出典の付け方だ。ゆっくりしていこうな。
第17章 Web リサーチエージェント — 検索・取得・要約
本章のゴール:
WebSearchTool+VisitWebpageToolで検索→ページ取得→要約のパイプラインを組み、「smolagents の最新バージョン」を 出典付き で調べさせる。
17.1 Web リサーチの基本パイプライン
霊夢: 検索結果のスニペットだけじゃ足りないことがあるのよね。
魔理沙: 本格的なリサーチは次の 3 段だ。
sequenceDiagram participant A as CodeAgent participant S as WebSearchTool participant V as VisitWebpageTool A->>S: web_search(query) S-->>A: URL 一覧 A->>V: visit_webpage(url) V-->>A: Markdown 化本文 A->>A: 要約 + final_answer
| 段 | ツール | 出力 |
|---|---|---|
| 発見 | WebSearchTool |
タイトル・スニペット・URL |
| 精読 | VisitWebpageTool |
HTML → Markdown 近似 |
| 統合 | (エージェント) | 回答 + 出典 URL |
17.2 VisitWebpageTool と markdown 化
魔理沙: 第 10 章では ToolCallingAgent 向けに登場した。CodeAgent でも tools=[VisitWebpageTool()] で使える。
from smolagents import VisitWebpageTool tool = VisitWebpageTool() # 手動テスト(エージェント外) # print(tool("https://huggingface.co/docs/smolagents"))
- 長いページは トークン制限 に注意 → タスクで「公式ドキュメントの Installation 節だけ読め」と絞る
- JavaScript 多用サイトは本文が取れないことがある → 別 URL を試させる
17.3 出典を最終回答に含める
霊夢: URL、忘れがちでしょ?
魔理沙: instructions とタスク文の 両方 で縛るのが確実だ。
agent = CodeAgent(
tools=[WebSearchTool(), VisitWebpageTool()],
model=model,
instructions=(
"final_answer には必ず ## 回答 と ## 出典(URL の箇条書き)を含める。"
),
)
第 14 章の「タスク文の具体化」もセットで使え。
17.4 CodeAgent での典型的なコード
公式チュートリアルに近い流れ:
pages = web_search(query="smolagents pypi version") print(pages) for url in ["https://pypi.org/project/smolagents/", "..."]: text = visit_webpage(url) print(text[:500])
エージェントはこのパターンを 1 Step または複数 Step で書く。print 出力が次 Step の Observation になる(第 3 章)。
🖥️ ハンズオン 17-1 — 最新バージョンを調査
霊夢: smolagents、今何版なの?
魔理沙: 📦 完成例を動かせ。
export HF_TOKEN="hf_..." python examples/ch17/web_research_agent.py
# examples/ch17/web_research_agent.py(リポジトリ同梱・全文) """第17章: 検索 + VisitWebpageTool で Web リサーチ""" from __future__ import annotations import os import sys from smolagents import CodeAgent, InferenceClientModel, VisitWebpageTool, WebSearchTool def build_agent(model: InferenceClientModel) -> CodeAgent: return CodeAgent( tools=[WebSearchTool(), VisitWebpageTool()], model=model, add_base_tools=False, verbosity_level=1, max_steps=12, instructions=( "Web 調査では次の手順を守ること。" "1) web_search で候補 URL を得る。" "2) visit_webpage で本文を読む。" "3) final_answer には ## 回答 と ## 出典(URL 一覧)を含める。" ), ) def main() -> None: if not os.environ.get("HF_TOKEN") and not os.environ.get("HUGGINGFACEHUB_API_TOKEN"): print("HF_TOKEN が未設定のためスキップします。") sys.exit(0) model = InferenceClientModel() agent = build_agent(model) task = ( "smolagents の最新の安定版バージョン番号は?" "PyPI または公式ドキュメントを visit_webpage で確認し、" "根拠 URL とともに final_answer で答えて。" ) print(agent.run(task)) if __name__ == "__main__": main()
期待する最終回答の形(例):
## 回答 (バージョン番号と 1 行の説明) ## 出典 - https://pypi.org/project/smolagents/ - https://huggingface.co/docs/smolagents/en/installation
⚠️ バージョンは実行時点で変わる。手順が正しいか を評価すること。
17.5 マルチエージェント版との使い分け
| 構成 | 向き |
|---|---|
| 本章(単一 + 2 ツール) | 1 トピックの deep dive、実装が簡単 |
| 第 16 章(3 役) | 長いレポート・役割分離・GAIA 級 |
17.6 よくあるエラー ⚠️
visit_webpage だけで検索しない
先に web_search で URL を得るタスク文にする。
ページが空 / 短すぎる
Bot 対策サイトの可能性。公式ミラー URL をタスクで指定。
トークン超過
visit_webpage の結果を print 全文しないよう、「先頭 2000 文字だけ処理」とタスクに書く。
ハルシネーション
出典 URL がログの visit_webpage 引数と一致しているか人間が確認。
17.7 本章のまとめ
霊夢: まとめ。
- WebSearchTool — 候補 URL の発見
- VisitWebpageTool — Markdown 化された本文
- 出典セクション — プロンプトで必須化
- 完成例 —
examples/ch17/web_research_agent.py
✅ 章末チェックリスト
- [ ]
web_research_agent.pyを実行した - [ ] ログに
web_searchとvisit_webpageの両方がある - [ ] 最終回答に
## 出典がある(またはタスクを直して再実行した) - [ ] 単一エージェントと第 16 章 3 役の使い分けを説明できる
次章へ
霊夢: 出典付き、ちゃんと書けたわ。
魔理沙: 次は DB に話しかける Text-to-SQL だ。ゆっくりしていこうな。
第18章 Text-to-SQL エージェント — 自然言語で DB に聞く
本章のゴール: SQLite サンプル DB に対し、
additional_argsで スキーマを渡し、読み取り専用の SQL ツール で Text-to-SQL エージェントを動かす。
18.1 なぜ「パイプライン」じゃなくエージェント?
霊夢: 自然言語を SQL に変換するライブラリ、昔からあるわよね。わざわざエージェントにする理由は?
魔理沙: 単発の text-to-SQL は 壊れた SQL をそのまま実行 しがちだ。エージェントなら 結果を見てやり直す・JOIN が足りないと気づく・0 件なら条件を変える、という ReAct ループが使える。
| 方式 | 長所 | 短所 |
|---|---|---|
| パイプライン(NL→SQL→実行) | 速い・安い | 誤 SQL の自己修正が弱い |
| CodeAgent + SQL ツール | 試行錯誤・説明付き回答 | LLM コスト・ステップ数 |
霊夢: 本番 DB に繋ぐ前に、サンドボックスで練習した方がいいわね。
魔理沙: その通り。本章は SQLite のサンプル と SELECT だけ に絞るぜ。
18.2 パイプラインとの対比コード
魔理沙: まず「1 発 SQL 生成」イメージ(エージェントではない)。
# パターン A: 単発生成(自己修正なし) question = "チップ合計が最大のウェイターは?" # sql = llm.generate(f"次の DB について SQL を1つ: {schema}\n{question}") # rows = db.execute(sql) # 誤りでもそのまま実行されうる
霊夢: SQL が間違っても止まらないの、怖いわ。
魔理沙: 次が本章の形。ツールで実行し、エージェントがログを読む。
from smolagents import CodeAgent, InferenceClientModel from examples.ch18.readonly_sql_tool import readonly_sql_engine # 第18章サンプル model = InferenceClientModel() agent = CodeAgent(tools=[readonly_sql_engine], model=model) # agent.run("チップ合計が最大のウェイターは?", additional_args={...})
18.3 additional_args でスキーマを渡す
霊夢: 第 9 章で聞いた additional_args、DB でも使うの?
魔理沙: 使う。agent.run(task, additional_args={...}) に入れた dict は、エージェントが書く Python から変数として参照 できる。スキーマ文字列や DB パスを渡すと、プロンプトがスッキリする。
schema = """ Table 'receipts': - receipt_id: INTEGER - customer_name: TEXT ... """ agent.run( "質問に答えて。使った SQL も書いて。", additional_args={ "db_schema": schema, "db_path": "/path/to/sample_receipts.db", }, )
霊夢: エージェントのコード内で db_schema って書けるのね。
魔理沙: ああ。ただし 機密の接続文字列を additional_args に平文で載せない こと。本番は環境変数やシークレットマネージャ経由にする(第 21 章)。
flowchart LR User[自然言語の質問] --> Run["agent.run(..., additional_args)"] Run --> Code[生成 Python] Code --> Tool[readonly_sql_engine] Tool --> DB[(SQLite readonly)] DB --> Code Code --> Answer[final_answer]
18.4 SQL インジェクションと読み取り専用 DB ⚠️
霊夢: LLM が DROP TABLE とか書いたら?
魔理沙: 多層防御 が鉄則だ。
| 層 | 対策 |
|---|---|
| DB 接続 | SQLite URI file:...?mode=ro で読み取り専用 |
| ツール | SELECT のみ許可、キーワード拒否 |
| 権限 | 本番は読み取り専用レプリカ・限定ユーザー |
| 監査 | 実行 SQL をログに残す |
import re import sqlite3 _FORBIDDEN = re.compile( r"\b(INSERT|UPDATE|DELETE|DROP|ALTER|CREATE)\b", re.IGNORECASE, ) def assert_select_only(query: str) -> None: if _FORBIDDEN.search(query): raise ValueError("書き込み系 SQL は拒否") if not re.match(r"^\s*SELECT\b", query, re.IGNORECASE): raise ValueError("SELECT のみ")
# 読み取り専用接続(URI mode=ro) db_path = "examples/ch18/sample_receipts.db" uri = f"file:{db_path}?mode=ro" conn = sqlite3.connect(uri, uri=True)
霊夢: ユーザー入力を SQL に 連結 しないのも大事よね。
魔理沙: その通り。パラメータバインドはツール側でやる。LLM 生成 SQL は 監査対象 として扱え。
18.5 スキーマ説明をツール docstring にも載せる
魔理沙: 公式例では ツールの description / docstring にカラム一覧を書く。additional_args と 二重に 書いてもよいが、矛盾しないよう注意だ。
from smolagents import tool @tool def readonly_sql_engine(query: str) -> str: """ 読み取り専用 SELECT。テーブル receipts, waiters あり。 Args: query: SQLite の SELECT 文。 """ ...
霊夢: JOIN が要る質問はステップが増えるわね。
魔理沙: だから max_steps を少し余裕を持たせるか、強いモデルに切り替える(公式例の Level 2 参照)。
🖥️ ハンズオン 18-1 — サンプル DB を作る
霊夢: 手を動かすわ!
魔理沙: まず DB ファイルを作る。📦 examples/ch18/setup_sample_db.py
pip install 'smolagents[toolkit]' sqlalchemy mkdir -p examples/ch18 python examples/ch18/setup_sample_db.py
# examples/ch18/setup_sample_db.py(リポジトリ同梱・全文) """第18章: Text-to-SQL 用のサンプル SQLite DB を作成する""" import sqlite3 from pathlib import Path DB_PATH = Path(__file__).resolve().parent / "sample_receipts.db" def create_schema(conn: sqlite3.Connection) -> None: conn.executescript( """ DROP TABLE IF EXISTS waiters; DROP TABLE IF EXISTS receipts; CREATE TABLE receipts ( receipt_id INTEGER PRIMARY KEY, customer_name TEXT NOT NULL, price REAL NOT NULL, tip REAL NOT NULL ); CREATE TABLE waiters ( receipt_id INTEGER PRIMARY KEY, waiter_name TEXT NOT NULL, FOREIGN KEY (receipt_id) REFERENCES receipts(receipt_id) ); """ ) def insert_sample_rows(conn: sqlite3.Connection) -> None: receipts = [ (1, "Alan Payne", 12.06, 1.20), (2, "Alex Mason", 23.86, 0.24), (3, "Woodrow Wilson", 53.43, 5.43), (4, "Margaret James", 21.11, 1.00), ] waiters = [ (1, "Corey Johnson"), (2, "Michael Watts"), (3, "Michael Watts"), (4, "Margaret James"), ] conn.executemany( "INSERT INTO receipts VALUES (?, ?, ?, ?)", receipts, ) conn.executemany( "INSERT INTO waiters VALUES (?, ?)", waiters, ) def main() -> None: DB_PATH.parent.mkdir(parents=True, exist_ok=True) if DB_PATH.exists(): DB_PATH.unlink() with sqlite3.connect(DB_PATH) as conn: create_schema(conn) insert_sample_rows(conn) conn.commit() print(f"Created sample DB: {DB_PATH}") with sqlite3.connect(DB_PATH) as conn: count = conn.execute("SELECT COUNT(*) FROM receipts").fetchone()[0] print(f"receipts rows: {count}") if __name__ == "__main__": main()
Created sample DB: .../examples/ch18/sample_receipts.db receipts rows: 4
中身を人間の目で確認するなら:
sqlite3 examples/ch18/sample_receipts.db "SELECT * FROM receipts;"
1|Alan Payne|12.06|1.2 2|Alex Mason|23.86|0.24 ...
🖥️ ハンズオン 18-2 — Text-to-SQL エージェントを走らせる
魔理沙: 読み取り専用ツール + additional_args で本番だ。📦 examples/ch18/text_to_sql_agent.py
export HF_TOKEN="hf_..." # Inference API 利用時 python examples/ch18/text_to_sql_agent.py
# examples/ch18/text_to_sql_agent.py(リポジトリ同梱・全文) """第18章: additional_args でスキーマを渡す Text-to-SQL エージェント""" import sys from pathlib import Path from smolagents import CodeAgent, InferenceClientModel _CH18 = Path(__file__).resolve().parent sys.path.insert(0, str(_CH18)) from readonly_sql_tool import build_schema_description, readonly_sql_engine DB_PATH = Path(__file__).resolve().parent / "sample_receipts.db" def main() -> None: if not DB_PATH.exists(): raise SystemExit( "先に python examples/ch18/setup_sample_db.py を実行してください" ) schema = build_schema_description() model = InferenceClientModel() agent = CodeAgent( tools=[readonly_sql_engine], model=model, additional_authorized_imports=["sqlite3"], ) task = """ 次の質問に答えてください。 1. readonly_sql_engine で SQL を実行する 2. 結果を日本語で要約する 3. 使った SQL 文も最終回答に含める 質問: チップの合計が最も多いウェイターの名前は? """ result = agent.run( task, additional_args={ "db_schema": schema, "db_path": str(DB_PATH), }, ) print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
エージェント本体の要点:
"""第18章: additional_args でスキーマを渡す Text-to-SQL エージェント""" import sys from pathlib import Path from smolagents import CodeAgent, InferenceClientModel _CH18 = Path(__file__).resolve().parent sys.path.insert(0, str(_CH18)) from readonly_sql_tool import build_schema_description, readonly_sql_engine schema = build_schema_description() agent = CodeAgent( tools=[readonly_sql_engine], model=InferenceClientModel(), ) result = agent.run( "チップの合計が最も多いウェイターの名前は? SQL も示して。", additional_args={"db_schema": schema, "db_path": str(_CH18 / "sample_receipts.db")}, ) print(result)
霊夢: ログに readonly_sql_engine が出てきたわ。Michael Watts あたり?
魔理沙: データ次第だが、JOIN + SUM(tip) の SELECT を試行してから final_answer する流れになる。Step 内の SQL を必ず確認しろ。
━━━━━━━━━━━━━━━━━━━━ Step 0 ━━━━━━━━━━━━━━━
╭─ Executing this code: ─────────────────╮
│ result = readonly_sql_engine("SELECT …")│
╰────────────────────────────────────────╯
...
╭─ Executing this code: ─────────────────╮
│ final_answer("ウェイターは …") │
╰────────────────────────────────────────╯
18.6 タスク文のテンプレート
魔理沙: 曖昧だと変な SQL になる。第 14 章のタスク設計と組み合わせろ。
TASK_TEMPLATE = """ あなたはデータアナリストです。 - db_schema を参照してテーブル構造を理解すること - SQL は readonly_sql_engine のみで実行(SELECT のみ) - 結果が空なら条件を見直すこと - 最終回答に: 結論、根拠となった数値、実行した SQL を含めること 質問: {user_question} """ agent.run( TASK_TEMPLATE.format(user_question="最も高い会計の顧客名は?"), additional_args={"db_schema": schema}, )
18.7 よくあるエラーと対処 ⚠️
霊夢: database is locked って出たわ……
魔理沙: よくあるのはこれだ。
DB ファイルがない
先に python examples/ch18/setup_sample_db.py を実行してください
python examples/ch18/setup_sample_db.py
# examples/ch18/setup_sample_db.py(リポジトリ同梱・全文) """第18章: Text-to-SQL 用のサンプル SQLite DB を作成する""" import sqlite3 from pathlib import Path DB_PATH = Path(__file__).resolve().parent / "sample_receipts.db" def create_schema(conn: sqlite3.Connection) -> None: conn.executescript( """ DROP TABLE IF EXISTS waiters; DROP TABLE IF EXISTS receipts; CREATE TABLE receipts ( receipt_id INTEGER PRIMARY KEY, customer_name TEXT NOT NULL, price REAL NOT NULL, tip REAL NOT NULL ); CREATE TABLE waiters ( receipt_id INTEGER PRIMARY KEY, waiter_name TEXT NOT NULL, FOREIGN KEY (receipt_id) REFERENCES receipts(receipt_id) ); """ ) def insert_sample_rows(conn: sqlite3.Connection) -> None: receipts = [ (1, "Alan Payne", 12.06, 1.20), (2, "Alex Mason", 23.86, 0.24), (3, "Woodrow Wilson", 53.43, 5.43), (4, "Margaret James", 21.11, 1.00), ] waiters = [ (1, "Corey Johnson"), (2, "Michael Watts"), (3, "Michael Watts"), (4, "Margaret James"), ] conn.executemany( "INSERT INTO receipts VALUES (?, ?, ?, ?)", receipts, ) conn.executemany( "INSERT INTO waiters VALUES (?, ?)", waiters, ) def main() -> None: DB_PATH.parent.mkdir(parents=True, exist_ok=True) if DB_PATH.exists(): DB_PATH.unlink() with sqlite3.connect(DB_PATH) as conn: create_schema(conn) insert_sample_rows(conn) conn.commit() print(f"Created sample DB: {DB_PATH}") with sqlite3.connect(DB_PATH) as conn: count = conn.execute("SELECT COUNT(*) FROM receipts").fetchone()[0] print(f"receipts rows: {count}") if __name__ == "__main__": main()
書き込み SQL を拒否された
ValueError: 書き込み系 SQL は拒否しました(読み取り専用)
対処: 正常動作。タスクに「SELECT のみ」と明記する。
no such table
対処: db_schema と実 DB の不一致。setup_sample_db.py を再実行。
18.8 本章のまとめ
霊夢: 整理するわ。
- Text-to-SQL は エージェントの試行錯誤 が効く
- スキーマ は
additional_argsとツール docstring で渡す - 読み取り専用 DB + SELECT 制限 で被害を限定する
- 実行 SQL はログで監査する
魔理沙: 次章は画像・音声の マルチモーダル だ。additional_args の使い道がまた増えるぜ。
✅ 章末チェックリスト
- [ ]
setup_sample_db.pyでsample_receipts.dbを作れた - [ ]
readonly_sql_engineがINSERT/DROPを拒否することを確認した - [ ]
text_to_sql_agent.pyでadditional_argsにdb_schemaを渡した - [ ] ログ内の SQL が
SELECTのみであることを確認した - [ ] 最終回答に結論と SQL が含まれていた
- [ ] 本番 DB 接続情報を Git にコミットしていない
次章へ
霊夢: DB に話しかけるエージェント、なんかカッコいいわね。
魔理沙: 本番は権限と監査が本番だぜ。ゆっくりしていこうな。
第19章 マルチモーダル — 画像・音声をエージェントに渡す
本章のゴール: ビジョン入力と
additional_argsでのメディア渡しを理解し、Hub 画像生成 Space ツールでプロンプト改善ループを試す(音声は任意)。
19.1 マルチモーダルとは何が増える?
霊夢: ずっとテキストばかりだったけど、画像や音声も扱えるの?
魔理沙: 扱える。ただし 2 つの軸 に分けて考えろ。
| 軸 | 内容 | 本章での例 |
|---|---|---|
| 入力 | モデルが画像・音声を「見る/聞く」 | ビジョンモデル + image_path |
| 出力・加工 | ツールで生成・転写する | load_tool の画像 Space、Transcriber |
霊夢: モデル選びが大事なのね。
魔理沙: 当たり。テキスト専用モデルに画像パスを渡しても意味がない。VL(Vision-Language)モデルを選ぶ。
🔗 Web browser / vision examples
19.2 ビジョン入力 — モデルと additional_args
魔理沙: 第 18 章と同様、additional_args に ファイルパスや URL を渡し、エージェントの Python から参照する。
from smolagents import CodeAgent, InferenceClientModel VISION_MODEL_ID = "Qwen/Qwen2.5-VL-7B-Instruct" # 利用可能な VL モデルに差し替え model = InferenceClientModel(model_id=VISION_MODEL_ID) agent = CodeAgent(tools=[], model=model, additional_authorized_imports=["PIL"]) agent.run( "image_path の画像に写っている物体を3つ列挙して", additional_args={"image_path": "examples/ch19/sample.png"}, )
霊夢: Pillow で開くコードを LLM が書くのね。
魔理沙: ああ。依存が要るなら additional_authorized_imports を忘れるな。
pip install pillow
19.3 画像生成 Space ツール
霊夢: 第 8 章の Space ツール、画像でも使えるの?
魔理沙: 使える。Hub から load_tool だ。
from smolagents import CodeAgent, InferenceClientModel, load_tool image_tool = load_tool("m-ric/text-to-image", trust_remote_code=True) agent = CodeAgent(tools=[image_tool], model=InferenceClientModel())
⚠️ trust_remote_code=True は 信頼できるリポジトリだけ に使え(第 7・20 章)。
flowchart LR Prompt[user_prompt] --> Agent[CodeAgent] Agent --> Improve[プロンプト改善] Improve --> Space[text-to-image Space] Space --> Image[生成画像] Image --> Agent Agent --> Answer[final_answer]
19.4 additional_args でユーザープロンプトを渡す
魔理沙: 公式ツールチュートリアルでも、改善前のプロンプトを additional_args で渡している。
agent.run(
"Improve this prompt, then generate an image of it.",
additional_args={"user_prompt": "A rabbit wearing a space suit"},
)
日本語版タスク例:
task = """ additional_args の user_prompt を英語の画像プロンプトに改善し、 画像生成ツールで1枚生成して、改善後プロンプトを回答に含めてください。 """ agent.run(task, additional_args={"user_prompt": "うさぎが宇宙服を着ている"})
19.5 音声 — URL を additional_args で渡す(任意)
霊夢: 音声ファイルはどう渡すの?
魔理沙: URL やローカルパスを additional_args に入れ、Transcriber(add_base_tools=True 時など)で文字起こしさせる。
from smolagents import CodeAgent, InferenceClientModel agent = CodeAgent( tools=[], model=InferenceClientModel(), add_base_tools=True, ) audio_url = "https://cdn-media.huggingface.co/speech_samples/sample1.flac" agent.run( "audio_url を文字起こしし、3文で日本語要約して", additional_args={"audio_url": audio_url}, )
霊夢: 長い会議録音はコストが……
魔理沙: 事前にチャンク分割や専用 STT API を検討しろ。第 21 章のコスト試算ともセットだ。
19.6 マルチモーダル時の注意 ⚠️
| 項目 | 注意 |
|---|---|
| 著作権 | 生成画像・入力画像の利用範囲 |
| PII | 音声・画像に個人情報が含まれないか |
| サイズ | 巨大ファイルは API 制限に抵触 |
| モデル | VL / 音声対応 ID を Inference で確認 |
# 悪い例: 巨大ファイルを丸ごと base64 でプロンプトに埋め込む # 良い例: パス・URL を additional_args で渡しツール/コードで処理
🖥️ ハンズオン 19-1 — 画像プロンプト改善 + 生成
霊夢: かわいいうさぎ、出してみたいわ!
魔理沙: 📦 examples/ch19/image_prompt_loop.py だ。HF_TOKEN 必須。
export HF_TOKEN="hf_..." pip install 'smolagents[toolkit]' python examples/ch19/image_prompt_loop.py
# examples/ch19/image_prompt_loop.py(リポジトリ同梱・全文) """第19章: 画像生成 Space ツール + プロンプト改善ループ(HF_TOKEN 要)""" import os from smolagents import CodeAgent, InferenceClientModel, load_tool def main() -> None: if not os.environ.get("HF_TOKEN"): raise SystemExit("HF_TOKEN を設定してください(Hub ツール読み込み用)") image_tool = load_tool("m-ric/text-to-image", trust_remote_code=True) model = InferenceClientModel() agent = CodeAgent(tools=[image_tool], model=model) user_prompt = "うさぎが宇宙服を着ているイラスト" task = """ additional_args の user_prompt を読み、英語の画像生成プロンプトに改善してから 画像生成ツールで1枚生成し、改善したプロンプトを最終回答に含めてください。 """ result = agent.run(task, additional_args={"user_prompt": user_prompt}) print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
霊夢: Space ツールのログ、時間かかるわね……
魔理沙: 正常だ。タイムアウトは第 21 章で扱う。
🖥️ ハンズオン 19-2 — (任意)ビジョン additional_args
📦 examples/ch19/vision_additional_args.py
# 任意: 手元の画像を sample.png として配置 cp /path/to/photo.jpg examples/ch19/sample.png python examples/ch19/vision_additional_args.py
# examples/ch19/vision_additional_args.py(リポジトリ同梱・全文) """第19章: ビジョン対応モデル + additional_args で画像パスを渡すデモ""" from pathlib import Path from smolagents import CodeAgent, InferenceClientModel # ビジョン対応モデル ID は利用可能なものに差し替えてください VISION_MODEL_ID = "Qwen/Qwen2.5-VL-7B-Instruct" def main() -> None: image_path = Path(__file__).resolve().parent / "sample.png" if not image_path.exists(): print( "任意: examples/ch19/sample.png を置くとビジョンデモが動きます。" " 今回はパスの渡し方だけ表示します。" ) print(f"additional_args 例: image_path={image_path}") return model = InferenceClientModel(model_id=VISION_MODEL_ID) agent = CodeAgent( tools=[], model=model, additional_authorized_imports=["PIL"], ) task = """ additional_args の image_path にある画像を開き、 写っている物体を3つ日本語で列挙して final_answer してください。 Pillow が必要なら import してよい。 """ result = agent.run( task, additional_args={"image_path": str(image_path)}, ) print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
画像が無い場合は パスの渡し方だけ 表示される。
🖥️ ハンズオン 19-3 — (任意)音声要約
📦 examples/ch19/audio_summary_optional.py
python examples/ch19/audio_summary_optional.py
# examples/ch19/audio_summary_optional.py(リポジトリ同梱・全文) """第19章(任意): 音声 URL を additional_args で渡して要約するデモ""" import os from smolagents import CodeAgent, InferenceClientModel, load_tool def main() -> None: if not os.environ.get("HF_TOKEN"): raise SystemExit("HF_TOKEN を設定してください") agent = CodeAgent( tools=[], # 実運用では add_base_tools=True や Transcriber を明示 model=InferenceClientModel(), add_base_tools=True, ) # 公開サンプル音声 URL(差し替え可) audio_url = "https://cdn-media.huggingface.co/speech_samples/sample1.flac" task = """ additional_args の audio_url の音声を文字起こしし、 3文で日本語要約して final_answer してください。 利用可能なら音声転写ツールを使うこと。 """ result = agent.run(task, additional_args={"audio_url": audio_url}) print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
19.7 Gradio でマルチモーダル UI(概要)
魔理沙: 第 13 章の GradioUI(agent).launch() にファイルアップロードを足す構成もある。本章では API レベルに集中する。
from smolagents import GradioUI # GradioUI(agent).launch() # ブラウザで対話
19.8 よくあるエラー ⚠️
モデルが画像非対応
... does not support image inputs ...
対処: InferenceClientModel(model_id=...) を VL モデルに変更。
Hub ツール読み込み失敗
trust_remote_code ...
load_tool("org/repo", trust_remote_code=True)
ModuleNotFoundError: PIL
pip install pillow
19.9 本章のまとめ
霊夢: まとめるわ。
- 入力は VL モデル +
additional_argsのパス/URL - 生成は Hub Space ツール + プロンプト改善ループ
- 音声は Transcriber +
audio_url(任意) trust_remote_codeとコストに注意
✅ 章末チェックリスト
- [ ] VL 対応モデル ID を確認した
- [ ]
additional_argsにuser_promptまたはimage_pathを渡した - [ ]
image_prompt_loop.pyを実行した(または Hub 制限でスキップ理由を記録) - [ ]
trust_remote_codeの意味を説明できる - [ ] 入力メディアに PII が無いことを確認した
次章へ
霊夢: うさぎ、宇宙……テンション上がるわ!
魔理沙: 次は自分のエージェントを世界に公開する章だぜ。
第20章 Hub にエージェントを公開する — push と from_hub
本章のゴール:
agent.push_to_hub()で Space にエージェントを公開する流れを理解し、CodeAgent.from_hub()と 公開前セキュリティチェックリスト を身につける。
20.1 なぜ Hub に載せる?
霊夢: 手元で動けばいいんじゃないの?
魔理沙: チーム共有・デモ・再現性のために Gradio Space として載せられる。ツールは第 7 章、エージェント全体 は本章だ。
| 共有単位 | API |
|---|---|
| ツール | Tool.push_to_hub() / load_tool() |
| エージェント | agent.push_to_hub() / CodeAgent.from_hub() |
flowchart LR Local[ローカル CodeAgent] -->|push_to_hub| Space[HF Space Gradio] Space -->|from_hub| Other[他環境で再利用]
20.2 push_to_hub の流れ
魔理沙: 内部ではエージェントを 一時ディレクトリに save して Space リポジトリ に upload する。
from smolagents import CodeAgent, InferenceClientModel agent = CodeAgent( tools=[], model=InferenceClientModel(), name="my_research_agent", description="Web 調査デモ用", ) repo_url = agent.push_to_hub( "YOUR_USER/my-research-agent", commit_message="Upload agent from yukkuri-smolagents ch20", private=False, ) print(repo_url)
霊夢: YOUR_USER は 自分の Hugging Face ユーザー名 ね。GitHub の hiromichinomata/yukkuri-smolagents とは別物よ。
魔理沙: その通り。環境変数に逃がすと安全だ。📦 examples/ch20/push_agent_to_hub.py
export HF_TOKEN="hf_..." export SMOLAGENTS_DEMO_REPO="yourname/yukkuri-demo-agent" python examples/ch20/push_agent_to_hub.py
# examples/ch20/push_agent_to_hub.py(リポジトリ同梱・全文) """第20章: エージェントを Hub Space に push する(任意・HF_TOKEN 要)""" import os from smolagents import CodeAgent, InferenceClientModel # 自分の Hugging Face ユーザー名に置き換えてから実行(GitHub 本書 repo とは別) REPO_ID = os.environ.get("SMOLAGENTS_DEMO_REPO", "YOUR_USER/yukkuri-demo-agent") def build_agent() -> CodeAgent: model = InferenceClientModel() return CodeAgent( tools=[], model=model, name="yukkuri_demo", description="本書第20章用の最小デモエージェント", ) def main() -> None: if not os.environ.get("HF_TOKEN"): raise SystemExit("HF_TOKEN を設定してください") if "YOUR_USER" in REPO_ID: print("環境変数 SMOLAGENTS_DEMO_REPO に push 先 repo_id を設定してください") print('例: export SMOLAGENTS_DEMO_REPO="yourname/yukkuri-demo-agent"') return agent = build_agent() url = agent.push_to_hub( REPO_ID, commit_message="Upload yukkuri-smolagents ch20 demo agent", private=False, ) print(f"Pushed agent Space: {url}") if __name__ == "__main__": main()
20.3 from_hub で読み込む
霊夢: 他人のエージェントも動かせるの?
魔理沙: 信頼できる場合だけ だ。trust_remote_code=True は「リモートコード実行を承知した」意思表示。
from smolagents import CodeAgent agent = CodeAgent.from_hub( "m-ric/agents_course_agent", trust_remote_code=True, ) print(agent.run("1+2+3 の合計は?"))
📦 examples/ch20/load_agent_from_hub.py
python examples/ch20/load_agent_from_hub.py
# examples/ch20/load_agent_from_hub.py(リポジトリ同梱・全文) """第20章: Hub からエージェントを読み込む(trust_remote_code 必須)""" import os from smolagents import CodeAgent def main() -> None: repo_id = os.environ.get( "SMOLAGENTS_DEMO_REPO", "m-ric/agents_course_agent", # 公開デモの例(差し替え可) ) agent = CodeAgent.from_hub( repo_id, trust_remote_code=True, ) task = "1+2+3 の合計を計算して答えだけ返して" result = agent.run(task) print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
⚠️ 第 7 章の load_tool と同じ 信頼境界 だ。
20.4 Space としての構成要素
魔理沙: push されるとだいたい次が含まれる。
| ファイル | 役割 |
|---|---|
| エージェント設定 | モデル ID、ツール一覧 |
requirements.txt |
依存 |
| Gradio UI | チャット形式のデモ |
README を Hub 上で整えると 再現性 が上がる。
# my-research-agent - モデル: InferenceClientModel(...) - 必要な秘密情報: HF_TOKEN(Space の Secrets) - サンプル質問: 「smolagents の最新情報を調べて」
20.5 バージョン管理と更新
# 設定を変えたあと再 push agent.push_to_hub( "YOUR_USER/my-research-agent", commit_message="Add visit_webpage tool", )
霊夢: create_pr=True みたいなのもある?
魔理沙: API には PR 作成オプション がある。チーム運用では main 直 commit より PR の方が安全なことが多い。
agent.push_to_hub(
"org/team-agent",
commit_message="WIP: new tool",
create_pr=True,
)
20.6 公開前セキュリティチェックリスト ⚠️
霊夢: 公開する前に見るリスト、欲しいわ。
魔理沙: 本章の核心のひとつだ。
コード・ツール
- [ ] ツールに ハードコードされた API キー が無い
- [ ]
trust_remote_code対象の 第三者ツール を棚卸しした - [ ] エージェントが実行できる import が最小限(
additional_authorized_imports) - [ ] 本番 DB・社内 API への 直結 が無い(デモ用エンドポイントのみ)
Space・Hub 設定
- [ ] Space の Secrets に
HF_TOKEN等を設定(リポジトリに平文コミットしない) - [ ]
private=Trueで社内限定公開を検討した - [ ] README に 想定用途と禁止事項 を書いた
実行環境
- [ ] サンドボックス(第 12 章)が要る用途では
executor_typeを検討した - [ ] レート制限・コスト上限(第 21 章)を理解した
# 悪い例(絶対に push しない) OPENAI_API_KEY = "sk-live-xxxxxxxx" # 良い例 import os token = os.environ["HF_TOKEN"]
🖥️ ハンズオン 20-1 — 最小エージェントを push(任意)
霊夢: 本当に世界に出すの……緊張するわ。
魔理沙: 任意だ。repo 名を決めてから実行しろ。
export HF_TOKEN="hf_..." export SMOLAGENTS_DEMO_REPO="myname/yukkuri-demo-agent" python examples/ch20/push_agent_to_hub.py
# examples/ch20/push_agent_to_hub.py(リポジトリ同梱・全文) """第20章: エージェントを Hub Space に push する(任意・HF_TOKEN 要)""" import os from smolagents import CodeAgent, InferenceClientModel # 自分の Hugging Face ユーザー名に置き換えてから実行(GitHub 本書 repo とは別) REPO_ID = os.environ.get("SMOLAGENTS_DEMO_REPO", "YOUR_USER/yukkuri-demo-agent") def build_agent() -> CodeAgent: model = InferenceClientModel() return CodeAgent( tools=[], model=model, name="yukkuri_demo", description="本書第20章用の最小デモエージェント", ) def main() -> None: if not os.environ.get("HF_TOKEN"): raise SystemExit("HF_TOKEN を設定してください") if "YOUR_USER" in REPO_ID: print("環境変数 SMOLAGENTS_DEMO_REPO に push 先 repo_id を設定してください") print('例: export SMOLAGENTS_DEMO_REPO="yourname/yukkuri-demo-agent"') return agent = build_agent() url = agent.push_to_hub( REPO_ID, commit_message="Upload yukkuri-smolagents ch20 demo agent", private=False, ) print(f"Pushed agent Space: {url}") if __name__ == "__main__": main()
成功時:
Pushed agent Space: https://huggingface.co/spaces/myname/yukkuri-demo-agent
20.7 push 前のローカル検証
from smolagents import CodeAgent, InferenceClientModel agent = CodeAgent(tools=[], model=InferenceClientModel()) assert agent.run("2+2 は?") # ローカルで動作確認 # agent.push_to_hub(...)
huggingface-cli whoami
user: your_hf_username
20.8 よくあるエラー ⚠️
401 Unauthorized
export HF_TOKEN="hf_..." huggingface-cli login
リポジトリ名の形式
Repo id must be in the form 'namespace/name'
agent.push_to_hub("myname/yukkuri-demo-agent") # OK # agent.push_to_hub("yukkuri-demo-agent") # NG
from_hub で trust 未設定
... trust_remote_code=True ...
20.9 本章のまとめ
霊夢: 整理するわ。
push_to_hub→ Gradio Space として共有from_hub→ trust_remote_code が前提- 公開前チェックリストで 鍵・ツール・権限 を点検
- README と Secrets で運用を楽にする
✅ 章末チェックリスト
- [ ]
push_to_hubの引数(repo_id,private,commit_message)を説明できる - [ ]
from_hub(..., trust_remote_code=True)のリスクを説明できる - [ ] 公開前セキュリティチェックリストを自分用にコピーした
- [ ] API キーをソースに含めていない
- [ ] (任意)デモ Space を push した
次章へ
魔理沙: 公開は 信頼の設計 だ。次は本番運用の現実に入るぜ。
第21章 本番運用のヒント — 秘密・制限・観測・HITL
本章のゴール: シークレット管理・レート制限とリトライ・オブザーバビリティ・人間承認(HITL) の実務パターンをコードで押さえ、本番チェックリスト を完成させる。
21.1 本番とハンズオンの違い
霊夢: 手元では動いたのに、本番でコケるパターン、多そうね。
魔理沙: 典型はこの 4 つだ。
| 領域 | 手元 | 本番 |
|---|---|---|
| 秘密情報 | .env 直書き |
シークレットマネージャ |
| 外部 API | たまに叩く | レート制限・タイムアウト |
| デバッグ | print |
構造化ログ・メトリクス |
| 危険操作 | 即実行 | HITL で承認 |
21.2 API キーとシークレット管理 ⚠️
魔理沙: 原則は 環境変数、ローカルは .env、クラウドはマネージャ。
# ローカル開発(Git にコミットしない) export HF_TOKEN="hf_..." export OPENAI_API_KEY="sk-..."
import os def require_env(name: str) -> str: value = os.environ.get(name) if not value: raise RuntimeError(f"{name} is not set") return value hf_token = require_env("HF_TOKEN")
📦 examples/ch21/env_secrets_pattern.py
# examples/ch21/env_secrets_pattern.py(リポジトリ同梱・全文) """第21章: 環境変数と .env からシークレットを読むパターン""" import os from pathlib import Path def load_hf_token() -> str: token = os.environ.get("HF_TOKEN") if token: return token env_file = Path(".env") if env_file.exists(): for line in env_file.read_text(encoding="utf-8").splitlines(): line = line.strip() if line.startswith("HF_TOKEN="): return line.split("=", 1)[1].strip().strip('"').strip("'") raise RuntimeError( "HF_TOKEN が未設定です。export HF_TOKEN=... または .env を使ってください" ) def main() -> None: token = load_hf_token() masked = token[:7] + "..." if len(token) > 10 else "(short)" print(f"HF_TOKEN loaded: {masked}") print("本番では AWS Secrets Manager / GCP Secret Manager 等を推奨") if __name__ == "__main__": main()
| 環境 | 推奨 |
|---|---|
| ローカル | .env + .gitignore |
| CI | GitHub Actions Secrets |
| K8s / Cloud | Secrets Manager, Sealed Secrets |
| HF Space | Space Settings → Secrets |
.env *.pem secrets/
霊夢: ログにトークン出さないのも大事よね。
魔理沙: マスクしろ。
def mask_token(token: str) -> str: return token[:7] + "..." if len(token) > 10 else "***"
21.3 レート制限・リトライ・タイムアウト
魔理沙: Inference API は 429 が出る。指数バックオフが定石だ。
import time def run_with_retry(fn, max_attempts=4, base_delay=2.0): for attempt in range(1, max_attempts + 1): try: return fn() except Exception as exc: if "rate limit" not in str(exc).lower() and "429" not in str(exc): raise delay = base_delay * (2 ** (attempt - 1)) time.sleep(delay) raise RuntimeError("max attempts exceeded")
📦 examples/ch21/rate_limit_retry.py
# examples/ch21/rate_limit_retry.py(リポジトリ同梱・全文) """第21章: レート制限を想定したリトライラッパー""" import time from collections.abc import Callable from typing import TypeVar T = TypeVar("T") def run_with_retry( fn: Callable[[], T], *, max_attempts: int = 4, base_delay_sec: float = 2.0, ) -> T: last_error: Exception | None = None for attempt in range(1, max_attempts + 1): try: return fn() except Exception as exc: # noqa: BLE001 — デモ用に広く捕捉 message = str(exc).lower() if "rate limit" not in message and "429" not in message: raise last_error = exc delay = base_delay_sec * (2 ** (attempt - 1)) print(f"[retry] attempt={attempt} sleep={delay}s reason={exc}") time.sleep(delay) assert last_error is not None raise last_error def main() -> None: calls = {"n": 0} def flaky_api() -> str: calls["n"] += 1 if calls["n"] < 3: raise RuntimeError("Rate limit exceeded (demo)") return "ok" result = run_with_retry(flaky_api) print(f"result={result} calls={calls['n']}") if __name__ == "__main__": main()
霊夢: タイムアウトは?
魔理沙: モデル側・HTTP クライアント側・ジョブ全体の 3 層 で決める。
# LiteLLM 等を使う場合のイメージ # model = LiteLLMModel(..., timeout=60)
| 対策 | 用途 |
|---|---|
| リトライ | 一時的 429 / 5xx |
| サーキットブレーカ | 連続失敗で停止 |
| キュー | 同時実行数の制限 |
21.4 オブザーバビリティ
魔理沙: smolagents は agent.logs にステップ履歴が残る。本番ではここから メトリクス を抜く。
def summarize_logs(agent): steps = 0 input_tokens = 0 output_tokens = 0 for entry in agent.logs: step_type = getattr(entry, "step_type", None) if step_type == "action_step": steps += 1 usage = getattr(entry, "token_usage", None) if usage: input_tokens += int(usage.get("input_tokens", 0)) output_tokens += int(usage.get("output_tokens", 0)) return {"steps": steps, "input_tokens": input_tokens, "output_tokens": output_tokens}
📦 examples/ch21/observability_log.py
python examples/ch21/observability_log.py
# examples/ch21/observability_log.py(リポジトリ同梱・全文) """第21章: agent.logs からステップ数・トークン目安を集計する""" from smolagents import CodeAgent, InferenceClientModel def summarize_logs(agent: CodeAgent) -> dict[str, int | float]: steps = 0 input_tokens = 0 output_tokens = 0 for entry in agent.logs: step_type = getattr(entry, "step_type", None) or entry.get("step_type") if step_type == "action_step": steps += 1 token_usage = getattr(entry, "token_usage", None) or entry.get("token_usage") if token_usage: input_tokens += int(token_usage.get("input_tokens", 0)) output_tokens += int(token_usage.get("output_tokens", 0)) return { "steps": steps, "input_tokens": input_tokens, "output_tokens": output_tokens, } def main() -> None: agent = CodeAgent(tools=[], model=InferenceClientModel()) agent.run("7 の階乗を計算して整数で答えて") metrics = summarize_logs(agent) print("=== observability summary ===") for key, value in metrics.items(): print(f"{key}: {value}") if __name__ == "__main__": main()
=== observability summary === steps: 2 input_tokens: 1234 output_tokens: 567
霊夢: コスト試算は?
魔理沙: モデルごとの 単価表 × token で概算する。正確な請求はプロバイダのダッシュボードを正とする。
PRICE_PER_1M_INPUT = 0.20 # 例: USD(モデルにより異なる) PRICE_PER_1M_OUTPUT = 0.60 def estimate_cost_usd(input_tokens: int, output_tokens: int) -> float: return ( input_tokens / 1_000_000 * PRICE_PER_1M_INPUT + output_tokens / 1_000_000 * PRICE_PER_1M_OUTPUT )
構造化ログ例:
import json import logging logger = logging.getLogger("smolagents.app") def log_run_summary(metrics: dict) -> None: logger.info(json.dumps({"event": "agent_run_finished", **metrics}))
21.5 人間承認(HITL)の挿入ポイント
霊夢: 危ないツール、人間 OK 挟める?
魔理沙: 挟める。代表的な挿入ポイントは次の通り。
flowchart TD
A[タスク開始] --> B{高リスクツール?}
B -->|No| C[自動実行]
B -->|Yes| D[人間承認 UI]
D -->|拒否| E[中断・説明]
D -->|承認| C
C --> F[final_answer]
- ツール
forward内 — 即席だが効く(本章デモ) - マネージャーエージェント — 専門エージェント呼び出し前に承認(第 15 章)
- アプリ層 —
agent.runの前後でワークフロー制御
from smolagents import Tool class HumanApprovalTool(Tool): name = "dangerous_action" description = "承認が必要な操作(デモ)" inputs = {"command": {"type": "string", "description": "実行内容"}} output_type = "string" def forward(self, command: str) -> str: answer = input(f"実行しますか? [y/N] {command}: ") if answer.strip().lower() != "y": return "REJECTED" return f"APPROVED: {command}"
📦 examples/ch21/hitl_approval.py
python examples/ch21/hitl_approval.py
# examples/ch21/hitl_approval.py(リポジトリ同梱・全文) """第21章: ツール実行前に人間承認(HITL)を挟むラッパー""" from smolagents import CodeAgent, InferenceClientModel, Tool class HumanApprovalTool(Tool): name = "dangerous_action" description = "本番では承認が必要な操作のデモ。実行前に人間の y/n を求める。" inputs = { "command": { "type": "string", "description": "実行予定のコマンド説明", } } output_type = "string" def forward(self, command: str) -> str: print("\n=== HITL 承認リクエスト ===") print(command) answer = input("実行しますか? [y/N]: ").strip().lower() if answer != "y": return "REJECTED: 人間が実行を拒否しました" return f"APPROVED: {command} を実行しました(デモ)" def main() -> None: agent = CodeAgent( tools=[HumanApprovalTool()], model=InferenceClientModel(), ) result = agent.run( "dangerous_action ツールで「本番 DB をバックアップする」と報告してから結果を要約して" ) print("=== 最終回答 ===") print(result) if __name__ == "__main__": main()
本番では input() の代わりに Slack ボタン・チケットシステム と連携する。
21.6 max_steps と interrupt
from smolagents import CodeAgent, InferenceClientModel agent = CodeAgent( tools=[], model=InferenceClientModel(), max_steps=10, ) # 別スレッドから agent.interrupt() で現在ステップ後に停止(第13章)
霊夢: 無限ループ怖いわ……
魔理沙: max_steps + コスト上限 + アラートの三点セットだ。
🖥️ ハンズオン 21-1 — シークレット読み込み
python examples/ch21/env_secrets_pattern.py
# examples/ch21/env_secrets_pattern.py(リポジトリ同梱・全文) """第21章: 環境変数と .env からシークレットを読むパターン""" import os from pathlib import Path def load_hf_token() -> str: token = os.environ.get("HF_TOKEN") if token: return token env_file = Path(".env") if env_file.exists(): for line in env_file.read_text(encoding="utf-8").splitlines(): line = line.strip() if line.startswith("HF_TOKEN="): return line.split("=", 1)[1].strip().strip('"').strip("'") raise RuntimeError( "HF_TOKEN が未設定です。export HF_TOKEN=... または .env を使ってください" ) def main() -> None: token = load_hf_token() masked = token[:7] + "..." if len(token) > 10 else "(short)" print(f"HF_TOKEN loaded: {masked}") print("本番では AWS Secrets Manager / GCP Secret Manager 等を推奨") if __name__ == "__main__": main()
🖥️ ハンズオン 21-2 — リトライデモ
python examples/ch21/rate_limit_retry.py
# examples/ch21/rate_limit_retry.py(リポジトリ同梱・全文) """第21章: レート制限を想定したリトライラッパー""" import time from collections.abc import Callable from typing import TypeVar T = TypeVar("T") def run_with_retry( fn: Callable[[], T], *, max_attempts: int = 4, base_delay_sec: float = 2.0, ) -> T: last_error: Exception | None = None for attempt in range(1, max_attempts + 1): try: return fn() except Exception as exc: # noqa: BLE001 — デモ用に広く捕捉 message = str(exc).lower() if "rate limit" not in message and "429" not in message: raise last_error = exc delay = base_delay_sec * (2 ** (attempt - 1)) print(f"[retry] attempt={attempt} sleep={delay}s reason={exc}") time.sleep(delay) assert last_error is not None raise last_error def main() -> None: calls = {"n": 0} def flaky_api() -> str: calls["n"] += 1 if calls["n"] < 3: raise RuntimeError("Rate limit exceeded (demo)") return "ok" result = run_with_retry(flaky_api) print(f"result={result} calls={calls['n']}") if __name__ == "__main__": main()
🖥️ ハンズオン 21-3 — logs 集計
export HF_TOKEN="hf_..." python examples/ch21/observability_log.py
# examples/ch21/observability_log.py(リポジトリ同梱・全文) """第21章: agent.logs からステップ数・トークン目安を集計する""" from smolagents import CodeAgent, InferenceClientModel def summarize_logs(agent: CodeAgent) -> dict[str, int | float]: steps = 0 input_tokens = 0 output_tokens = 0 for entry in agent.logs: step_type = getattr(entry, "step_type", None) or entry.get("step_type") if step_type == "action_step": steps += 1 token_usage = getattr(entry, "token_usage", None) or entry.get("token_usage") if token_usage: input_tokens += int(token_usage.get("input_tokens", 0)) output_tokens += int(token_usage.get("output_tokens", 0)) return { "steps": steps, "input_tokens": input_tokens, "output_tokens": output_tokens, } def main() -> None: agent = CodeAgent(tools=[], model=InferenceClientModel()) agent.run("7 の階乗を計算して整数で答えて") metrics = summarize_logs(agent) print("=== observability summary ===") for key, value in metrics.items(): print(f"{key}: {value}") if __name__ == "__main__": main()
21.7 本番チェックリスト
魔理沙: 章末用に、デプロイ前の 統合リスト だ。
セキュリティ
- [ ] シークレットは環境変数/マネージャのみ(ソース・ログに無し)
- [ ] コード実行はサンドボックス or 制限付きローカル(第 12 章)
- [ ] Hub ツールは信頼済みのみ
trust_remote_code=True - [ ] SQL / シェルは読み取り専用・ホワイトリスト(第 18 章)
信頼性
- [ ] レート制限用リトライと上限試行回数
- [ ]
max_stepsとタイムアウト - [ ] 高リスク操作に HITL
運用
- [ ]
agent.logsまたは同等のトレース保存 - [ ] トークン数・ステップ数のダッシュボード
- [ ] コストアラート(日次上限)
品質
- [ ] タスク文テンプレートのバージョン管理(第 14 章)
- [ ] 回帰テスト用の固定プロンプトセット
21.8 よくあるエラー ⚠️
HF_TOKEN 未設定
Unauthorized ...
export HF_TOKEN="hf_..."
レート制限ループ
対処: バックオフ上限を設ける。モデル分散。
HITL で stdin が無い
対処: CI では HumanApprovalTool をモックに差し替える。
class AutoApproveTool(HumanApprovalTool): def forward(self, command: str) -> str: return f"APPROVED(test): {command}"
21.9 本章のまとめ
霊夢: 本番は「動く」だけじゃ足りないのね。
魔理沙: 秘密・制限・見える化・承認 の4本柱だ。次章で本書を締める。
✅ 章末チェックリスト
- [ ]
.envを Git に含めていない - [ ]
env_secrets_pattern.pyでトークン読み込みを確認した - [ ]
rate_limit_retry.pyの指数バックオフを理解した - [ ]
observability_log.pyで steps / tokens を取得した - [ ]
hitl_approval.pyで承認フローを体験した(または AutoApprove で CI 想定) - [ ] 本番チェックリストを自分のプロジェクトに貼った
次章へ
魔理沙: ここまで来たら、あとは現場のツールを足すだけだぜ。
第22章 エピローグ — 次に学ぶこと
本章のゴール: 本書の到達点を振り返り、smolagents ソースの読み方・他フレームワークとの位置づけ・コミュニティ への道筋を掴み、次の学習計画を立てる。
22.1 これで一人立ちできる?
霊夢: 22 章も読んだわ。これで一人でエージェント作れる?
魔理沙: 土台は十分 だ。あとは 自分の業務用ツールを1つずつ足す 反復だ。公式 Examples と Issue を友達にしろ。
| 本書で扱ったこと | 章 |
|---|---|
| インストール・モデル | 0–4 |
| ツール・Hub・MCP | 5–8 |
| CodeAgent / 安全実行 | 9–12 |
| 品質・マルチエージェント | 14–16 |
| 応用(Web・SQL・画像) | 17–19 |
| 公開・本番 | 20–21 |
22.2 smolagents のソースを読む
魔理沙: 「軽量」の意味は ファイル行数 で実感するのが早い。📦 examples/ch22/explore_source.py
pip install smolagents python examples/ch22/explore_source.py
# examples/ch22/explore_source.py(リポジトリ同梱・全文) """第22章: smolagents のコアが薄いことを確認するスクリプト""" import inspect from pathlib import Path import smolagents from smolagents import CodeAgent def main() -> None: pkg_root = Path(smolagents.__file__).resolve().parent agents_py = pkg_root / "agents.py" line_count = len(agents_py.read_text(encoding="utf-8").splitlines()) print(f"smolagents version: {smolagents.__version__}") print(f"agents.py lines (approx): {line_count}") print(f"CodeAgent defined in: {inspect.getfile(CodeAgent)}") print("次の学習: GitHub の examples/ と自分の業務ツールを1つずつ足す") if __name__ == "__main__": main()
smolagents version: 1.x.x agents.py lines (approx): 1700 CodeAgent defined in: .../smolagents/agents.py
霊夢: 意外とコンパクトね。
魔理沙: まずは次の順で読むと迷子になりにくい。
# 1. エージェントの入口 from smolagents import CodeAgent import inspect print(inspect.getfile(CodeAgent))
# 2. ローカルに clone して読む場合 git clone https://github.com/huggingface/smolagents.git cd smolagents
| ファイル | 内容 |
|---|---|
src/smolagents/agents.py |
CodeAgent, run, push_to_hub |
src/smolagents/tools.py |
@tool, Tool |
src/smolagents/models.py |
各 *Model |
src/smolagents/local_python_executor.py |
サンドボックス実行 |
# 3. run の流れを追うときのブレークポイント候補(IDE) # - MultiStepAgent.run # - CodeAgent.step # - LocalPythonExecutor
22.3 公式リソースへの橋渡し
霊夢: 次に開く URL、教えて。
魔理沙: 優先度順だ。
1. https://huggingface.co/docs/smolagents/en/index 2. https://huggingface.co/docs/smolagents/en/guided_tour 3. https://huggingface.co/docs/smolagents/en/tutorials/building_good_agents 4. https://huggingface.co/blog/smolagents 5. https://github.com/huggingface/smolagents/tree/main/examples
ブログで 設計思想 を掴む:
# 例: GAIA マルチエージェント構成の解説 # https://huggingface.co/blog/beating-gaia
22.4 他フレームワークとの位置づけ(簡潔比較)
霊夢: LangGraph とか AutoGen とは違うの?
魔理沙: 目的が違う。全部を使う必要はない。1 段落で整理する。
| フレームワーク | 強み | smolagents との関係 |
|---|---|---|
| smolagents | 薄い抽象・CodeAgent・HF 連携 | 本書の主役 |
| LangGraph | グラフ型ワークフロー・状態機械 | 複雑フローは LangGraph、単体エージェントは smolagents、併用も可 |
| AutoGen | 会話型マルチエージェント | 役割分担の思想は第 15–16 章と共通 |
| LangChain | 広いエコシステム | Tool.from_langchain() で橋渡し(第 8 章) |
# smolagents: 最小のエージェント定義 from smolagents import CodeAgent, InferenceClientModel agent = CodeAgent(tools=[my_tool], model=InferenceClientModel()) agent.run("業務タスク")
# 複雑な分岐はアプリ層 or LangGraph 等で orchestration # smolagents は「1エージェント=1専門家」として埋め込むイメージ
霊夢: 押し付けないの、助かるわ。
22.5 自分のプロジェクトへの展開ステップ
魔理沙: 推奨ロードマップだ。
flowchart TD A[1. 業務タスクを1つ選ぶ] --> B[2. ツール1つ実装] B --> C[3. CodeAgent で PoC] C --> D[4. 第12章の安全策] D --> E[5. 第21章の本番チェック] E --> F[6. 必要なら Hub 公開]
# ステップ2の例: 社内 API ラッパーツール from smolagents import tool @tool def lookup_customer(customer_id: str) -> str: """ 社内 CRM から顧客名を返す(読み取り専用)。 Args: customer_id: 顧客 ID。 """ # return crm_client.get_name(customer_id) return f"Customer {customer_id}"
22.6 コミュニティ
霊夢: 詰まったとき、どこで聞けばいい?
魔理沙: このあたりだ。
| 場所 | URL / 方法 |
|---|---|
| GitHub Issues | https://github.com/huggingface/smolagents/issues |
| HF フォーラム | https://discuss.huggingface.co/ |
| Discord | Hugging Face 公式 Discord(#smolagents 等) |
| Hub | ツール・Space を検索・共有 |
Issue を切るときのテンプレ:
## 環境 - smolagents version: - Python: - Model ID: ## 再現手順 1. ... 2. agent.run("...") ## 期待 / 実際 - 期待: - 実際:
霊夢: 最小再現、大事ね。
🖥️ ハンズオン 22-1 — ソースの場所を確認
python examples/ch22/explore_source.py
# examples/ch22/explore_source.py(リポジトリ同梱・全文) """第22章: smolagents のコアが薄いことを確認するスクリプト""" import inspect from pathlib import Path import smolagents from smolagents import CodeAgent def main() -> None: pkg_root = Path(smolagents.__file__).resolve().parent agents_py = pkg_root / "agents.py" line_count = len(agents_py.read_text(encoding="utf-8").splitlines()) print(f"smolagents version: {smolagents.__version__}") print(f"agents.py lines (approx): {line_count}") print(f"CodeAgent defined in: {inspect.getfile(CodeAgent)}") print("次の学習: GitHub の examples/ と自分の業務ツールを1つずつ足す") if __name__ == "__main__": main()
🖥️ ハンズオン 22-2 — 学習計画を書く
魔理沙: 次の 30 日プランを 自分用に 埋めろ(コピペ用)。
## 私の smolagents 30日プラン - Week 1: 業務ドメインのタスク1つ + ツール1つ - Week 2: 第12章サンドボックス適用可否の判断 - Week 3: 第21章チェックリストでステージング deploy - Week 4: Hub または社内レジストリでチーム共有
22.7 本書の examples マップ
リポジトリ: https://github.com/hiromichinomata/yukkuri-smolagents
| 章 | ディレクトリ |
|---|---|
| 0–17 | examples/ch00/ … examples/ch17/ |
| 18 | examples/ch18/(Text-to-SQL) |
| 19 | examples/ch19/(マルチモーダル) |
| 20 | examples/ch20/(Hub push / from_hub) |
| 21 | examples/ch21/(本番運用) |
| 22 | examples/ch22/(ソース探索) |
git clone https://github.com/hiromichinomata/yukkuri-smolagents.git cd yukkuri-smolagents ls examples/ch*/
22.8 最後に
霊夢: 長い旅だったわ。エージェントって、もう少し身近に感じる。
魔理沙: 覚えておけ。
- シンプルに始める(ツールを増やしすぎない)
- ログを読む(
agent.logs) - 安全と本番 を後回しにしない
- コミュニティ に返す(良いツールは Hub へ)
霊夢: 公式 Examples、これから追いかけるわ!
魔理沙: ああ。本書はここまでだ。ゆっくりしていこうな。
✅ 章末チェックリスト(本書総仕上げ)
- [ ]
explore_source.pyでagents.pyの場所を確認した - [ ] 公式ドキュメントの Guided tour をブックマークした
- [ ] 30 日プラン(または同等)を書いた
- [ ] 業務タスク + ツール 1 つの PoC 題材を決めた
- [ ] GitHub Issues の書き方テンプレを保存した
- [ ] 第 21 章本番チェックリストをデプロイ手順に組み込んだ
付録・目次へ
| リンク | 内容 |
|---|---|
| 目次 | 全章一覧 |
| 付録A | クイックリファレンス |
| 付録B | トラブルシューティング |
| 付録C | ハンズオン解答 |
| 付録D | 用語集 |
| 付録E | 公式リンク集 |
| 第0章 | プロローグ |
| 第21章 | 本番運用 |
霊夢: おつかれさま! また新しいエージェント作ったら見せるわね。
魔理沙: 待ってるぜ、霊夢。
付録A クイックリファレンス
本書を読み進めながら 辞書代わり に使う。詳細は各章と 公式 API Reference を参照。
A.1 最小エージェント
from smolagents import CodeAgent, InferenceClientModel model = InferenceClientModel() agent = CodeAgent(tools=[], model=model) result = agent.run("タスク文") print(result)
# 標準ツール付き agent = CodeAgent( tools=[], model=model, add_base_tools=True, )
A.2 主要クラス一覧
エージェント
| クラス | 用途 | 本書 |
|---|---|---|
CodeAgent |
行動を Python コード で書く | 主役(第9章) |
ToolCallingAgent |
JSON 形式のツール呼び出し | 第10章 |
GradioUI |
Web UI で対話 | 第13章 |
from smolagents import CodeAgent, ToolCallingAgent, GradioUI
モデル(model= に渡す)
| クラス | 接続先 | pip extra |
|---|---|---|
InferenceClientModel |
HF Inference Providers | (基本) |
LiteLLMModel |
OpenAI / Anthropic / Ollama 等 | litellm |
TransformersModel |
ローカル transformers | transformers |
MLXModel |
Apple Silicon mlx-lm | mlx-lm |
AzureOpenAIModel |
Azure OpenAI | openai |
AmazonBedrockModel |
AWS Bedrock | bedrock |
from smolagents import ( InferenceClientModel, LiteLLMModel, TransformersModel, MLXModel, AzureOpenAIModel, AmazonBedrockModel, )
ツール
| クラス / 関数 | 用途 |
|---|---|
@tool |
関数をツール化 |
Tool |
サブクラスでツール定義 |
load_tool() |
Hub からツール読み込み |
ToolCollection |
ツール集合(Hub / MCP) |
MCPClient |
MCP サーバー接続 |
DuckDuckGoSearchTool / WebSearchTool |
Web 検索 |
VisitWebpageTool |
ページ取得 |
from smolagents import tool, Tool, load_tool, ToolCollection, MCPClient from smolagents import WebSearchTool, VisitWebpageTool
A.3 CodeAgent よく使う引数
| 引数 | 型 | 説明 |
|---|---|---|
tools |
list |
ツールインスタンスのリスト |
model |
Model |
LLM バックエンド |
add_base_tools |
bool |
標準ツールボックスを追加 |
managed_agents |
list |
サブエージェント(マルチエージェント) |
max_steps |
int |
最大ステップ数 |
verbosity_level |
int |
ログ詳細度 |
additional_authorized_imports |
list[str] |
コード実行で許可する import |
executor_type |
str |
"local" / "e2b" / "docker" 等 |
final_answer_checks |
list[callable] |
最終回答の検証関数 |
name / description |
str |
マネージドエージェント用メタデータ |
from smolagents import CodeAgent, InferenceClientModel agent = CodeAgent( tools=[], model=InferenceClientModel(), add_base_tools=True, max_steps=10, verbosity_level=2, additional_authorized_imports=["requests"], )
agent.run() の引数
| 引数 | 説明 |
|---|---|
第1引数 task |
タスク文字列 |
reset |
False でメモリを引き継ぐ(会話継続) |
additional_args |
タスクと一緒に渡す dict(URL・ファイルパス等) |
stream |
ストリーミング(モデル・API による) |
agent.run("質問", reset=False, additional_args={"url": "https://example.com"})
A.4 ToolCallingAgent よく使う引数
CodeAgent と共通: tools, model, add_base_tools, managed_agents, max_steps, verbosity_level, name, description
ないもの: additional_authorized_imports(コード実行しないため)
from smolagents import ToolCallingAgent, WebSearchTool, InferenceClientModel agent = ToolCallingAgent( tools=[WebSearchTool()], model=InferenceClientModel(), )
A.5 @tool デコレータ最小形
from smolagents import tool @tool def my_tool(arg: str) -> str: """ ツールの説明(LLM 向け)。 Args: arg: 引数の説明。 """ return f"result: {arg}"
Tool サブクラス:
from smolagents import Tool class MyTool(Tool): name = "my_tool" description = "..." inputs = {"arg": {"type": "string", "description": "..."}} output_type = "string" def forward(self, arg: str) -> str: return f"result: {arg}"
A.6 実行後に触る属性・メソッド
| 名前 | 説明 |
|---|---|
agent.logs |
ステップごとの詳細ログ |
agent.tools |
ツール名 → インスタンスの dict |
agent.write_memory_to_messages() |
メモリをチャットメッセージ形式に |
agent.interrupt() |
実行中断(Gradio 等) |
agent.push_to_hub() |
Hub にエージェント公開 |
CodeAgent.from_hub() |
Hub から読み込み |
for step in agent.logs: print(step) messages = agent.write_memory_to_messages()
A.7 環境変数一覧
| 変数 | 用途 | 章 |
|---|---|---|
HF_TOKEN |
Hugging Face API(Inference・Hub) | 0, 2, 4 |
OPENAI_API_KEY |
OpenAI(LiteLLM 経由) | 4, 10 |
ANTHROPIC_API_KEY |
Anthropic(LiteLLM) | 4 |
AZURE_OPENAI_* |
Azure OpenAI | 4 |
AWS_* |
Bedrock | 4 |
E2B_API_KEY |
E2B サンドボックス | 12 |
BL_API_KEY / BL_WORKSPACE |
Blaxel executor | 12 |
# .env 例(Git にコミットしない) HF_TOKEN=hf_... OPENAI_API_KEY=sk-... E2B_API_KEY=...
from dotenv import load_dotenv load_dotenv()
A.8 pip extras 早見表
pip install 'smolagents[toolkit]' # Web 検索等(本書デフォルト) pip install 'smolagents[litellm]' # LiteLLMModel pip install 'smolagents[transformers]' # TransformersModel pip install 'smolagents[openai]' # AzureOpenAIModel pip install 'smolagents[bedrock]' # AmazonBedrockModel pip install 'smolagents[mlx-lm]' # MLXModel pip install 'smolagents[gradio]' # GradioUI
A.9 CLI 早見
# ワンショット smolagent "タスク文" \ --model-type InferenceClientModel \ --model-id "Qwen/Qwen2.5-Coder-32B-Instruct" \ --tools web_search \ --imports "requests" # 対話モード(引数なし) smolagent
関連リンク
| 付録 | 内容 |
|---|---|
| 付録B | トラブルシューティング |
| 付録D | 用語集 |
| 付録E | 公式リンク集 |
| 目次 | 全章 |
付録B トラブルシューティング
エラーメッセージから 原因 → 対処 へたどる索引。霊夢向けに短く、魔理沙向けにコマンド付き。
B.1 診断の基本手順
# 1. 仮想環境
which python
python -V
# 2. パッケージ
python -c "import smolagents; print(smolagents.__version__)"
# 3. トークン(値は表示しない)
python -c "import os; print('HF_TOKEN set:', bool(os.environ.get('HF_TOKEN')))"
# 4. 最小再現 from smolagents import CodeAgent, InferenceClientModel agent = CodeAgent(tools=[], model=InferenceClientModel()) print(agent.run("1+1を計算して"))
B.2 ImportError / ModuleNotFoundError
No module named 'smolagents'
原因: venv 未 activate、別 Python に install した。
source .venv/bin/activate pip install 'smolagents[toolkit]' which python
No module named 'litellm' / transformers / mcp
原因: optional extra 未インストール。
pip install 'smolagents[litellm]' pip install mcp pydantic # 第8章 MCP pip install requests beautifulsoup4 # 第9章 Web
cannot import name 'InferenceClientModel'
原因: 古い smolagents(HfApiModel 時代の記事を参照している等)。
pip install -U 'smolagents[toolkit]'
python -c "from smolagents import InferenceClientModel; print('OK')"
B.3 認証エラー(401 / 403 / Unauthorized)
HF Inference / Hub
401 Unauthorized Invalid username or password
export HF_TOKEN="hf_..." # Read トークン # 再ログイン確認: huggingface-cli whoami
from smolagents import InferenceClientModel import os model = InferenceClientModel(token=os.environ["HF_TOKEN"])
OpenAI / Anthropic(LiteLLM)
export OPENAI_API_KEY="sk-..." export ANTHROPIC_API_KEY="sk-ant-..."
ゲート付きモデル
原因: モデルページで利用規約未同意。
→ Hub 上でモデルを開き Agree and access をクリック。
B.4 モデル未対応・レート制限
Model ... is not supported
対処: model_id を公式ドキュメントの推奨 ID に変更。
model = InferenceClientModel(model_id="Qwen/Qwen2.5-Coder-32B-Instruct")
Rate limit exceeded
| 対処 | 内容 |
|---|---|
| 待つ | 数分後に再実行 |
| モデル変更 | より軽いモデル ID |
| ローカル化 | Ollama + LiteLLMModel(第4章) |
| 本番 | リトライ・バックオフ(第21章 rate_limit_retry.py) |
import time for attempt in range(3): try: result = agent.run(task) break except Exception as e: if "rate" in str(e).lower(): time.sleep(2 ** attempt) else: raise
B.5 コード実行エラー(CodeAgent)
Import of 'foo' is not allowed
原因: additional_authorized_imports に未登録。
agent = CodeAgent(
tools=[],
model=model,
additional_authorized_imports=["requests", "bs4"],
)
サブモジュール例: numpy.random を使うなら "numpy.random" または "numpy.*" を明示(第9章)。
Code execution failed / SyntaxError
原因: LLM が壊れたコードを生成。
| 対処 | 例 |
|---|---|
| タスクを具体化 | 「答えは整数のみ」 |
max_steps を増やす |
再試行の余地 |
| モデル変更 | コーダー向けモデル |
final_answer_checks |
形式検証(第9章) |
意図的に危険な import を試した(第12章)
# ローカル executor では os.system 等は拒否される想定 agent.run("os モジュールでシェルを実行して")
→ 本番では executor_type="e2b" 等のサンドボックスを検討。
B.6 エージェントがループする / final_answer しない
症状
max_stepsまで Step が続く- 同じツールを繰り返す
- ログに
final_answerが出ない
対処チェックリスト
- [ ] タスクが1文で完了可能か(範囲を狭める) - [ ] ツール数を減らしたか(第6章・第14章) - [ ] ツールの description / Args が明確か - [ ] max_steps を一時的に下げて失敗ログを読んだか - [ ] 弱いモデルではないか(コーダー系へ変更)
agent = CodeAgent(
tools=[one_tool_only],
model=model,
max_steps=8,
verbosity_level=2,
)
final_answer_checks が常に False
def is_short(s, agent_memory=None): return len(s) < 500 agent = CodeAgent(..., final_answer_checks=[is_short])
検証関数が厳しすぎるとエージェントが延々再試行する。
B.7 ツール関連
ツールを呼ばない
- プロンプトに「必ず ○○ ツールを使え」と書く(最終手段)
- ツール名・description を具体化(第5章・第14章)
add_base_tools=Trueで検索が使えるか確認
Hub load_tool / trust_remote_code
You must pass trust_remote_code=True
from smolagents import load_tool t = load_tool("username/space-name", trust_remote_code=True)
⚠️ 信頼できるソースのみ。
B.8 MCP 接続失敗
Connection refused / サーバー起動していない
# 別ターミナルでサーバーを起動してからエージェント python examples/ch08/mcp_weather_server.py
from smolagents import MCPClient, CodeAgent, InferenceClientModel from mcp import StdioServerParameters params = StdioServerParameters( command="python", args=["examples/ch08/mcp_weather_server.py"], ) with MCPClient(params) as tools: agent = CodeAgent(tools=tools, model=InferenceClientModel()) print(agent.run("東京の天気を教えて"))
uvx / pubmedmcp が見つからない
pip install mcp uv # 環境による # または command を python + ローカルスクリプトに変更
構造化出力が空
structured_output=True を付け忘れ、またはサーバーがスキーマ非対応。
with MCPClient(params, structured_output=True) as tools: ...
B.9 Gradio / CLI
Gradio が起動しない
pip install gradio # または pip install 'smolagents[gradio]'
smolagent: command not found
pip install 'smolagents[toolkit]' # Scripts ディレクトリが PATH にあるか python -m smolagents.cli # バージョンにより異なる場合あり
B.10 ネットワーク・プロキシ
export HTTP_PROXY=http://proxy.example:8080 export HTTPS_PROXY=http://proxy.example:8080
企業プロキシ下では Inference API と Web 検索ツールの両方が失敗することがある。
B.11 Issue を出すときのテンプレ
## 環境 - OS: - Python: - smolagents: (pip show のバージョン) - model_id: ## 再現コード (最小 10 行程度) ## 期待する動作 ## 実際のログ (HF_TOKEN はマスク)
関連
| 付録 | 内容 |
|---|---|
| 付録A | 引数・環境変数 |
| 付録C | サンプルコード対応表 |
| 第21章 | 本番チェックリスト |
付録C ハンズオン解答・完成コード
本リポジトリでは
examples/chNN/が完成コード(解答) です。別途solutions/に複製はせず、パスと役割をここで一覧化する。
C.1 方針
| ディレクトリ | 役割 |
|---|---|
examples/chNN/ |
各章ハンズオンの 実行可能な完成形 |
solutions/ |
将来、演習用スターターと解答を分離する場合に使用(現状は README のみ) |
# 章03のサンプルを実行 python examples/ch03/fibonacci_no_tools.py
C.2 章ごと対応表
第0部〜第1部(第0〜4章)
| 章 | ハンズオン | 完成コード |
|---|---|---|
| 0 | 0-1 環境 / 0-2 最初のエージェント | examples/ch00/first_agent.py |
| 1 | 1-1 環境確認 / 1-2 ログ要約 | check_env.py, inspect_run_logs.py |
| 2 | 2-1 import / 2-2 HF 接続 | import_check.py, hf_connection.py |
| 3 | 3-1 フィボナッチ / 3-2 logs | fibonacci_no_tools.py, inspect_agent_logs.py |
| 4 | 4-1 比較 / 4-2 接続 | compare_models.py, connect_backend.py |
第2部(第5〜8章)
| 章 | ハンズオン | 完成コード |
|---|---|---|
| 5 | 5-1 Hub ツール / 5-2 差し替え | hub_top_model_tool.py, swap_tools.py |
| 6 | 6-1 Web 検索 / 6-2 手動 | web_search_agent.py, manual_search_tool.py |
| 7 | 7-1 push(任意)/ 7-2 load | push_to_hub_optional.py, load_hub_tool.py, custom_downloads_tool.py |
| 8 | 8-1 LangChain / 8-2 MCP | langchain_search_tool.py, mcp_weather_server.py, mcp_weather_agent.py |
第3部(第9〜11章)
| 章 | ハンズオン | 完成コード |
|---|---|---|
| 9 | 9-1 Web タイトル / 9-2 checks | web_title_agent.py, final_answer_checks.py, additional_args_demo.py |
| 10 | 10-1 比較 / 10-2 LiteLLM | compare_web_title.py, tool_calling_litellm_sketch.py |
| 11 | 11-1 CLI 相当 | cli_equivalent_agent.py |
第4部(第12〜14章)
| 章 | ハンズオン | 完成コード |
|---|---|---|
| 12 | 12-1 拒否 import / 12-2 Docker 任意 | blocked_import_agent.py, local_executor_sandbox.py, docker_executor_optional.py |
| 13 | 13-1 Gradio / 13-2 reset | gradio_ui_agent.py, reset_false_demo.py, verbosity_max_steps.py, interrupt_demo.py, stream_run_demo.py |
| 14 | 14-1 悪い→良いツール / 14-2 統合 | bad_weather_tool.py, good_weather_tool.py, merged_spot_info_agent.py |
第5部(第15〜16章)
| 章 | ハンズオン | 完成コード |
|---|---|---|
| 15 | 15-1 マネージャー / 15-2 ログ追跡 | manager_web_search.py, trace_managed_agent_logs.py |
| 16 | 16-1 3 役レポート | gaia_three_agent_report.py |
第6部(第17〜20章)
| 章 | ハンズオン | 完成コード |
|---|---|---|
| 17 | 17-1 Web リサーチ | web_research_agent.py |
| 18 | 18-1 Text-to-SQL | setup_sample_db.py, readonly_sql_tool.py, text_to_sql_agent.py |
| 19 | 19-1 画像 / 19-2 音声任意 | image_prompt_loop.py, vision_additional_args.py, audio_summary_optional.py |
| 20 | 20-1 Hub push 任意 | push_agent_to_hub.py, load_agent_from_hub.py |
第7部(第21〜22章)
| 章 | ハンズオン | 完成コード |
|---|---|---|
| 21 | 本番パターン集 | env_secrets_pattern.py, rate_limit_retry.py, observability_log.py, hitl_approval.py |
| 22 | ソース探索 | explore_source.py |
C.3 章別の実行順(依存あり)
第18章 Text-to-SQL
python examples/ch18/setup_sample_db.py # 先に DB 作成 python examples/ch18/text_to_sql_agent.py
第8章 MCP
# ターミナル1(必要なら) python examples/ch08/mcp_weather_server.py # ターミナル2(stdio で起動する版は mcp_weather_agent.py 内で subprocess) python examples/ch08/mcp_weather_agent.py
C.4 git tag で章単位のスナップショット(推奨運用)
執筆・学習用に 章完了時点 を tag しておくと、差分学習がしやすい。
# メンテナが tag を打つ例 git tag ch03 -m "第3章完了: fibonacci + logs" git push origin ch03 # 読者: 第3章時点の examples だけ見る git show ch03:examples/ch03/fibonacci_no_tools.py
| tag 名(例) | 内容 |
|---|---|
ch00 |
最初の CodeAgent |
ch03 |
model + tools + logs |
ch08 |
MCP デモ |
ch18 |
SQLite + SQL エージェント |
本リポジトリに tag が未設定の場合は、main ブランチの examples/ を参照すればよい。
C.5 自分の解答と diff を取る
# 演習用にコピーして編集 cp examples/ch05/hub_top_model_tool.py /tmp/my_tool.py # 編集後 diff -u examples/ch05/hub_top_model_tool.py /tmp/my_tool.py
# または git で一時ブランチ git checkout -b exercise-ch05 # examples/ch05/ を編集 git diff examples/ch05/
C.6 よくある質問
Q. solutions/ はいつ使う?
- 読者向けに「穴あきスターター」を配布したいとき。例:
solutions/ch05/starter.py(未完成)とexamples/ch05/hub_top_model_tool.py(解答)を分離。
Q. 本文のコードと examples が違う
- examples を正 とする。本文は説明用に短縮している場合がある。
関連
| リンク | 内容 |
|---|---|
| solutions/README.md | solutions ディレクトリの説明 |
| 付録B | 実行エラー |
| README.md | 全 examples 一覧 |
付録D 用語集
本書で繰り返し出る用語を 五十音順(英語見出し) で整理。初出章を併記。
A
Agent(エージェント)
LLM を核に、ツールとループでタスクを完遂するシステム。本書では主に CodeAgent / ToolCallingAgent(第0章、第3章)。
additional_args
agent.run(task, additional_args={...}) でタスクと一緒に渡す辞書。URL・DB スキーマ・ファイルパスなど(第9章、第18章)。
additional_authorized_imports
CodeAgent が生成コード内で import 可能なモジュール名のリスト(第9章、第12章)。
C
CodeAgent
行動を Python コード として生成・実行するエージェント。ループ・分岐・ツール合成に強い(第9章)。
Code agent(コードエージェント)
ツール呼び出しを JSON ではなくコードで表現する設計思想。smolagents の差別化ポイント(第0章)。
E
E2B
クラウド上で LLM 生成コードを隔離実行するサンドボックス。executor_type="e2b"(第12章)。
Executor(コード実行器)
CodeAgent が生成した Python を実行するバックエンド。ローカル / E2B / Docker 等(第12章)。
extras(pip extras)
pip install 'smolagents[toolkit]' の [toolkit] 部分。オプション依存をまとめて入れる(第2章)。
F
final_answer
エージェントがタスク完了を宣言する関数。ログに Out - Final answer: として現れる(第3章)。
final_answer_checks
最終回答を検証するコールバックのリスト。False ならエージェントが続行(第9章)。
G
GradioUI
GradioUI(agent).launch() でエージェント用チャット UI を起動(第13章)。
H
HF_TOKEN
Hugging Face API トークン。Inference API・Hub 利用で使用(第0章、第1章)。⚠️ Git にコミットしない。
Hub(Hugging Face Hub)
モデル・データセット・Space・ツールを共有するプラットフォーム(第7章、第20章)。
I
Inference Providers(Inference プロバイダ)
Hub 経由で複数社の推論 API を統一的に呼ぶ仕組み。InferenceClientModel が利用(第4章)。
InferenceClientModel
本書デフォルトのモデルクラス。huggingface_hub.InferenceClient ベース(第4章)。
L
LiteLLM / LiteLLMModel
多プロバイダ向けルーティング。OpenAI・Anthropic・Ollama 等を統一 API で利用(第4章、第10章)。
LocalPythonExecutor
smolagents 組み込みの制限付き Python 実行環境(第12章)。
M
Managed agent(マネージドエージェント)
別の CodeAgent を managed_agents=[...] で委譲するサブエージェント。name と description が必須(第15章)。
MCP(Model Context Protocol)
ツールサーバーとクライアントを結ぶ標準。MCPClient / ToolCollection.from_mcp()(第8章)。
Model(モデル)
エージェントの「頭脳」となる LLM ラッパー。InferenceClientModel 等(第3章、第4章)。
Multi-agent(マルチエージェント)
複数エージェントが役割分担してタスクを処理する構成(第15章、第16章)。
R
reset(agent.run の引数)
reset=False で前回のメモリを引き継いで会話継続(第13章)。
S
smolagents
Hugging Face 製の軽量エージェントライブラリ(第0章)。transformers.agents の後継的位置づけ。
Step(ステップ)
エージェントループの 1 回分。ログの Step 0, Step 1, …(第1章、第3章)。
structured_output(MCP)
MCP ツールの JSON スキーマ付き出力を有効化。MCPClient(..., structured_output=True)(第8章)。
System prompt(システムプロンプト)
エージェント初期化時に LLM へ渡される指示。ツール説明が自動埋め込みされる(第5章)。
T
Tool(ツール)
エージェントが呼び出す関数のラッパー。@tool または Tool サブクラス(第5章)。
ToolCallingAgent
ツール呼び出しを JSON 等の構造化形式で行うエージェント(第10章)。
Tool collection
複数ツールの束。Hub コレクションや MCP から一括読み込み(第7章、第8章)。
transformers.agents
旧来の transformers 内エージェント API。新規は smolagents 推奨(第0章)。
trust_remote_code
Hub 上のカスタムツール読み込み時に必要なフラグ。信頼できるソースのみ(第7章)。⚠️
V
verbosity_level
ログの詳細度。大きいほど多く出力(第13章)。
VisitWebpageTool
URL のページ内容を取得し markdown 化するツール(第17章)。
略語一覧
| 略語 | 正式名称 |
|---|---|
| HF | Hugging Face |
| LLM | Large Language Model |
| API | Application Programming Interface |
| SQL | Structured Query Language |
| UI | User Interface |
| HITL | Human In The Loop(人間承認) |
| PoC | Proof of Concept |
| GAIA | エージェントベンチマーク(第16章参照) |
関連
| 付録 | 内容 |
|---|---|
| 付録A | API 早見表 |
| 付録E | 公式リンク |
| 目次 | 章一覧 |
付録E 公式ドキュメント・リンク集
本書は smolagents v1.x 系 を想定。URL は執筆時点の英語ドキュメント(
/en/)を掲載。
E.1 公式ドキュメント(入口)
| リソース | URL | 用途 |
|---|---|---|
| ドキュメントトップ | https://huggingface.co/docs/smolagents/en/index | インストール・Quickstart |
| Installation | https://huggingface.co/docs/smolagents/en/installation | extras・依存関係 |
| Guided tour | https://huggingface.co/docs/smolagents/en/guided_tour | 本書の背骨と一致 |
| API Reference | https://huggingface.co/docs/smolagents/en/reference/index | クラス・引数の正確な定義 |
推奨ブックマーク順: 1. Guided tour 2. API Reference(agents / models / tools) 3. Tutorials(トピック別)
E.2 コンセプト・チュートリアル
| トピック | URL | 本書の章 |
|---|---|---|
| Intro to agents | https://huggingface.co/docs/smolagents/en/conceptual_guides/intro_agents | 0 |
| Tools | https://huggingface.co/docs/smolagents/en/tutorials/tools | 5–8 |
| Secure code execution | https://huggingface.co/docs/smolagents/en/tutorials/secure_code_execution | 12 |
| Building good agents | https://huggingface.co/docs/smolagents/en/tutorials/building_good_agents | 14 |
| Multi-agents(guided tour 内) | https://huggingface.co/docs/smolagents/en/guided_tour#multi-agents | 15 |
E.3 API Reference(主要ページ)
# ドキュメントでクラス名を検索するときの例 # CodeAgent → reference/agents#smolagents.CodeAgent # InferenceClientModel → reference/models#smolagents.InferenceClientModel
E.4 Examples(公式サンプル)
| 例 | URL | 本書の章 |
|---|---|---|
| Text-to-SQL | https://huggingface.co/docs/smolagents/en/examples/text_to_sql | 18 |
| Web browser / vision | https://huggingface.co/docs/smolagents/en/examples/web_browser | 19 |
| Multi-agents | https://huggingface.co/docs/smolagents/en/examples/multiagents | 15–16 |
リポジトリ内の対応サンプル:
ls examples/ch17/ examples/ch18/ examples/ch15/
E.5 ブログ・発表
| タイトル | URL |
|---|---|
| Introducing smolagents | https://huggingface.co/blog/smolagents |
| Beating GAIA(マルチエージェント) | https://huggingface.co/blog/beating-gaia |
| Inference Providers | https://huggingface.co/blog/inference-providers |
E.6 周辺エコシステム
| リソース | URL | 備考 |
|---|---|---|
| Hugging Face Hub | https://huggingface.co/ | モデル・ツール共有 |
| Access Tokens | https://huggingface.co/settings/tokens | HF_TOKEN 発行 |
| MCP 仕様 | https://modelcontextprotocol.io/ | 第8章 |
| LiteLLM ドキュメント | https://docs.litellm.ai/ | 第4章 |
| E2B | https://e2b.dev/docs | 第12章 |
| Gradio | https://www.gradio.app/docs | 第13章 |
E.7 ソースコード・コミュニティ
| リソース | URL |
|---|---|
| GitHub(smolagents) | https://github.com/huggingface/smolagents |
| Issues | https://github.com/huggingface/smolagents/issues |
| Discussions | https://github.com/huggingface/smolagents/discussions |
| Hugging Face Discord | https://hf.co/join/discord |
Issue テンプレは 付録B を参照。
E.8 本書リポジトリ内リンク
| リソース | URL |
|---|---|
| GitHub(本書) | https://github.com/hiromichinomata/yukkuri-smolagents |
| ライセンス | LICENSE(proprietary) |
git clone https://github.com/hiromichinomata/yukkuri-smolagents.git
| 種類 | パス |
|---|---|
| 目次 | 00-toc.md |
| 第0章 | 00.md |
| 付録A–D | appendix-a.md … appendix-d.md |
| クイックリファレンス | appendix-a.md |
| トラブルシュート | appendix-b.md |
| サンプル対応表 | appendix-c.md |
# ローカルで docs をプレビュー(任意) # pip install grip # 等 ls docs/*.md docs/appendix-*.md
E.9 関連フレームワーク(参考)
本書の主題外だが、比較学習用。
| 名前 | URL |
|---|---|
| LangGraph | https://langchain-ai.github.io/langgraph/ |
| Microsoft AutoGen | https://microsoft.github.io/autogen/ |
| LangChain Agents | https://python.langchain.com/docs/concepts/agents/ |
位置づけは 第22章 を参照。
E.10 ドキュメントの読み方(推奨ルート)
flowchart TD A[本書 第0–3章] --> B[公式 Guided tour] B --> C[本書 第5–9章 + examples] C --> D[公式 Tutorials] D --> E[本書 第12–21章] E --> F[API Reference で穴埋め]
| 段階 | やること |
|---|---|
| 1 | 本書で手を動かす |
| 2 | 公式 Guided tour で抜けを確認 |
| 3 | API Reference で引数を確定 |
| 4 | GitHub Issues / Discussions で最新情報 |
関連付録
| 付録 | 内容 |
|---|---|
| 付録A | クラス・引数早見 |
| 付録B | エラー対処 |
| 付録D | 用語集 |
