LangChain ヒューマンインザループ
本番環境では、メール送信・削除実行・支払い処理など、人による確認が必要な操作があります。
ヒューマンインザループ(HITL)は、エージェントを重要な場面で一時停止させ、人による承認を待ってから続行させます。
interrupt()——ツール内での実行一時停止
interrupt()この関数を使うと、ツールの実行途中で一時停止し、外部からの入力を待ってから続行できます:
例
from langgraph.types import interrupt
# ツール内で interrupt() を使って一時停止
def send_email(to: str, subject: str, body: str) -> str:
"""メールを送信する(人による承認が必要)"""
# 実行を一時停止し、外部に承認リクエストを送信
approval = interrupt({
"action": "send_email",
"to": to,
"subject": subject,
"body": body,
"message": "このメールを送信しますか?"
})
# 外部から approval が渡されるのを待って続行
if approval.get("approved"):
return f"メールを {to} に送信しました"
else:
return f"メール送信は拒否されました:{approval.get('reason', 'ユーザーによるキャンセル')}"
# ツール内で interrupt() を使って一時停止
def send_email(to: str, subject: str, body: str) -> str:
"""メールを送信する(人による承認が必要)"""
# 実行を一時停止し、外部に承認リクエストを送信
approval = interrupt({
"action": "send_email",
"to": to,
"subject": subject,
"body": body,
"message": "このメールを送信しますか?"
})
# 外部から approval が渡されるのを待って続行
if approval.get("approved"):
return f"メールを {to} に送信しました"
else:
return f"メール送信は拒否されました:{approval.get('reason', 'ユーザーによるキャンセル')}"
interrupt() のワークフロー:
- ツールが interrupt() を呼び出す → エージェントの実行が一時停止
- 外部システムが中断情報を取得し、ユーザーに表示する
- ユーザーの決定後、Command(resume=...) で実行を再開する
- interrupt() がユーザーから渡された値を返し、ツールの実行を続行する
完全な例——承認フロー
例
from dotenv import load_dotenv
load_dotenv()
from langgraph.types import interrupt, Command
from langgraph.checkpoint.memory import InMemorySaver
from langchain.tools import tool
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
@tool
def delete_course(course_name: str) -> str:
"""コースを削除する(承認が必要)。
Args:
course_name: 削除するコース名
"""
# 一時停止して承認を待つ
approval = interrupt({
"action": "delete_course",
"course": course_name,
"message": f"コース『{course_name}』の削除を確認しますか?この操作は取り消せません。"
})
if approval.get("confirmed"):
return f"コース『{course_name}』は削除されました"
else:
return f"削除操作はキャンセルされました"
checkpointer = InMemorySaver()
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
tools=[delete_course],
checkpointer=checkpointer,
system_prompt="あなたはサンプル の管理者アシスタントです。",
)
config = {"configurable": {"thread_id": "admin-001"}}
# ステップ1: 削除リクエストを開始する(中断が発生します)
print("=== 実行開始 ===")
result = agent.invoke(
{"messages": [HumanMessage(content="コース『古い Java チュートリアル』を削除してください")]},
config=config,
)
# エージェントが一時停止しているか確認
state = agent.get_state(config)
print(f"状態: {state.next}") # ('tools',) は tools ノードで一時停止していることを示す
print(f"中断情報: {state.tasks)
# ステップ2: 人による承認(ユーザーが「確認」をクリックしたことをシミュレート)
print("\n=== 人による承認 ===")
resume_value = {"confirmed": True, "operator": "管理者 张三"}
result = agent.invoke(
Command(resume=resume_value),
config=config,
)
print(f"最終応答: {result['messages'][-1].content}")
load_dotenv()
from langgraph.types import interrupt, Command
from langgraph.checkpoint.memory import InMemorySaver
from langchain.tools import tool
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
@tool
def delete_course(course_name: str) -> str:
"""コースを削除する(承認が必要)。
Args:
course_name: 削除するコース名
"""
# 一時停止して承認を待つ
approval = interrupt({
"action": "delete_course",
"course": course_name,
"message": f"コース『{course_name}』の削除を確認しますか?この操作は取り消せません。"
})
if approval.get("confirmed"):
return f"コース『{course_name}』は削除されました"
else:
return f"削除操作はキャンセルされました"
checkpointer = InMemorySaver()
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
tools=[delete_course],
checkpointer=checkpointer,
system_prompt="あなたはサンプル の管理者アシスタントです。",
)
config = {"configurable": {"thread_id": "admin-001"}}
# ステップ1: 削除リクエストを開始する(中断が発生します)
print("=== 実行開始 ===")
result = agent.invoke(
{"messages": [HumanMessage(content="コース『古い Java チュートリアル』を削除してください")]},
config=config,
)
# エージェントが一時停止しているか確認
state = agent.get_state(config)
print(f"状態: {state.next}") # ('tools',) は tools ノードで一時停止していることを示す
print(f"中断情報: {state.tasks)
# ステップ2: 人による承認(ユーザーが「確認」をクリックしたことをシミュレート)
print("\n=== 人による承認 ===")
resume_value = {"confirmed": True, "operator": "管理者 张三"}
result = agent.invoke(
Command(resume=resume_value),
config=config,
)
print(f"最終応答: {result['messages'][-1].content}")
実行結果:
=== 开始执行 ===
ステータス: ('tools',)
中断信息: (Interrupt(value={'action': 'delete_course', ...}),)
=== 人工审批 ===
最終返答: 「古いJavaチュートリアル」コースを削除しました。
interrupt_before / interrupt_after パラメータ
ツール内で interrupt() を使うだけでなく、create_agent() でグローバルな中断ポイントを設定することもできます:
例
from langgraph.checkpoint.memory import InMemorySaver
checkpointer = InMemorySaver()
agent = create_agent(
model="deepseek:deepseek-v4-flash",
tools=[some_tool],
checkpointer=checkpointer,
# ツールノードの前に一時停止(ツールを呼び出すたびに承認が必要)
interrupt_before=["tools"],
# モデルノードの後に一時停止(モデルが応答するたびに確認できる)
# interrupt_after=["model"],
)
checkpointer = InMemorySaver()
agent = create_agent(
model="deepseek:deepseek-v4-flash",
tools=[some_tool],
checkpointer=checkpointer,
# ツールノードの前に一時停止(ツールを呼び出すたびに承認が必要)
interrupt_before=["tools"],
# モデルノードの後に一時停止(モデルが応答するたびに確認できる)
# interrupt_after=["model"],
)
| パラメータ | 一時停止のタイミング | 適用シナリオ |
|---|---|---|
| interrupt_before=["tools"] | ツールを実行するたびに前 | すべてのツール呼び出しに承認が必要 |
| interrupt_before=["model"] | モデルを呼び出すたびに前 | モデル処理前にメッセージを人為的にレビュー |
| interrupt_after=["model"] | モデルが応答するたびに後 | モデル出力を確認してから続行するか決定 |
| interrupt_after=["tools"] | ツールを実行するたびに後 | ツールの結果を確認してから次のステップを決定 |
HITL の典型的なアーキテクチャ
実際の Web アプリケーションでは、HITL は通常次のように実装されます:
例
# バックエンド: ユーザーメッセージを受け取り、中断ポイントまで処理し、中断情報を返す
def handle_user_message(thread_id: str, message: str):
config = {"configurable": {"thread_id": thread_id}}
result = agent.invoke(
{"messages": [HumanMessage(content=message)]},
config=config,
)
state = agent.get_state(config)
# 承認待ちかどうかを確認
if state.tasks and state.tasks[0].interrupts:
return {
"status": "pending_approval",
"interrupt": state.tasks[0].interrupts[0].value,
"thread_id": thread_id,
}
return {
"status": "completed",
"reply": result["messages"][-1].content,
}
# バックエンド: ユーザーの承認を処理
def handle_approval(thread_id: str, approved: bool, reason: str = ""):
config = {"configurable": {"thread_id": thread_id}}
result = agent.invoke(
Command(resume={"confirmed": approved, "reason": reason}),
config=config,
)
return {"status": "completed", "reply": result["messages"][-1].content}
def handle_user_message(thread_id: str, message: str):
config = {"configurable": {"thread_id": thread_id}}
result = agent.invoke(
{"messages": [HumanMessage(content=message)]},
config=config,
)
state = agent.get_state(config)
# 承認待ちかどうかを確認
if state.tasks and state.tasks[0].interrupts:
return {
"status": "pending_approval",
"interrupt": state.tasks[0].interrupts[0].value,
"thread_id": thread_id,
}
return {
"status": "completed",
"reply": result["messages"][-1].content,
}
# バックエンド: ユーザーの承認を処理
def handle_approval(thread_id: str, approved: bool, reason: str = ""):
config = {"configurable": {"thread_id": thread_id}}
result = agent.invoke(
Command(resume={"confirmed": approved, "reason": reason}),
config=config,
)
return {"status": "completed", "reply": result["messages"][-1].content}
その他の拡張HITL を使用するには Checkpointer が必要です。エージェントが interrupt() で一時停止するとき、状態を永続化しなければ、再開時に正しく実行を続行できないためです。