libs/core/langchain_core/chat_history.py PYTHON 247 lines View on github.com → Search inside
1"""Chat message history stores a history of the message interactions in a chat."""23from __future__ import annotations45from abc import ABC, abstractmethod6from typing import TYPE_CHECKING78from pydantic import BaseModel, Field910from langchain_core.messages import (11    AIMessage,12    BaseMessage,13    HumanMessage,14    get_buffer_string,15)16from langchain_core.runnables.config import run_in_executor1718if TYPE_CHECKING:19    from collections.abc import Sequence202122class BaseChatMessageHistory(ABC):23    """Abstract base class for storing chat message history.2425    Implementations guidelines:2627    Implementations are expected to over-ride all or some of the following methods:2829    * `add_messages`: sync variant for bulk addition of messages30    * `aadd_messages`: async variant for bulk addition of messages31    * `messages`: sync variant for getting messages32    * `aget_messages`: async variant for getting messages33    * `clear`: sync variant for clearing messages34    * `aclear`: async variant for clearing messages3536    `add_messages` contains a default implementation that calls `add_message`37    for each message in the sequence. This is provided for backwards compatibility38    with existing implementations which only had `add_message`.3940    Async variants all have default implementations that call the sync variants.41    Implementers can choose to override the async implementations to provide42    truly async implementations.4344    Usage guidelines:4546    When used for updating history, users should favor usage of `add_messages`47    over `add_message` or other variants like `add_user_message` and `add_ai_message`48    to avoid unnecessary round-trips to the underlying persistence layer.4950    Example:51        ```python52        import json53        import os54        from langchain_core.messages import messages_from_dict, message_to_dict555657        class FileChatMessageHistory(BaseChatMessageHistory):58            storage_path: str59            session_id: str6061            @property62            def messages(self) -> list[BaseMessage]:63                try:64                    with open(65                        os.path.join(self.storage_path, self.session_id),66                        "r",67                        encoding="utf-8",68                    ) as f:69                        messages_data = json.load(f)70                    return messages_from_dict(messages_data)71                except FileNotFoundError:72                    return []7374            def add_messages(self, messages: Sequence[BaseMessage]) -> None:75                all_messages = list(self.messages)  # Existing messages76                all_messages.extend(messages)  # Add new messages7778                serialized = [message_to_dict(message) for message in all_messages]79                file_path = os.path.join(self.storage_path, self.session_id)80                os.makedirs(os.path.dirname(file_path), exist_ok=True)81                with open(file_path, "w", encoding="utf-8") as f:82                    json.dump(serialized, f)8384            def clear(self) -> None:85                file_path = os.path.join(self.storage_path, self.session_id)86                os.makedirs(os.path.dirname(file_path), exist_ok=True)87                with open(file_path, "w", encoding="utf-8") as f:88                    json.dump([], f)89        ```90    """9192    messages: list[BaseMessage]93    """A property or attribute that returns a list of messages.9495    In general, getting the messages may involve IO to the underlying persistence96    layer, so this operation is expected to incur some latency.97    """9899    async def aget_messages(self) -> list[BaseMessage]:100        """Async version of getting messages.101102        Can over-ride this method to provide an efficient async implementation.103104        In general, fetching messages may involve IO to the underlying persistence105        layer.106107        Returns:108            The messages.109        """110        return await run_in_executor(None, lambda: self.messages)111112    def add_user_message(self, message: HumanMessage | str) -> None:113        """Convenience method for adding a human message string to the store.114115        !!! note116117            This is a convenience method. Code should favor the bulk `add_messages`118            interface instead to save on round-trips to the persistence layer.119120        This method may be deprecated in a future release.121122        Args:123            message: The `HumanMessage` to add to the store.124        """125        if isinstance(message, HumanMessage):126            self.add_message(message)127        else:128            self.add_message(HumanMessage(content=message))129130    def add_ai_message(self, message: AIMessage | str) -> None:131        """Convenience method for adding an `AIMessage` string to the store.132133        !!! note134135            This is a convenience method. Code should favor the bulk `add_messages`136            interface instead to save on round-trips to the persistence layer.137138        This method may be deprecated in a future release.139140        Args:141            message: The `AIMessage` to add.142        """143        if isinstance(message, AIMessage):144            self.add_message(message)145        else:146            self.add_message(AIMessage(content=message))147148    def add_message(self, message: BaseMessage) -> None:149        """Add a Message object to the store.150151        Args:152            message: A `BaseMessage` object to store.153154        Raises:155            NotImplementedError: If the sub-class has not implemented an efficient156                `add_messages` method.157        """158        if type(self).add_messages != BaseChatMessageHistory.add_messages:159            # This means that the sub-class has implemented an efficient add_messages160            # method, so we should use it.161            self.add_messages([message])162        else:163            msg = (164                "add_message is not implemented for this class. "165                "Please implement add_message or add_messages."166            )167            raise NotImplementedError(msg)168169    def add_messages(self, messages: Sequence[BaseMessage]) -> None:170        """Add a list of messages.171172        Implementations should over-ride this method to handle bulk addition of messages173        in an efficient manner to avoid unnecessary round-trips to the underlying store.174175        Args:176            messages: A sequence of `BaseMessage` objects to store.177        """178        for message in messages:179            self.add_message(message)180181    async def aadd_messages(self, messages: Sequence[BaseMessage]) -> None:182        """Async add a list of messages.183184        Args:185            messages: A sequence of `BaseMessage` objects to store.186        """187        await run_in_executor(None, self.add_messages, messages)188189    @abstractmethod190    def clear(self) -> None:191        """Remove all messages from the store."""192193    async def aclear(self) -> None:194        """Async remove all messages from the store."""195        await run_in_executor(None, self.clear)196197    def __str__(self) -> str:198        """Return a string representation of the chat history."""199        return get_buffer_string(self.messages)200201202class InMemoryChatMessageHistory(BaseChatMessageHistory, BaseModel):203    """In memory implementation of chat message history.204205    Stores messages in a memory list.206    """207208    messages: list[BaseMessage] = Field(default_factory=list)209    """A list of messages stored in memory."""210211    async def aget_messages(self) -> list[BaseMessage]:212        """Async version of getting messages.213214        Can over-ride this method to provide an efficient async implementation.215216        In general, fetching messages may involve IO to the underlying persistence217        layer.218219        Returns:220            List of messages.221        """222        return self.messages223224    def add_message(self, message: BaseMessage) -> None:225        """Add a self-created message to the store.226227        Args:228            message: The message to add.229        """230        self.messages.append(message)231232    async def aadd_messages(self, messages: Sequence[BaseMessage]) -> None:233        """Async add messages to the store.234235        Args:236            messages: The messages to add.237        """238        self.add_messages(messages)239240    def clear(self) -> None:241        """Clear all messages from the store."""242        self.messages = []243244    async def aclear(self) -> None:245        """Async clear all messages from the store."""246        self.clear()

Code quality findings 7

Ensure functions have docstrings for documentation
missing-docstring
def messages(self) -> list[BaseMessage]:
Ensure functions have docstrings for documentation
missing-docstring
def add_messages(self, messages: Sequence[BaseMessage]) -> None:
Avoid unnecessary list conversions; use generators where possible
unnecessary-list
all_messages = list(self.messages) # Existing messages
Ensure functions have docstrings for documentation
missing-docstring
def clear(self) -> None:
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, AIMessage):
Use isinstance() for type checking instead of type()
type-check
if type(self).add_messages != BaseChatMessageHistory.add_messages:

Get this view in your editor

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