Skip to main content

Firestore Chat Memory

For longer-term persistence across chat sessions, you can swap out the default in-memory chatHistory that backs chat memory classes like BufferMemory for a firestore.

Setup​

First, install the Firebase admin package in your project:

npm install firebase-admin
npm install @langchain/openai @langchain/community

Visit the Project Settings page from your Firebase project and select the Service accounts tab.

Inside the Service accounts tab, click the Generate new private key button inside the Firebase Admin SDK section to download a JSON file containing your service account's credentials.

Using the downloaded JSON file, pass in the projectId, privateKey, and clientEmail to the config object of the FirestoreChatMessageHistory class, like shown below:

import { FirestoreChatMessageHistory } from "@langchain/community/stores/message/firestore";
import admin from "firebase-admin";

const messageHistory = new FirestoreChatMessageHistory({
collections: ["chats"],
docs: ["user-id"],
sessionId: "user-id",
userId: "a@example.com",
config: {
projectId: "YOUR-PROJECT-ID",
credential: admin.credential.cert({
projectId: "YOUR-PROJECT-ID",
privateKey:
"-----BEGIN PRIVATE KEY-----\nCHANGE-ME\n-----END PRIVATE KEY-----\n",
clientEmail: "CHANGE-ME@CHANGE-ME-TOO.iam.gserviceaccount.com",
}),
},
});

Here, the collections field should match the names and ordering of the collections in your database. The same goes for docs, it should match the names and ordering of the docs in your database.

Usage​

import { BufferMemory } from "langchain/memory";
import { FirestoreChatMessageHistory } from "@langchain/community/stores/message/firestore";
import { ChatOpenAI } from "@langchain/openai";
import { ConversationChain } from "langchain/chains";
import admin from "firebase-admin";

const memory = new BufferMemory({
chatHistory: new FirestoreChatMessageHistory({
collections: ["langchain"],
docs: ["lc-example"],
sessionId: "lc-example-id",
userId: "a@example.com",
config: {
projectId: "YOUR-PROJECT-ID",
credential: admin.credential.cert({
projectId: "YOUR-PROJECT-ID",
privateKey:
"-----BEGIN PRIVATE KEY-----\nnCHANGE-ME\n-----END PRIVATE KEY-----\n",
clientEmail: "CHANGE-ME@CHANGE-ME-TOO.iam.gserviceaccount.com",
}),
},
}),
});

const model = new ChatOpenAI();
const chain = new ConversationChain({ llm: model, memory });

const res1 = await chain.invoke({ input: "Hi! I'm Jim." });
console.log({ res1 });
/*
{ res1: { text: "Hello Jim! It's nice to meet you. My name is AI. How may I assist you today?" } }
*/

const res2 = await chain.invoke({ input: "What did I just say my name was?" });
console.log({ res2 });

/*
{ res1: { text: "You said your name was Jim." } }
*/

API Reference:

Nested Collections​

The FirestoreChatMessageHistory class supports nested collections, and dynamic collection/doc names.

The example below shows how to add and retrieve messages from a database with the following structure:

/chats/{chat-id}/bots/{bot-id}/messages/{message-id}
import { BufferMemory } from "langchain/memory";
import { FirestoreChatMessageHistory } from "@langchain/community/stores/message/firestore";
import { ChatOpenAI } from "@langchain/openai";
import { ConversationChain } from "langchain/chains";
import admin from "firebase-admin";

const memory = new BufferMemory({
chatHistory: new FirestoreChatMessageHistory({
collections: ["chats", "bots"],
docs: ["chat-id", "bot-id"],
sessionId: "user-id",
userId: "a@example.com",
config: {
projectId: "YOUR-PROJECT-ID",
credential: admin.credential.cert({
projectId: "YOUR-PROJECT-ID",
privateKey:
"-----BEGIN PRIVATE KEY-----\nnCHANGE-ME\n-----END PRIVATE KEY-----\n",
clientEmail: "CHANGE-ME@CHANGE-ME-TOO.iam.gserviceaccount.com",
}),
},
}),
});

const model = new ChatOpenAI();
const chain = new ConversationChain({ llm: model, memory });

const res1 = await chain.invoke({ input: "Hi! I'm Jim." });
console.log({ res1 });
/*
{ res1: { response: 'Hello Jim! How can I assist you today?' } }
*/

const res2 = await chain.invoke({ input: "What did I just say my name was?" });
console.log({ res2 });

/*
{ res2: { response: 'You just said that your name is Jim.' } }
*/

API Reference:

Firestore Rules​

If your collection name is "chathistory," you can configure Firestore rules as follows.

      match /chathistory/{sessionId} {
allow read: if request.auth.uid == resource.data.createdBy;
allow write: if request.auth.uid == request.resource.data.createdBy;
}
match /chathistory/{sessionId}/messages/{messageId} {
allow read: if request.auth.uid == resource.data.createdBy;
allow write: if request.auth.uid == request.resource.data.createdBy;
}

Help us out by providing feedback on this documentation page: