Skip to content

Comments

fix(core): drop orphaned ToolMessages in trim_messages with strategy='last'#35313

Open
Nilesh Hadalgi (nileshhadalgi016) wants to merge 1 commit intolangchain-ai:masterfrom
nileshhadalgi016:nileshhadalgi016/fix-trim-messages-orphaned-tool-messages
Open

fix(core): drop orphaned ToolMessages in trim_messages with strategy='last'#35313
Nilesh Hadalgi (nileshhadalgi016) wants to merge 1 commit intolangchain-ai:masterfrom
nileshhadalgi016:nileshhadalgi016/fix-trim-messages-orphaned-tool-messages

Conversation

@nileshhadalgi016

Description

Fixes #33245

When trim_messages uses strategy="last", it can return ToolMessages at the start of the result without their corresponding AIMessage (which contains the tool_calls). This produces invalid message history that causes LLM API errors like:

Error code: 400 - Invalid parameter: messages with role 'tool' must be a response to a preceding message with 'tool_calls'

Root Cause

_last_max_tokens works by reversing the messages, calling _first_max_tokens, and re-reversing. When the token budget cuts off an AIMessage that has tool_calls, the corresponding ToolMessage(s) may still be included in the result — but now appear without their parent AIMessage, making the message history invalid.

Fix

Added a _drop_orphaned_tool_messages() helper that post-processes the trimmed result:

  1. Scans left-to-right, collecting tool_call IDs from AIMessages
  2. Removes any ToolMessage whose tool_call_id does not match a preceding AIMessage

This approach:

  • Only removes messages (never adds), so the token limit is always respected
  • Is called at the end of _last_max_tokens, after the result is re-reversed
  • Has no effect when there are no orphaned tool messages

Tests Added

  • test_trim_messages_last_drops_orphaned_tool_messages — reproduces the exact bug from the issue
  • test_trim_messages_last_keeps_valid_tool_messages — ensures valid tool message pairs are preserved
  • test_trim_messages_last_multiple_tool_calls — tests AIMessage with multiple tool_calls where both ToolMessages should be dropped

All 157 existing tests in test_utils.py continue to pass with zero regressions.

…'last'

When trim_messages uses strategy='last', it can return ToolMessages at the
start of the result without their corresponding AIMessage (which contains
the tool_calls). This produces invalid message history that causes LLM API
errors.

Add _drop_orphaned_tool_messages() helper that post-processes the trimmed
result by scanning left-to-right, collecting tool_call IDs from AIMessages,
and removing any ToolMessage whose tool_call_id doesn't match a preceding
AIMessage. This only removes messages (never adds), so token limits remain
respected.

Fixes langchain-ai#33245
Copilot AI review requested due to automatic review settings February 18, 2026 18:08
@github-actions github-actions bot added core `langchain-core` package issues & PRs fix For PRs that implement a fix external labels Feb 18, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a bug in trim_messages where using strategy="last" could return invalid message histories containing orphaned ToolMessage objects without their corresponding AIMessage (which contains the tool_calls). This caused LLM API errors requiring tool messages to follow AI messages with tool calls.

Changes:

  • Added _drop_orphaned_tool_messages() helper function to identify and remove ToolMessages whose AIMessage was trimmed away
  • Integrated the helper into _last_max_tokens() after re-reversing messages but before adding back system messages
  • Added 3 comprehensive test cases covering the bug scenario, valid tool messages, and multiple tool calls

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
libs/core/langchain_core/messages/utils.py Adds _drop_orphaned_tool_messages() helper and integrates it into _last_max_tokens() to filter out orphaned tool messages
libs/core/tests/unit_tests/messages/test_utils.py Adds 3 test cases covering orphaned tool message dropping, valid tool message preservation, and multiple tool calls scenarios

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core `langchain-core` package issues & PRs external fix For PRs that implement a fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

trim_messages returning invalid messages history

1 participant