-
Couldn't load subscription status.
- Fork 19.5k
feat(langchain_v1): add end_tools exit behavior to ToolCallLimitMiddleware #33641
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
base: master
Are you sure you want to change the base?
Conversation
Add new `end_tools` exit behavior that allows agents to continue with partial tool results when limits are exceeded, instead of stopping entirely. ## Changes - Add `end_tools` exit behavior to ToolCallLimitMiddleware - Implement `wrap_tool_call` hook to block tool execution when limits exceeded - Update counting logic to work for both parallel and sequential execution - Add comprehensive tests for all three exit behaviors (end, end_tools, error) ## Behavior - `end`: Stops entire agent execution (unchanged) - `end_tools` (new): Blocks tool execution but lets agent continue with partial results - `error`: Raises exception (unchanged) ## Tests - All 16 tests passing - Added 6 new tests covering parallel execution, sequential execution, specific tool limiting, and thread limits - Works correctly for both parallel (batch) and sequential (one-at-a-time) tool calls
- Simplify verbose comments in tool_call_limit.py - Use .lower() for case-insensitive string comparison - Add test_end_tools_sequential() to explicitly test sequential tool calls - All 17 tests passing (10 existing + 7 new)
- Move Callable and Command imports to TYPE_CHECKING block - Fix line length violations (max 100 characters) - Run ruff format to fix formatting - All 17 tests passing
- Handle case where msg.content could be list or string - Convert to string before calling .lower() - Mypy now passes cleanly
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.
Am I correct that this doesn't block other tools that haven't yet exceeded their limits?
Is there a better name we could use that's more expressive about this?
Seems super reasonable though to want to continue w/ ones that have not yet hit their limit.
| def wrap_tool_call( | ||
| self, | ||
| request: ToolCallRequest, | ||
| execute: Callable[[ToolCallRequest], ToolMessage | Command], |
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 not call this handler?
| tool_name=self.tool_name, | ||
| ) | ||
| return ToolMessage( | ||
| content=f"{limit_message} Do not call any more tools.", |
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.
Should this be specific to this tool?
Summary
Add new
end_toolsexit behavior toToolCallLimitMiddlewarethat allows agents to continue with partial tool results when limits are exceeded, instead of stopping entirely.This enables graceful degradation where agents can work with partial information instead of failing completely when tool limits are reached.
Changes
Core Implementation
end_toolsto theexit_behaviorparameter (alongside existingendanderror)wrap_tool_callhook to intercept and block individual tool executionsbefore_modelhook to count successful tool executions from previous iterationsExit Behaviors
end: Stops entire agent execution (unchanged)end_tools(new): Blocks tool execution when limits exceeded, but allows agent to continue with partial resultserror: RaisesToolCallLimitExceededError(unchanged)Example Usage
Testing
test_end_behavior()- Basic end behaviortest_end_tools_behavior()- Core end_tools functionality with parallel executiontest_error_behavior()- Error behaviortest_end_tools_with_specific_tool()- Tool-specific limitingtest_end_tools_thread_limit()- Thread-level limits with checkpointertest_comparison_all_three_behaviors()- Side-by-side comparisonTechnical Details
Position-Based Blocking
The implementation uses position-based logic to determine which tools execute even with parallel execution:
count + 0 + 1 = 1 ≤ limit→ Executecount + 1 + 1 = 2 ≤ limit→ Executecount + 2 + 1 = 3 > limit→ BlockExecution Patterns
Works correctly for both:
Benefits
exit_behavior="end"continues to work unchanged