Custom chat history
To create your own custom chat history class for a backing store, you
can extend the
BaseListChatMessageHistory
class. This requires you to implement the following methods:
- addMessage, which adds a- BaseMessageto the store for the current session. This usually involves serializing them into a simple object representation (defined as- StoredMessagebelow) that the backing store can handle.
- getMessages, which loads messages for a session and returns them as an array of- BaseMessages. For most databases, this involves deserializing stored messages into- BaseMessages.
In addition, there are some optional methods that are nice to override:
- clear, which removes all messages from the store.
- addMessages, which will add multiple messages at a time to the current session. This can save round-trips to and from the backing store if many messages are being saved at once. The default implementation will call- addMessageonce per input message.
Hereβs an example that stores messages in-memory. For long-term
persistence, you should use a real database. Youβll notice we use the
mapChatMessagesToStoredMessages and mapStoredMessagesToChatMessages
helper methods for consistent serialization and deserialization:
import { BaseListChatMessageHistory } from "@langchain/core/chat_history";
import {
  BaseMessage,
  StoredMessage,
  mapChatMessagesToStoredMessages,
  mapStoredMessagesToChatMessages,
} from "@langchain/core/messages";
// Not required, but usually chat message histories will handle multiple sessions
// for different users, and should take some kind of sessionId as input.
export interface CustomChatMessageHistoryInput {
  sessionId: string;
}
export class CustomChatMessageHistory extends BaseListChatMessageHistory {
  lc_namespace = ["langchain", "stores", "message"];
  sessionId: string;
  // Simulate a real database layer. Stores serialized objects.
  fakeDatabase: Record<string, StoredMessage[]> = {};
  constructor(fields: CustomChatMessageHistoryInput) {
    super(fields);
    this.sessionId = fields.sessionId;
  }
  async getMessages(): Promise<BaseMessage[]> {
    const messages = this.fakeDatabase[this.sessionId] ?? [];
    return mapStoredMessagesToChatMessages(messages);
  }
  async addMessage(message: BaseMessage): Promise<void> {
    if (this.fakeDatabase[this.sessionId] === undefined) {
      this.fakeDatabase[this.sessionId] = [];
    }
    const serializedMessages = mapChatMessagesToStoredMessages([message]);
    this.fakeDatabase[this.sessionId].push(serializedMessages[0]);
  }
  async addMessages(messages: BaseMessage[]): Promise<void> {
    if (this.fakeDatabase[this.sessionId] === undefined) {
      this.fakeDatabase[this.sessionId] = [];
    }
    const existingMessages = this.fakeDatabase[this.sessionId];
    const serializedMessages = mapChatMessagesToStoredMessages(messages);
    this.fakeDatabase[this.sessionId] =
      existingMessages.concat(serializedMessages);
  }
  async clear(): Promise<void> {
    delete this.fakeDatabase[this.sessionId];
  }
}
You can then use this chat history as usual:
import { AIMessage, HumanMessage } from "@langchain/core/messages";
const chatHistory = new CustomChatMessageHistory({ sessionId: "test" });
await chatHistory.addMessages([
  new HumanMessage("Hello there!"),
  new AIMessage("Hello to you too!"),
]);
await chatHistory.getMessages();
[
  HumanMessage {
    lc_serializable: true,
    lc_kwargs: { content: "Hello there!", additional_kwargs: {} },
    lc_namespace: [ "langchain_core", "messages" ],
    content: "Hello there!",
    name: undefined,
    additional_kwargs: {}
  },
  AIMessage {
    lc_serializable: true,
    lc_kwargs: { content: "Hello to you too!", additional_kwargs: {} },
    lc_namespace: [ "langchain_core", "messages" ],
    content: "Hello to you too!",
    name: undefined,
    additional_kwargs: {}
  }
]