-
Notifications
You must be signed in to change notification settings - Fork 19.6k
feat(langchain_v1): add on_tool_call middleware hook #33329
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
|
||
|
|
||
| ToolCallHandler = Callable[ | ||
| [ToolCallRequest, Any, Any], Generator[ToolCallRequest, ToolCallResponse, ToolCallResponse] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this part?
[ToolCallRequest, Any, Any]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a note, but in tool node those things don't behave as state and runtime necessarily... We can update the 3rd one to guaranteed runtime in a bit
| input_type: Literal["list", "dict", "tool_calls"], | ||
| config: RunnableConfig, | ||
| input: list[AnyMessage] | dict[str, Any] | BaseModel, | ||
| runtime: Any, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense we don't want to import here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar notes as previous PR, would love to look at tracing more carefully for nested handlers.
| # Verify nested retry pattern | ||
| assert "outer_0" in call_log | ||
| assert "inner_0" in call_log | ||
| assert "inner_1" in call_log | ||
|
|
||
| tool_messages = [m for m in result["messages"] if isinstance(m, ToolMessage)] | ||
| assert len(tool_messages) == 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we confirm the exact contents + order of the call log?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we do in the test below, so this seems not relevant
| def on_tool_call( | ||
| self, | ||
| request: ToolCallRequest, | ||
| state: StateT, # noqa: ARG002 | ||
| runtime: Runtime[ContextT], # noqa: ARG002 | ||
| ) -> Generator[ToolCallRequest, ToolCallResponse, ToolCallResponse]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah yeah no async support right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not right now. It would be post v1 thing. And you can get really far without async logic with this interceptor pattern -- you need async if the actual interceptor needs to do IO (e.g., access redis)
|
|
||
|
|
||
| ToolCallHandler = Callable[ | ||
| [ToolCallRequest, Any, Any], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sydney-runkle , i'll likely need to fix this in 2 separate changes:
- We need a fix to expose actual state here (similar to the patch I made a few weeks back -- i'll port it to this code)
- We need to update the unit tests for ToolNode to always run within langgraph runtime (as it can only be used from within langgraph runtime), that'll allow us to guarantee that the runtime exists (except for python 3.10 async)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's check if we're OK with functionality everywhere, and can then do a quick follow through to fix types
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exciting pattern.
Want to further investigate tracing. Happy to help document!
Adds generator-based middleware for intercepting tool execution in agents. Middleware can retry on errors, cache results, modify requests, or short-circuit execution.
Implementation
Middleware Protocol
Composition
Multiple middleware compose automatically (first = outermost), with
_chain_tool_call_handlers()stacking them like nested function calls.Examples
Retry on error:
Cache results:
Emulate tools with LLM
Modify requests: