libs/core/langchain_core/messages/utils.py PYTHON 2,425 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 2,425.
1"""Module contains utility functions for working with messages.23Some examples of what you can do with these functions include:45* Convert messages to strings (serialization)6* Convert messages from dicts to Message objects (deserialization)7* Filter messages from a list of messages based on name, type or id etc.8"""910from __future__ import annotations1112import base6413import inspect14import json15import logging16import math17from collections.abc import Callable, Iterable, Sequence18from functools import partial, wraps19from typing import (20    TYPE_CHECKING,21    Annotated,22    Any,23    Concatenate,24    Literal,25    ParamSpec,26    Protocol,27    TypeVar,28    cast,29    overload,30)31from xml.sax.saxutils import escape, quoteattr3233from pydantic import Discriminator, Field, Tag3435from langchain_core.exceptions import ErrorCode, create_message36from langchain_core.messages.ai import AIMessage, AIMessageChunk37from langchain_core.messages.base import BaseMessage, BaseMessageChunk38from langchain_core.messages.block_translators.openai import (39    convert_to_openai_data_block,40)41from langchain_core.messages.chat import ChatMessage, ChatMessageChunk42from langchain_core.messages.content import (43    is_data_content_block,44)45from langchain_core.messages.function import FunctionMessage, FunctionMessageChunk46from langchain_core.messages.human import HumanMessage, HumanMessageChunk47from langchain_core.messages.modifier import RemoveMessage48from langchain_core.messages.system import SystemMessage, SystemMessageChunk49from langchain_core.messages.tool import ToolCall, ToolMessage, ToolMessageChunk50from langchain_core.utils.pydantic import model_json_schema as get_model_json_schema5152if TYPE_CHECKING:53    from langchain_core.language_models import BaseLanguageModel54    from langchain_core.prompt_values import PromptValue55    from langchain_core.runnables.base import Runnable56    from langchain_core.tools import BaseTool5758try:59    from langchain_text_splitters import TextSplitter6061    _HAS_LANGCHAIN_TEXT_SPLITTERS = True62except ImportError:63    _HAS_LANGCHAIN_TEXT_SPLITTERS = False6465logger = logging.getLogger(__name__)666768def _get_type(v: Any) -> str:69    """Get the type associated with the object for serialization purposes."""70    if isinstance(v, dict) and "type" in v:71        result = v["type"]72    elif hasattr(v, "type"):73        result = v.type74    else:75        msg = (76            f"Expected either a dictionary with a 'type' key or an object "77            f"with a 'type' attribute. Instead got type {type(v)}."78        )79        raise TypeError(msg)80    if not isinstance(result, str):81        msg = f"Expected 'type' to be a str, got {type(result).__name__}"82        raise TypeError(msg)83    return result848586AnyMessage = Annotated[87    Annotated[AIMessage, Tag(tag="ai")]88    | Annotated[HumanMessage, Tag(tag="human")]89    | Annotated[ChatMessage, Tag(tag="chat")]90    | Annotated[SystemMessage, Tag(tag="system")]91    | Annotated[FunctionMessage, Tag(tag="function")]92    | Annotated[ToolMessage, Tag(tag="tool")]93    | Annotated[AIMessageChunk, Tag(tag="AIMessageChunk")]94    | Annotated[HumanMessageChunk, Tag(tag="HumanMessageChunk")]95    | Annotated[ChatMessageChunk, Tag(tag="ChatMessageChunk")]96    | Annotated[SystemMessageChunk, Tag(tag="SystemMessageChunk")]97    | Annotated[FunctionMessageChunk, Tag(tag="FunctionMessageChunk")]98    | Annotated[ToolMessageChunk, Tag(tag="ToolMessageChunk")],99    Field(discriminator=Discriminator(_get_type)),100]101"""A type representing any defined `Message` or `MessageChunk` type."""102103104def _has_base64_data(block: dict[str, Any]) -> bool:105    """Check if a content block contains base64 encoded data.106107    Args:108        block: A content block dictionary.109110    Returns:111        Whether the block contains base64 data.112    """113    # Check for explicit base64 field (standard content blocks)114    if block.get("base64"):115        return True116117    # Check for data: URL in url field118    url = block.get("url", "")119    if isinstance(url, str) and url.startswith("data:"):120        return True121122    # Check for OpenAI-style image_url with data: URL123    image_url = block.get("image_url", {})124    if isinstance(image_url, dict):125        url = image_url.get("url", "")126        if isinstance(url, str) and url.startswith("data:"):127            return True128129    return False130131132_XML_CONTENT_BLOCK_MAX_LEN = 500133134135def _truncate(text: str, max_len: int = _XML_CONTENT_BLOCK_MAX_LEN) -> str:136    """Truncate text to `max_len` characters, adding ellipsis if truncated."""137    if len(text) <= max_len:138        return text139    return text[:max_len] + "..."140141142def _format_content_block_xml(block: dict[str, Any]) -> str | None:143    """Format a content block as XML.144145    Args:146        block: A LangChain content block.147148    Returns:149        XML string representation of the block, or `None` if the block should be150            skipped.151152    Note:153        Plain text document content, server tool call arguments, and server tool154        result outputs are truncated to 500 characters.155    """156    block_type = block.get("type", "")157158    # Skip blocks with base64 encoded data159    if _has_base64_data(block):160        return None161162    # Text blocks163    if block_type == "text":164        text = block.get("text", "")165        return escape(text) if text else None166167    # Reasoning blocks168    if block_type == "reasoning":169        reasoning = block.get("reasoning", "")170        if reasoning:171            return f"<reasoning>{escape(reasoning)}</reasoning>"172        return None173174    # Image blocks (URL only, base64 already filtered)175    if block_type == "image":176        url = block.get("url")177        file_id = block.get("file_id")178        if url:179            return f"<image url={quoteattr(url)} />"180        if file_id:181            return f"<image file_id={quoteattr(file_id)} />"182        return None183184    # OpenAI-style image_url blocks185    if block_type == "image_url":186        image_url = block.get("image_url", {})187        if isinstance(image_url, dict):188            url = image_url.get("url", "")189            if url and not url.startswith("data:"):190                return f"<image url={quoteattr(url)} />"191        return None192193    # Audio blocks (URL only)194    if block_type == "audio":195        url = block.get("url")196        file_id = block.get("file_id")197        if url:198            return f"<audio url={quoteattr(url)} />"199        if file_id:200            return f"<audio file_id={quoteattr(file_id)} />"201        return None202203    # Video blocks (URL only)204    if block_type == "video":205        url = block.get("url")206        file_id = block.get("file_id")207        if url:208            return f"<video url={quoteattr(url)} />"209        if file_id:210            return f"<video file_id={quoteattr(file_id)} />"211        return None212213    # Plain text document blocks214    if block_type == "text-plain":215        text = block.get("text", "")216        return escape(_truncate(text)) if text else None217218    # Server tool call blocks (from AI messages)219    if block_type == "server_tool_call":220        tc_id = quoteattr(str(block.get("id") or ""))221        tc_name = quoteattr(str(block.get("name") or ""))222        tc_args_json = json.dumps(block.get("args", {}), ensure_ascii=False)223        tc_args = escape(_truncate(tc_args_json))224        return (225            f"<server_tool_call id={tc_id} name={tc_name}>{tc_args}</server_tool_call>"226        )227228    # Server tool result blocks229    if block_type == "server_tool_result":230        tool_call_id = quoteattr(str(block.get("tool_call_id") or ""))231        status = quoteattr(str(block.get("status") or ""))232        output = block.get("output")233        if output:234            output_json = json.dumps(output, ensure_ascii=False)235            output_str = escape(_truncate(output_json))236        else:237            output_str = ""238        return (239            f"<server_tool_result tool_call_id={tool_call_id} status={status}>"240            f"{output_str}</server_tool_result>"241        )242243    # Unknown block type - skip silently244    return None245246247def _get_message_type_str(248    m: BaseMessage,249    human_prefix: str,250    ai_prefix: str,251    system_prefix: str,252    function_prefix: str,253    tool_prefix: str,254) -> str:255    """Get the type string for XML message element.256257    Args:258        m: The message to get the type string for.259        human_prefix: The prefix to use for `HumanMessage`.260        ai_prefix: The prefix to use for `AIMessage`.261        system_prefix: The prefix to use for `SystemMessage`.262        function_prefix: The prefix to use for `FunctionMessage`.263        tool_prefix: The prefix to use for `ToolMessage`.264265    Returns:266        The type string for the message element.267268    Raises:269        ValueError: If an unsupported message type is encountered.270    """271    if isinstance(m, HumanMessage):272        return human_prefix.lower()273    if isinstance(m, AIMessage):274        return ai_prefix.lower()275    if isinstance(m, SystemMessage):276        return system_prefix.lower()277    if isinstance(m, FunctionMessage):278        return function_prefix.lower()279    if isinstance(m, ToolMessage):280        return tool_prefix.lower()281    if isinstance(m, ChatMessage):282        return m.role283    msg = f"Got unsupported message type: {m}"284    raise ValueError(msg)285286287def get_buffer_string(288    messages: Sequence[BaseMessage],289    human_prefix: str = "Human",290    ai_prefix: str = "AI",291    *,292    system_prefix: str = "System",293    function_prefix: str = "Function",294    tool_prefix: str = "Tool",295    message_separator: str = "\n",296    format: Literal["prefix", "xml"] = "prefix",  # noqa: A002297) -> str:298    r"""Convert a sequence of messages to strings and concatenate them into one string.299300    Args:301        messages: Messages to be converted to strings.302        human_prefix: The prefix to prepend to contents of `HumanMessage`s.303        ai_prefix: The prefix to prepend to contents of `AIMessage`.304        system_prefix: The prefix to prepend to contents of `SystemMessage`s.305        function_prefix: The prefix to prepend to contents of `FunctionMessage`s.306        tool_prefix: The prefix to prepend to contents of `ToolMessage`s.307        message_separator: The separator to use between messages.308        format: The output format. `'prefix'` uses `Role: content` format (default).309            For multimodal messages, only string content and `text` blocks are310            included; non-text blocks such as images, audio, and video are omitted.311312            `'xml'` uses XML-style `<message type='role'>` format with proper313            character escaping, which is useful when message content may314            contain role-like prefixes that could cause ambiguity.315316            Use `'xml'` when you need a structured representation of supported317            multimodal content blocks.318319    Returns:320        A single string concatenation of all input messages.321322    Raises:323        ValueError: If an unsupported message type is encountered.324325    !!! warning326327        If a message is an `AIMessage` and contains both tool calls under `tool_calls`328        and a function call under `additional_kwargs["function_call"]`, only the tool329        calls will be appended to the string representation.330331    !!! note "XML format"332333        When using `format='xml'`:334335        - All messages use uniform `<message type="role">content</message>` format.336        - The `type` attribute uses `human_prefix` (lowercased) for `HumanMessage`,337            `ai_prefix` (lowercased) for `AIMessage`, `system_prefix` (lowercased)338            for `SystemMessage`, `function_prefix` (lowercased) for `FunctionMessage`,339            `tool_prefix` (lowercased) for `ToolMessage`, and the original role340            (unchanged) for `ChatMessage`.341        - Message content is escaped using `xml.sax.saxutils.escape()`.342        - Attribute values are escaped using `xml.sax.saxutils.quoteattr()`.343        - AI messages with tool calls use nested structure with `<content>` and344            `<tool_call>` elements.345        - For multi-modal content (list of content blocks), supported block types346            are: `text`, `reasoning`, `image` (URL/file_id only), `image_url`347            (OpenAI-style, URL only), `audio` (URL/file_id only), `video` (URL/file_id348            only), `text-plain`, `server_tool_call`, and `server_tool_result`.349        - Content blocks with base64-encoded data are skipped (including blocks350            with `base64` field or `data:` URLs).351        - Unknown block types are skipped.352        - Plain text document content (`text-plain`), server tool call arguments,353            and server tool result outputs are truncated to 500 characters.354355    Example:356        Default prefix format:357358        ```python359        from langchain_core.messages import AIMessage, HumanMessage, get_buffer_string360361        messages = [362            HumanMessage(content="Hi, how are you?"),363            AIMessage(content="Good, how are you?"),364        ]365        get_buffer_string(messages)366        # -> "Human: Hi, how are you?\nAI: Good, how are you?"367        ```368369        XML format (useful when content contains role-like prefixes):370371        ```python372        messages = [373            HumanMessage(content="Example: Human: some text"),374            AIMessage(content="I see the example."),375        ]376        get_buffer_string(messages, format="xml")377        # -> '<message type="human">Example: Human: some text</message>\\n'378        # -> '<message type="ai">I see the example.</message>'379        ```380381        XML format with special characters (automatically escaped):382383        ```python384        messages = [385            HumanMessage(content="Is 5 < 10 & 10 > 5?"),386        ]387        get_buffer_string(messages, format="xml")388        # -> '<message type="human">Is 5 &lt; 10 &amp; 10 &gt; 5?</message>'389        ```390391        XML format with tool calls:392393        ```python394        messages = [395            AIMessage(396                content="I'll search for that.",397                tool_calls=[398                    {"id": "call_123", "name": "search", "args": {"query": "weather"}}399                ],400            ),401        ]402        get_buffer_string(messages, format="xml")403        # -> '<message type="ai">\\n'404        # -> '  <content>I\\'ll search for that.</content>\\n'405        # -> '  <tool_call id="call_123" name="search">'406        # -> '{"query": "weather"}</tool_call>\\n'407        # -> '</message>'408        ```409    """410    if format not in {"prefix", "xml"}:411        msg = (412            f"Unrecognized format={format!r}. Supported formats are 'prefix' and 'xml'."413        )414        raise ValueError(msg)415416    string_messages = []417    for m in messages:418        if isinstance(m, HumanMessage):419            role = human_prefix420        elif isinstance(m, AIMessage):421            role = ai_prefix422        elif isinstance(m, SystemMessage):423            role = system_prefix424        elif isinstance(m, FunctionMessage):425            role = function_prefix426        elif isinstance(m, ToolMessage):427            role = tool_prefix428        elif isinstance(m, ChatMessage):429            role = m.role430        else:431            msg = f"Got unsupported message type: {m}"432            raise ValueError(msg)  # noqa: TRY004433434        if format == "xml":435            msg_type = _get_message_type_str(436                m, human_prefix, ai_prefix, system_prefix, function_prefix, tool_prefix437            )438439            # Format content blocks440            if isinstance(m.content, str):441                content_parts = [escape(m.content)] if m.content else []442            else:443                # List of content blocks444                content_parts = []445                for block in m.content:446                    if isinstance(block, str):447                        if block:448                            content_parts.append(escape(block))449                    else:450                        formatted = _format_content_block_xml(block)451                        if formatted:452                            content_parts.append(formatted)453454            # Check if this is an AIMessage with tool calls455            has_tool_calls = isinstance(m, AIMessage) and m.tool_calls456            has_function_call = (457                isinstance(m, AIMessage)458                and not m.tool_calls459                and "function_call" in m.additional_kwargs460            )461462            if has_tool_calls or has_function_call:463                # Use nested structure for AI messages with tool calls464                # Type narrowing: at this point m is AIMessage (verified above)465                ai_msg = cast("AIMessage", m)466                parts = [f"<message type={quoteattr(msg_type)}>"]467                if content_parts:468                    parts.append(f"  <content>{' '.join(content_parts)}</content>")469470                if has_tool_calls:471                    for tc in ai_msg.tool_calls:472                        tc_id = quoteattr(str(tc.get("id") or ""))473                        tc_name = quoteattr(str(tc.get("name") or ""))474                        tc_args = escape(475                            json.dumps(tc.get("args", {}), ensure_ascii=False)476                        )477                        parts.append(478                            f"  <tool_call id={tc_id} name={tc_name}>"479                            f"{tc_args}</tool_call>"480                        )481                elif has_function_call:482                    fc = ai_msg.additional_kwargs["function_call"]483                    fc_name = quoteattr(str(fc.get("name") or ""))484                    fc_args = escape(str(fc.get("arguments") or "{}"))485                    parts.append(486                        f"  <function_call name={fc_name}>{fc_args}</function_call>"487                    )488489                parts.append("</message>")490                message = "\n".join(parts)491            else:492                # Simple structure for messages without tool calls493                joined_content = " ".join(content_parts)494                message = (495                    f"<message type={quoteattr(msg_type)}>{joined_content}</message>"496                )497        else:  # format == "prefix"498            content = m.text499            message = f"{role}: {content}"500            tool_info = ""501            if isinstance(m, AIMessage):502                if m.tool_calls:503                    tool_info = str(m.tool_calls)504                elif "function_call" in m.additional_kwargs:505                    # Legacy behavior assumes only one function call per message506                    tool_info = str(m.additional_kwargs["function_call"])507            if tool_info:508                message += tool_info  # Preserve original behavior509510        string_messages.append(message)511512    return message_separator.join(string_messages)513514515def _message_from_dict(message: dict[str, Any]) -> BaseMessage:516    type_ = message["type"]517    if type_ == "human":518        return HumanMessage(**message["data"])519    if type_ == "ai":520        return AIMessage(**message["data"])521    if type_ == "system":522        return SystemMessage(**message["data"])523    if type_ == "chat":524        return ChatMessage(**message["data"])525    if type_ == "function":526        return FunctionMessage(**message["data"])527    if type_ == "tool":528        return ToolMessage(**message["data"])529    if type_ == "remove":530        return RemoveMessage(**message["data"])531    if type_ == "AIMessageChunk":532        return AIMessageChunk(**message["data"])533    if type_ == "HumanMessageChunk":534        return HumanMessageChunk(**message["data"])535    if type_ == "FunctionMessageChunk":536        return FunctionMessageChunk(**message["data"])537    if type_ == "ToolMessageChunk":538        return ToolMessageChunk(**message["data"])539    if type_ == "SystemMessageChunk":540        return SystemMessageChunk(**message["data"])541    if type_ == "ChatMessageChunk":542        return ChatMessageChunk(**message["data"])543    msg = f"Got unexpected message type: {type_}"544    raise ValueError(msg)545546547def messages_from_dict(messages: Sequence[dict[str, Any]]) -> list[BaseMessage]:548    """Convert a sequence of messages from dicts to `Message` objects.549550    Args:551        messages: Sequence of messages (as dicts) to convert.552553    Returns:554        list of messages (BaseMessages).555556    """557    return [_message_from_dict(m) for m in messages]558559560def message_chunk_to_message(chunk: BaseMessage) -> BaseMessage:561    """Convert a message chunk to a `Message`.562563    Args:564        chunk: Message chunk to convert.565566    Returns:567        Message.568    """569    if not isinstance(chunk, BaseMessageChunk):570        return chunk571    # chunk classes always have the equivalent non-chunk class as their first parent572    ignore_keys = ["type"]573    if isinstance(chunk, AIMessageChunk):574        ignore_keys.extend(["tool_call_chunks", "chunk_position"])575    return cast(576        "BaseMessage",577        chunk.__class__.__mro__[1](578            **{k: v for k, v in chunk.__dict__.items() if k not in ignore_keys}579        ),580    )581582583MessageLikeRepresentation = (584    BaseMessage585    | list[str]586    | tuple[str, str | list[str | dict[str, Any]]]587    | str588    | dict[str, Any]589)590"""A type representing the various ways a message can be represented."""591592593def _create_message_from_message_type(594    message_type: str,595    content: str | list[str | dict[str, Any]],596    name: str | None = None,597    tool_call_id: str | None = None,598    tool_calls: list[dict[str, Any]] | None = None,599    id: str | None = None,600    **additional_kwargs: Any,601) -> BaseMessage:602    """Create a message from a `Message` type and content string.603604    Args:605        message_type: the type of the message (e.g., `'human'`, `'ai'`, etc.).606        content: the content string.607        name: the name of the message.608        tool_call_id: the tool call id.609        tool_calls: the tool calls.610        id: the id of the message.611        additional_kwargs: additional keyword arguments.612613    Returns:614        a message of the appropriate type.615616    Raises:617        ValueError: if the message type is not one of `'human'`, `'user'`, `'ai'`,618            `'assistant'`, `'function'`, `'tool'`, `'system'`, or619            `'developer'`.620    """621    kwargs: dict[str, Any] = {}622    if name is not None:623        kwargs["name"] = name624    if tool_call_id is not None:625        kwargs["tool_call_id"] = tool_call_id626    if additional_kwargs:627        if response_metadata := additional_kwargs.pop("response_metadata", None):628            kwargs["response_metadata"] = response_metadata629        kwargs["additional_kwargs"] = additional_kwargs630        additional_kwargs.update(additional_kwargs.pop("additional_kwargs", {}))631    if id is not None:632        kwargs["id"] = id633    if tool_calls is not None:634        kwargs["tool_calls"] = []635        for tool_call in tool_calls:636            # Convert OpenAI-format tool call to LangChain format.637            if "function" in tool_call:638                args = tool_call["function"]["arguments"]639                if isinstance(args, str):640                    args = json.loads(args, strict=False)641                kwargs["tool_calls"].append(642                    {643                        "name": tool_call["function"]["name"],644                        "args": args,645                        "id": tool_call["id"],646                        "type": "tool_call",647                    }648                )649            else:650                kwargs["tool_calls"].append(tool_call)651    if message_type in {"human", "user"}:652        if example := kwargs.get("additional_kwargs", {}).pop("example", False):653            kwargs["example"] = example654        message: BaseMessage = HumanMessage(content=content, **kwargs)655    elif message_type in {"ai", "assistant"}:656        if example := kwargs.get("additional_kwargs", {}).pop("example", False):657            kwargs["example"] = example658        message = AIMessage(content=content, **kwargs)659    elif message_type in {"system", "developer"}:660        if message_type == "developer":661            kwargs["additional_kwargs"] = kwargs.get("additional_kwargs") or {}662            kwargs["additional_kwargs"]["__openai_role__"] = "developer"663        message = SystemMessage(content=content, **kwargs)664    elif message_type == "function":665        message = FunctionMessage(content=content, **kwargs)666    elif message_type == "tool":667        artifact = kwargs.get("additional_kwargs", {}).pop("artifact", None)668        status = kwargs.get("additional_kwargs", {}).pop("status", None)669        if status is not None:670            kwargs["status"] = status671        message = ToolMessage(content=content, artifact=artifact, **kwargs)672    elif message_type == "remove":673        message = RemoveMessage(**kwargs)674    else:675        msg = (676            f"Unexpected message type: '{message_type}'. Use one of 'human',"677            f" 'user', 'ai', 'assistant', 'function', 'tool', 'system', or 'developer'."678        )679        msg = create_message(message=msg, error_code=ErrorCode.MESSAGE_COERCION_FAILURE)680        raise ValueError(msg)681    return message682683684# Map of class names emitted in the `Serializable` constructor-envelope685# (`{"lc": 1, "type": "constructor", "id": [..., "<ClassName>"],686# "kwargs": {...}}`) to the message-type strings687# `_create_message_from_message_type` accepts. Read by688# `_convert_to_message`'s dict branch when unpacking that wire shape.689# Kept as a hardcoded allowlist of strings rather than a class registry690# lookup so dispatch never resolves to a class chosen by the caller.691_LC_CONSTRUCTOR_NAME_TO_TYPE: dict[str, str] = {692    "HumanMessage": "human",693    "HumanMessageChunk": "human",694    "AIMessage": "ai",695    "AIMessageChunk": "ai",696    "SystemMessage": "system",697    "SystemMessageChunk": "system",698    "FunctionMessage": "function",699    "FunctionMessageChunk": "function",700    "ToolMessage": "tool",701    "ToolMessageChunk": "tool",702    "RemoveMessage": "remove",703}704705706def _convert_to_message(message: MessageLikeRepresentation) -> BaseMessage:707    """Instantiate a `Message` from a variety of message formats.708709    The message format can be one of the following:710711    - `BaseMessagePromptTemplate`712    - `BaseMessage`713    - 2-tuple of (role string, template); e.g., (`'human'`, `'{user_input}'`)714    - dict: a message dict with role and content keys715    - dict: the `Serializable` constructor-envelope wire shape716        `{"lc": 1, "type": "constructor", "id": [..., "<ClassName>"],717        "kwargs": {...}}` — unpacked structurally and routed through the718        standard dict-with-type dispatch.719    - string: shorthand for (`'human'`, template); e.g., `'{user_input}'`720721    Args:722        message: a representation of a message in one of the supported formats.723724    Returns:725        An instance of a message or a message template.726727    Raises:728        NotImplementedError: if the message type is not supported.729        ValueError: if the message dict does not contain the required keys.730731    """732    if isinstance(message, BaseMessage):733        message_ = message734    elif isinstance(message, Sequence):735        if isinstance(message, str):736            message_ = _create_message_from_message_type("human", message)737        else:738            try:739                message_type_str, template = message740            except ValueError as e:741                msg = "Message as a sequence must be (role string, template)"742                raise NotImplementedError(msg) from e743            message_ = _create_message_from_message_type(message_type_str, template)744    elif isinstance(message, dict):745        # `Serializable` constructor-envelope wire shape. Detect structurally, map746        # the class name to a known message-type string via a hardcoded747        # allowlist, and recurse with the canonical748        # `{"type": ..., **kwargs}` shape  no `load()`, no dynamic749        # class instantiation.750        if (751            message.get("lc") == 1752            and message.get("type") == "constructor"753            and isinstance(message.get("id"), list)754            and message["id"]755            and isinstance(message.get("kwargs"), dict)756        ):757            mapped = _LC_CONSTRUCTOR_NAME_TO_TYPE.get(message["id"][-1])758            if mapped is not None:759                return _convert_to_message({"type": mapped, **message["kwargs"]})760761        msg_kwargs = message.copy()762        try:763            try:764                msg_type = msg_kwargs.pop("role")765            except KeyError:766                msg_type = msg_kwargs.pop("type")767            # None msg content is not allowed768            msg_content = msg_kwargs.pop("content") or ""769        except KeyError as e:770            msg = f"Message dict must contain 'role' and 'content' keys, got {message}"771            msg = create_message(772                message=msg, error_code=ErrorCode.MESSAGE_COERCION_FAILURE773            )774            raise ValueError(msg) from e775        message_ = _create_message_from_message_type(776            msg_type, msg_content, **msg_kwargs777        )778    else:779        msg = f"Unsupported message type: {type(message)}"  # type: ignore[unreachable]780        msg = create_message(message=msg, error_code=ErrorCode.MESSAGE_COERCION_FAILURE)781        raise NotImplementedError(msg)782783    return message_784785786def convert_to_messages(787    messages: Iterable[MessageLikeRepresentation] | PromptValue,788) -> list[BaseMessage]:789    """Convert a sequence of messages to a list of messages.790791    Args:792        messages: Sequence of messages to convert.793794    Returns:795        list of messages (BaseMessages).796797    """798    # Import here to avoid circular imports799    from langchain_core.prompt_values import PromptValue  # noqa: PLC0415800801    if isinstance(messages, PromptValue):802        return messages.to_messages()803    return [_convert_to_message(m) for m in messages]804805806_P = ParamSpec("_P")807_R_co = TypeVar("_R_co", covariant=True)808809810class _RunnableSupportCallable(Protocol[_P, _R_co]):811    @overload812    def __call__(813        self,814        messages: None = None,815        *args: _P.args,816        **kwargs: _P.kwargs,817    ) -> Runnable[Sequence[MessageLikeRepresentation], _R_co]: ...818819    @overload820    def __call__(821        self,822        messages: Sequence[MessageLikeRepresentation] | PromptValue,823        *args: _P.args,824        **kwargs: _P.kwargs,825    ) -> _R_co: ...826827    def __call__(828        self,829        messages: Sequence[MessageLikeRepresentation] | PromptValue | None = None,830        *args: _P.args,831        **kwargs: _P.kwargs,832    ) -> _R_co | Runnable[Sequence[MessageLikeRepresentation], _R_co]: ...833834835def _runnable_support(836    func: Callable[837        Concatenate[Sequence[MessageLikeRepresentation] | PromptValue, _P], _R_co838    ],839) -> _RunnableSupportCallable[_P, _R_co]:840    @wraps(func)841    def wrapped(842        messages: Sequence[MessageLikeRepresentation] | PromptValue | None = None,843        *args: _P.args,844        **kwargs: _P.kwargs,845    ) -> _R_co | Runnable[Sequence[MessageLikeRepresentation], _R_co]:846        # Import locally to prevent circular import.847        from langchain_core.runnables.base import RunnableLambda  # noqa: PLC0415848849        if messages is not None:850            return func(messages, *args, **kwargs)851        return RunnableLambda(partial(func, **kwargs), name=func.__name__)852853    return cast("_RunnableSupportCallable[_P, _R_co]", wrapped)854855856@_runnable_support857def filter_messages(858    messages: Iterable[MessageLikeRepresentation] | PromptValue,859    *,860    include_names: Sequence[str] | None = None,861    exclude_names: Sequence[str] | None = None,862    include_types: Sequence[str | type[BaseMessage]] | None = None,863    exclude_types: Sequence[str | type[BaseMessage]] | None = None,864    include_ids: Sequence[str] | None = None,865    exclude_ids: Sequence[str] | None = None,866    exclude_tool_calls: Sequence[str] | bool | None = None,867) -> list[BaseMessage]:868    """Filter messages based on `name`, `type` or `id`.869870    Args:871        messages: Sequence Message-like objects to filter.872        include_names: Message names to include.873        exclude_names: Messages names to exclude.874        include_types: Message types to include. Can be specified as string names875            (e.g. `'system'`, `'human'`, `'ai'`, ...) or as `BaseMessage`876            classes (e.g. `SystemMessage`, `HumanMessage`, `AIMessage`, ...).877878        exclude_types: Message types to exclude. Can be specified as string names879            (e.g. `'system'`, `'human'`, `'ai'`, ...) or as `BaseMessage`880            classes (e.g. `SystemMessage`, `HumanMessage`, `AIMessage`, ...).881882        include_ids: Message IDs to include.883        exclude_ids: Message IDs to exclude.884        exclude_tool_calls: Tool call IDs to exclude.885            Can be one of the following:886            - `True`: All `AIMessage` objects with tool calls and all `ToolMessage`887                objects will be excluded.888            - a sequence of tool call IDs to exclude:889                - `ToolMessage` objects with the corresponding tool call ID will be890                    excluded.891                - The `tool_calls` in the AIMessage will be updated to exclude892                    matching tool calls. If all `tool_calls` are filtered from an893                    AIMessage, the whole message is excluded.894895    Returns:896        A list of Messages that meets at least one of the `incl_*` conditions and none897        of the `excl_*` conditions. If not `incl_*` conditions are specified then898        anything that is not explicitly excluded will be included.899900    Raises:901        ValueError: If two incompatible arguments are provided.902903    Example:904        ```python905        from langchain_core.messages import (906            filter_messages,907            AIMessage,908            HumanMessage,909            SystemMessage,910        )911912        messages = [913            SystemMessage("you're a good assistant."),914            HumanMessage("what's your name", id="foo", name="example_user"),915            AIMessage("steve-o", id="bar", name="example_assistant"),916            HumanMessage(917                "what's your favorite color",918                id="baz",919            ),920            AIMessage(921                "silicon blue",922                id="blah",923            ),924        ]925926        filter_messages(927            messages,928            include_names=("example_user", "example_assistant"),929            include_types=("system",),930            exclude_ids=("bar",),931        )932        ```933934        ```python935        [936            SystemMessage("you're a good assistant."),937            HumanMessage("what's your name", id="foo", name="example_user"),938        ]939        ```940    """941    messages = convert_to_messages(messages)942    filtered: list[BaseMessage] = []943    for msg in messages:944        if (945            (exclude_names and msg.name in exclude_names)946            or (exclude_types and _is_message_type(msg, exclude_types))947            or (exclude_ids and msg.id in exclude_ids)948        ):949            continue950951        if exclude_tool_calls is True and (952            (isinstance(msg, AIMessage) and msg.tool_calls)953            or isinstance(msg, ToolMessage)954        ):955            continue956957        new_msg = msg958        if isinstance(exclude_tool_calls, (list, tuple, set)):959            if isinstance(msg, AIMessage) and msg.tool_calls:960                tool_calls = [961                    tool_call962                    for tool_call in msg.tool_calls963                    if tool_call["id"] not in exclude_tool_calls964                ]965                if not tool_calls:966                    continue967968                content = msg.content969                # handle Anthropic content blocks970                if isinstance(msg.content, list):971                    content = [972                        content_block973                        for content_block in msg.content974                        if (975                            not isinstance(content_block, dict)976                            or content_block.get("type") != "tool_use"977                            or content_block.get("id") not in exclude_tool_calls978                        )979                    ]980981                new_msg = msg.model_copy(982                    update={"tool_calls": tool_calls, "content": content}983                )984            elif (985                isinstance(msg, ToolMessage) and msg.tool_call_id in exclude_tool_calls986            ):987                continue988989        # default to inclusion when no inclusion criteria given.990        if (991            not (include_types or include_ids or include_names)992            or (include_names and new_msg.name in include_names)993            or (include_types and _is_message_type(new_msg, include_types))994            or (include_ids and new_msg.id in include_ids)995        ):996            filtered.append(new_msg)997998    return filtered99910001001@_runnable_support1002def merge_message_runs(1003    messages: Iterable[MessageLikeRepresentation] | PromptValue,1004    *,1005    chunk_separator: str = "\n",1006) -> list[BaseMessage]:1007    r"""Merge consecutive Messages of the same type.10081009    !!! note1010        `ToolMessage` objects are not merged, as each has a distinct tool call id that1011        can't be merged.10121013    Args:1014        messages: Sequence Message-like objects to merge.1015        chunk_separator: Specify the string to be inserted between message chunks.10161017    Returns:1018        list of BaseMessages with consecutive runs of message types merged into single1019        messages. By default, if two messages being merged both have string contents,1020        the merged content is a concatenation of the two strings with a new-line1021        separator.1022        The separator inserted between message chunks can be controlled by specifying1023        any string with `chunk_separator`. If at least one of the messages has a list1024        of content blocks, the merged content is a list of content blocks.10251026    Example:1027        ```python1028        from langchain_core.messages import (1029            merge_message_runs,1030            AIMessage,1031            HumanMessage,1032            SystemMessage,1033            ToolCall,1034        )10351036        messages = [1037            SystemMessage("you're a good assistant."),1038            HumanMessage(1039                "what's your favorite color",1040                id="foo",1041            ),1042            HumanMessage(1043                "wait your favorite food",1044                id="bar",1045            ),1046            AIMessage(1047                "my favorite colo",1048                tool_calls=[1049                    ToolCall(1050                        name="blah_tool", args={"x": 2}, id="123", type="tool_call"1051                    )1052                ],1053                id="baz",1054            ),1055            AIMessage(1056                [{"type": "text", "text": "my favorite dish is lasagna"}],1057                tool_calls=[1058                    ToolCall(1059                        name="blah_tool",1060                        args={"x": -10},1061                        id="456",1062                        type="tool_call",1063                    )1064                ],1065                id="blur",1066            ),1067        ]10681069        merge_message_runs(messages)1070        ```10711072        ```python1073        [1074            SystemMessage("you're a good assistant."),1075            HumanMessage(1076                "what's your favorite color\\n"1077                "wait your favorite food", id="foo",1078            ),1079            AIMessage(1080                [1081                    "my favorite colo",1082                    {"type": "text", "text": "my favorite dish is lasagna"}1083                ],1084                tool_calls=[1085                    ToolCall({1086                        "name": "blah_tool",1087                        "args": {"x": 2},1088                        "id": "123",1089                        "type": "tool_call"1090                    }),1091                    ToolCall({1092                        "name": "blah_tool",1093                        "args": {"x": -10},1094                        "id": "456",1095                        "type": "tool_call"1096                    })1097                ]1098                id="baz"1099            ),1100        ]11011102        ```1103    """1104    if not messages:1105        return []1106    messages = convert_to_messages(messages)1107    merged: list[BaseMessage] = []1108    for msg in messages:1109        last = merged.pop() if merged else None1110        if not last:1111            merged.append(msg)1112        elif isinstance(msg, ToolMessage) or not isinstance(msg, last.__class__):1113            merged.extend([last, msg])1114        else:1115            last_chunk = _msg_to_chunk(last)1116            curr_chunk = _msg_to_chunk(msg)1117            if curr_chunk.response_metadata:1118                curr_chunk.response_metadata.clear()1119            if (1120                isinstance(last_chunk.content, str)1121                and isinstance(curr_chunk.content, str)1122                and last_chunk.content1123                and curr_chunk.content1124            ):1125                last_chunk.content += chunk_separator1126            merged.append(_chunk_to_msg(last_chunk + curr_chunk))1127    return merged112811291130# TODO: Update so validation errors (for token_counter, for example) are raised on1131# init not at runtime.1132@_runnable_support1133def trim_messages(1134    messages: Iterable[MessageLikeRepresentation] | PromptValue,1135    *,1136    max_tokens: int,1137    token_counter: Callable[[list[BaseMessage]], int]1138    | Callable[[BaseMessage], int]1139    | BaseLanguageModel[Any]1140    | Literal["approximate"],1141    strategy: Literal["first", "last"] = "last",1142    allow_partial: bool = False,1143    end_on: str | type[BaseMessage] | Sequence[str | type[BaseMessage]] | None = None,1144    start_on: str | type[BaseMessage] | Sequence[str | type[BaseMessage]] | None = None,1145    include_system: bool = False,1146    text_splitter: Callable[[str], list[str]] | TextSplitter | None = None,1147) -> list[BaseMessage]:1148    r"""Trim messages to be below a token count.11491150    `trim_messages` can be used to reduce the size of a chat history to a specified1151    token or message count.11521153    In either case, if passing the trimmed chat history back into a chat model1154    directly, the resulting chat history should usually satisfy the following1155    properties:11561157    1. The resulting chat history should be valid. Most chat models expect that chat1158        history starts with either (1) a `HumanMessage` or (2) a `SystemMessage`1159        followed by a `HumanMessage`. To achieve this, set `start_on='human'`.1160        In addition, generally a `ToolMessage` can only appear after an `AIMessage`1161        that involved a tool call.1162    2. It includes recent messages and drops old messages in the chat history.1163        To achieve this set the `strategy='last'`.1164    3. Usually, the new chat history should include the `SystemMessage` if it1165        was present in the original chat history since the `SystemMessage` includes1166        special instructions to the chat model. The `SystemMessage` is almost always1167        the first message in the history if present. To achieve this set the1168        `include_system=True`.11691170    !!! note1171        The examples below show how to configure `trim_messages` to achieve a behavior1172        consistent with the above properties.11731174    Args:1175        messages: Sequence of Message-like objects to trim.1176        max_tokens: Max token count of trimmed messages.1177        token_counter: Function or llm for counting tokens in a `BaseMessage` or a1178            list of `BaseMessage`.11791180            If a `BaseLanguageModel` is passed in then1181            `BaseLanguageModel.get_num_tokens_from_messages()` will be used. Set to1182            `len` to count the number of **messages** in the chat history.11831184            You can also use string shortcuts for convenience:11851186            - `'approximate'`: Uses `count_tokens_approximately` for fast, approximate1187                token counts.11881189            !!! note11901191                `count_tokens_approximately` (or the shortcut `'approximate'`) is1192                recommended for using `trim_messages` on the hot path, where exact token1193                counting is not necessary.11941195        strategy: Strategy for trimming.11961197            - `'first'`: Keep the first `<= n_count` tokens of the messages.1198            - `'last'`: Keep the last `<= n_count` tokens of the messages.1199        allow_partial: Whether to split a message if only part of the message can be1200            included.12011202            If `strategy='last'` then the last partial contents of a message are1203            included. If `strategy='first'` then the first partial contents of a1204            message are included.1205        end_on: The message type to end on.12061207            If specified then every message after the last occurrence of this type is1208            ignored. If `strategy='last'` then this is done before we attempt to get the1209            last `max_tokens`. If `strategy='first'` then this is done after we get the1210            first `max_tokens`. Can be specified as string names (e.g. `'system'`,1211            `'human'`, `'ai'`, ...) or as `BaseMessage` classes (e.g. `SystemMessage`,1212            `HumanMessage`, `AIMessage`, ...). Can be a single type or a list of types.12131214        start_on: The message type to start on.12151216            Should only be specified if `strategy='last'`. If specified then every1217            message before the first occurrence of this type is ignored. This is done1218            after we trim the initial messages to the last `max_tokens`. Does not apply1219            to a `SystemMessage` at index 0 if `include_system=True`. Can be specified1220            as string names (e.g. `'system'`, `'human'`, `'ai'`, ...) or as1221            `BaseMessage` classes (e.g. `SystemMessage`, `HumanMessage`, `AIMessage`,1222            ...). Can be a single type or a list of types.12231224        include_system: Whether to keep the `SystemMessage` if there is one at index1225            `0`.12261227            Should only be specified if `strategy="last"`.1228        text_splitter: Function or `langchain_text_splitters.TextSplitter` for1229            splitting the string contents of a message.12301231            Only used if `allow_partial=True`. If `strategy='last'` then the last split1232            tokens from a partial message will be included. if `strategy='first'` then1233            the first split tokens from a partial message will be included. Token1234            splitter assumes that separators are kept, so that split contents can be1235            directly concatenated to recreate the original text. Defaults to splitting1236            on newlines.12371238    Returns:1239        List of trimmed `BaseMessage`.12401241    Raises:1242        ValueError: if two incompatible arguments are specified or an unrecognized1243            `strategy` is specified.12441245    Example:1246        Trim chat history based on token count, keeping the `SystemMessage` if1247        present, and ensuring that the chat history starts with a `HumanMessage` (or a1248        `SystemMessage` followed by a `HumanMessage`).12491250        ```python1251        from langchain_core.messages import (1252            AIMessage,1253            HumanMessage,1254            BaseMessage,1255            SystemMessage,1256            trim_messages,1257        )12581259        messages = [1260            SystemMessage("you're a good assistant, you always respond with a joke."),1261            HumanMessage("i wonder why it's called langchain"),1262            AIMessage(1263                'Well, I guess they thought "WordRope" and "SentenceString" just '1264                "didn't have the same ring to it!"1265            ),1266            HumanMessage("and who is harrison chasing anyways"),1267            AIMessage(1268                "Hmmm let me think.\n\nWhy, he's probably chasing after the last "1269                "cup of coffee in the office!"1270            ),1271            HumanMessage("what do you call a speechless parrot"),1272        ]127312741275        trim_messages(1276            messages,1277            max_tokens=45,1278            strategy="last",1279            token_counter=ChatOpenAI(model="openai:gpt-5.5"),1280            # Most chat models expect that chat history starts with either:1281            # (1) a HumanMessage or1282            # (2) a SystemMessage followed by a HumanMessage1283            start_on="human",1284            # Usually, we want to keep the SystemMessage1285            # if it's present in the original history.1286            # The SystemMessage has special instructions for the model.1287            include_system=True,1288            allow_partial=False,1289        )1290        ```12911292        ```python1293        [1294            SystemMessage(1295                content="you're a good assistant, you always respond with a joke."1296            ),1297            HumanMessage(content="what do you call a speechless parrot"),1298        ]1299        ```13001301        Trim chat history using approximate token counting with `'approximate'`:13021303        ```python1304        trim_messages(1305            messages,1306            max_tokens=45,1307            strategy="last",1308            # Using the "approximate" shortcut for fast token counting1309            token_counter="approximate",1310            start_on="human",1311            include_system=True,1312        )13131314        # This is equivalent to using `count_tokens_approximately` directly1315        from langchain_core.messages.utils import count_tokens_approximately13161317        trim_messages(1318            messages,1319            max_tokens=45,1320            strategy="last",1321            token_counter=count_tokens_approximately,1322            start_on="human",1323            include_system=True,1324        )1325        ```13261327        Trim chat history based on the message count, keeping the `SystemMessage` if1328        present, and ensuring that the chat history starts with a HumanMessage (1329        or a `SystemMessage` followed by a `HumanMessage`).13301331            trim_messages(1332                messages,1333                # When `len` is passed in as the token counter function,1334                # max_tokens will count the number of messages in the chat history.1335                max_tokens=4,1336                strategy="last",1337                # Passing in `len` as a token counter function will1338                # count the number of messages in the chat history.1339                token_counter=len,1340                # Most chat models expect that chat history starts with either:1341                # (1) a HumanMessage or1342                # (2) a SystemMessage followed by a HumanMessage1343                start_on="human",1344                # Usually, we want to keep the SystemMessage1345                # if it's present in the original history.1346                # The SystemMessage has special instructions for the model.1347                include_system=True,1348                allow_partial=False,1349            )13501351        ```python1352        [1353            SystemMessage(1354                content="you're a good assistant, you always respond with a joke."1355            ),1356            HumanMessage(content="and who is harrison chasing anyways"),1357            AIMessage(1358                content="Hmmm let me think.\n\nWhy, he's probably chasing after "1359                "the last cup of coffee in the office!"1360            ),1361            HumanMessage(content="what do you call a speechless parrot"),1362        ]1363        ```1364        Trim chat history using a custom token counter function that counts the1365        number of tokens in each message.13661367        ```python1368        messages = [1369            SystemMessage("This is a 4 token text. The full message is 10 tokens."),1370            HumanMessage(1371                "This is a 4 token text. The full message is 10 tokens.", id="first"1372            ),1373            AIMessage(1374                [1375                    {"type": "text", "text": "This is the FIRST 4 token block."},1376                    {"type": "text", "text": "This is the SECOND 4 token block."},1377                ],1378                id="second",1379            ),1380            HumanMessage(1381                "This is a 4 token text. The full message is 10 tokens.", id="third"1382            ),1383            AIMessage(1384                "This is a 4 token text. The full message is 10 tokens.",1385                id="fourth",1386            ),1387        ]138813891390        def dummy_token_counter(messages: list[BaseMessage]) -> int:1391            # treat each message like it adds 3 default tokens at the beginning1392            # of the message and at the end of the message. 3 + 4 + 3 = 10 tokens1393            # per message.13941395            default_content_len = 41396            default_msg_prefix_len = 31397            default_msg_suffix_len = 313981399            count = 01400            for msg in messages:1401                if isinstance(msg.content, str):1402                    count += (1403                        default_msg_prefix_len1404                        + default_content_len1405                        + default_msg_suffix_len1406                    )1407                if isinstance(msg.content, list):1408                    count += (1409                        default_msg_prefix_len1410                        + len(msg.content) * default_content_len1411                        + default_msg_suffix_len1412                    )1413            return count1414        ```14151416        First 30 tokens, allowing partial messages:1417        ```python1418        trim_messages(1419            messages,1420            max_tokens=30,1421            token_counter=dummy_token_counter,1422            strategy="first",1423            allow_partial=True,1424        )1425        ```14261427        ```python1428        [1429            SystemMessage("This is a 4 token text. The full message is 10 tokens."),1430            HumanMessage(1431                "This is a 4 token text. The full message is 10 tokens.",1432                id="first",1433            ),1434            AIMessage(1435                [{"type": "text", "text": "This is the FIRST 4 token block."}],1436                id="second",1437            ),1438        ]1439        ```1440    """1441    # Validate arguments1442    if start_on and strategy == "first":1443        msg = "start_on parameter is only valid with strategy='last'"1444        raise ValueError(msg)1445    if include_system and strategy == "first":1446        msg = "include_system parameter is only valid with strategy='last'"1447        raise ValueError(msg)14481449    messages = convert_to_messages(messages)14501451    # Handle string shortcuts for token counter1452    if isinstance(token_counter, str):1453        if token_counter in _TOKEN_COUNTER_SHORTCUTS:1454            actual_token_counter = _TOKEN_COUNTER_SHORTCUTS[token_counter]1455        else:1456            available_shortcuts = ", ".join(1457                f"'{key}'" for key in _TOKEN_COUNTER_SHORTCUTS1458            )1459            msg = (1460                f"Invalid token_counter shortcut '{token_counter}'. "1461                f"Available shortcuts: {available_shortcuts}."1462            )1463            raise ValueError(msg)1464    else:1465        # Type narrowing: at this point token_counter is not a str1466        actual_token_counter = token_counter  # type: ignore[assignment]14671468    if hasattr(actual_token_counter, "get_num_tokens_from_messages"):1469        list_token_counter = actual_token_counter.get_num_tokens_from_messages1470    elif callable(actual_token_counter):1471        if (1472            next(1473                iter(inspect.signature(actual_token_counter).parameters.values())1474            ).annotation1475            is BaseMessage1476        ):14771478            def list_token_counter(messages: Sequence[BaseMessage]) -> int:1479                return sum(actual_token_counter(msg) for msg in messages)  # type: ignore[arg-type, misc]14801481        else:1482            list_token_counter = actual_token_counter1483    else:1484        msg = (  # type: ignore[unreachable]1485            f"'token_counter' expected to be a model that implements "1486            f"'get_num_tokens_from_messages()' or a function. Received object of type "1487            f"{type(actual_token_counter)}."1488        )1489        raise ValueError(msg)14901491    text_splitter_fn: Callable[[str], list[str]]1492    if _HAS_LANGCHAIN_TEXT_SPLITTERS and isinstance(text_splitter, TextSplitter):1493        text_splitter_fn = text_splitter.split_text1494    elif text_splitter:1495        text_splitter_fn = cast("Callable[[str], list[str]]", text_splitter)1496    else:1497        text_splitter_fn = _default_text_splitter14981499    if strategy == "first":1500        return _first_max_tokens(1501            messages,1502            max_tokens=max_tokens,1503            token_counter=list_token_counter,1504            text_splitter=text_splitter_fn,1505            partial_strategy="first" if allow_partial else None,1506            end_on=end_on,1507        )1508    if strategy == "last":1509        return _last_max_tokens(1510            messages,1511            max_tokens=max_tokens,1512            token_counter=list_token_counter,1513            allow_partial=allow_partial,1514            include_system=include_system,1515            start_on=start_on,1516            end_on=end_on,1517            text_splitter=text_splitter_fn,1518        )1519    msg = f"Unrecognized {strategy=}. Supported strategies are 'last' and 'first'."  # type: ignore[unreachable]1520    raise ValueError(msg)152115221523_SingleMessage = BaseMessage | str | dict[str, Any]1524_T = TypeVar("_T", bound=_SingleMessage)1525# A sequence of _SingleMessage that is NOT a bare str1526_MultipleMessages = Sequence[_T]152715281529@overload1530def convert_to_openai_messages(1531    messages: _SingleMessage,1532    *,1533    text_format: Literal["string", "block"] = "string",1534    include_id: bool = False,1535    pass_through_unknown_blocks: bool = True,1536) -> dict[str, Any]: ...153715381539@overload1540def convert_to_openai_messages(1541    messages: _MultipleMessages[Any],1542    *,1543    text_format: Literal["string", "block"] = "string",1544    include_id: bool = False,1545    pass_through_unknown_blocks: bool = True,1546) -> list[dict[str, Any]]: ...154715481549def convert_to_openai_messages(1550    messages: MessageLikeRepresentation | Sequence[MessageLikeRepresentation],1551    *,1552    text_format: Literal["string", "block"] = "string",1553    include_id: bool = False,1554    pass_through_unknown_blocks: bool = True,1555) -> dict[str, Any] | list[dict[str, Any]]:1556    """Convert LangChain messages into OpenAI message dicts.15571558    Args:1559        messages: Message-like object or iterable of objects whose contents are1560            in OpenAI, Anthropic, Bedrock Converse, or VertexAI formats.1561        text_format: How to format string or text block contents:1562            - `'string'`:1563                If a message has a string content, this is left as a string. If1564                a message has content blocks that are all of type `'text'`, these1565                are joined with a newline to make a single string. If a message has1566                content blocks and at least one isn't of type `'text'`, then1567                all blocks are left as dicts.1568            - `'block'`:1569                If a message has a string content, this is turned into a list1570                with a single content block of type `'text'`. If a message has1571                content blocks these are left as is.1572        include_id: Whether to include message IDs in the openai messages, if they1573            are present in the source messages.1574        pass_through_unknown_blocks: Whether to include content blocks with unknown1575            formats in the output. If `False`, an error is raised if an unknown1576            content block is encountered.15771578    Raises:1579        ValueError: if an unrecognized `text_format` is specified, or if a message1580            content block is missing expected keys.15811582    Returns:1583        The return type depends on the input type:15841585        - dict:1586            If a single message-like object is passed in, a single OpenAI message1587            dict is returned.1588        - list[dict]:1589            If a sequence of message-like objects are passed in, a list of OpenAI1590            message dicts is returned.15911592    Example:1593        ```python1594        from langchain_core.messages import (1595            convert_to_openai_messages,1596            AIMessage,1597            SystemMessage,1598            ToolMessage,1599        )16001601        messages = [1602            SystemMessage([{"type": "text", "text": "foo"}]),1603            {1604                "role": "user",1605                "content": [1606                    {"type": "text", "text": "what's in this"},1607                    {1608                        "type": "image_url",1609                        "image_url": {"url": "data:image/png;base64,'/9j/4AAQSk'"},1610                    },1611                ],1612            },1613            AIMessage(1614                "",1615                tool_calls=[1616                    {1617                        "name": "analyze",1618                        "args": {"baz": "buz"},1619                        "id": "1",1620                        "type": "tool_call",1621                    }1622                ],1623            ),1624            ToolMessage("foobar", tool_call_id="1", name="bar"),1625            {"role": "assistant", "content": "that's nice"},1626        ]1627        oai_messages = convert_to_openai_messages(messages)1628        # -> [1629        #   {'role': 'system', 'content': 'foo'},1630        #   {'role': 'user', 'content': [{'type': 'text', 'text': 'what's in this'}, {'type': 'image_url', 'image_url': {'url': "data:image/png;base64,'/9j/4AAQSk'"}}]},1631        #   {'role': 'assistant', 'tool_calls': [{'type': 'function', 'id': '1','function': {'name': 'analyze', 'arguments': '{"baz": "buz"}'}}], 'content': ''},1632        #   {'role': 'tool', 'name': 'bar', 'content': 'foobar'},1633        #   {'role': 'assistant', 'content': 'that's nice'}1634        # ]1635        ```16361637    !!! version-added "Added in `langchain-core` 0.3.11"16381639    """  # noqa: E5011640    if text_format not in {"string", "block"}:1641        err = f"Unrecognized {text_format=}, expected one of 'string' or 'block'."1642        raise ValueError(err)16431644    oai_messages: list[dict[str, Any]] = []16451646    messages_: Sequence[MessageLikeRepresentation]1647    if is_single := isinstance(messages, (BaseMessage, dict, str)):1648        messages_ = [messages]1649    else:1650        messages_ = cast("Sequence[MessageLikeRepresentation]", messages)16511652    for i, message in enumerate(convert_to_messages(messages_)):1653        oai_msg: dict[str, Any] = {"role": _get_message_openai_role(message)}1654        tool_messages: list[dict[str, Any]] = []1655        content: str | list[dict[str, Any]]16561657        if message.name:1658            oai_msg["name"] = message.name1659        if isinstance(message, AIMessage) and message.tool_calls:1660            oai_msg["tool_calls"] = _convert_to_openai_tool_calls(message.tool_calls)1661        if message.additional_kwargs.get("refusal"):1662            oai_msg["refusal"] = message.additional_kwargs["refusal"]1663        if isinstance(message, ToolMessage):1664            oai_msg["tool_call_id"] = message.tool_call_id1665        if include_id and message.id:1666            oai_msg["id"] = message.id16671668        if not message.content:1669            content = "" if text_format == "string" else []1670        elif isinstance(message.content, str):1671            if text_format == "string":1672                content = message.content1673            else:1674                content = [{"type": "text", "text": message.content}]1675        elif text_format == "string" and all(1676            isinstance(block, str) or block.get("type") == "text"1677            for block in message.content1678        ):1679            content = "\n".join(1680                block if isinstance(block, str) else block["text"]1681                for block in message.content1682            )1683        else:1684            content = []1685            for j, block in enumerate(message.content):1686                # OpenAI format1687                if isinstance(block, str):1688                    content.append({"type": "text", "text": block})1689                elif block.get("type") == "text":1690                    if missing := [k for k in ("text",) if k not in block]:1691                        err = (1692                            f"Unrecognized content block at "1693                            f"messages[{i}].content[{j}] has 'type': 'text' "1694                            f"but is missing expected key(s) "1695                            f"{missing}. Full content block:\n\n{block}"1696                        )1697                        raise ValueError(err)1698                    content.append({"type": block["type"], "text": block["text"]})1699                elif block.get("type") == "image_url":1700                    if missing := [k for k in ("image_url",) if k not in block]:1701                        err = (1702                            f"Unrecognized content block at "1703                            f"messages[{i}].content[{j}] has 'type': 'image_url' "1704                            f"but is missing expected key(s) "1705                            f"{missing}. Full content block:\n\n{block}"1706                        )1707                        raise ValueError(err)1708                    content.append(1709                        {1710                            "type": "image_url",1711                            "image_url": block["image_url"],1712                        }1713                    )1714                # Standard multi-modal content block1715                elif is_data_content_block(block):1716                    formatted_block = convert_to_openai_data_block(block)1717                    if (1718                        formatted_block.get("type") == "file"1719                        and "file" in formatted_block1720                        and "filename" not in formatted_block["file"]1721                    ):1722                        logger.info("Generating a fallback filename.")1723                        formatted_block["file"]["filename"] = "LC_AUTOGENERATED"1724                    content.append(formatted_block)1725                # Anthropic and Bedrock converse format1726                elif (block.get("type") == "image") or "image" in block:1727                    # Anthropic1728                    if source := block.get("source"):1729                        if missing := [1730                            k for k in ("media_type", "type", "data") if k not in source1731                        ]:1732                            err = (1733                                f"Unrecognized content block at "1734                                f"messages[{i}].content[{j}] has 'type': 'image' "1735                                f"but 'source' is missing expected key(s) "1736                                f"{missing}. Full content block:\n\n{block}"1737                            )1738                            raise ValueError(err)1739                        content.append(1740                            {1741                                "type": "image_url",1742                                "image_url": {1743                                    "url": (1744                                        f"data:{source['media_type']};"1745                                        f"{source['type']},{source['data']}"1746                                    )1747                                },1748                            }1749                        )1750                    # Bedrock converse1751                    elif image := block.get("image"):1752                        if missing := [1753                            k for k in ("source", "format") if k not in image1754                        ]:1755                            err = (1756                                f"Unrecognized content block at "1757                                f"messages[{i}].content[{j}] has key 'image', "1758                                f"but 'image' is missing expected key(s) "1759                                f"{missing}. Full content block:\n\n{block}"1760                            )1761                            raise ValueError(err)1762                        b64_image = _bytes_to_b64_str(image["source"]["bytes"])1763                        content.append(1764                            {1765                                "type": "image_url",1766                                "image_url": {1767                                    "url": (1768                                        f"data:image/{image['format']};base64,{b64_image}"1769                                    )1770                                },1771                            }1772                        )1773                    else:1774                        err = (1775                            f"Unrecognized content block at "1776                            f"messages[{i}].content[{j}] has 'type': 'image' "1777                            f"but does not have a 'source' or 'image' key. Full "1778                            f"content block:\n\n{block}"1779                        )1780                        raise ValueError(err)1781                # OpenAI file format1782                elif (1783                    block.get("type") == "file"1784                    and isinstance(block.get("file"), dict)1785                    and isinstance(block.get("file", {}).get("file_data"), str)1786                ):1787                    if block.get("file", {}).get("filename") is None:1788                        logger.info("Generating a fallback filename.")1789                        block["file"]["filename"] = "LC_AUTOGENERATED"1790                    content.append(block)1791                # OpenAI audio format1792                elif (1793                    block.get("type") == "input_audio"1794                    and isinstance(block.get("input_audio"), dict)1795                    and isinstance(block.get("input_audio", {}).get("data"), str)1796                    and isinstance(block.get("input_audio", {}).get("format"), str)1797                ):1798                    content.append(block)1799                elif block.get("type") == "tool_use":1800                    if missing := [1801                        k for k in ("id", "name", "input") if k not in block1802                    ]:1803                        err = (1804                            f"Unrecognized content block at "1805                            f"messages[{i}].content[{j}] has 'type': "1806                            f"'tool_use', but is missing expected key(s) "1807                            f"{missing}. Full content block:\n\n{block}"1808                        )1809                        raise ValueError(err)1810                    if not any(1811                        tool_call["id"] == block["id"]1812                        for tool_call in cast("AIMessage", message).tool_calls1813                    ):1814                        oai_msg["tool_calls"] = oai_msg.get("tool_calls", [])1815                        oai_msg["tool_calls"].append(1816                            {1817                                "type": "function",1818                                "id": block["id"],1819                                "function": {1820                                    "name": block["name"],1821                                    "arguments": json.dumps(1822                                        block["input"], ensure_ascii=False1823                                    ),1824                                },1825                            }1826                        )1827                elif block.get("type") == "function_call":  # OpenAI Responses1828                    if not any(1829                        tool_call["id"] == block.get("call_id")1830                        for tool_call in cast("AIMessage", message).tool_calls1831                    ):1832                        if missing := [1833                            k1834                            for k in ("call_id", "name", "arguments")1835                            if k not in block1836                        ]:1837                            err = (1838                                f"Unrecognized content block at "1839                                f"messages[{i}].content[{j}] has 'type': "1840                                f"'tool_use', but is missing expected key(s) "1841                                f"{missing}. Full content block:\n\n{block}"1842                            )1843                            raise ValueError(err)1844                        oai_msg["tool_calls"] = oai_msg.get("tool_calls", [])1845                        oai_msg["tool_calls"].append(1846                            {1847                                "type": "function",1848                                "id": block.get("call_id"),1849                                "function": {1850                                    "name": block.get("name"),1851                                    "arguments": block.get("arguments"),1852                                },1853                            }1854                        )1855                    if pass_through_unknown_blocks:1856                        content.append(block)1857                elif block.get("type") == "tool_result":1858                    if missing := [1859                        k for k in ("content", "tool_use_id") if k not in block1860                    ]:1861                        msg = (1862                            f"Unrecognized content block at "1863                            f"messages[{i}].content[{j}] has 'type': "1864                            f"'tool_result', but is missing expected key(s) "1865                            f"{missing}. Full content block:\n\n{block}"1866                        )1867                        raise ValueError(msg)1868                    tool_message = ToolMessage(1869                        block["content"],1870                        tool_call_id=block["tool_use_id"],1871                        status="error" if block.get("is_error") else "success",1872                    )1873                    # Recurse to make sure tool message contents are OpenAI format.1874                    tool_messages.extend(1875                        convert_to_openai_messages(1876                            [tool_message], text_format=text_format1877                        )1878                    )1879                elif (block.get("type") == "json") or "json" in block:1880                    if "json" not in block:1881                        msg = (1882                            f"Unrecognized content block at "1883                            f"messages[{i}].content[{j}] has 'type': 'json' "1884                            f"but does not have a 'json' key. Full "1885                            f"content block:\n\n{block}"1886                        )1887                        raise ValueError(msg)1888                    content.append(1889                        {1890                            "type": "text",1891                            "text": json.dumps(block["json"]),1892                        }1893                    )1894                elif (block.get("type") == "guard_content") or "guard_content" in block:1895                    if (1896                        "guard_content" not in block1897                        or "text" not in block["guard_content"]1898                    ):1899                        msg = (1900                            f"Unrecognized content block at "1901                            f"messages[{i}].content[{j}] has 'type': "1902                            f"'guard_content' but does not have a "1903                            f"messages[{i}].content[{j}]['guard_content']['text'] "1904                            f"key. Full content block:\n\n{block}"1905                        )1906                        raise ValueError(msg)1907                    text = block["guard_content"]["text"]1908                    if isinstance(text, dict):1909                        text = text["text"]1910                    content.append({"type": "text", "text": text})1911                # VertexAI format1912                elif block.get("type") == "media":1913                    if missing := [k for k in ("mime_type", "data") if k not in block]:1914                        err = (1915                            f"Unrecognized content block at "1916                            f"messages[{i}].content[{j}] has 'type': "1917                            f"'media' but does not have key(s) {missing}. Full "1918                            f"content block:\n\n{block}"1919                        )1920                        raise ValueError(err)1921                    if "image" not in block["mime_type"]:1922                        err = (1923                            f"OpenAI messages can only support text and image data."1924                            f" Received content block with media of type:"1925                            f" {block['mime_type']}"1926                        )1927                        raise ValueError(err)1928                    b64_image = _bytes_to_b64_str(block["data"])1929                    content.append(1930                        {1931                            "type": "image_url",1932                            "image_url": {1933                                "url": (f"data:{block['mime_type']};base64,{b64_image}")1934                            },1935                        }1936                    )1937                elif (1938                    block.get("type") in {"thinking", "reasoning"}1939                    or pass_through_unknown_blocks1940                ):1941                    content.append(block)1942                else:1943                    err = (1944                        f"Unrecognized content block at "1945                        f"messages[{i}].content[{j}] does not match OpenAI, "1946                        f"Anthropic, Bedrock Converse, or VertexAI format. Full "1947                        f"content block:\n\n{block}"1948                    )1949                    raise ValueError(err)1950            if text_format == "string" and not any(1951                block["type"] != "text" for block in content1952            ):1953                content = "\n".join(block["text"] for block in content)1954        oai_msg["content"] = content1955        if message.content and not oai_msg["content"] and tool_messages:1956            oai_messages.extend(tool_messages)1957        else:1958            oai_messages.extend([oai_msg, *tool_messages])19591960    if is_single:1961        return oai_messages[0]1962    return oai_messages196319641965def _first_max_tokens(1966    messages: Sequence[BaseMessage],1967    *,1968    max_tokens: int,1969    token_counter: Callable[[list[BaseMessage]], int],1970    text_splitter: Callable[[str], list[str]],1971    partial_strategy: Literal["first", "last"] | None = None,1972    end_on: str | type[BaseMessage] | Sequence[str | type[BaseMessage]] | None = None,1973) -> list[BaseMessage]:1974    messages = list(messages)1975    if not messages:1976        return messages19771978    # Check if all messages already fit within token limit1979    if token_counter(messages) <= max_tokens:1980        # When all messages fit, only apply end_on filtering if needed1981        if end_on:1982            for _ in range(len(messages)):1983                if not _is_message_type(messages[-1], end_on):1984                    messages.pop()1985                else:1986                    break1987        return messages19881989    # Use binary search to find the maximum number of messages within token limit1990    left, right = 0, len(messages)1991    max_iterations = len(messages).bit_length()1992    for _ in range(max_iterations):1993        if left >= right:1994            break1995        mid = (left + right + 1) // 21996        if token_counter(messages[:mid]) <= max_tokens:1997            left = mid1998            idx = mid1999        else:2000            right = mid - 1

Code quality findings 100

Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(v, dict) and "type" in v:
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if not isinstance(result, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(url, str) and url.startswith("data:"):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(image_url, dict):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(url, str) and url.startswith("data:"):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(image_url, dict):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(m, HumanMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(m, AIMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(m, SystemMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(m, FunctionMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(m, ToolMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(m, ChatMessage):
Ensure functions have docstrings for documentation
missing-docstring
def get_buffer_string(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(m, HumanMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(m, AIMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(m, SystemMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(m, FunctionMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(m, ToolMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(m, ChatMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(m.content, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(block, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
has_tool_calls = isinstance(m, AIMessage) and m.tool_calls
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
isinstance(m, AIMessage)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(m, AIMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if not isinstance(chunk, BaseMessageChunk):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(chunk, AIMessageChunk):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(args, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message, BaseMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(message, Sequence):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(message, dict):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
and isinstance(message.get("id"), list)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
and isinstance(message.get("kwargs"), dict)
Ensure functions have docstrings for documentation
missing-docstring
def convert_to_messages(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(messages, PromptValue):
Ensure functions have docstrings for documentation
missing-docstring
def wrapped(
Ensure functions have docstrings for documentation
missing-docstring
def filter_messages(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
or isinstance(msg, ToolMessage)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(exclude_tool_calls, (list, tuple, set)):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(msg, AIMessage) and msg.tool_calls:
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(msg.content, list):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
not isinstance(content_block, dict)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
isinstance(msg, ToolMessage) and msg.tool_call_id in exclude_tool_calls
Ensure functions have docstrings for documentation
missing-docstring
def merge_message_runs(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(msg, ToolMessage) or not isinstance(msg, last.__class__):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
isinstance(last_chunk.content, str)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
and isinstance(curr_chunk.content, str)
Ensure functions have docstrings for documentation
missing-docstring
def trim_messages(
Ensure functions have docstrings for documentation
missing-docstring
def dummy_token_counter(messages: list[BaseMessage]) -> int:
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(msg.content, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(msg.content, list):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(token_counter, str):
Ensure functions have docstrings for documentation
missing-docstring
def list_token_counter(messages: Sequence[BaseMessage]) -> int:
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if _HAS_LANGCHAIN_TEXT_SPLITTERS and isinstance(text_splitter, TextSplitter):
Ensure functions have docstrings for documentation
missing-docstring
def convert_to_openai_messages(
Ensure functions have docstrings for documentation
missing-docstring
def convert_to_openai_messages(
Ensure functions have docstrings for documentation
missing-docstring
def convert_to_openai_messages(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if is_single := isinstance(messages, (BaseMessage, dict, str)):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message, AIMessage) and message.tool_calls:
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message, ToolMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(message.content, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
isinstance(block, str) or block.get("type") == "text"
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
block if isinstance(block, str) else block["text"]
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(block, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
and isinstance(block.get("file"), dict)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
and isinstance(block.get("file", {}).get("file_data"), str)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
and isinstance(block.get("input_audio"), dict)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
and isinstance(block.get("input_audio", {}).get("data"), str)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
and isinstance(block.get("input_audio", {}).get("format"), str)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(text, dict):
Avoid unnecessary list conversions; use generators where possible
unnecessary-list
messages = list(messages)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(messages[idx].content, list):
Avoid unnecessary list conversions; use generators where possible
unnecessary-list
excluded.content = list(reversed(excluded.content))
Avoid unnecessary list conversions; use generators where possible
unnecessary-list
excluded.content = list(reversed(excluded.content))
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(excluded.content, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(excluded.content, list) and excluded.content:
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(block, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(block, dict) and block.get("type") == "text":
Avoid unnecessary list conversions; use generators where possible
unnecessary-list
split_texts = list(reversed(split_texts))
Avoid unnecessary list conversions; use generators where possible
unnecessary-list
content_splits = list(reversed(content_splits))
Avoid unnecessary list conversions; use generators where possible
unnecessary-list
messages = list(messages)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if include_system and len(messages) > 0 and isinstance(messages[0], SystemMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message, msg_cls):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(chunk, chunk_cls):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
types = [type_] if isinstance(type_, (str, type)) else type_
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
types_str = [t for t in types if isinstance(t, str)]
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
types_types = tuple(t for t in types if isinstance(t, type))
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
return message.type in types_str or isinstance(message, types_types)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message, AIMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message, HumanMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message, ToolMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message, SystemMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if not isinstance(role, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message, FunctionMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message, ChatMessage):
Ensure functions have docstrings for documentation
missing-docstring
def count_tokens_approximately(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(tool, dict):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(schema, dict):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message.content, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(message.content, list):

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.