TL;DR,
Is there a way to catch node errors, while adding these errors to the state and short-circuit the graph.
I should add these errors to the state because I want to provide it to my end-users to explain why the app isn’t working. I also want to short circuit the graph because my nodes are dependent to each other.
A perfect solution would be like:
Whenever an exception is caught, skip everything in the graph and go to the handle_error node, then END.
I then could be able to both update the state and short-circuit the graph. Which’d be awesome. And I only can imagine this solution with something like Command(jump_to=“handle_error”).
If it aligns with maintainers’ view, I’d be glad to work on that.
If there is a much more easy way you know that I can solve this problem, I’d love to hear from you.
Look at this basic example:
from langgraph.graph import START, END, StateGraph, MessagesState
class State(MessagesState):
text: str
def node_a(state: State) -> State:
modified_text = state["text"] + "a"
return {
"text": modified_text,
"messages": [
{
"type": "tool",
"content": "node_a successfully modified the text",
"tool_call_id": "1",
"ok": True,
}
],
}
def node_b(state: State) -> State:
modified_text = state["text"] + "b"
return {
"text": modified_text,
"messages": [
{
"type": "tool",
"content": "node_b successfully modified the text",
"tool_call_id": "2",
"ok": True,
}
],
}
def node_c(state: State) -> State:
modified_text = state["text"] + "c"
return {
"text": modified_text,
"messages": [
{
"type": "tool",
"content": "node c successfully modified the text",
"tool_call_id": "3",
"ok": True,
}
],
}
graph = StateGraph(State)
graph.add_node("node_a", node_a)
graph.add_node("node_b", node_b)
graph.add_node("node_c", node_c)
graph.add_edge(START, "node_a")
graph.add_edge("node_a", "node_b")
graph.add_edge("node_b", "node_c")
print(graph.compile().invoke({"text": ""}))
# Output:
"""
{
"messages": [
ToolMessage(
content="node_a successfully modified the text",
id="130bc0d7-ded7-4983-906a-7ba2fdf83755",
tool_call_id="1",
),
ToolMessage(
content="node_b successfully modified the text",
id="697287c7-ba4f-4afd-a4f7-a3c9a5be5d17",
tool_call_id="2",
),
ToolMessage(
content="node c successfully modified the text",
id="f7cda129-ae86-4c42-957a-fbe7221e6f68",
tool_call_id="3",
),
],
"text": "abc",
}
"""
Assume that every node here represents some logic that generates/retrieves necessary information to give the end-user what they want. For example, you can think that node_a generates excel files, node_b performs fuzzy match and node_c retrieves google drive files from drive api. What I’m trying to say is, these nodes can be used as tools without being given to a react agent.
Now consider we need to catch the exceptions thrown from these nodes, and append a ToolMessage to the messages with ok arg is set to False, for further logging or to explain the end-user why our app isn’t working. Since the nodes are sequential and -assume that- dependent on each other, we want our graph to short circuit if an exception occurs -because in our graph, “ac” means nothing without “b”, you got the idea-
We can’t throw exceptions from our graph and catch them in our code which calls invoke on the graph, because we still want to append to the messages channel. Writing to a checkpointer from outside of a graph is tricky, unnecessary and kills the elegance of LangGraph.
Adding an error handler decorator is one intuitive way to go, as discussed in the #6170
and #6571.
I learned from #6571 that Command() adds a dynamic edge, static edges still execute. So trying to make Command() to override static edges is not a valid solution.
I tried a lot of things, but I can’t seem to find a way -without hacky solutions- to implement the logic I explained. I think if Command had a parameter like jump_to, it’d be pretty easy to do. I’d create an error handler decorator, if I catch an exception, I’d goto handle_error node, by updating the state.