from langchain.agents import create_agentfrom langchain.agents.middleware import HumanInTheLoopMiddleware from langgraph.checkpoint.memory import InMemorySaver agent = create_agent( model="gpt-5.4", tools=[write_file, execute_sql, read_data], middleware=[ HumanInTheLoopMiddleware( interrupt_on={ "write_file": True, # All decisions (approve, edit, reject, respond) allowed "execute_sql": {"allowed_decisions": ["approve", "reject"]}, # No editing allowed "read_data": False, # Safe operation, no approval needed }, # Prefix for interrupt messages - combined with tool name and args to form the full message # e.g., "Tool execution pending approval: execute_sql with query='DELETE FROM...'" # Individual tools can override this by specifying a "description" in their interrupt config description_prefix="Tool execution pending approval", ), ], # Human-in-the-loop requires checkpointing to handle interrupts. # In production, use a persistent checkpointer like AsyncPostgresSaver. checkpointer=InMemorySaver(),)
from langgraph.types import Command# Human-in-the-loop leverages LangGraph's persistence layer.# You must provide a thread ID to associate the execution with a conversation thread,# so the conversation can be paused and resumed (as is needed for human review).config = {"configurable": {"thread_id": "some_id"}}# Run the graph until the interrupt is hit.result = agent.invoke( { "messages": [ { "role": "user", "content": "Delete old records from the database", } ] }, config=config, version="v2",)# result is a GraphOutput with .value and .interruptsprint(result.interrupts)# > (# > Interrupt(# > value={# > 'action_requests': [# > {# > 'name': 'execute_sql',# > 'arguments': {'query': 'DELETE FROM records WHERE created_at < NOW() - INTERVAL \'30 days\';'},# > 'description': 'Tool execution pending approval\n\nTool: execute_sql\nArgs: {...}'# > }# > ],# > 'review_configs': [# > {# > 'action_name': 'execute_sql',# > 'allowed_decisions': ['approve', 'reject']# > }# > ]# > }# > ),# > )# Resume with approval decisionagent.invoke( Command( resume={"decisions": [{"type": "approve"}]} # or "reject" ), config=config, # Same thread ID to resume the paused conversation version="v2",)
agent.invoke( Command( # Decisions are provided as a list, one per action under review. # The order of decisions must match the order of actions # in the interrupt request. resume={ "decisions": [ { "type": "approve", } ] } ), config=config, # Same thread ID to resume the paused conversation version="v2",)
使用 edit 在执行前修改工具调用。
提供包含新工具名称和参数的已编辑动作。
agent.invoke( Command( # Decisions are provided as a list, one per action under review. # The order of decisions must match the order of actions # in the interrupt request. resume={ "decisions": [ { "type": "edit", # Edited action with tool name and args "edited_action": { # Tool name to call. # Will usually be the same as the original action. "name": "new_tool_name", # Arguments to pass to the tool. "args": {"key1": "new_value", "key2": "original_value"}, } } ] } ), config=config, # Same thread ID to resume the paused conversation version="v2",)
agent.invoke( Command( # Decisions are provided as a list, one per action under review. # The order of decisions must match the order of actions # in the interrupt request. resume={ "decisions": [ { "type": "reject", # Optional: explain why the action was rejected # and whether the agent should retry a different approach. "message": "User rejected this action. Do not retry this tool call.", } ] } ), config=config, # Same thread ID to resume the paused conversation version="v2",)
agent.invoke( Command( # Decisions are provided as a list, one per action under review. # The order of decisions must match the order of actions # in the interrupt request. resume={ "decisions": [ { "type": "respond", # The human's reply, returned directly as the tool result "message": "Blue.", } ] } ), config=config, # Same thread ID to resume the paused conversation version="v2",)
from langgraph.types import Commandconfig = {"configurable": {"thread_id": "some_id"}}# Stream agent progress and LLM tokens until interruptfor chunk in agent.stream( {"messages": [{"role": "user", "content": "Delete old records from the database"}]}, config=config, stream_mode=["updates", "messages"], version="v2",): if chunk["type"] == "messages": # LLM token token, metadata = chunk["data"] if token.content: print(token.content, end="", flush=True) elif chunk["type"] == "updates": # Check for interrupt if "__interrupt__" in chunk["data"]: print(f"\n\nInterrupt: {chunk['data']['__interrupt__']}")# Resume with streaming after human decisionfor chunk in agent.stream( Command(resume={"decisions": [{"type": "approve"}]}), config=config, stream_mode=["updates", "messages"], version="v2",): if chunk["type"] == "messages": token, metadata = chunk["data"] if token.content: print(token.content, end="", flush=True)