テクノロジー

コーディングエージェントのツール群:Read・Edit・Bashからサブエージェントまで何を持たせるか

管理者2026.06.11 公開 ・ 14 min read
コーディングエージェントのツール群:Read・Edit・Bashからサブエージェントまで何を持たせるか

01はじめに

この記事の対象読者

  • LLMエージェントを業務システムに組み込もうとしているエンジニア
  • Claude Code や Codex CLI などのコーディングエージェントを使い始め、ツール設計の考え方を深く知りたい方
  • Function Calling の基礎は理解しており、エージェントへの「何を持たせるか」という設計判断を整理したい方

TypeScript の基本的な読み書きができることを前提にしています。 Function Calling の基礎については(関連記事: ツール呼び出し(Function Calling)の実装パターン)を、MCP によるツール拡張については(関連記事: MCP(Model Context Protocol)入門:ツール連携を標準化する)を参照してください。

実行環境

本記事のコード例は以下の環境を想定しています。

  • Node.js: v22.3.0
  • TypeScript: v5.5.2
  • @anthropic-ai/sdk: v0.26.0
  • openai: v4.52.7

製品仕様に関するおことわり

本記事では Claude Code・Codex CLI・Gemini CLI などの具体的なツール名・ツール構成を取り上げますが、これらは執筆時点(2026年6月)の公開情報・公式ドキュメント・OSS実装に基づいた説明です。 各製品はアップデートが速く、ツール名・挙動・権限モデルが変更される可能性があります。 最新の仕様は各公式ドキュメントを確認してください。

この記事で得られること

  • コーディングエージェントが共通して持つツールの分類と、各分類の設計背景
  • Read / Edit / Bash という三本柱がそれぞれどういう原則のもとで設計されているか
  • サブエージェント(Task 委譲)がなぜ必要になるかの論理的説明
  • 自作エージェントへのツール設計転用の考え方と、TypeScript によるツール定義の実装例

02コーディングエージェントに共通するツールの全体像

コーディングエージェントは「モデルがコンピュータを操作してソフトウェアタスクを完遂する」という用途のため、ツールセットが比較的整理されています。 プロダクトごとに名前は異なりますが、大きく6つの分類に収まります。

分類 代表的なツール名 主な用途
ファイル操作系 Read, Write, Edit ファイルの読み取り・新規作成・部分編集
探索系 Glob, Grep ファイル名パターン検索・内容検索
実行系 Bash, Shell コマンド・テスト・ビルド・git 操作
Web系 WebSearch, WebFetch 最新情報の取得・ドキュメント参照
計画・状態系 TodoWrite, Memory, CLAUDE.md タスクリスト管理・プロジェクト指示の永続化
委譲系 Task, SubAgent サブエージェントへの処理委譲・並列化

さらに MCP によって外部サービス接続のツールが動的に追加される形が一般的です。 この6分類と MCP 拡張という構造は、Claude Code・Codex CLI・Gemini CLI のいずれにも概ね当てはまります。

以降の節で、各分類の設計背景を順番に掘り下げます。


03ファイル操作系:Read・Write・Editを分ける理由

3ツール分割の意図

ファイル操作をひとつの「write_file」ツールにまとめてしまうことも技術的には可能です。 それでも実際のコーディングエージェントが Read・Write・Edit の3つに分けている理由は、操作の危険度と用途がそれぞれ異なるからです。

  • Read は読み取り専用で副作用がありません。何度呼んでも状態は変わりません。
  • Write は対象パスに新規ファイルを作成、または既存ファイルを全量置換します。破壊的な操作です。
  • Edit は既存ファイルの特定の文字列を別の文字列に置き換える、差分適用型の操作です。

Read を危険操作なし・Edit を低リスク・Write を高リスクと区別することで、エージェントに「まず読んでから変える」という動線が自然に生まれます。 また、権限モデルを実装するとき(たとえば Read はサンドボックス外のファイルも許可するが Write/Edit はプロジェクト配下のみ)という分離も可能になります。

Editが「完全一致置換」を採用している背景

Claude Code の Edit ツールは「old_string を new_string に置き換える」という仕様を採用しています。 行番号指定や行範囲指定ではなく、文字列の完全一致を条件にしている点が特徴的です。

この設計には筆者も最初は戸惑いました。 行番号指定のほうが直感的に思えますが、実際に使い込むと完全一致置換の利点が見えてきます。

まず、行番号は事前に Read ツールでファイルを読んだ時点からずれることがあります。 別のツール呼び出しや複数の Edit が連続した場合、行番号の整合性を保つためのカウントがモデルにとって認知負荷になります。 一方、文字列の完全一致であれば「今読んでいるこのコードを、このコードに変える」という自然な表現ができます。

また、完全一致はマッチ失敗が起きやすい反面、失敗が明示的に検出できるという利点もあります。 old_string が見つからなければエラーを返せばよいのです。 行番号指定の場合、番号がずれていても操作は「成功」してしまう場合があります。

// Edit ツールの定義例(Claude Code スタイルの完全一致置換)
const editTool = {
  name: "edit_file",
  description: `
    ファイルの特定の文字列を別の文字列に置き換えます。
    old_string はファイル内に一意に存在する必要があります。
    複数箇所に同じ文字列があると失敗します。
    replace_all: true を指定すると全箇所を置換します。
    このツールを呼ぶ前に必ず read_file でファイル内容を確認してください。
  `.trim(),
  input_schema: {
    type: "object" as const,
    properties: {
      file_path: {
        type: "string",
        description: "対象ファイルの絶対パス",
      },
      old_string: {
        type: "string",
        description: "置き換え対象の文字列(ファイル内に一意で存在すること)",
      },
      new_string: {
        type: "string",
        description: "置き換え後の文字列",
      },
      replace_all: {
        type: "boolean",
        description: "true の場合、すべての一致箇所を置換する(省略時: false)",
        default: false,
      },
    },
    required: ["file_path", "old_string", "new_string"],
  },
};

description の「このツールを呼ぶ前に必ず read_file でファイル内容を確認してください」という一文がポイントです。 ツール記述(description)は、モデルがツールを使う前に読む「説明書」です。 使う順序・条件・注意点をここに書くことで、モデルの行動を誘導できます。

Write はいつ使うか

Write(全量上書き)は、ファイルが存在しない新規作成か、リファクタリングで内容をほぼ全部入れ替えるケースに向いています。 数行の修正に Write を使うと、ファイル全体をコンテキストに含めて出力し直す必要があり、コンテキスト使用量が膨らみます。 筆者が観察した限り、実際のエージェントは数行修正であれば Edit を優先し、新規作成または50%以上の変更のときに Write を使う傾向があります。


04探索系:まず検索、次に読む

なぜ「検索→読む」が標準動線なのか

コーディングエージェントに Glob(ファイル名パターン検索)と Grep(ファイル内容検索)が標準装備されている理由は、コンテキストウィンドウの節約にあります。

エージェントが「auth 関連のコードを修正したい」と判断したとき、ファイルを片っ端から Read するのは非効率です。 数百ファイルを Read するとコンテキストが一瞬で埋まります。

Glob で **/auth/**/*.ts のようなパターンを投げると、ファイルパスの一覧だけが返ります。 次に Grep で authMiddleware のような関数名を検索すると、その文字列が含まれるファイルと行番号の一覧が返ります。 この2ステップで候補を絞り込んでから Read でファイルを読むと、コンテキスト消費を最小化できます。

「まず検索、次に読む」は検索系ツールの description に明示的に書かれていることが多い動線で、モデルがこの順序を自然に選ぶように誘導されています。

// Glob ツールの定義例
const globTool = {
  name: "glob",
  description: `
    ファイル名のglobパターンに一致するファイルのパス一覧を返します。
    ファイル内容は返しません。
    特定のファイルを探すときは read_file より先にこのツールを使ってください。
    戻り値はファイルパスの配列です。一致がない場合は空配列を返します。
  `.trim(),
  input_schema: {
    type: "object" as const,
    properties: {
      pattern: {
        type: "string",
        description: "globパターン(例: src/**/*.ts, **/README.md)",
      },
      base_path: {
        type: "string",
        description: "検索のベースディレクトリ(省略時はプロジェクトルート)",
      },
    },
    required: ["pattern"],
  },
};
// Grep ツールの定義例
const grepTool = {
  name: "grep",
  description: `
    ファイルの中身を正規表現で検索し、マッチした行とファイルパスを返します。
    関数名・クラス名・定数名など、特定のシンボルがどこで使われているかを調べるときに使います。
    大量のファイルを read するより先にこのツールで候補を絞ることを推奨します。
    マッチが多い場合は先頭250件で打ち切ります。
  `.trim(),
  input_schema: {
    type: "object" as const,
    properties: {
      pattern: {
        type: "string",
        description: "検索する正規表現パターン",
      },
      path: {
        type: "string",
        description: "検索対象のディレクトリまたはファイルパス(省略時はプロジェクトルート)",
      },
      glob: {
        type: "string",
        description: "検索対象を絞るファイルglobパターン(例: *.ts, **/*.json)",
      },
      output_mode: {
        type: "string",
        enum: ["content", "files_with_matches", "count"],
        description: "content: マッチ行を返す / files_with_matches: ファイルパスのみ返す / count: 件数のみ返す",
        default: "files_with_matches",
      },
    },
    required: ["pattern"],
  },
};

output_mode を持たせている点は参考になる設計です。 「どのファイルにあるかだけ知りたい」場合と「マッチ行の内容を見たい」場合で返す情報量を切り替えることで、モデルへの出力が必要以上に大きくなるのを防ぎます。


05実行系:Bashの設計と許可フロー

Bash は最強かつ最危険なツール

Bash(またはシェル実行)ツールは、コーディングエージェントのツール群の中で最も汎用性が高く、同時に最も危険です。 テスト実行・ビルド・git操作・パッケージインストール・任意のコマンド実行が可能なため、エージェントの能力を大きく広げます。

一方で、rm -rf /git push --force、外部へのデータ送信なども技術的には実行できてしまいます。

そのため、コーディングエージェントは Bash ツールに対して**許可フロー(permission flow)**を設けることが多いです。 Claude Code の場合、--allowedTools フラグで実行を許可するコマンドのリストを制限できます。 Codex CLI も同様に --ask-for-approval オプションで許可レベルを指定でき、untrusted(デフォルト)・on-requestnever の3段階から選べます。

タイムアウトと出力制限

Bash ツールの実装で見落とされがちな点が、タイムアウトと出力サイズの制限です。

モデルが npm run build を呼んだとき、ビルドが5分かかるケースもあります。 タイムアウトを設けないと、エージェントが応答を待ち続けてセッションが詰まります。

また、git log --oneline のようなコマンドがリポジトリの全コミット(数千行)を返す場合、そのままモデルに渡すとコンテキストの大部分を消費します。 出力を一定行数・バイト数で切り詰めてから返す設計が実用上は必須です。

// Bash ツールの定義例(タイムアウト・出力制限付き)
const bashTool = {
  name: "bash",
  description: `
    シェルコマンドを実行します。テスト・ビルド・git操作に使います。
    タイムアウトは30秒です。長時間かかるコマンドは適切に分割してください。
    出力は先頭10,000文字で打ち切られます。
    rm -rf・外部への送信・認証情報を含む操作は禁止されています。
    このツールで実行できる操作はプロジェクトルート配下に限定されています。
  `.trim(),
  input_schema: {
    type: "object" as const,
    properties: {
      command: {
        type: "string",
        description: "実行するシェルコマンド",
      },
      working_directory: {
        type: "string",
        description: "コマンドを実行するディレクトリ(省略時はプロジェクトルート)",
      },
      timeout_ms: {
        type: "number",
        description: "タイムアウトのミリ秒数(省略時: 30000)",
        default: 30000,
      },
    },
    required: ["command"],
  },
};

// 実装側での出力切り詰め処理の例
async function executeBash(
  command: string,
  options: { workingDirectory?: string; timeoutMs?: number } = {}
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
  const { workingDirectory = process.cwd(), timeoutMs = 30_000 } = options;
  const MAX_OUTPUT_CHARS = 10_000;

  const { execAsync } = await import("child_process").then((m) => ({
    execAsync: (cmd: string) =>
      new Promise<{ stdout: string; stderr: string; exitCode: number }>(
        (resolve, reject) => {
          m.exec(
            cmd,
            { cwd: workingDirectory, timeout: timeoutMs },
            (err, stdout, stderr) => {
              if (err && (err as NodeJS.ErrnoException).killed) {
                reject(new Error(`コマンドがタイムアウトしました(${timeoutMs}ms)`));
              } else {
                // 非ゼロ終了でも stdout/stderr と実際の終了コードを返す
                const exitCode = err?.code ?? 0;
                resolve({ stdout, stderr, exitCode });
              }
            }
          );
        }
      ),
  }));

  const { stdout, stderr, exitCode } = await execAsync(command);

  // 出力が大きすぎる場合は先頭だけ返す
  const truncate = (text: string): string => {
    if (text.length <= MAX_OUTPUT_CHARS) return text;
    return (
      text.slice(0, MAX_OUTPUT_CHARS) +
      `\n...[出力が${text.length}文字のため${MAX_OUTPUT_CHARS}文字で打ち切りました]`
    );
  };

  return {
    stdout: truncate(stdout),
    stderr: truncate(stderr),
    exitCode,
  };
}

出力切り詰めのメッセージ(...[出力が○○文字のため打ち切りました])をモデルに返すことで、「続きがある」ことをモデルに伝えられます。 黙って切り詰めると、モデルが出力が完全だと誤解して誤った判断をする可能性があります。


06Web系:最新情報の取得と検証

WebSearch と WebFetch の使い分け

コーディングエージェントが持つ Web 系ツールは、大きく「検索」と「取得」の2種類に分かれます。

  • WebSearch: 検索クエリを投げてタイトル・URL・スニペットの一覧を返す
  • WebFetch: 指定した URL の HTML またはテキストコンテンツを取得する

この2ステップ分割は Glob→Read と同じ思想です。 まず検索で候補 URL を絞り込み、次に必要な URL だけ取得することで、不要なコンテンツをモデルのコンテキストに流し込まずに済みます。

Web 取得情報の扱い

Web 系ツールで得た情報は「鮮度はあるが信頼性は未確認」という性質を持ちます。 特に技術ドキュメントのバージョン・APIの仕様は、取得した内容がどのバージョンに対応するかを明示する必要があります。

コーディングエージェントがこの問題に対して取る一般的な対処は「一次情報(公式 GitHub・公式ドキュメント)を優先して取得する」という description への記述と、「取得した内容には出典 URL を必ず含めて返す」という出力フォーマット指定です。

出典 URL をツールの戻り値に含めることで、最終的なエージェントの回答にソースが紐づき、後から人間が確認できます。


07計画・状態系:タスクリストとメモリ

タスクリスト管理ツールがある理由

Claude Code には TaskCreate / TaskGet / TaskList / TaskUpdate(旧称: TodoWrite / TodoRead)に相当する「タスクリスト管理」ツールが存在します。 Codex CLI も内部的なプランニングステップを持ちます。

これは単なる便利機能ではなく、長いセッションでのコンテキスト管理という問題への対処です。

複数ファイルにまたがる作業(たとえば「認証機能をリファクタリングする」)は、一度のツール呼び出しでは完結しません。 ステップA→ステップB→ステップC という順序で進める必要があります。

タスクリストをツールで永続化しておくことで、以下のメリットがあります。

  • 途中でコンテキストが圧迫されても、残タスクを失わない
  • どこまで完了してどこが残っているかをモデルが常に参照できる
  • セッションを中断・再開するときに前の状態から続けられる
// TodoWrite 相当のタスクリスト管理ツール
interface TodoItem {
  id: string;
  content: string;
  status: "pending" | "in_progress" | "completed" | "cancelled";
  priority: "high" | "medium" | "low";
}

const todoWriteTool = {
  name: "todo_write",
  description: `
    タスクリストを更新します。
    複数ステップのタスクに取り組むときは、このツールで進捗を管理してください。
    タスクを開始するとき: status を in_progress に変更する
    タスクが終わったとき: status を completed に変更する
    一度に in_progress にできるタスクは1つのみです。
  `.trim(),
  input_schema: {
    type: "object" as const,
    properties: {
      todos: {
        type: "array",
        description: "更新後のタスクリスト全体",
        items: {
          type: "object",
          properties: {
            id: { type: "string" },
            content: { type: "string" },
            status: {
              type: "string",
              enum: ["pending", "in_progress", "completed", "cancelled"],
            },
            priority: {
              type: "string",
              enum: ["high", "medium", "low"],
            },
          },
          required: ["id", "content", "status", "priority"],
        },
      },
    },
    required: ["todos"],
  },
};

「一度に in_progress にできるタスクは1つのみです」という制約を description に書いておくと、モデルが並列処理を試みて状態が曖昧になる問題を防げます。

CLAUDE.md / AGENTS.md というメモリファイルの仕組み

Claude Code は CLAUDE.md、Codex CLI は AGENTS.mdAGENTS.override.md による上書きも可能)というプロジェクト指示ファイルを読み込みます。 Gemini CLI も同様のコンテキストファイルの仕組みを持ちます。

これはツールというよりも「暗黙のプロンプト注入」ですが、エージェントの状態管理という観点では重要な仕組みです。

