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 aBaseMessage
to the store for the current session. This usually involves serializing them into a simple object representation (defined asStoredMessage
below) that the backing store can handle.getMessages
, which loads messages for a session and returns them as an array ofBaseMessage
s. For most databases, this involves deserializing stored messages intoBaseMessage
s.
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 calladdMessage
once 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: {}
}
]