feat(langchain): ToolErrorMiddleware - #38781
Conversation
ToolErrorMiddleware
Eugene Yurtsev (eyurtsev)
left a comment
There was a problem hiding this comment.
Looks good!
One more possibility is to just accept a single on_error callable and have the user responsible for all the logic... unless we want to provide some pre-built on_errors (like the code is doing now)
Main thing is to not invoke async code on the sync path
Sydney Runkle (sydney-runkle)
left a comment
There was a problem hiding this comment.
API LGTM
|
|
||
| def __init__( | ||
| self, | ||
| catch: Catch, |
There was a problem hiding this comment.
kwarg only?
There was a problem hiding this comment.
removed catch in favor of required (a)on_error, per discussion above.
|
|
||
| OnError = Callable[ | ||
| [Exception, "ToolCallRequest"], | ||
| "str | list[str | dict[Any, Any]] | Awaitable[str | list[str | dict[Any, Any]]]", |
There was a problem hiding this comment.
this is not intelligible, what is this?
There was a problem hiding this comment.
can we just have it return a ToolMessage?
There was a problem hiding this comment.
str | list[str | dict[Any, Any]] is how content is typed on our messages :)
I've replaced this with str | list[ContentBlock] for simplicity and readability.
asking for a ToolMessage doesn't change the fact that users need to supply the content. they would just also need to supply tool_call_id, name, and status, which we can do for them.
## Summary Ports `ToolErrorMiddleware` from [langchain-ai/langchain#38781](langchain-ai/langchain#38781) to LangChain.js. The new middleware lets agents selectively convert tool execution failures into model-visible error messages while preserving original exceptions by default. ## Changes - Add and export `toolErrorMiddleware`, `ToolErrorHandler`, and `ToolErrorMiddlewareConfig` from `langchain`. - Support synchronous or asynchronous error handlers and structured `ToolMessage` content. - Allow handling to be restricted by tool name or tool instance. - Propagate unhandled failures and LangGraph control-flow signals unchanged, avoiding disclosure of raw exception details unless the handler explicitly includes them. - Add focused coverage for error handling, propagation, filtering, structured content, disclosure control, and graph interrupts.
|
Hey folks, writing the initial issue was a long process of tinkering with the error APIs, so I am very thankful for this pull request and I hope this will increase LangChain agents overall quality with better error management :) |
|
Thanks for your work writing up the issue, Eric Burel (@eric-burel)! The middleware is released, would appreciate any feedback you have. Docs: https://docs.langchain.com/oss/python/langchain/middleware/built-in#tool-error |
A model that passes malformed arguments to `ask_user` (for example an empty questions list, or a choice with a blank value) previously crashed the whole run: the tool raises `ValueError`, `ToolNode` never converted it, and the error bubbled up as a fatal `Agent error`. The model never saw the failure and got no chance to reissue corrected arguments, so the user's turn dead-ended. Now those validation errors surface to the model as an error `ToolMessage` it can read and fix in one retry, and the run continues. --- This wires langchain's `ToolErrorMiddleware` (available since `langchain>=1.3.14`; this package already requires `>=1.3.15`) into the agent's middleware stack, scoped to `ask_user` — the tool that validates model-authored arguments by raising `ToolArgumentError`, a `ValueError` subclass introduced here so recovery keys off intent rather than a shared built-in type. (`read_file` is deliberately out of scope: it already catches its own argument errors and returns an error `ToolMessage`, and its remaining `ValueError`s are backend invariants that must stay fatal.) The `on_error` handler returns a message naming the tool and the validation detail (which the existing error text already carries) for `ToolArgumentError`, and returns `None` for everything else so unexpected errors still propagate and halt the run. `ToolErrorMiddleware` re-raises LangGraph control-flow signals (`GraphBubbleUp` / interrupts) unchanged, so `ask_user`'s `interrupt()`-based flow is unaffected. The tool itself still raises; only the run-level handling changes. This is the approach decided externally and discussed in langchain-ai/langchain#38781.
|
Hi, thanks for this update, it definitely adresses my issue as is. I'd like to include it in my LangChain course, however currently the documentation section is empty. The docstring seems to properly explain how it works though. Edit: actually something was wrong in my browser somehow and the section was hidden... sounds perfect thanks again! |

Resolves #37195
Adds a
ToolErrorMiddlewarethat allows specification of exceptions to be caught and translated into ToolMessages.Usage patterns below.
Basic usage:
MyErrorbecomesToolMessage("Tool 'failing_tool' failed with MyError")