You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
langchain/libs/core/langchain_core/chat_history.py

171 lines
6.1 KiB
Python

from __future__ import annotations
from abc import ABC, abstractmethod
from typing import List, Sequence, Union
from langchain_core.messages import (
AIMessage,
BaseMessage,
HumanMessage,
get_buffer_string,
)
from langchain_core.runnables import run_in_executor
class BaseChatMessageHistory(ABC):
"""Abstract base class for storing chat message history.
Implementations guidelines:
Implementations are expected to over-ride all or some of the following methods:
* add_messages: sync variant for bulk addition of messages
* aadd_messages: async variant for bulk addition of messages
* messages: sync variant for getting messages
* aget_messages: async variant for getting messages
* clear: sync variant for clearing messages
* aclear: async variant for clearing messages
add_messages contains a default implementation that calls add_message
for each message in the sequence. This is provided for backwards compatibility
with existing implementations which only had add_message.
Async variants all have default implementations that call the sync variants.
Implementers can choose to over-ride the async implementations to provide
truly async implementations.
Usage guidelines:
When used for updating history, users should favor usage of `add_messages`
over `add_message` or other variants like `add_user_message` and `add_ai_message`
to avoid unnecessary round-trips to the underlying persistence layer.
Example: Shows a default implementation.
.. code-block:: python
class FileChatMessageHistory(BaseChatMessageHistory):
storage_path: str
session_id: str
@property
def messages(self):
with open(os.path.join(storage_path, session_id), 'r:utf-8') as f:
messages = json.loads(f.read())
return messages_from_dict(messages)
def add_messages(self, messages: Sequence[BaseMessage]) -> None:
all_messages = list(self.messages) # Existing messages
all_messages.extend(messages) # Add new messages
serialized = [message_to_dict(message) for message in all_messages]
# Can be further optimized by only writing new messages
# using append mode.
with open(os.path.join(storage_path, session_id), 'w') as f:
json.dump(f, messages)
def clear(self):
with open(os.path.join(storage_path, session_id), 'w') as f:
f.write("[]")
"""
messages: List[BaseMessage]
"""A property or attribute that returns a list of messages.
In general, getting the messages may involve IO to the underlying
persistence layer, so this operation is expected to incur some
latency.
"""
async def aget_messages(self) -> List[BaseMessage]:
"""Async version of getting messages.
Can over-ride this method to provide an efficient async implementation.
In general, fetching messages may involve IO to the underlying
persistence layer.
"""
return await run_in_executor(None, lambda: self.messages)
core(minor): Allow explicit types for ChatMessageHistory adds (#14967) <!-- Thank you for contributing to LangChain! Replace this entire comment with: - **Description:** a description of the change, - **Issue:** the issue # it fixes (if applicable), - **Dependencies:** any dependencies required for this change, - **Tag maintainer:** for a quicker response, tag the relevant maintainer (see below), - **Twitter handle:** we announce bigger features on Twitter. If your PR gets announced, and you'd like a mention, we'll gladly shout you out! Please make sure your PR is passing linting and testing before submitting. Run `make format`, `make lint` and `make test` to check this locally. See contribution guidelines for more information on how to write/run tests, lint, etc: https://python.langchain.com/docs/contributing/ If you're adding a new integration, please include: 1. a test for the integration, preferably unit tests that do not rely on network access, 2. an example notebook showing its use. It lives in `docs/extras` directory. If no one reviews your PR within a few days, please @-mention one of @baskaryan, @eyurtsev, @hwchase17. --> ## Description Changes the behavior of `add_user_message` and `add_ai_message` to allow for messages of those types to be passed in. Currently, if you want to use the `add_user_message` or `add_ai_message` methods, you have to pass in a string. For `add_message` on `ChatMessageHistory`, however, you have to pass a `BaseMessage`. This behavior seems a bit inconsistent. Personally, I'd love to be able to be explicit that I want to `add_user_message` and pass in a `HumanMessage` without having to grab the `content` attribute. This PR allows `add_user_message` to accept `HumanMessage`s or `str`s and `add_ai_message` to accept `AIMessage`s or `str`s to add that functionality and ensure backwards compatibility. ## Issue * None ## Dependencies * None ## Tag maintainer @hinthornw @baskaryan ## Note `make test` results in `make: *** No rule to make target 'test'. Stop.`
7 months ago
def add_user_message(self, message: Union[HumanMessage, str]) -> None:
"""Convenience method for adding a human message string to the store.
Please note that this is a convenience method. Code should favor the
bulk add_messages interface instead to save on round-trips to the underlying
persistence layer.
This method may be deprecated in a future release.
Args:
core(minor): Allow explicit types for ChatMessageHistory adds (#14967) <!-- Thank you for contributing to LangChain! Replace this entire comment with: - **Description:** a description of the change, - **Issue:** the issue # it fixes (if applicable), - **Dependencies:** any dependencies required for this change, - **Tag maintainer:** for a quicker response, tag the relevant maintainer (see below), - **Twitter handle:** we announce bigger features on Twitter. If your PR gets announced, and you'd like a mention, we'll gladly shout you out! Please make sure your PR is passing linting and testing before submitting. Run `make format`, `make lint` and `make test` to check this locally. See contribution guidelines for more information on how to write/run tests, lint, etc: https://python.langchain.com/docs/contributing/ If you're adding a new integration, please include: 1. a test for the integration, preferably unit tests that do not rely on network access, 2. an example notebook showing its use. It lives in `docs/extras` directory. If no one reviews your PR within a few days, please @-mention one of @baskaryan, @eyurtsev, @hwchase17. --> ## Description Changes the behavior of `add_user_message` and `add_ai_message` to allow for messages of those types to be passed in. Currently, if you want to use the `add_user_message` or `add_ai_message` methods, you have to pass in a string. For `add_message` on `ChatMessageHistory`, however, you have to pass a `BaseMessage`. This behavior seems a bit inconsistent. Personally, I'd love to be able to be explicit that I want to `add_user_message` and pass in a `HumanMessage` without having to grab the `content` attribute. This PR allows `add_user_message` to accept `HumanMessage`s or `str`s and `add_ai_message` to accept `AIMessage`s or `str`s to add that functionality and ensure backwards compatibility. ## Issue * None ## Dependencies * None ## Tag maintainer @hinthornw @baskaryan ## Note `make test` results in `make: *** No rule to make target 'test'. Stop.`
7 months ago
message: The human message to add
"""
core(minor): Allow explicit types for ChatMessageHistory adds (#14967) <!-- Thank you for contributing to LangChain! Replace this entire comment with: - **Description:** a description of the change, - **Issue:** the issue # it fixes (if applicable), - **Dependencies:** any dependencies required for this change, - **Tag maintainer:** for a quicker response, tag the relevant maintainer (see below), - **Twitter handle:** we announce bigger features on Twitter. If your PR gets announced, and you'd like a mention, we'll gladly shout you out! Please make sure your PR is passing linting and testing before submitting. Run `make format`, `make lint` and `make test` to check this locally. See contribution guidelines for more information on how to write/run tests, lint, etc: https://python.langchain.com/docs/contributing/ If you're adding a new integration, please include: 1. a test for the integration, preferably unit tests that do not rely on network access, 2. an example notebook showing its use. It lives in `docs/extras` directory. If no one reviews your PR within a few days, please @-mention one of @baskaryan, @eyurtsev, @hwchase17. --> ## Description Changes the behavior of `add_user_message` and `add_ai_message` to allow for messages of those types to be passed in. Currently, if you want to use the `add_user_message` or `add_ai_message` methods, you have to pass in a string. For `add_message` on `ChatMessageHistory`, however, you have to pass a `BaseMessage`. This behavior seems a bit inconsistent. Personally, I'd love to be able to be explicit that I want to `add_user_message` and pass in a `HumanMessage` without having to grab the `content` attribute. This PR allows `add_user_message` to accept `HumanMessage`s or `str`s and `add_ai_message` to accept `AIMessage`s or `str`s to add that functionality and ensure backwards compatibility. ## Issue * None ## Dependencies * None ## Tag maintainer @hinthornw @baskaryan ## Note `make test` results in `make: *** No rule to make target 'test'. Stop.`
7 months ago
if isinstance(message, HumanMessage):
self.add_message(message)
else:
self.add_message(HumanMessage(content=message))
core(minor): Allow explicit types for ChatMessageHistory adds (#14967) <!-- Thank you for contributing to LangChain! Replace this entire comment with: - **Description:** a description of the change, - **Issue:** the issue # it fixes (if applicable), - **Dependencies:** any dependencies required for this change, - **Tag maintainer:** for a quicker response, tag the relevant maintainer (see below), - **Twitter handle:** we announce bigger features on Twitter. If your PR gets announced, and you'd like a mention, we'll gladly shout you out! Please make sure your PR is passing linting and testing before submitting. Run `make format`, `make lint` and `make test` to check this locally. See contribution guidelines for more information on how to write/run tests, lint, etc: https://python.langchain.com/docs/contributing/ If you're adding a new integration, please include: 1. a test for the integration, preferably unit tests that do not rely on network access, 2. an example notebook showing its use. It lives in `docs/extras` directory. If no one reviews your PR within a few days, please @-mention one of @baskaryan, @eyurtsev, @hwchase17. --> ## Description Changes the behavior of `add_user_message` and `add_ai_message` to allow for messages of those types to be passed in. Currently, if you want to use the `add_user_message` or `add_ai_message` methods, you have to pass in a string. For `add_message` on `ChatMessageHistory`, however, you have to pass a `BaseMessage`. This behavior seems a bit inconsistent. Personally, I'd love to be able to be explicit that I want to `add_user_message` and pass in a `HumanMessage` without having to grab the `content` attribute. This PR allows `add_user_message` to accept `HumanMessage`s or `str`s and `add_ai_message` to accept `AIMessage`s or `str`s to add that functionality and ensure backwards compatibility. ## Issue * None ## Dependencies * None ## Tag maintainer @hinthornw @baskaryan ## Note `make test` results in `make: *** No rule to make target 'test'. Stop.`
7 months ago
def add_ai_message(self, message: Union[AIMessage, str]) -> None:
"""Convenience method for adding an AI message string to the store.
Please note that this is a convenience method. Code should favor the bulk
add_messages interface instead to save on round-trips to the underlying
persistence layer.
This method may be deprecated in a future release.
Args:
core(minor): Allow explicit types for ChatMessageHistory adds (#14967) <!-- Thank you for contributing to LangChain! Replace this entire comment with: - **Description:** a description of the change, - **Issue:** the issue # it fixes (if applicable), - **Dependencies:** any dependencies required for this change, - **Tag maintainer:** for a quicker response, tag the relevant maintainer (see below), - **Twitter handle:** we announce bigger features on Twitter. If your PR gets announced, and you'd like a mention, we'll gladly shout you out! Please make sure your PR is passing linting and testing before submitting. Run `make format`, `make lint` and `make test` to check this locally. See contribution guidelines for more information on how to write/run tests, lint, etc: https://python.langchain.com/docs/contributing/ If you're adding a new integration, please include: 1. a test for the integration, preferably unit tests that do not rely on network access, 2. an example notebook showing its use. It lives in `docs/extras` directory. If no one reviews your PR within a few days, please @-mention one of @baskaryan, @eyurtsev, @hwchase17. --> ## Description Changes the behavior of `add_user_message` and `add_ai_message` to allow for messages of those types to be passed in. Currently, if you want to use the `add_user_message` or `add_ai_message` methods, you have to pass in a string. For `add_message` on `ChatMessageHistory`, however, you have to pass a `BaseMessage`. This behavior seems a bit inconsistent. Personally, I'd love to be able to be explicit that I want to `add_user_message` and pass in a `HumanMessage` without having to grab the `content` attribute. This PR allows `add_user_message` to accept `HumanMessage`s or `str`s and `add_ai_message` to accept `AIMessage`s or `str`s to add that functionality and ensure backwards compatibility. ## Issue * None ## Dependencies * None ## Tag maintainer @hinthornw @baskaryan ## Note `make test` results in `make: *** No rule to make target 'test'. Stop.`
7 months ago
message: The AI message to add.
"""
core(minor): Allow explicit types for ChatMessageHistory adds (#14967) <!-- Thank you for contributing to LangChain! Replace this entire comment with: - **Description:** a description of the change, - **Issue:** the issue # it fixes (if applicable), - **Dependencies:** any dependencies required for this change, - **Tag maintainer:** for a quicker response, tag the relevant maintainer (see below), - **Twitter handle:** we announce bigger features on Twitter. If your PR gets announced, and you'd like a mention, we'll gladly shout you out! Please make sure your PR is passing linting and testing before submitting. Run `make format`, `make lint` and `make test` to check this locally. See contribution guidelines for more information on how to write/run tests, lint, etc: https://python.langchain.com/docs/contributing/ If you're adding a new integration, please include: 1. a test for the integration, preferably unit tests that do not rely on network access, 2. an example notebook showing its use. It lives in `docs/extras` directory. If no one reviews your PR within a few days, please @-mention one of @baskaryan, @eyurtsev, @hwchase17. --> ## Description Changes the behavior of `add_user_message` and `add_ai_message` to allow for messages of those types to be passed in. Currently, if you want to use the `add_user_message` or `add_ai_message` methods, you have to pass in a string. For `add_message` on `ChatMessageHistory`, however, you have to pass a `BaseMessage`. This behavior seems a bit inconsistent. Personally, I'd love to be able to be explicit that I want to `add_user_message` and pass in a `HumanMessage` without having to grab the `content` attribute. This PR allows `add_user_message` to accept `HumanMessage`s or `str`s and `add_ai_message` to accept `AIMessage`s or `str`s to add that functionality and ensure backwards compatibility. ## Issue * None ## Dependencies * None ## Tag maintainer @hinthornw @baskaryan ## Note `make test` results in `make: *** No rule to make target 'test'. Stop.`
7 months ago
if isinstance(message, AIMessage):
self.add_message(message)
else:
self.add_message(AIMessage(content=message))
def add_message(self, message: BaseMessage) -> None:
"""Add a Message object to the store.
Args:
message: A BaseMessage object to store.
"""
if type(self).add_messages != BaseChatMessageHistory.add_messages:
# This means that the sub-class has implemented an efficient add_messages
# method, so we should use it.
self.add_messages([message])
else:
raise NotImplementedError(
"add_message is not implemented for this class. "
"Please implement add_message or add_messages."
)
def add_messages(self, messages: Sequence[BaseMessage]) -> None:
"""Add a list of messages.
Implementations should over-ride this method to handle bulk addition of messages
in an efficient manner to avoid unnecessary round-trips to the underlying store.
Args:
messages: A list of BaseMessage objects to store.
"""
for message in messages:
self.add_message(message)
async def aadd_messages(self, messages: Sequence[BaseMessage]) -> None:
"""Add a list of messages.
Args:
messages: A list of BaseMessage objects to store.
"""
await run_in_executor(None, self.add_messages, messages)
@abstractmethod
def clear(self) -> None:
"""Remove all messages from the store"""
async def aclear(self) -> None:
"""Remove all messages from the store"""
await run_in_executor(None, self.clear)
def __str__(self) -> str:
"""Return a string representation of the chat history."""
return get_buffer_string(self.messages)