core: fix trim_messages token_counter detection for lambdas, subclasses, and string annotations#35663
Conversation
…ubclasses, and string annotations
The `trim_messages` function uses `inspect.signature` to detect whether
`token_counter` is a per-message callable (takes a single BaseMessage)
or a per-list callable (takes a list of BaseMessage). The previous
implementation used an identity check (`annotation is BaseMessage`)
which failed for:
1. Lambdas (no annotations possible)
2. Un-annotated functions
3. String annotations ("BaseMessage")
4. Subclass annotations (e.g. HumanMessage)
5. Postponed annotations (from __future__ import annotations)
In all these cases the per-message callable was misclassified as a
per-list counter, causing `AttributeError: 'list' object has no
attribute 'content'` at runtime.
The fix introduces `_is_per_message_token_counter()` which uses a
three-tier detection strategy:
1. Class annotation: `issubclass` instead of `is` (handles subclasses)
2. String annotation: match against known message class names (handles
`from __future__ import annotations` and explicit string annotations)
3. No annotation: probe by calling with a dummy HumanMessage and
catching TypeError/AttributeError (handles lambdas and un-annotated
functions while correctly classifying `len` as a list counter)
Fixes langchain-ai#35629
Merging this PR will not alter performance
|
|
Hi! Creator of the issue here Thanks for working on this. However, I already have a PR open to fix it from when I originally opened the issue ( and there 4 other PRs too ) . Please try to avoid duplicates :) |
ccurme (ccurme)
left a comment
There was a problem hiding this comment.
Duplicated with #35630.
Description
Fixes #35629
trim_messagesdetects whethertoken_counteris a per-message callable (Callable[[BaseMessage], int]) or a per-list callable (Callable[[list[BaseMessage]], int]) by inspecting the first parameter's type annotation. The current check uses identity comparison (annotation is BaseMessage), which fails for five common cases:is BaseMessageinspect.Parameter.emptyFalseinspect.Parameter.emptyFalse"BaseMessage")strFalseHumanMessage)type(notBaseMessage)Falsefrom __future__ import annotations)strFalseIn all cases the per-message callable is silently misclassified as a per-list counter, and the user gets a confusing
AttributeError: 'list' object has no attribute 'content'at runtime.Fix
Introduces
_is_per_message_token_counter()with a three-tier detection strategy:issubclass(annotation, BaseMessage)instead ofannotation is BaseMessage. Handles subclass annotations likeHumanMessage.BaseMessage,HumanMessage,AIMessage, etc.). Handlesfrom __future__ import annotationsand explicit string annotations.HumanMessageand catchingTypeError/AttributeError. Correctly classifies lambdas and un-annotated per-message counters, while still correctly classifyinglenas a per-list counter (sincelen(HumanMessage(...))raisesTypeError).Tests
Added 5 new test cases covering all the failing scenarios from the issue, plus a regression test confirming
token_counter=len(documented per-list usage) still works:test_trim_messages_lambda_token_countertest_trim_messages_unannotated_token_countertest_trim_messages_string_annotation_token_countertest_trim_messages_subclass_annotation_token_countertest_trim_messages_list_counter_len_still_worksReproducer (from issue)
This PR was authored by Claude, an AI assistant by Anthropic, as part of an autonomous AI employment initiative. See https://github.com/MaxwellCalkin/career for details.