feat(langchain): add interrupt_when field to InterruptOnConfig

This commit is contained in:
Nick Hollon
2026-05-12 15:30:33 -07:00
parent d0d78f1aeb
commit fc0a6595d7
2 changed files with 42 additions and 0 deletions

View File

@@ -14,6 +14,7 @@ from langchain.agents.middleware.types import (
ResponseT,
StateT,
)
from langchain.tools import ToolRuntime
class Action(TypedDict):
@@ -130,6 +131,18 @@ class _DescriptionFactory(Protocol):
...
class _InterruptWhen(Protocol):
"""Predicate that decides whether a tool call should trigger an interrupt."""
def __call__(
self,
tool_call: ToolCall,
runtime: ToolRuntime[ContextT, Any],
) -> bool:
"""Return `True` to interrupt this tool call, `False` to auto-approve it."""
...
class InterruptOnConfig(TypedDict):
"""Configuration for an action requiring human in the loop.
@@ -178,6 +191,24 @@ class InterruptOnConfig(TypedDict):
args_schema: NotRequired[dict[str, Any]]
"""JSON schema for the args associated with the action, if edits are allowed."""
interrupt_when: NotRequired[_InterruptWhen]
"""Optional predicate gating whether this tool call triggers an interrupt.
Called with the proposed `ToolCall` and a `ToolRuntime` constructed by the
middleware. Return `True` to interrupt (with this config's `allowed_decisions`)
or `False` to auto-approve the call as if the tool were not listed in
`interrupt_on`.
Predicates must be synchronous and deterministic — LangGraph interrupt replay
on resume requires the same interrupts to fire on each evaluation. Exceptions
propagate.
Note: the `ToolRuntime` constructed for predicate evaluation has three
deviations from a tool-time `ToolRuntime`: `tools=[]`, `config={}`, and
`execution_info`/`server_info=None`. Predicates should rely on `state`,
`tool_call_id`, `context`, and `store` rather than these fields.
"""
class HumanInTheLoopMiddleware(AgentMiddleware[StateT, ContextT, ResponseT]):
"""Human in the loop middleware."""

View File

@@ -883,3 +883,14 @@ def test_human_in_the_loop_middleware_preserves_order_with_rejections() -> None:
assert isinstance(tool_message, ToolMessage)
assert tool_message.content == "Rejected tool B"
assert tool_message.tool_call_id == "id_b"
def test_interrupt_on_config_accepts_interrupt_when() -> None:
"""`InterruptOnConfig` accepts an optional `interrupt_when` predicate."""
from langchain.agents.middleware.human_in_the_loop import _InterruptWhen # noqa: F401
config: InterruptOnConfig = {
"allowed_decisions": ["approve", "reject"],
"interrupt_when": lambda _tc, _rt: True,
}
assert config["interrupt_when"](None, None) is True # type: ignore[arg-type]