AI & Agent Dev Bug Sandbox logo
AI & Agent Dev Bug Sandbox
Back to Radar

ToolCallLimitMiddleware Emits Duplicate ToolMessage For Already Answered Tool Calls

ToolCallLimitMiddleware unconditionally emits ToolMessages for blocked or stopped tool calls, even when those tool_call_ids already have a ToolMessage in the message history, producing duplicate ToolMessages that violate the one-result-per-tool_call_id invariant and cause provider API failures or persistent broken threads.

highConfidence 95%LangChainAffected V1.3.16

Origin Analysis

The middleware's after_model() determines which tool calls to message based solely on its own limit bookkeeping and exit_behavior, without checking whether a ToolMessage already exists for each tool_call_id in state.messages. This leads to duplicate ToolMessages for calls that were previously answered (e.g., by structured output handling or HumanInTheLoopMiddleware).
Run the provided minimal reproduction: create a ToolCallLimitMiddleware with thread_limit=1 and exit_behavior='end'; construct ToolCallLimitState with an AIMessage containing tool calls 'call_A' (search) and 'call_B' (calculator), and an existing ToolMessage for 'call_B'. Call middleware.after_model(state, None). Observe that the returned messages contain a ToolMessage for both 'call_A' and 'call_B', resulting in two ToolMessages for 'call_B' when combined with the pre-existing one.

Fixing Code Block

Edge Case Audit

This change filters based solely on the presence of a ToolMessage with the same tool_call_id, which is safe because tool_call_ids are unique per assistant tool call. However, if a tool_call_id is reused across separate assistant turns (which should not happen in well-formed histories), the filter would incorrectly suppress the new message. Additionally, the fix assumes the existing ToolMessage is valid and final; if the existing ToolMessage was generated erroneously or belongs to a different branch, it may hide a legitimate need for a new response. No destructive operations are performed. To roll back, revert the after_model method to the previous unconditional emission logic. Ensure regression tests cover both already-answered and unanswered tool calls under both exit_behavior modes, and verify integration with HumanInTheLoopMiddleware and response_format strategies.

Ecosystem Topology