CLAUDE.md にプロジェクト固有のルール(使用技術・禁止操作・命名規則・テスト方法)を書いておくと、毎回プロンプトで伝えなくてもエージェントがそれを前提に動きます。

業務エージェントに転用するときも、同様の「プロジェクト指示ファイル」の仕組みを持たせることで、エージェントへの初期コンテキスト提供を自動化できます。


08委譲系:サブエージェントが必要になる場面

コンテキストウィンドウの壁

コーディングエージェントが「サブエージェントへの委譲」というツールを持つ理由は、コンテキストウィンドウの上限という物理的な制約です。

大規模なリファクタリング・複数のコンポーネントにまたがる機能追加・大量のファイルを読んで分析するタスクでは、単一のコンテキストに収まりません。

Claude Code の場合、Agent ツール(旧称: Task)を呼ぶとサブエージェントが新しいコンテキストで起動します。 サブエージェントは独立したツールセットを持ち、完了したら結果をメインエージェントに返します。

この委譲モデルには3つのメリットがあります。

  1. コンテキストの分離: メインエージェントのコンテキストに中間ファイルの内容が積み上がらない
  2. 並列化: 独立したタスク(テスト実行・ドキュメント生成など)を同時進行できる
  3. 失敗の局所化: サブタスクが失敗しても、メインセッションは続けられる
// Task(サブエージェント委譲)ツールの定義例
const taskTool = {
  name: "task",
  description: `
    新しいサブエージェントを起動してタスクを委譲します。
    以下のケースで使ってください:
    - 現在のコンテキストが圧迫されているとき
    - 独立した調査タスク(「○○ファイルのエラーを調べる」など)を分離したいとき
    - 複数の独立タスクを並列実行したいとき
    サブエージェントのコンテキストは完全に独立しています。
    現在の会話履歴はサブエージェントに渡りません。
    prompt に必要な背景情報を明示的に含めてください。
  `.trim(),
  input_schema: {
    type: "object" as const,
    properties: {
      prompt: {
        type: "string",
        description: "サブエージェントへの指示(背景情報・完了条件を含む)",
      },
      description: {
        type: "string",
        description: "このタスクの短い説明(ログ・UIの表示用)",
      },
    },
    required: ["prompt", "description"],
  },
};

description の「現在の会話履歴はサブエージェントに渡りません。prompt に必要な背景情報を明示的に含めてください」という記述が重要です。 これを書かないと、モデルが「サブエージェントは現在の状況を知っている」と誤解して必要な情報を渡し忘れます。


09主要コーディングエージェントのツール構成比較

以下は2026年6月時点の公開情報をもとにまとめた比較表です。 内部実装の詳細は非公開のものもあるため、各製品の公開ドキュメント・README・OSS実装から推測できる範囲で記載しています。 正確な最新情報は各公式ドキュメントを参照してください。

ツール分類 Claude Code Codex CLI Gemini CLI Cursor(エージェントモード)
Read
Write(新規作成)
Edit(部分編集) ○(完全一致置換型)
Glob(ファイル名検索)
Grep(内容検索)
Bash/Shell ○(許可制) ○(許可モードあり) ○(制限あり)
WebSearch ○(Google検索連携) △(設定依存)
WebFetch △(設定依存)
タスクリスト管理 ○(TaskCreate/TaskGet 等) ○(プランニングステップ)
プロジェクト指示ファイル ○(CLAUDE.md) ○(AGENTS.md相当) ○(.gemini/等) ○(.cursorrules)
サブエージェント(Agent) ○(執筆時点) △(公式情報では未確認) △(設定依存)
MCP拡張

各製品の特徴を簡単に補足します。

Claude Code はサブエージェント(Agent ツール)が充実しており、並列処理の記述がしやすいのが特徴です。 CLAUDE.md によるプロジェクト指示の仕組みが丁寧に設計されており、指示ファイルを階層的(リポジトリルート・ディレクトリ単位)に配置できます。

Codex CLI(OpenAI)はサンドボックス環境への対応が早く、ネットワーク遮断・ファイルシステム制限をオプションで有効にできます。 OpenAI Responses API の built-in tools(コード実行・ファイル検索)と組み合わせる構成が増えています。

Gemini CLI は Google 検索との統合が強みです。WebSearch が Google の検索インフラを直接呼べるため、情報の新鮮さに優位性があります。 Gemini 2.5 Pro 以降はコンテキストウィンドウが非常に大きく、サブエージェント分割の必要性が相対的に低い場面もあります。

Cursor はエディタ統合という点で独自の位置づけを持ちます。 Composer Agent モードでのツール利用はエディタ文脈と密結合しており、standalone なエージェントとは用途が異なります。


10各ツール設計から学べる原則

主要エージェントのツール設計を観察すると、共通する設計原則が見えてきます。

原則1:description はモデルへの「動作マニュアル」として書く

ツールの description は、そのツールを使うモデルへの説明書です。 単に「何をするか」だけでなく、「いつ使うか」「使う前に何をすべきか」「何が返るか」「何はやってはいけないか」を含めると、モデルの動作が安定します。

よくある失敗は、description を短くしすぎることです。 「ファイルを読みます」だけでは、いつ使うべきか・制限は何かがわからず、モデルが適切な場面で適切な使い方をできません。

原則2:読み取り系と書き込み系を明示的に区別する

ツールリストに「副作用なし」「副作用あり」の区分けを持たせることで、権限チェックの実装が楽になります。 また、モデルも「まず副作用なしで情報を集め、準備が整ったら副作用ありで変更する」という自然な動線を選びやすくなります。

原則3:長大な出力は積極的に切り詰める

モデルへの出力が大きすぎると、コンテキストを圧迫してパフォーマンスが落ちます。 ファイル内容・コマンド出力・検索結果にはそれぞれ上限を設け、打ち切ったことをメッセージとして明示する実装パターンが有効です。

// 長大出力の切り詰めユーティリティ
function truncateOutput(
  content: string,
  maxChars: number,
  label = "コンテンツ"
): string {
  if (content.length <= maxChars) return content;

  const truncatedCount = content.length - maxChars;
  return (
    content.slice(0, maxChars) +
    `\n...[${label}が${content.length}文字のため先頭${maxChars}文字のみ表示。残り${truncatedCount}文字は省略されました]`
  );
}

// 利用例
import { readFile } from "node:fs/promises";
const fileContent = await readFile(filePath, "utf-8");
const result = truncateOutput(fileContent, 20_000, `${filePath}の内容`);

原則4:ツール数とコンテキスト消費のトレードオフ

モデルにツールを渡すとき、ツールの定義(名前・description・スキーマ)自体がコンテキストトークンを消費します。 ツールが多ければ多いほど、モデルが「どれを使うか」を判断するコストも増えます。

実際のコーディングエージェントが持つツール数は、コア機能で5〜10個程度に収めているケースが多いです。 MCP でオプション拡張する形を採ることで、常時コンテキストに載るツール数を最小化しています。

業務エージェントを設計する際にも「本当に毎回使うツールはいくつか」を問い直すことが、パフォーマンスと精度の両面で有効です。

原則5:危険操作には必ず確認フローを挟む

書き込み・実行・外部送信に該当するツールには、requires_confirmation: true 相当のフラグを設けるか、ツール呼び出しをインターセプトして人間の確認を取るステップを入れることが実運用上の標準です。

モデルが完全自律で動く「フルオートモード」は、内部環境のテスト・実験では有効ですが、本番データに触れる業務エージェントでは確認ステップを省略しないことを筆者は推奨します。


11自作エージェントへの転用:最小ツールセットの設計

業務エージェントが最初に持つべきツール

コーディングエージェントのツール設計を業務エージェント(例:問い合わせ対応・ドキュメント生成・データ集計など)に転用するとき、最初から全ツールを揃える必要はありません。

筆者が業務エージェントを設計するとき、最小構成として意識しているのは以下の4つです。

ツール 業務エージェントでの対応
Read(読み取り) ドキュメント取得・DB検索・外部API参照(副作用なし)
Write(書き込み) レポート生成・DB更新・外部API更新(副作用あり)
探索(検索) ナレッジベース検索・社内ドキュメント検索
タスクリスト 複数ステップの進捗管理

この4分類から始め、必要に応じてツールを追加する進め方が安定しています。 最初から全部入りにすると、どのツールが使われているのかデバッグしにくくなります。

TypeScript でのツール定義例:業務エージェント版

以下は「社内ナレッジベースを検索して回答する」業務エージェントを想定したツール定義例です。

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

