こんにちは、Lenaです。先週、あるエージェントがツール呼び出しを処理する様子を見ていて、少し引っかかるものがありました。
呼び出しは失敗しました。けれどそのエージェントは、そのまま先へ進んでしまったんです。リトライも、ログも、何もありません。まるで何も起きなかったかのように動き続けました。
私は少しその場で考え込みました。クラッシュしたからではありません。そうした失敗を受け止めるべきロジックを、どこに置けばいいのかが見えなかったからです。
そこで初めて、フックというものをもう少し注意深く見るようになりました。
エージェントフックとは何か
エージェントフック とは、エージェントの実行ライフサイクルにあらかじめ用意された割り込みポイントで、そこへカスタムロジックを差し込める仕組みです。エージェントの中核となる推論ループそのものを書き換えるわけではありません。ツール呼び出しの前、モデル応答の後、エラーが表面化したとき、といった特定の瞬間に接続され、エージェント本体に手を入れずに処理を挟めます。
すでにエージェントフレームワークで何かを作っているなら、名前を付けていなくても見覚えのあるパターンだと思います。モデル呼び出しの前に文脈を差し込む処理や、出力が伝播する前にそれを捕まえる処理を書いたことがあるなら、それはフックです。
Pre-hook / Post-hook / Error Hook の違い
呼び方はフレームワークごとに少し違っても、構造はだいたい共通しています。
Pre-hook はステップの実行前に走ります。入力を検査したり、書き換えたりできます。たとえば認証ヘッダーを差し込む、文脈を追加する、必須フィールドがそろっているか検証する、といった使い方です。before_model フックなら、会話が長くなりすぎていないかを確認し、モデルが見る前に切り詰めるかもしれません。
Post-hook はステップ完了後に走ります。出力を受け取り、それを変換したり、ログに残したり、下流の処理を起動したりできます。after_tool_call フックなら、エージェントが先へ進む前にツールの返り値を記録できます。
Error Hook は、私が観察していていちばん興味深いところです。チェーン内のどこかで失敗が起きたときに発火します。ツールがタイムアウトする。API が 429 を返す。モデル出力がバリデーションを通らない。リトライロジックやフォールバックのルーティング、グレースフルデグラデーションが置かれるのは、たいていこの場所です。そもそも用意されていれば、ですが。
フックとミドルウェアの違い
この違いはかなり繊細ですし、フレームワーク側も必ずしも同じ言葉で整理してくれないので、私は何度も混乱しました。
ミドルウェア は実行全体、あるいはその大きなレイヤーを包み込みます。リクエストが入り、ミドルウェアのスタックを通過し、反対側から出てくる。フック はもっと粒度が細かいものです。全体を包むのではなく、実行サイクルの中にある名前付きイベントへ接続されます。
LangChain のエージェントアーキテクチャでは、wrap_model_call はミドルウェアのように振る舞います。モデル呼び出しそのものを外側から包むからです。一方で before_tool_call や after_tool_call は、より狭い意味でのフックです。特定の地点で発火しますが、全体をラップするわけではありません。LangChain のエージェントドキュメント はこの 2 つのパターンを一緒に扱っています。概念としては少し違っても、実際の使われ方としては正確です。
フックはどこで使われるのか
ツール呼び出しのインターセプト
おそらく、私がいちばんよく見かけるのはここです。ツール呼び出しは、エージェントが API、データベース、ファイルシステムといった外部システムに触れる場所です。そして外部システムは、エージェントだけではきれいに扱えない形で失敗します。
before_tool_call フックなら、呼び出し前に入力を検証できます。wrap_tool_call フックなら、失敗を捕捉してバックオフ付きで再試行できます。after_tool_call フックなら、エージェントが処理する前に不揃いなレスポンス形式を正規化できます。
こうしたものがないと、ツールの失敗は静かに伝播していきがちです。
メモリの読み書きフック
メモリ操作も、フックが大きな効き目を持つ場所です。メモリを読む前なら、現在のタスク文脈に応じて何を表に出すかを絞り込めます。メモリに書き込んだ後なら、既存レコードと矛盾して見えるエントリにフラグを立てられます。
Microsoft の agent pipeline architecture では、AIContextProviders を通じてこれに近いアプローチを取っています。各 LLM 呼び出しの前に動き、メッセージ履歴を拡張する注入可能なコンポーネントです。明示的にフックとは呼ばれていませんが、パターンは同じです。実行上の特定地点に差し込まれるカスタムロジックであり、組み合わせ可能で、置き換えもできます。
出力検証フック
モデル出力を検証する Post-hook は、私が見る限り、あまり活用されていません。エージェントが応答を生成したとして、それは本当に期待するスキーマに合っているのでしょうか。許容範囲を外れた値を含んでいないでしょうか。下流に渡して安全でしょうか。
こうした問題が伝播する前に止めるバリデーションフックは、後から原因を追うよりずっと安く済みます。
エージェントの流れを壊さないフック設計
Stateless Hook と Stateful Hook
Stateless Hook は、入力を読み、出力を返しますが、自分の外にある状態へ依存したり、それを書き換えたりしません。挙動が読みやすいです。Stateful Hook は共有状態を読んだり書いたりするので、順序依存や競合条件を持ち込みます。とくにマルチエージェントや並列実行の場面では、それが表面化しやすくなります。
Stateless Hook はテストしやすく、考えやすく、組み合わせても安全です。 Stateful Hook が必要になる場面はありますが、私がフックベースのシステムで見てきた微妙なバグの多くは、たいていここから始まっていました。LangGraph の extensibility patterns でも、この種の問題を避けるために、状態変更は明示的かつ限定的に保つべきだと勧めています。
副作用の封じ込め
外部 API を呼ぶ、データベースに書き込む、通知を飛ばす。そうしたフックは副作用を持つフックです。副作用そのものが悪いわけではありません。ただ、境界が明確で、可能なら冪等で、フック側の失敗がエージェント本体へ連鎖しないよう中核の実行経路から切り離されている必要があります。
実運用では、私はまだこの部分を少し曖昧だと感じています。「エージェントのロジック」と「フックがしてよいこと」の境界が、いつもはっきり定義されているわけではないからです。
再利用しやすいフックパターン
ツール失敗向けのリトライフック
リトライフックはツール呼び出しを包み込み、特定の失敗条件で再実行します。タイムアウト、レート制限エラー、一時的なネットワーク障害。設計上のポイントは 3 つです。どのエラーで再試行するのか。何回まで試すのか。どのバックオフ戦略を使うのか。
LangChain のミドルウェアプロトコルにある wrap_tool_call は、まさにこれを扱います。handler コールバックのパターンにより、各ミドルウェア層は内側の実行への参照を受け取れるため、ツール自体を変えずにリトライロジックを差し込めます。
実行ログのための監査フック
監査フックは、意味のある各ステップの後で発火し、何が起きたかを記録します。どのツールが呼ばれたか。何を返したか。どれだけ時間がかかったか。成功したのか。時間がたつほど、このログは本番環境でエージェントの挙動をデバッグするための主要な成果物になっていきます。
Microsoft Agent Governance Toolkit は、これをさらに強いガバナンスゲートとして構成しています。すべてのツール呼び出し、出力、エージェント間のやり取りが、実行前にポリシーエンジンを通り、監査証跡を残します。根底にあるパターンは同じですが、より強く設計された形です。
API 呼び出し向けのレート制限フック
レート制限フックは、外向きの API 呼び出しを追跡し、しきい値に近づいたときに遅延やキューイングを入れます。考え方自体は単純ですが、エージェントのワークフローが規模に応じて限界へ触れ始めるまでは、つい投資を後回しにしがちです。
差し込む場所が重要です。早すぎる段階で走るフックは、必要以上にスロットリングしてしまいます。遅すぎると、もう呼び出しは済んでいます。レート制限ロジックは、通常、ツール呼び出しに対する Pre-hook に置くのが正しい挿入ポイントです。 呼び出しに十分近くて正確であり、かつ介入するには十分早いからです。
限界とトレードオフ
フックは現実の問題を解決します。コアを書き換えずに、実行チェーンへロジックを差し込めることです。ただし、そのぶん新しい面も増えます。
実行順序が重要になります。 フックはたいてい決まった順番で走ります。すべての before フックが順番に動き、そのあと本体の呼び出しが走り、最後に after フックが逆順で動く。複数のフックが同じ状態を書き換えると、処理順の意味は大きくなり、その影響は必ずしも直感的ではありません。
フックはエラーを黙って飲み込むことがあります。 失敗を捕捉して処理する Error Hook は、エージェント側から見ると「失敗が起きた」こと自体を見えなくしてしまうかもしれません。それが正しいかどうかは文脈次第で、判断を誤りやすいところでもあります。
フックの連鎖をデバッグするのは、直線的なコードを追うより難しいです。 何かが期待どおりに動かないとき、原因は合成されたフックスタックのどの層にもありえます。可観測性、つまりトレース、構造化ログ、明示的なフック ID は、本番では必須です。OpenTelemetry specification はここで理解しておく価値があります。今の本番向けエージェントフレームワークの多くはこの形式でトレースを出力しており、フックはその計測レイヤーになることが多いからです。
フックが正しい抽象化になる場所と、逆に複雑さを持ち込みすぎる場所の境界を、私はまだ考え続けています。その線引きは固定ではありません。
FAQ
- AI におけるエージェントフックとは何ですか。
エージェントフックとは、エージェントの実行サイクルにある特定段階へ差し込むカスタムロジックのポイントです。たとえばツール呼び出しの前、モデル応答の後、あるいはエラー発生時です。フックを使うと、開発者はコアのエージェントループを変えずに、挙動をインターセプトし、検査し、変更し、拡張できます。LangChain、LangGraph、Microsoft Agent Framework などの主要フレームワークがこの考え方をサポートしています。
- エージェントにおけるフックとミドルウェアの違いは何ですか。
ミドルウェアは通常、実行レイヤー全体を包み込みます。たとえば、すべてのリクエストが通過するパイプラインデコレータのようなものです。フックはもっと粒度が細かく、サイクル内の名前付きイベントに接続されます。実際には、多くのフレームワークがその両方を使います。モデル呼び出しにはミドルウェア型のラッパーを、before_tool_call や after_model のような離散イベントにはフック型のリスナーを使う、といった具合です。境界は端のほうで少し曖昧になります。
- AI エージェントの実行で Pre-hook と Post-hook はどう動きますか。
Pre-hook はステップの前に走ります。入力を書き換えたり、文脈を差し込んだり、条件によって実行を止めたりできます。Post-hook はステップ完了後に走ります。出力を受け取り、それをログに残したり、検証したり、下流の処理を起動したりできます。実行順序は固定です。すべての Pre-hook が順に発火し、そのあとステップが実行され、最後に Post-hook が逆順で発火します。このパターンは、Web サーバーのミドルウェアにおけるリクエスト / レスポンスサイクルとよく似ています。
- コアを変更せずに AI エージェントへカスタムロジックを追加するにはどうすればいいですか。
標準的な方法は、エージェントの実行パイプラインにフックやミドルウェアを登録することです。LangChain なら、middleware オブジェクトやデコレートした関数を create_agent に渡します。Microsoft Agent Framework なら、.Use() デコレータや AIContextProviders を使います。原理はどのフレームワークでも共通です。割り込みポイントを定義し、ロジックを書き、それを登録する。エージェントのコアループはそのまま残ります。
本番システムでフックのパターンがどう進化するのか、これからも見ていこうと思います。見えないままでいることを前提に設計された仕組みって、壊れたときか、そもそもそこになくて別のものが壊れたときにだけ輪郭が出る。その感じが、少しおもしろいんです。




