Graph Hooks
Hooks in LangGraph4j provide a powerful mechanism to intercept the execution of graph nodes and edges. They allow you to run custom code before, after, or around the core logic of a node or a conditional edge. This is particularly useful for tasks such as:
- Logging and tracing execution flow.
- Collecting metrics and performance data.
- Modifying the state between steps.
- Implementing custom debugging tools.
- Altering the control flow of the graph dynamically.
Hook Interfaces
There are two main interfaces for hooks:
org.bsc.langgraph4j.hook.NodeHook: For instrumenting graph nodes.org.bsc.langgraph4j.hook.EdgeHook: For instrumenting conditional edges (since only they have an associated function).
Hook Types
Both NodeHook and EdgeHook support three distinct features, allowing for fine-grained control over the execution interception:
BeforeCall: This hook is executed before the node or edge function is called. It receives the currentnodeId,AgentStateandRunnableConfig.AfterCall: This hook is executed after the node or edge function completes. It receives thenodeId,AgentState,RunnableConfig, and the result of the previous function call.WrapCall: This hook "wraps" the execution of the node or edge function. It allows you to execute code both before and after the function call, and even to decide whether to call the original function at all. It receives thenodeId,AgentState,RunnableConfigand the action to execute.
Node Hooks
Below the available node hooks
public interface NodeHook {
@FunctionalInterface
interface BeforeCall<State extends AgentState> {
CompletableFuture<Map<String, Object>> applyBefore( String nodeId, State state, RunnableConfig config );
}
@FunctionalInterface
interface AfterCall<State extends AgentState> {
CompletableFuture<Map<String, Object>> applyAfter( String nodeId, State state, RunnableConfig config, Map<String, Object> lastResult ) ;
}
@FunctionalInterface
interface WrapCall<State extends AgentState> {
CompletableFuture<Map<String, Object>> applyWrap( String nodeId, State state, RunnableConfig config, AsyncNodeActionWithConfig<State> action);
}
}
Edge Hooks
Below the available edge hooks
public interface EdgeHook {
@FunctionalInterface
interface BeforeCall<State extends AgentState> {
CompletableFuture<Command> applyBefore( String sourceId, State state, RunnableConfig config );
}
@FunctionalInterface
interface AfterCall<State extends AgentState> {
CompletableFuture<Command> applyAfter( String sourceId, State state, RunnableConfig config, Command lastResult ) ;
}
@FunctionalInterface
interface WrapCall<State extends AgentState> {
CompletableFuture<Command> applyWrap( String sourceId, State state, RunnableConfig config, AsyncCommandAction<State> action);
}
}
Registering Hooks
You can add hooks to a StateGraph instance in two ways:
- Global Hooks: These hooks apply to all nodes or conditional edges in the graph.
- ID-Specific Hooks: These hooks apply only to a specific node or conditional edge, identified by its unique ID.
You can register multiple hooks, both global and specific.
Here are the methods available on the StateGraph class for adding hooks:
// Node Hooks
StateGraph<State> addBeforeCallNodeHook(NodeHook.BeforeCall<State> hook)
StateGraph<State> addBeforeCallNodeHook(String nodeId, NodeHook.BeforeCall<State> hook)
StateGraph<State> addAfterCallNodeHook(NodeHook.AfterCall<State> hook)
StateGraph<State> addAfterCallNodeHook(String nodeId, NodeHook.AfterCall<State> hook)
StateGraph<State> addWrapCallNodeHook(NodeHook.WrapCall<State> hook)
StateGraph<State> addWrapCallNodeHook(String nodeId, NodeHook.WrapCall<State> hook)
// Edge Hooks
StateGraph<State> addBeforeCallEdgeHook(EdgeHook.BeforeCall<State> hook)
StateGraph<State> addBeforeCallEdgeHook(String edgeId, EdgeHook.BeforeCall<State> hook)
StateGraph<State> addAfterCallEdgeHook(EdgeHook.AfterCall<State> hook)
StateGraph<State> addAfterCallEdgeHook(String edgeId, EdgeHook.AfterCall<State> hook)
StateGraph<State> addWrapCallEdgeHook(EdgeHook.WrapCall<State> hook)
StateGraph<State> addWrapCallEdgeHook(String edgeId, EdgeHook.WrapCall<State> hook)
Execution Order
When multiple hooks are registered for the same node or edge, they are executed in a specific order:
BeforeCallHooks: Executed in a LIFO (Last-In, First-Out) order. The last hook added is the first one to be executed.AfterCallHooks: Also executed in a LIFO order.WrapCallHooks: Executed in a FIFO (First-In, First-Out) order. The first hook added is the first one to wrap the action, meaning its "before" logic runs first, and its "after" logic runs last.