Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 52 additions & 6 deletions libs/core/langchain_core/messages/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,57 @@
return merged


def _is_per_message_counter(fn: Callable) -> bool:
"""Detect whether *fn* is a per-message ``(BaseMessage) -> int`` counter.

The previous heuristic used ``annotation is BaseMessage`` (identity check),
which fails for lambdas, un-annotated functions, string annotations,
subclass annotations, and postponed annotations
(``from __future__ import annotations``).

This implementation resolves annotations via :func:`typing.get_type_hints`
and falls back to inspecting the raw annotation when that is not possible
(e.g. for lambdas). If the first parameter's resolved type is
``BaseMessage`` or a subclass thereof the function is treated as a
per-message counter.
"""
try:
params = list(inspect.signature(fn).parameters.values())
except (ValueError, TypeError):
return False
if not params:
return False
first = params[0]

# Try to resolve annotations (handles string annotations and
# ``from __future__ import annotations``).
try:
from typing import get_type_hints # noqa: PLC0415

hints = get_type_hints(fn)
if hints:
first_type = next(iter(hints.values()))
return isinstance(first_type, type) and issubclass(
first_type, BaseMessage
)
except Exception:
pass

Check failure on line 1113 in libs/core/langchain_core/messages/utils.py

View workflow job for this annotation

GitHub Actions / lint (libs/core, 3.14) / Python 3.14

ruff (S110)

langchain_core/messages/utils.py:1112:5: S110 `try`-`except`-`pass` detected, consider logging the exception

Check failure on line 1113 in libs/core/langchain_core/messages/utils.py

View workflow job for this annotation

GitHub Actions / lint (libs/core, 3.10) / Python 3.10

ruff (S110)

langchain_core/messages/utils.py:1112:5: S110 `try`-`except`-`pass` detected, consider logging the exception

Check failure on line 1113 in libs/core/langchain_core/messages/utils.py

View workflow job for this annotation

GitHub Actions / lint (libs/core, 3.11) / Python 3.11

ruff (S110)

langchain_core/messages/utils.py:1112:5: S110 `try`-`except`-`pass` detected, consider logging the exception

Check failure on line 1113 in libs/core/langchain_core/messages/utils.py

View workflow job for this annotation

GitHub Actions / lint (libs/core, 3.13) / Python 3.13

ruff (S110)

langchain_core/messages/utils.py:1112:5: S110 `try`-`except`-`pass` detected, consider logging the exception

Check failure on line 1113 in libs/core/langchain_core/messages/utils.py

View workflow job for this annotation

GitHub Actions / lint (libs/core, 3.12) / Python 3.12

ruff (S110)

langchain_core/messages/utils.py:1112:5: S110 `try`-`except`-`pass` detected, consider logging the exception

# Fall back to raw annotation for cases where get_type_hints fails
# (e.g. lambdas, some built-ins).
ann = first.annotation
if ann is inspect.Parameter.empty:
# No annotation at all — probe by calling with a dummy message.
# A per-message counter will return an int; a list counter will
# typically raise (e.g. ``len`` receives a BaseMessage).
try:
probe = fn(HumanMessage(content="x"))
return isinstance(probe, (int, float))
except Exception:
return False
return isinstance(ann, type) and issubclass(ann, BaseMessage)


# TODO: Update so validation errors (for token_counter, for example) are raised on
# init not at runtime.
@_runnable_support
Expand Down Expand Up @@ -1417,12 +1468,7 @@
if hasattr(actual_token_counter, "get_num_tokens_from_messages"):
list_token_counter = actual_token_counter.get_num_tokens_from_messages
elif callable(actual_token_counter):
if (
next(
iter(inspect.signature(actual_token_counter).parameters.values())
).annotation
is BaseMessage
):
if _is_per_message_counter(actual_token_counter):

def list_token_counter(messages: Sequence[BaseMessage]) -> int:
return sum(actual_token_counter(msg) for msg in messages) # type: ignore[arg-type, misc]
Expand Down
Loading