Lesson 02 · 基礎

ループは変わらない、ツールが増えるだけ

「ループは一行も変えていない。TOOLS 配列に追加しただけだ。」——s02_tool_use.py より。

⏱ 約 10 分 · 📝 3 つのインタラクティブ要素 · 🧑‍💻 出典 shareAI-lab · s02_tool_use.py

ツールを追加するとき何を変えるのか

S01 の agent は bash しか使えない。read_file / write_file / edit_file も使えるようにするには、どう変えればいいか?

多くの人の最初の反応は「ループを変える」だ——違う。ループは一行も変えなくていい。必要なのは3つだけ:

  1. Python のハンドラ関数を書く(run_read(path, limit))。
  2. それを TOOL_HANDLERS マッピングテーブルに登録する("read_file": lambda **kw: run_read(...))。
  3. TOOLS 配列に JSON schema の宣言を追加する(このツールの名前と受け付けるパラメータをモデルに伝える)。

ループが tool_use ブロックを受け取ると、block.name を使って dispatch map から関数を引き、実行し、出力を tool_result に詰めて返す。bash と全く同じ経路だ。

# Dispatch map: name → handler lambda
TOOL_HANDLERS = {
    "bash":       lambda **kw: run_bash(kw["command"]),
    "read_file":  lambda **kw: run_read(kw["path"], kw.get("limit")),
    "write_file": lambda **kw: run_write(kw["path"], kw["content"]),
    "edit_file":  lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"]),
}

dispatch がどうルーティングするか

以下の widget でモデルが送ってくる可能性のある tool_use リクエストをクリックし、それが具体的な Python 関数へどうルーティングされるかを確認しよう。注目点:block.name がどの経路を通るかを決める。

safe_path:省けない防衛ライン

agent にファイルアクセス権限を与えるとき、セキュリティ上の最大リスクはパス逃逸だ:モデルは /home/user/project/ 内で作業すべきなのに、read_file("../../etc/passwd") を送ってくるケースだ。

s02 にはこれを防ぐ小さな関数がある:

def safe_path(p: str) -> Path:
    path = (WORKDIR / p).resolve()  # 正規化、.. とシンボリックリンクを解決
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path

肝は .resolve().is_relative_to() の2ステップだ——絶対パスに変換してからサンドボックス内にあるかチェックする。前者を省くと foo/../../etc のようなパスが通り抜ける。後者を省くとチェック自体が行われない。

パスの安全性を判定する

以下の5つのパスはすべてモデルが送ってくる可能性のある read_file の引数だ。safe_path が通過を許可するもの・拒否するものはどれか。WORKDIR = /home/user/project を前提とする。

危険なツールを agent に追加しない

ツールを追加するのは簡単だが忘れてはならない:追加するツールはすべてモデルの能力境界を広げる。s02 では bash にブラックリスト(rm -rf /sudoshutdown)があり、write_file には safe_path 制約がある。本番にツールを追加する前に3つ問いかけよう:

  • このツールでモデルが取り消せない操作をできるか?(rm、メール送信、git push)
  • 何を漏洩させうるか?(環境変数、.ssh、Cookie)
  • エラー出力が命令として回り込む可能性はあるか?(ツール出力経由の prompt injection)

s07 のレッスンでこれらの判断をコードから外に出して宣言的に管理する「権限レイヤー」を追加する。

Interactive

Widget 1 · Tool Dispatch · tool_use がハンドラへルーティングされる様子

tool_use リクエストをクリックし、ループ内で block.name がどう対応する関数を引き当て・実行・tool_result に詰め込むかを確認しよう。

モデルが送ってきた tool_use ブロック
TOOL_HANDLERS マッピングテーブル

        
実行結果 · tool_result ブロック
(上の tool_use をクリックしてルーティングを確認)
Interactive

Widget 2 · Safe Path · 5つのパスの逃逸検出

各パスは safe_path() で処理される。WORKDIR = /home/user/project を前提に、「通過」か「拒否」かを選択しよう——path escape への直感を試す。

正解 0 / 5
Interactive

Widget 3 · Add a Tool · glob ツールを追加するために変更が必要な箇所

新しいツール glob(pattern) を追加してファイルを一括検索できるようにしたい。空欄を埋めよう:3箇所すべて変更が必要で、一つでも欠ければ動かない。

第1箇所 · ハンドラ関数を書く
def run_glob(pattern: str) -> str:
    import glob
    return "\n".join(glob.glob(pattern))
第2箇所 · dispatch map に登録する(正しい一行を選択)
第3箇所 · TOOLS 配列に schema を追加する(正しい組み合わせを選択)