04. Human-in-the-loop(interrupt / resume)¶
ゴール¶
- agent の途中で人間に判断を仰ぐ仕組みを実装する
interrupt()とCommand(resume=...)の使い方を覚える- これが agent harness 設計の 核。AI が暴走しない安全装置
なぜ HITL が要るか¶
- LLM は時々間違える。取り返しがつかない操作(メール送信、決済、本番DB書込)の前に人が見たい
- ツール選択の根拠が薄いとき、ユーザーに確認したい
- 規制業務では監査要件で必須
interrupt の基本¶
langgraph.types.interrupt() を呼ぶと、その地点でグラフが停止し、呼び出し元に制御を返す。
from langgraph.types import interrupt, Command
def review_node(state: State) -> dict:
proposed = state["messages"][-1].content
decision = interrupt({
"question": "この内容で送信していいですか?",
"proposed": proposed,
})
if decision["approve"]:
return {"messages": [("system", "approved")]}
else:
return {"messages": [("system", f"rejected: {decision['reason']}")]}
実行:
config = {"configurable": {"thread_id": "task-1"}}
# 1回目:interrupt まで進む
result = graph.invoke({"messages": [("user", "顧客にお詫びメールを送って")]}, config)
print(result["__interrupt__"]) # interrupt の payload が見える
# ここで人間がレビュー(CLI / Slack / Web UI どれでも可)
# 2回目:Command(resume=...) で再開
graph.invoke(Command(resume={"approve": True}), config)
ポイント:
interrupt(...)の 引数が人間に見せる payloadCommand(resume=...)の 引数が人間の決定- checkpointer が必須(state を保存しないと再開できない)
設置ポイントの選び方¶
| パターン | 例 |
|---|---|
| Before tool call | 危険なツール(メール送信、本番API)の前 |
| After plan | LLM が立てた計画を承認してから実行 |
| Approve final answer | 最終応答をユーザーに見せる前 |
| Edit state | 「LLM の draft を人が手で直す」用途 |
実用パターン1: 計画レビュー¶
def plan_node(state):
plan = llm.invoke(state["messages"] + [("system", "計画だけ立てて、まだ実行しないで")])
return {"plan": plan.content, "messages": [plan]}
def review_plan(state):
decision = interrupt({"type": "plan_review", "plan": state["plan"]})
if decision["action"] == "approve":
return {}
elif decision["action"] == "edit":
return {"plan": decision["edited_plan"]}
else:
return {"plan": "", "messages": [("user", "計画を立て直して: " + decision["feedback"])]}
実用パターン2: 危険ツールの ガード¶
DANGEROUS = {"send_email", "delete_user", "execute_sql"}
def maybe_interrupt(state):
last = state["messages"][-1]
if any(c["name"] in DANGEROUS for c in last.tool_calls):
return "human_review"
return "tools"
add_conditional_edges で agent → maybe_interrupt → human_review or tools という分岐に。
Try¶
- ハンズオン2章の ReAct agent に「
calculatorを呼ぶ前に必ず人間確認」を入れる - CLI で
input("approve? y/n: ")を呼んで Command(resume) する簡易レビューUIを作る - interrupt 中に thread_id を別プロセスで
get_stateして、本当に止まっているか確認 - (難)reject されたら別の道を選ぶ分岐を作る
Slack 連携の発想(先取り)¶
本番で HITL を使うときは、CLI ではなく Slack 等が普通。
1. interrupt 発生
2. webhook で Slack に「承認ボタン付き」メッセージ
3. ユーザーがクリック → API server が Command(resume) を投げる
4. graph が続きを実行
これを Temporal Workflow の外殻で組むと、Slack 待ちで数日寝かせても安全(→ Step 06 で扱う)。
学んだこと¶
interrupt()でグラフを止め、Command(resume=...)で再開- Checkpointer が必須(state がないと再開できない)
- HITL は agent harness の安全装置。いつ・どこに置くかの設計が腕の見せどころ
- Slack/メール経由の HITL は別レイヤー(API + webhook)で組む
設計上の問い¶
学習しながら考えること。答えは1つではない:
- どのツールが危険か? 業務ドメインで違う
- 承認者は誰? 単一ユーザー?複数承認?
- タイムアウトは? 24h 返事がなければデフォルト reject か escalate か
- 拒否されたら? やり直し?別ルート?タスク終了?
これらの問いを Temporal の Signal / Timer / Retry policy にどうマッピングするかが、次章 06_temporal_integration のテーマ。
次は 05_multi_agent。専門家エージェントを組み合わせる。
作成: 2026-05-16 / 最終更新: 2026-05-16