// ナレッジベース検索ツール(副作用なし)
const searchKnowledgeTool: Anthropic.Tool = {
  name: "search_knowledge_base",
  description: `
    社内ナレッジベースを全文検索し、関連するドキュメントのタイトル・概要・URLを返します。
    質問に答える前にこのツールで関連情報を収集してください。
    結果は最大5件です。見つからない場合は空配列を返します。
    このツールは読み取り専用で副作用はありません。
  `.trim(),
  input_schema: {
    type: "object",
    properties: {
      query: {
        type: "string",
        description: "検索クエリ(自然語または技術用語)",
      },
      category: {
        type: "string",
        enum: ["product", "engineering", "operations", "hr", "all"],
        description: "検索対象のカテゴリ(省略時: all)",
        default: "all",
      },
    },
    required: ["query"],
  },
};

// チケット作成ツール(副作用あり)
const createTicketTool: Anthropic.Tool = {
  name: "create_support_ticket",
  description: `
    サポートチケットを作成します。
    ナレッジベースで解決できない問い合わせにのみ使ってください。
    チケットが作成されると担当者にメールが送信されます。
    実行前にユーザーの確認を取ってください。
  `.trim(),
  input_schema: {
    type: "object",
    properties: {
      title: {
        type: "string",
        description: "チケットのタイトル(50文字以内)",
      },
      description: {
        type: "string",
        description: "問い合わせ内容の詳細",
      },
      priority: {
        type: "string",
        enum: ["low", "medium", "high", "urgent"],
        description: "優先度",
        default: "medium",
      },
      user_email: {
        type: "string",
        description: "問い合わせ者のメールアドレス",
      },
    },
    required: ["title", "description", "user_email"],
  },
};

// エージェント実行のメインループ
async function runSupportAgent(userMessage: string): Promise<string> {
  const tools = [searchKnowledgeTool, createTicketTool];
  const messages: Anthropic.MessageParam[] = [
    { role: "user", content: userMessage },
  ];

  // ツール呼び出しループ
  while (true) {
    const response = await client.messages.create({
      model: "claude-sonnet-4-6",
      max_tokens: 4096,
      tools,
      messages,
      system: `
        あなたは社内ヘルプデスクのサポートエージェントです。
        ナレッジベースを検索して回答してください。
        解決できない場合はチケットを作成しますが、
        作成前にユーザーの確認を必ず取ってください。
      `.trim(),
    });

    // テキスト回答のみの場合は終了
    if (response.stop_reason === "end_turn") {
      const textContent = response.content.find((c) => c.type === "text");
      return textContent?.text ?? "";
    }

    // ツール呼び出しがある場合は処理する
    if (response.stop_reason === "tool_use") {
      // アシスタントの応答をメッセージ履歴に追加
      messages.push({ role: "assistant", content: response.content });

      // 各ツール呼び出しを処理
      const toolResults: Anthropic.ToolResultBlockParam[] = [];

      for (const block of response.content) {
        if (block.type !== "tool_use") continue;

        let result: string;

        try {
          if (block.name === "search_knowledge_base") {
            // 実際の検索処理(ここではサンプルレスポンス)
            result = JSON.stringify({
              results: [
                {
                  title: "パスワードリセット手順",
                  summary: "Okta からパスワードをリセットする方法",
                  url: "https://wiki.example.internal/password-reset",
                },
              ],
            });
          } else if (block.name === "create_support_ticket") {
            // 実際のチケット作成処理(ここではサンプルレスポンス)
            result = JSON.stringify({
              ticket_id: "HELP-1234",
              status: "created",
              message: "チケットを作成しました。担当者が2営業日以内に対応します。",
            });
          } else {
            result = JSON.stringify({ error: `不明なツール: ${block.name}` });
          }
        } catch (err) {
          result = JSON.stringify({
            error: err instanceof Error ? err.message : "ツール実行エラー",
          });
        }

        toolResults.push({
          type: "tool_result",
          tool_use_id: block.id,
          content: result,
        });
      }

      // ツール結果をメッセージ履歴に追加して次のループへ
      messages.push({ role: "user", content: toolResults });
    }
  }
}

このコードで意識した点をいくつか挙げます。

まず、search_knowledge_base は副作用なしであることを description と search_ というプレフィックスの両方で示しています。 create_support_ticket は「実行前にユーザーの確認を取ってください」と description に明記しています。

次に、ツール実行のエラーハンドリングで例外をキャッチし、エラーメッセージをツール結果として返しています。 エラーを黙って無視するとモデルが「ツールが成功した」と誤解するため、失敗した事実をモデルに伝えることが重要です。

OpenAI APIでの同等実装

OpenAI の claude-sonnet-4-6 に相当するモデル(gpt-5.4-mini 等)で同様のパターンを書く場合の例です。

import OpenAI from "openai";

const openai = new OpenAI();

// ツール定義(OpenAI Chat Completions API 形式)
const tools: OpenAI.ChatCompletionTool[] = [
  {
    type: "function",
    function: {
      name: "search_knowledge_base",
      description: `
        社内ナレッジベースを全文検索し、関連するドキュメントを返します。
        質問に答える前にこのツールで情報を収集してください。
        副作用はありません。
      `.trim(),
      parameters: {
        type: "object",
        properties: {
          query: {
            type: "string",
            description: "検索クエリ",
          },
        },
        required: ["query"],
        additionalProperties: false,
      },
      strict: true,
    },
  },
];

async function runOpenAISupportAgent(userMessage: string): Promise<string> {
  const messages: OpenAI.ChatCompletionMessageParam[] = [
    {
      role: "system",
      content: "あなたは社内ヘルプデスクのサポートエージェントです。",
    },
    { role: "user", content: userMessage },
  ];

  while (true) {
    const response = await openai.chat.completions.create({
      model: "gpt-5.4-mini",
      max_completion_tokens: 4096,
      tools,
      messages,
    });

    const choice = response.choices[0];

    // テキスト回答のみの場合は終了
    if (choice.finish_reason === "stop") {
      return choice.message.content ?? "";
    }

    // ツール呼び出しがある場合は処理する
    if (choice.finish_reason === "tool_calls") {
      messages.push(choice.message);

      for (const toolCall of choice.message.tool_calls ?? []) {
        let result: string;

        try {
          const args = JSON.parse(toolCall.function.arguments) as {
            query: string;
          };

          if (toolCall.function.name === "search_knowledge_base") {
            // 検索処理(サンプル)
            result = JSON.stringify({ results: [] });
          } else {
            result = JSON.stringify({ error: `不明なツール: ${toolCall.function.name}` });
          }
        } catch (err) {
          result = JSON.stringify({ error: "実行エラー" });
        }

        messages.push({
          role: "tool",
          tool_call_id: toolCall.id,
          content: result,
        });
      }
    }
  }
}

Anthropic SDK と OpenAI SDK でツール結果の渡し方が異なる点に注意が必要です。 Anthropic は role: "user" の中に tool_result ブロックを入れる形式で、OpenAI は role: "tool" のメッセージを個別に追加する形式です。 どちらも「ツール結果をモデルに渡してから次の生成を呼ぶ」という本質的な流れは同じです。


12まとめ

コーディングエージェントのツール設計を整理すると、以下のポイントが浮かび上がります。

コーディングエージェントのツールは「ファイル操作・探索・実行・Web・計画状態・委譲」の6分類に収まり、その設計には明確な意図があります。 Read/Write/Edit を分けるのは危険度の違いです。 Glob/Grep を先に使うのはコンテキスト節約のためです。 Bash に許可フローを持たせるのは不可逆操作への安全弁です。 タスクリストを持つのは長いセッションでの状態保持のためです。 サブエージェントを使うのはコンテキストウィンドウの上限への対処です。

これらの設計原則は業務エージェントにもそのまま転用できます。 特に「description をモデルへの動作マニュアルとして書く」「長大出力を積極的に切り詰める」「副作用ありのツールには確認フローを挟む」の3点は、エージェントの安定性と安全性を高めるうえで効果が大きいと筆者は感じています。

本記事で示したツール定義はあくまでパターンのひとつです。 業務の特性・リスク許容度・コンテキスト制約によって最適な構成は変わります。 まず最小4ツールで動かし、実際の失敗パターンを観察しながら追加・調整していく進め方が、筆者の経験では安定しています。

(関連記事: ツール呼び出し(Function Calling)の実装パターン) (関連記事: MCP(Model Context Protocol)入門:ツール連携を標準化する) (関連記事: エージェントの責務分割:1エージェント1タスク設計の考え方)


13参考文献

Author
管理者
Agent Store

記事で紹介した技術を、実際の業務でお試しください。

業務に合うエージェントを条件で絞り込んで選べます。すべて無料で、今すぐ利用できます。

エージェント一覧を見る →

コメント

まだコメントはありません。最初のコメントを投稿してみましょう。

コメントを投稿

ゲストコメントは管理者の承認後に公開されます。 ログインするとすぐにコメントが公開されます。

当サイトではCookieを使用しています。詳しくはCookieポリシーをご覧ください。