This commit is contained in:
Harrison Chase
2025-08-26 20:26:34 -07:00
parent 75c73369a3
commit cc97fad7e5
2 changed files with 111 additions and 1 deletions
@@ -1 +1,98 @@
# Needs ability to jump to another node (back to model node, in case of reject or response from user)
from langgraph.agent.types import AgentMiddleware, AgentState, AgentUpdate, AgentGoTo
from typing import Dict, Any, List, Optional, Union
from langgraph.types import interrupt
from langgraph.prebuilt.interrupt import (
HumanInterruptConfig,
ActionRequest,
HumanInterrupt,
HumanResponse,
)
ToolInterruptConfig = Dict[str, HumanInterruptConfig]
class HumanInTheLoopMiddleware(AgentMiddleware):
def __init__(self,
tool_configs: ToolInterruptConfig,
message_prefix: str = "Tool execution requires approval",):
super().__init__()
self.tool_configs = tool_configs
self.message_prefix = message_prefix
def after_model(self, state: AgentState) -> AgentUpdate | AgentGoTo | None:
messages = state.messages
if not messages:
return
last_message = messages[-1]
if not hasattr(last_message, "tool_calls") or not last_message.tool_calls:
return
# Separate tool calls that need interrupts from those that don't
interrupt_tool_calls = []
auto_approved_tool_calls = []
for tool_call in last_message.tool_calls:
tool_name = tool_call["name"]
if tool_name in self.tool_configs:
interrupt_tool_calls.append(tool_call)
else:
auto_approved_tool_calls.append(tool_call)
# If no interrupts needed, return early
if not interrupt_tool_calls:
return
approved_tool_calls = auto_approved_tool_calls.copy()
# Process all tool calls that need interrupts in parallel
requests = []
for tool_call in interrupt_tool_calls:
tool_name = tool_call["name"]
tool_args = tool_call["args"]
description = f"{self.message_prefix}\n\nTool: {tool_name}\nArgs: {tool_args}"
tool_config = self.tool_configs[tool_name]
request: HumanInterrupt = {
"action_request": ActionRequest(
action=tool_name,
args=tool_args,
),
"config": tool_config,
"description": description,
}
requests.append(request)
responses: List[HumanResponse] = interrupt(requests)
for i, response in enumerate(responses):
tool_call = interrupt_tool_calls[i]
if response["type"] == "accept":
approved_tool_calls.append(tool_call)
elif response["type"] == "edit":
edited: ActionRequest = response["args"]
new_tool_call = {
"name": tool_call["name"],
"args": edited["args"],
"id": tool_call["id"],
}
approved_tool_calls.append(new_tool_call)
elif response["type"] == "ignore":
# NOTE: does not work with multiple interrupts
return {"goto": "__end__"}
elif response["type"] == "response":
# NOTE: does not work with multiple interrupts
tool_message = {"role": "tool", "tool_call_id": tool_call["id"], "content": response["args"]}
return {
"messages": [tool_message],
"goto": "model"
}
else:
raise ValueError(f"Unknown response type: {response['type']}")
last_message.tool_calls = approved_tool_calls
return {"messages": [last_message]}
@@ -0,0 +1,13 @@
from langgraph.agent.types import AgentMiddleware, AgentState, ModelRequest
from typing import Dict, Any, List, Optional, Union
from langgraph.types import interrupt
class SwarmMiddleWare(AgentMiddleware):
def __init__(self, model_configs: dict[str, dict]):
super().__init__()
def modify_model_request(
self, request: ModelRequest, state: AgentState
) -> ModelRequest: