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 aBaseMessageto the store for the current session. This usually involves serializing them into a simple object representation (defined asStoredMessagebelow) that the backing store can handle.getMessages, which loads messages for a session and returns them as an array ofBaseMessages. For most databases, this involves deserializing stored messages intoBaseMessages.
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 calladdMessageonce 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: {}
}
]