メインコンテンツへスキップ

9.3.7 高度なツールパターン【オプション】

この節の位置づけ

ツールが2つか3つしかないなら、
そのまま登録して振り分けるだけでも、たいていは十分です。

でもツールが増えてくると、すぐに新しい問題が出てきます。

  • 同じツールが何度も呼ばれる
  • ある呼び出しはまとめて処理したほうがよい
  • よくある処理フローは、いつも同じツールの組み合わせになる

この段階になると、ツール層にも「デザインパターン」が必要だと分かってきます。

この節で学ぶのは、次のことです。

ツールをただのバラバラな関数から、組み合わせ可能で再利用できる能力レイヤーへ引き上げる方法。

学習目標

  • キャッシュ、再試行、バッチ、複合ツールなどの高度なパターンが、それぞれ何を解決するのか理解する
  • 「ツールのラッパー層」がなぜ重要か理解する
  • 実行可能なサンプルを通して、組み合わせ可能なツール実行器を理解する
  • ツール層を「関数の集まり」から「システム部品」へと捉える意識を身につける

なぜツール層にもパターンが必要なのか?

同じ問題が何度も出てくるから

例えば:

  • 毎回の呼び出しでログを残したい
  • あるAPIはよくタイムアウトするので再試行したい
  • 同じ問い合わせが短時間に何度も来るのでキャッシュしたい
  • ある作業はいつも「検索してから要約する」流れになる

こうしたロジックを毎回それぞれのツールに手書きしていると、
すぐにシステムが管理しづらくなります。

パターンの価値は「高度に見えること」ではない

大事なのは次の点です。

  • 再利用できる
  • 一貫性を保てる
  • 重複実装を減らせる

これは、バックエンドのミドルウェアの考え方にとてもよく似ています。

たとえ話:ツール本体は家電、パターンは電源タップや電圧安定器

買ってきた家電は、それだけでも使えます。
でも機器が増えてくると、
自然に次のようなものが必要になります。

  • 電源タップ
  • 電圧安定器
  • タイマー

ツールパターンも、Agent に対しては同じ役割をします。


よく使う4つの高度なツールパターン

再試行ラッパー

向いている場面:

  • 一時的な失敗
  • 上流APIの一時的な不安定さ

キャッシュラッパー

向いている場面:

  • 短時間に同じ問い合わせが頻繁に起こる
  • 読み取り専用のツール

バッチツール

向いている場面:

  • 似た質問をまとめて一度に処理したい
  • 似たリクエストをまとめて処理したい

複合ツール

向いている場面:

  • いつも同じ複数ツールをセットで使う

例えば:

  • ドキュメント検索 -> rerank -> 要約

このような場合、毎回 Agent にその場で組み立てさせるより、
もっと高いレベルの複合ツールとしてまとめたほうがよいです。


まずは「組み合わせ可能なツールラッパー」の例を動かしてみよう

次の例では、3つのことを行います。

  1. 下層ツールにキャッシュを付ける
  2. ツールに再試行を付ける
  3. さらに複合ツールとして組み合わせる
from functools import wraps


def cache_tool(fn):
cache = {}

@wraps(fn)
def wrapper(*args):
if args in cache:
return {"source": "cache", "value": cache[args]}
value = fn(*args)
cache[args] = value
return {"source": "tool", "value": value}

return wrapper


def retry_tool(fn, retries=2):
@wraps(fn)
def wrapper(*args):
last_error = None
for _ in range(retries + 1):
try:
return fn(*args)
except Exception as e:
last_error = str(e)
return {"error": f"retry_failed:{last_error}"}

return wrapper


@cache_tool
def search_docs(keyword):
docs = {
"返金": "返金は7日以内かつ学習進捗が20%未満である必要があります。",
"修了証": "すべての必修項目を完了し、テストに合格すると修了証を取得できます。",
}
return docs.get(keyword, "関連する文書が見つかりません")


def summarize(text):
return f"要約:{text[:18]}..."


def search_and_summarize(keyword):
doc = search_docs(keyword)
if "error" in doc:
return doc
return {
"keyword": keyword,
"raw": doc,
"summary": summarize(doc["value"]),
}


print(search_and_summarize("返金"))
print(search_and_summarize("返金"))

期待される出力:

{'keyword': '返金', 'raw': {'source': 'tool', 'value': '返金は7日以内かつ学習進捗が20%未満である必要があります。'}, 'summary': '要約:返金は7日以内かつ学習進捗が20%未...'}
{'keyword': '返金', 'raw': {'source': 'cache', 'value': '返金は7日以内かつ学習進捗が20%未満である必要があります。'}, 'summary': '要約:返金は7日以内かつ学習進捗が20%未...'}

高度な tool pattern の実行結果図

source を先に見る

1回目の同じ query は tool から返り、2回目は cache から返ります。後半の例では、いつ batch 化し、いつ「内部資料 + 外部資料」を安定した workflow にまとめるかを見ます。

このコードで一番学ぶべきことは?

ツール層には「ツール本体」だけがあるわけではない、ということです。
実際のシステムでは、よく次のようなことを先に行います。

  • ラップする
  • 強化する
  • 組み合わせる

そして Agent が呼ぶのは、
元の関数ではなく、強化された能力であることが多いです。

なぜキャッシュは読み取り専用ツールに向いているのか?

読み取り専用ツールは、短時間に何度も呼ばれても、
返り値がすぐには変わらないことが多いからです。

例えば:

  • 返金ポリシーの確認
  • 製品説明の確認

こうしたツールは短時間キャッシュを入れると、
コストをかなり下げやすくなります。

なぜ「検索 + 要約」を複合ツールにするのか?

