ValueError Traceback (most recent call last)
Cell In[3], line 44
40 graph.add_edge("tools", END)
42 app = graph.compile()
---> 44 result = app.invoke({
45 "messages": [
46 HumanMessage(content="hello")
47 ]
48 })
50 print("\n=== RESULT ===")
51 print(result)
File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langgraph/pregel/main.py:3050, in Pregel.invoke(self, input, config, context, stream_mode, print_mode, output_keys, interrupt_before, interrupt_after, durability, **kwargs)
3047 chunks: list[dict[str, Any] | Any] = []
3048 interrupts: list[Interrupt] = []
-> 3050 for chunk in self.stream(
3051 input,
3052 config,
3053 context=context,
3054 stream_mode=["updates", "values"]
3055 if stream_mode == "values"
3056 else stream_mode,
3057 print_mode=print_mode,
3058 output_keys=output_keys,
3059 interrupt_before=interrupt_before,
3060 interrupt_after=interrupt_after,
3061 durability=durability,
3062 **kwargs,
3063 ):
3064 if stream_mode == "values":
3065 if len(chunk) == 2:
File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langgraph/pregel/main.py:2633, in Pregel.stream(self, input, config, context, stream_mode, print_mode, output_keys, interrupt_before, interrupt_after, durability, subgraphs, debug, **kwargs)
2631 for task in loop.match_cached_writes():
2632 loop.output_writes(task.id, task.writes, cached=True)
-> 2633 for _ in runner.tick(
2634 [t for t in loop.tasks.values() if not t.writes],
2635 timeout=self.step_timeout,
2636 get_waiter=get_waiter,
2637 schedule_task=loop.accept_push,
2638 ):
2639 # emit output
2640 yield from _output(
2641 stream_mode, print_mode, subgraphs, stream.get, queue.Empty
2642 )
2643 loop.after_tick()
File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langgraph/pregel/_runner.py:167, in PregelRunner.tick(self, tasks, reraise, timeout, retry_policy, get_waiter, schedule_task)
165 t = tasks[0]
166 try:
--> 167 run_with_retry(
168 t,
169 retry_policy,
170 configurable={
171 CONFIG_KEY_CALL: partial(
172 _call,
173 weakref.ref(t),
174 retry_policy=retry_policy,
175 futures=weakref.ref(futures),
176 schedule_task=schedule_task,
177 submit=self.submit,
178 ),
179 },
180 )
181 self.commit(t, None)
182 except Exception as exc:
File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langgraph/pregel/_retry.py:42, in run_with_retry(task, retry_policy, configurable)
40 task.writes.clear()
41 # run the task
---> 42 return task.proc.invoke(task.input, config)
43 except ParentCommand as exc:
44 ns: str = config[CONF][CONFIG_KEY_CHECKPOINT_NS]
File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langgraph/_internal/_runnable.py:656, in RunnableSeq.invoke(self, input, config, **kwargs)
654 # run in context
655 with set_config_context(config, run) as context:
--> 656 input = context.run(step.invoke, input, config, **kwargs)
657 else:
658 input = step.invoke(input, config)
File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langgraph/_internal/_runnable.py:400, in RunnableCallable.invoke(self, input, config, **kwargs)
398 run_manager.on_chain_end(ret)
399 else:
--> 400 ret = self.func(*args, **kwargs)
401 if self.recurse and isinstance(ret, Runnable):
402 return ret.invoke(input, config)
File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langgraph/prebuilt/tool_node.py:799, in ToolNode._func(self, input, config, runtime)
797 input_types = [input_type] * len(tool_calls)
798 with get_executor_for_config(config) as executor:
--> 799 outputs = list(
800 executor.map(self._run_one, tool_calls, input_types, tool_runtimes)
801 )
803 return self._combine_tool_outputs(outputs, input_type)
File ~/miniconda/envs/my_env/lib/python3.13/concurrent/futures/_base.py:619, in Executor.map.<locals>.result_iterator()
616 while fs:
617 # Careful not to keep a reference to the popped future
618 if timeout is None:
--> 619 yield _result_or_cancel(fs.pop())
620 else:
621 yield _result_or_cancel(fs.pop(), end_time - time.monotonic())
File ~/miniconda/envs/my_env/lib/python3.13/concurrent/futures/_base.py:317, in _result_or_cancel(***failed resolving arguments***)
315 try:
316 try:
--> 317 return fut.result(timeout)
318 finally:
319 fut.cancel()
File ~/miniconda/envs/my_env/lib/python3.13/concurrent/futures/_base.py:449, in Future.result(self, timeout)
447 raise CancelledError()
448 elif self._state == FINISHED:
--> 449 return self.__get_result()
451 self._condition.wait(timeout)
453 if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
File ~/miniconda/envs/my_env/lib/python3.13/concurrent/futures/_base.py:401, in Future.__get_result(self)
399 if self._exception:
400 try:
--> 401 raise self._exception
402 finally:
403 # Break a reference cycle with the exception in self._exception
404 self = None
File ~/miniconda/envs/my_env/lib/python3.13/concurrent/futures/thread.py:59, in _WorkItem.run(self)
56 return
58 try:
---> 59 result = self.fn(*self.args, **self.kwargs)
60 except BaseException as exc:
61 self.future.set_exception(exc)
File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langchain_core/runnables/config.py:546, in ContextThreadPoolExecutor.map.<locals>._wrapped_fn(*args)
545 def _wrapped_fn(*args: Any) -> T:
--> 546 return contexts.pop().run(fn, *args)
File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langgraph/prebuilt/tool_node.py:1010, in ToolNode._run_one(self, call, input_type, tool_runtime)
1006 config = tool_runtime.config
1008 if self._wrap_tool_call is None:
1009 # No wrapper - execute directly
-> 1010 return self._execute_tool_sync(tool_request, input_type, config)
1012 # Define execute callable that can be called multiple times
1013 def execute(req: ToolCallRequest) -> ToolMessage | Command:
File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langgraph/prebuilt/tool_node.py:916, in ToolNode._execute_tool_sync(self, request, input_type, config)
914 try:
915 try:
--> 916 response = tool.invoke(call_args, config)
917 except ValidationError as exc:
918 # Filter out errors for injected arguments
919 injected = self._injected_args.get(call["name"])
File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langchain_core/tools/base.py:605, in BaseTool.invoke(self, input, config, **kwargs)
597 @override
598 def invoke(
599 self,
(...)
602 **kwargs: Any,
603 ) -> Any:
604 tool_input, kwargs = _prep_run_args(input, config, **kwargs)
--> 605 return self.run(tool_input, **kwargs)
File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langchain_core/tools/base.py:932, in BaseTool.run(self, tool_input, verbose, start_color, color, callbacks, tags, metadata, run_name, run_id, config, tool_call_id, **kwargs)
930 if error_to_raise:
931 run_manager.on_tool_error(error_to_raise)
--> 932 raise error_to_raise
933 output = _format_output(content, artifact, tool_call_id, self.name, status)
934 run_manager.on_tool_end(output, color=color, name=self.name, **kwargs)
File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langchain_core/tools/base.py:898, in BaseTool.run(self, tool_input, verbose, start_color, color, callbacks, tags, metadata, run_name, run_id, config, tool_call_id, **kwargs)
896 if config_param := _get_runnable_config_param(self._run):
897 tool_kwargs |= {config_param: config}
--> 898 response = context.run(self._run, *tool_args, **tool_kwargs)
899 if self.response_format == "content_and_artifact":
900 msg = (
901 "Since response_format='content_and_artifact' "
902 "a two-tuple of the message content and raw tool output is "
903 f"expected. Instead, generated response is of type: "
904 f"{type(response)}."
905 )
File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langchain_core/tools/structured.py:93, in StructuredTool._run(self, config, run_manager, *args, **kwargs)
91 if config_param := _get_runnable_config_param(self.func):
92 kwargs[config_param] = config
---> 93 return self.func(*args, **kwargs)
94 msg = "StructuredTool does not support sync invocation."
95 raise NotImplementedError(msg)
Cell In[3], line 15
12 @tool
13 def exploding_tool():
14 """A tool that always raises an exception."""
---> 15 raise ValueError("boom")
ValueError: boom
During task with name 'tools' and id 'f1c7f354-d5cf-e2d9-fe91-2088176594ed'
After updating to langgraph-prebuilt 1.0.1 (via langgraph 1.0.2), I noticed a change in the default behavior of tool error handling.
Previously, tool nodes would automatically handle errors.
After this commit:
…the default changed, and tool error handling is now off unless explicitly enabled.
This modifies the behavior of existing graphs that relied on the old default.
Tool node error handling enabled by default, matching earlier versions.
Checked other resources
Example Code
Error Message and Stack Trace (if applicable)
ValueError Traceback (most recent call last) Cell In[3], line 44 40 graph.add_edge("tools", END) 42 app = graph.compile() ---> 44 result = app.invoke({ 45 "messages": [ 46 HumanMessage(content="hello") 47 ] 48 }) 50 print("\n=== RESULT ===") 51 print(result) File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langgraph/pregel/main.py:3050, in Pregel.invoke(self, input, config, context, stream_mode, print_mode, output_keys, interrupt_before, interrupt_after, durability, **kwargs) 3047 chunks: list[dict[str, Any] | Any] = [] 3048 interrupts: list[Interrupt] = [] -> 3050 for chunk in self.stream( 3051 input, 3052 config, 3053 context=context, 3054 stream_mode=["updates", "values"] 3055 if stream_mode == "values" 3056 else stream_mode, 3057 print_mode=print_mode, 3058 output_keys=output_keys, 3059 interrupt_before=interrupt_before, 3060 interrupt_after=interrupt_after, 3061 durability=durability, 3062 **kwargs, 3063 ): 3064 if stream_mode == "values": 3065 if len(chunk) == 2: File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langgraph/pregel/main.py:2633, in Pregel.stream(self, input, config, context, stream_mode, print_mode, output_keys, interrupt_before, interrupt_after, durability, subgraphs, debug, **kwargs) 2631 for task in loop.match_cached_writes(): 2632 loop.output_writes(task.id, task.writes, cached=True) -> 2633 for _ in runner.tick( 2634 [t for t in loop.tasks.values() if not t.writes], 2635 timeout=self.step_timeout, 2636 get_waiter=get_waiter, 2637 schedule_task=loop.accept_push, 2638 ): 2639 # emit output 2640 yield from _output( 2641 stream_mode, print_mode, subgraphs, stream.get, queue.Empty 2642 ) 2643 loop.after_tick() File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langgraph/pregel/_runner.py:167, in PregelRunner.tick(self, tasks, reraise, timeout, retry_policy, get_waiter, schedule_task) 165 t = tasks[0] 166 try: --> 167 run_with_retry( 168 t, 169 retry_policy, 170 configurable={ 171 CONFIG_KEY_CALL: partial( 172 _call, 173 weakref.ref(t), 174 retry_policy=retry_policy, 175 futures=weakref.ref(futures), 176 schedule_task=schedule_task, 177 submit=self.submit, 178 ), 179 }, 180 ) 181 self.commit(t, None) 182 except Exception as exc: File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langgraph/pregel/_retry.py:42, in run_with_retry(task, retry_policy, configurable) 40 task.writes.clear() 41 # run the task ---> 42 return task.proc.invoke(task.input, config) 43 except ParentCommand as exc: 44 ns: str = config[CONF][CONFIG_KEY_CHECKPOINT_NS] File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langgraph/_internal/_runnable.py:656, in RunnableSeq.invoke(self, input, config, **kwargs) 654 # run in context 655 with set_config_context(config, run) as context: --> 656 input = context.run(step.invoke, input, config, **kwargs) 657 else: 658 input = step.invoke(input, config) File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langgraph/_internal/_runnable.py:400, in RunnableCallable.invoke(self, input, config, **kwargs) 398 run_manager.on_chain_end(ret) 399 else: --> 400 ret = self.func(*args, **kwargs) 401 if self.recurse and isinstance(ret, Runnable): 402 return ret.invoke(input, config) File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langgraph/prebuilt/tool_node.py:799, in ToolNode._func(self, input, config, runtime) 797 input_types = [input_type] * len(tool_calls) 798 with get_executor_for_config(config) as executor: --> 799 outputs = list( 800 executor.map(self._run_one, tool_calls, input_types, tool_runtimes) 801 ) 803 return self._combine_tool_outputs(outputs, input_type) File ~/miniconda/envs/my_env/lib/python3.13/concurrent/futures/_base.py:619, in Executor.map.<locals>.result_iterator() 616 while fs: 617 # Careful not to keep a reference to the popped future 618 if timeout is None: --> 619 yield _result_or_cancel(fs.pop()) 620 else: 621 yield _result_or_cancel(fs.pop(), end_time - time.monotonic()) File ~/miniconda/envs/my_env/lib/python3.13/concurrent/futures/_base.py:317, in _result_or_cancel(***failed resolving arguments***) 315 try: 316 try: --> 317 return fut.result(timeout) 318 finally: 319 fut.cancel() File ~/miniconda/envs/my_env/lib/python3.13/concurrent/futures/_base.py:449, in Future.result(self, timeout) 447 raise CancelledError() 448 elif self._state == FINISHED: --> 449 return self.__get_result() 451 self._condition.wait(timeout) 453 if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]: File ~/miniconda/envs/my_env/lib/python3.13/concurrent/futures/_base.py:401, in Future.__get_result(self) 399 if self._exception: 400 try: --> 401 raise self._exception 402 finally: 403 # Break a reference cycle with the exception in self._exception 404 self = None File ~/miniconda/envs/my_env/lib/python3.13/concurrent/futures/thread.py:59, in _WorkItem.run(self) 56 return 58 try: ---> 59 result = self.fn(*self.args, **self.kwargs) 60 except BaseException as exc: 61 self.future.set_exception(exc) File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langchain_core/runnables/config.py:546, in ContextThreadPoolExecutor.map.<locals>._wrapped_fn(*args) 545 def _wrapped_fn(*args: Any) -> T: --> 546 return contexts.pop().run(fn, *args) File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langgraph/prebuilt/tool_node.py:1010, in ToolNode._run_one(self, call, input_type, tool_runtime) 1006 config = tool_runtime.config 1008 if self._wrap_tool_call is None: 1009 # No wrapper - execute directly -> 1010 return self._execute_tool_sync(tool_request, input_type, config) 1012 # Define execute callable that can be called multiple times 1013 def execute(req: ToolCallRequest) -> ToolMessage | Command: File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langgraph/prebuilt/tool_node.py:916, in ToolNode._execute_tool_sync(self, request, input_type, config) 914 try: 915 try: --> 916 response = tool.invoke(call_args, config) 917 except ValidationError as exc: 918 # Filter out errors for injected arguments 919 injected = self._injected_args.get(call["name"]) File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langchain_core/tools/base.py:605, in BaseTool.invoke(self, input, config, **kwargs) 597 @override 598 def invoke( 599 self, (...) 602 **kwargs: Any, 603 ) -> Any: 604 tool_input, kwargs = _prep_run_args(input, config, **kwargs) --> 605 return self.run(tool_input, **kwargs) File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langchain_core/tools/base.py:932, in BaseTool.run(self, tool_input, verbose, start_color, color, callbacks, tags, metadata, run_name, run_id, config, tool_call_id, **kwargs) 930 if error_to_raise: 931 run_manager.on_tool_error(error_to_raise) --> 932 raise error_to_raise 933 output = _format_output(content, artifact, tool_call_id, self.name, status) 934 run_manager.on_tool_end(output, color=color, name=self.name, **kwargs) File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langchain_core/tools/base.py:898, in BaseTool.run(self, tool_input, verbose, start_color, color, callbacks, tags, metadata, run_name, run_id, config, tool_call_id, **kwargs) 896 if config_param := _get_runnable_config_param(self._run): 897 tool_kwargs |= {config_param: config} --> 898 response = context.run(self._run, *tool_args, **tool_kwargs) 899 if self.response_format == "content_and_artifact": 900 msg = ( 901 "Since response_format='content_and_artifact' " 902 "a two-tuple of the message content and raw tool output is " 903 f"expected. Instead, generated response is of type: " 904 f"{type(response)}." 905 ) File ~/miniconda/envs/my_env/lib/python3.13/site-packages/langchain_core/tools/structured.py:93, in StructuredTool._run(self, config, run_manager, *args, **kwargs) 91 if config_param := _get_runnable_config_param(self.func): 92 kwargs[config_param] = config ---> 93 return self.func(*args, **kwargs) 94 msg = "StructuredTool does not support sync invocation." 95 raise NotImplementedError(msg) Cell In[3], line 15 12 @tool 13 def exploding_tool(): 14 """A tool that always raises an exception.""" ---> 15 raise ValueError("boom") ValueError: boom During task with name 'tools' and id 'f1c7f354-d5cf-e2d9-fe91-2088176594ed'Description
Hello,
After updating to langgraph-prebuilt 1.0.1 (via langgraph 1.0.2), I noticed a change in the default behavior of tool error handling.
Previously, tool nodes would automatically handle errors.
After this commit:
4ac1c62#diff-34ed372d5e45913593e00f5704aa61f05ebc1d0aafbfd032f12b29c0db448cbeL126
…the default changed, and tool error handling is now off unless explicitly enabled.
This modifies the behavior of existing graphs that relied on the old default.
Expected
Tool node error handling enabled by default, matching earlier versions.
Actual
Tool errors now propagate unless manually enabling tool_node_handling like below
Thanks for the work on the project!
Just reporting this in case the behavior change was unintentional.
System Info
System Information
Package Information
Optional packages not installed
Other Dependencies