Description
Currently, the interrupt function requires the value to be passed directly. This value is surfaced to the client when a GraphInterrupt is raised.
I propose extending interrupt (and adding/updating async_interrupt) to support callables (both sync and async). If a callable is provided, it should only be executed at the moment the interrupt is actually raised, rather than being evaluated beforehand and passed as a static argument.
Motivation
In complex nodes, the “value” surfaced to a human might require:
-
Expensive Computation: Generating a summary or a preview of a large dataset.
-
External Calls: Fetching the latest state from a DB or a 3-party API to ensure the human sees the most up-to-date context.
-
Cleanliness: Avoiding boilerplate logic inside the node to “prepare” the interrupt value if it’s only needed conditionally.
By passing a callable, we ensure these operations only happen if the graph hasn’t already been resumed for that specific interrupt index.
Proposed Changes
1. Support for Callables in interrupt
The interrupt function logic would check if value is callable. If it is, and no resume value is found in the scratchpad, it executes the callable to populate the Interrupt object.
2. Introduction of async_interrupt
Since many LangGraph workflows are async, providing an async_interrupt allows users to await database or API calls lazily.
Example implementation for async_interrupt:
Python
async def async_interrupt(value: Union[Any, Callable[[], Awaitable[Any]]]) -> Any:
# ... logic to check scratchpad ...
# When no resume value is found:
interrupt_value = await value() if callable(value) else value
raise GraphInterrupt(
(
Interrupt.from_ns(
value=interrupt_value,
ns=conf[CONFIG_KEY_CHECKPOINT_NS],
),
)
)
Use Case Example
Python
async def my_node(state: State):
# The 'get_latest_db_context' only runs if we actually
# hit the interrupt, not on every re-execution of the node.
answer = await async_interrupt(get_latest_db_context)
return {"data": answer}