この組み合わせは、かなり固定的だからです。
毎回 Agent に次のことを考えさせるよりも、

  • まず検索する
  • それから要約する

複合ツールとしてまとめたほうが、
速くて、ミスも減ります。


なぜバッチツールが重要なのか?

多くのリクエストは本質的にまとめて処理できるから

例えば:

  • 10件の注文状況を一度に確認する
  • まとまった価格計算を一度に行う
  • 一組のドキュメント要約を一度に取る

1件ずつ呼び出すと、
次のようなコストが無駄になりがちです。

  • ネットワーク往復
  • モデルのステップ数
  • スケジューリングのオーバーヘッド

最小限のバッチツールの例

def get_order_status_batch(order_ids):
mock_db = {
"A001": "未発送",
"A002": "発送済み",
"A003": "受領済み",
}
return {order_id: mock_db.get(order_id, "不明な注文") for order_id in order_ids}


print(get_order_status_batch(["A001", "A002", "A009"]))

期待される出力:

{'A001': '未発送', 'A002': '発送済み', 'A009': '不明な注文'}

このパターンは特に次のような場合に向いています。

  • バックエンド自体がバッチAPIをサポートしている
  • 1回あたりの呼び出しコストが高い

いつ一連のツールを「高度なツール」としてまとめるべきか?

組み合わせが十分に固定されているとき

もし処理の流れがいつも次のようなら:

  • search -> rerank -> summarize

これは複合ツールにするのにとても向いています。

Agent に細かいことをあまり考えさせたくないとき

Agent の仕事が、いつも低レベル操作にとどまる必要はありません。
基本動作が安定しているなら、
高レベルのツールにまとめることで、Agent は次のことに集中できます。

  • より高いレベルの判断

システムをより安定・高速・テストしやすくしたいとき

複合ツールは、一般に次のことがしやすくなります。

  • 単体テスト
  • 観測
  • レート制限

境界がはっきりするからです。


「知識ベース駆動の教材生成アシスタント」を作るなら、どの組み合わせを先にまとめるべきか?

この種のプロジェクトでは、ツールは自然と次のような種類に増えていきます。

  • 内部資料を検索する
  • 外部資料を検索する
  • 重複を除いて並べ替える
  • 教材 schema を生成する
  • Word に出力する

各ステップを毎回 Agent にその場で決めさせると、
たいてい次のような問題が起こります。

  • 順番が安定しない
  • 内部資料の確認を忘れることがある
  • 先に出力してから、あとで内容を補うことがある

なので、最初の段階で特に先にまとめる価値が高いのは、
次のような頻出の固定フローです。

複合ツール何を固定してくれるか
retrieve_teaching_materialsまず内部を検索し、次に外部を補足し、最後にまとめて重複を除く
build_courseware_outline概念、例題、練習問題を先に抽出してから schema に整理する
export_courseware_docまず schema を検証し、それからテンプレートを使って Word に出力する

まずは、次のように理解するとよいです。

よく一緒に起こる動作を、あらかじめ1つの安定した手順にまとめる。

もう少し実際のプロジェクトに近い最小複合ツールの例

def retrieve_internal_docs(topic):
return [{"source": "internal", "text": f"内部資料:{topic} の知識点と例題"}]


def retrieve_external_docs(topic):
return [{"source": "external", "text": f"外部資料:{topic} の補足説明"}]


def merge_materials(internal_docs, external_docs):
return internal_docs + external_docs


def retrieve_teaching_materials(topic):
internal_docs = retrieve_internal_docs(topic)
external_docs = retrieve_external_docs(topic)
return merge_materials(internal_docs, external_docs)


print(retrieve_teaching_materials("割引の応用問題"))

期待される出力:

[{'source': 'internal', 'text': '内部資料:割引の応用問題 の知識点と例題'}, {'source': 'external', 'text': '外部資料:割引の応用問題 の補足説明'}]

この例の大事な点は、コードがどれだけ複雑かではありません。
新人にまず見てもらいたいのは、次の点です。

  • 高度なツールパターンは「不思議な設計」ではない
  • プロジェクトで何度も出てくる流れを固めているだけ

よくある誤解

誤解1:高度なパターンとは「装飾子をたくさん書くこと」

違います。
大事なのは見た目の派手さではなく、
本当に重複する問題を減らせるかどうかです。

誤解2:キャッシュがあれば必ず良い

データの変化が速い場合、
キャッシュは古い結果のリスクを逆に増やすことがあります。

誤解3:組み合わせが多いほど強いシステムである

過度な抽象化は、システムを硬くしてしまいます。
大事なのは、組み合わせが安定しているか、そして頻繁に使われるかです。


まとめ

この節で一番大切なのは、いくつかのパターン名を覚えることではありません。
もっとエンジニアリング寄りの判断を身につけることです。

ツールが増えて、同じ問題が何度も出てくるようになったら、ツール層はキャッシュ、再試行、バッチ処理、複合化によって、ただの「関数の集まり」から組み合わせ可能な能力システムへ進化させる必要がある。

この感覚が身につくと、
後でコード Agent やマルチツールシステムを作るときに、
「関数をもう1つ足す」だけでは足りないと分かるようになります。


演習

  1. サンプルに timeout_tool ラッパーを追加し、それをどの層に置くべきか考えてみましょう。
  2. なぜキャッシュは読み取り専用ツールに向いていて、更新が多い書き込み操作には向いていないのでしょうか?
  3. 自分が作った Agent のタスクを1つ思い出し、その中で複合ツールにまとめやすい固定フローを見つけてみましょう。
  4. もしあるツールの組み合わせが安定しておらず、順番もよく変わるなら、それでも高度なツールとしてまとめますか? なぜですか?