ag2-hitl について
ag2 でエージェントに人の確認を組み込む開発者向けのスキルです。ツール実行中に入力を集める方法と、特定のツール実行前に承認を求める方法を説明します。
主な機能
- 01context.input() による入力収集
- 02hitl_hook による応答受付
- 03ミドルウェアによるツール呼び出しの制御
- 04プロンプトと拒否メッセージの設定
- 05タイムアウトと拒否への対応
活用シーン
- 01リスクのある操作前の確認
- 02会話中の認証情報や文脈の収集
- 03最終確定前の人によるレビュー
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ ag2ai/ ag2-skills --skill ag2-hitlインストール後、Claude Code で/ag2-hitlを入力して読み込まれたか確認します。
詳細
ヒューマン・イン・ザ・ループ
使用する場面
- リスクのある操作を行う前に、エージェントが確認を求める必要がある場合。
- 会話の途中で、エージェントがユーザーから情報を得る必要がある場合(パスワード、API キー、不足しているコンテキストなど)。
- 特定のツール呼び出しを実行前に人間の承認が必要にしたい場合(取り消し不能、高コスト、機密性の高い操作)。
- 品質保証 — 下書きを提示し、最終確定前に人間による編集や承認を得る場合。
目的に応じて、2 つの異なる仕組みを使い分けます。
| 必要なこと | 使用するもの |
|---|---|
| ツールが自由形式の質問をして、入力された回答を待つ | ツール内の context.input() + エージェントの hitl_hook |
| ツール呼び出しの本体が実行される前に、特定の呼び出しを承認/拒否する | approval_required() ツールミドルウェア |
パターン 1 — 自由形式の質問には context.input() を使用
ツールは Context.input(message, timeout=...) を使って入力を求めます。エージェントには、その入力を収集する hitl_hook が必要です。
from ag2 import Agent, Context, tool
from ag2.events import HumanInputRequest, HumanMessage
@tool
async def execute_query(context: Context) -> str:
answer = await context.input(
"Are you sure you want to run this query? (yes/no)",
timeout=60.0,
)
if answer.strip().lower() != "yes":
return "Query cancelled."
return "Query executed successfully."
def hitl_hook(event: HumanInputRequest) -> HumanMessage:
print(f"Agent asks: {event.content}")
return HumanMessage(content=input("Your answer: "))
agent = Agent("dba", tools=[execute_query], hitl_hook=hitl_hook)
フックには HumanInputRequest が渡され(プロンプトは event.content に含まれます)、HumanMessage または単なる str を返します(フレームワークが HumanMessage.ensure_message を使って str をラップします)。def と async def のどちらのフックもサポートされています。
構築後にフックを登録することもできます。
agent = Agent("dba", tools=[execute_query])
@agent.hitl_hook
async def async_hitl_hook(event: HumanInputRequest) -> HumanMessage:
answer = await collect_from_ui(event.content)
return HumanMessage(content=answer)
デコレーターは、コンストラクターで設定されたフックを上書きします。ただし、すでにフックが設定されていた場合(例:コンストラクター経由)、@agent.hitl_hook を適用すると RuntimeWarning("You already set HITL hook, provided value overrides it")が出力されます。警告を避けるため、フックを設定する場所は 1 か所だけにしてください。
フックでは依存性注入も利用できます。Context、Inject、Variable、Depends はツールと同じように機能します。
context.input() が呼び出されたときにフックが登録されていない場合、フレームワークは HumanInputNotProvidedError を発生させます。
パターン 2 — 特定のツール呼び出しには approval_required() を使用
組み込みの承認ミドルウェアを使って、単一のツールに承認ゲートを設定します。ツール本体の実行前にユーザーに確認が表示され、承認または拒否できます。
from ag2 import Agent, tool
from ag2.config import OpenAIConfig
from ag2.middleware import approval_required
@tool(middleware=[approval_required()])
def delete_account(user_id: str) -> str:
"""Deletes a user account by ID permanently."""
return f"Account {user_id} deleted."
agent = Agent(
"support",
config=OpenAIConfig(model="gpt-4o-mini"),
tools=[delete_account],
hitl_hook=lambda event: input(event.content),
)
エージェントが delete_account を呼び出すと、ユーザーには次のように表示されます(デフォルトの allow_always=True の場合)。
Agent wants to call the tool:
`delete_account`, {"user_id": "abc-123"}
Please approve or deny this request.
Y/N/Always?
照合前に回答は小文字に変換されます。y、yes、または 1 はこの呼び出しを承認します。always はこの呼び出しと、同じコンテキスト内での同じツールの以降の呼び出しをすべて承認します(コンテキストごとのバイパスフラグが設定されます)。それ以外はすべて拒否となり、エージェントには denied_message(デフォルトは "User denied the tool call request")が渡され、対応を調整できます。デフォルトの timeout は 30 秒です。allow_always=False を設定すると「Always」オプションがなくなり、プロンプトには Y/N? のみが表示されます。
approval_required() は内部で context.input() を呼び出すため、hitl_hook も必要です。フックがない場合は、実行時エラーが発生します。
カスタムプロンプト
@tool(middleware=[approval_required(
message="⚠️ Run `{tool_name}` with {tool_arguments}? (y/n)",
denied_message="Operation blocked by user.",
)])
def send_email(to: str, subject: str, body: str) -> str:
"""Send an email to the given address."""
...
{tool_name} と {tool_arguments} が埋め込まれます。
2 つのパターンの併用
実行中に入力を収集し、かつ承認も必要とするツールの場合:
@tool(middleware=[approval_required()])
async def schedule_report(name: str, context: Context) -> str:
"""Schedule a report — asks the user for the cadence, then runs after approval."""
cadence = await context.input("How often? (daily / weekly / monthly)")
return f"Scheduled '{name}' on {cadence} cadence."
承認ミドルウェアが最初に実行されます(最も外側)。承認されるとツール本体が実行され、context.input() によって 2 回目の人間とのやり取りが発生します。
詳細情報
- ソースドキュメント:
website/docs/user-guide/context/human_in_the_loop.mdx(context.input、hitl_hook)、website/docs/user-guide/tools/approval_required.mdx(approval_requiredミドルウェア)。 - ツールミドルウェア全般については
website/docs/user-guide/tools/tool_middleware.mdxを参照してください。BaseMiddleware.on_human_input()によるエージェント全体の HITL インターセプトについては、ag2-middlewareも参照してください。 - HITL フックはツールと同じように依存性注入をサポートします。
../ag2-add-custom-tool/references/dependency_injection.mdを参照してください。
よくある落とし穴
hitl_hookを指定せずにapproval_required()を使う — ミドルウェアはcontext.input()を呼び出すため、エージェントにはフックが必要です。指定しない場合、HumanInputNotProvidedErrorが発生します。- 拒否された場合の処理を忘れる —
context.input()はフックが返した値を返します。「はい」の場合だけ分岐すると、ほかの回答(無回答やデフォルト値を含む)では操作が続行されます。必ず値を検証してください。 - 非同期 UI で同期の
input()を使う —input()はイベントループをブロックします。CLI 以外のアプリでは、非同期フック(async def)と非同期の入力収集機構(WebSocket、メッセージキュー)を使用してください。 - タイムアウトを設定しない —
context.input(prompt)は無期限に待機する可能性があります。本番環境で使う場合は、timeout=60.0(秒)を渡してください。 - デコレーターのフックがコンストラクターのフックを上書きする — 両方を設定すると、デコレーターが優先され、
RuntimeWarningが出力されます。設定場所はどちらか一方にしてください。 HumanMessageが会話履歴に自動的に追加されると思い込む — 呼び出し元のツールの戻り値には含まれますが、実行途中でctx.input()を通じて収集した入力は、独立したユーザーターンとして扱われません。ツールのスコープ内に保持されます。