Chat Memory
Large language models (LLMs) are stateless, meaning they do not retain information about previous interactions. This can be a limitation when you want to maintain context or state across multiple interactions. To address this, NestJS AI provides chat memory features that allow you to store and retrieve information across multiple interactions with the LLM.
The ChatMemory abstraction allows you to implement various types of memory to support different use cases. The underlying storage of the messages is handled by the ChatMemoryRepository, whose sole responsibility is to store and retrieve messages. It’s up to the ChatMemory implementation to decide which messages to keep and when to remove them. Examples of strategies could include keeping the last N messages, keeping messages for a certain time period, or keeping messages up to a certain token limit.
Before choosing a memory type, it’s essential to understand the difference between chat memory and chat history.
-
Chat Memory. The information that a large-language model retains and uses to maintain contextual awareness throughout a conversation.
-
Chat History. The entire conversation history, including all messages exchanged between the user and the model.
The ChatMemory abstraction is designed to manage the chat memory. It allows you to store and retrieve messages that are relevant to the current conversation context. However, it is not the best fit for storing the chat history. If you need to maintain a complete record of all the messages exchanged, you should consider using a different approach, such as a dedicated persistence layer for the complete chat history.
Quick Start
Unlike the auto-configured Spring counterpart, NestJS AI does not register a default ChatMemory instance. You wire one explicitly by combining a ChatMemoryRepository (storage) with a ChatMemory implementation (eviction strategy). The simplest setup uses the in-memory repository together with MessageWindowChatMemory:
import {
InMemoryChatMemoryRepository,
MessageWindowChatMemory,
} from '@nestjs-ai/model';
const chatMemory = new MessageWindowChatMemory({
chatMemoryRepository: new InMemoryChatMemoryRepository(),
maxMessages: 20,
});
When using a persistent repository module (e.g. JSDBC or Redis), the repository is registered under CHAT_MEMORY_TOKEN and can be injected into your services with the @InjectChatMemory() decorator from @nestjs-ai/platform:
import { Injectable } from '@nestjs/common';
import { InjectChatMemory } from '@nestjs-ai/platform';
import {
type ChatMemoryRepository,
MessageWindowChatMemory,
} from '@nestjs-ai/model';
@Injectable()
export class ChatService {
private readonly chatMemory: MessageWindowChatMemory;
constructor(
@InjectChatMemory() chatMemoryRepository: ChatMemoryRepository,
) {
this.chatMemory = new MessageWindowChatMemory({
chatMemoryRepository,
maxMessages: 20,
});
}
}
The following sections describe further the different memory types and repositories available in NestJS AI.
Memory Types
The ChatMemory abstraction allows you to implement various types of memory to suit different use cases. The choice of memory type can significantly impact the performance and behavior of your application. This section describes the built-in memory types provided by NestJS AI and their characteristics.
Message Window Chat Memory
MessageWindowChatMemory maintains a window of messages up to a specified maximum size. When the number of messages exceeds the maximum, older messages are removed while preserving system messages. The default window size is 20 messages.
import { MessageWindowChatMemory } from '@nestjs-ai/model';
const memory = new MessageWindowChatMemory({
chatMemoryRepository,
maxMessages: 10,
});
Memory Storage
NestJS AI offers the ChatMemoryRepository abstraction for storing chat memory. This section describes the built-in repositories provided by NestJS AI and how to use them, but you can also implement your own repository if needed.
In-Memory Repository
InMemoryChatMemoryRepository stores messages in memory using a Map.
It is part of the @nestjs-ai/model package and can be instantiated directly:
import { InMemoryChatMemoryRepository } from '@nestjs-ai/model';
const repository = new InMemoryChatMemoryRepository();
There is no module wrapper for this repository because it has no configuration. Pass the instance directly to a ChatMemory implementation such as MessageWindowChatMemory.
JsdbcChatMemoryRepository
JsdbcChatMemoryRepository is a built-in implementation that uses JSDBC to store messages in a relational database. It supports multiple databases out-of-the-box and is suitable for applications that require persistent storage of chat memory.
Messages are retrieved in ascending timestamp order (oldest-to-newest), which is the expected format for LLM conversation history.
Install the package along with the underlying JSDBC port:
pnpm add @nestjs-ai/model-chat-memory-repository-jsdbc @nestjs-port/jsdbc
The JsdbcChatMemoryRepositoryModule requires a JsdbcTemplate to be available in the DI container (provided under JSDBC_TEMPLATE by @nestjs-port/jsdbc). Once that is in place, register the chat memory module:
import { Module } from '@nestjs/common';
import { JsdbcChatMemoryRepositoryModule } from '@nestjs-ai/model-chat-memory-repository-jsdbc';
@Module({
imports: [
// ...your JSDBC setup that provides JSDBC_TEMPLATE
JsdbcChatMemoryRepositoryModule.forFeature({
initializeSchema: true,
}),
],
})
export class AppModule {}
For dynamic configuration, use forFeatureAsync:
import { Module } from '@nestjs/common';
import { JsdbcChatMemoryRepositoryModule } from '@nestjs-ai/model-chat-memory-repository-jsdbc';
@Module({
imports: [
JsdbcChatMemoryRepositoryModule.forFeatureAsync({
useFactory: () => ({
initializeSchema: process.env.JSDBC_INIT_SCHEMA === 'true',
}),
}),
],
})
export class AppModule {}
The repository is registered under CHAT_MEMORY_TOKEN, so it can be injected with @InjectChatMemory().
If you’d rather create the JsdbcChatMemoryRepository manually, build it from a DataSource (or a JsdbcTemplate) and an optional dialect:
import {
JsdbcChatMemoryRepository,
PostgresChatMemoryRepositoryDialect,
} from '@nestjs-ai/model-chat-memory-repository-jsdbc';
const chatMemoryRepository = await JsdbcChatMemoryRepository.builder()
.jsdbcTemplate(jsdbcTemplate)
.dialect(new PostgresChatMemoryRepositoryDialect())
.build();
const chatMemory = new MessageWindowChatMemory({
chatMemoryRepository,
maxMessages: 10,
});
Supported Databases and Dialect Abstraction
NestJS AI supports multiple relational databases via a dialect abstraction. The following databases are supported out-of-the-box:
-
PostgreSQL
-
MySQL / MariaDB
-
SQL Server
-
SQLite
-
Oracle Database
The correct dialect can be auto-detected from the underlying DataSource when using JsdbcChatMemoryRepositoryDialectFactory.from(dataSource). You can extend support for other databases by extending the JsdbcChatMemoryRepositoryDialect class.
Module Options
| Property | Type | Default | Description |
|---|---|---|---|
|
|
|
When |
Schema Initialization
When initializeSchema is true, the module runs a vendor-specific SQL script to create the SPRING_AI_CHAT_MEMORY table. The dialect is detected from the JSDBC DataSource. Pre-bundled scripts are provided for PostgreSQL/H2/HSQL, MySQL, MariaDB, SQL Server, SQLite, and Oracle.
If you manage schema with a migration tool, set initializeSchema: false (the default) and run the migrations yourself. The schema constants (POSTGRESQL_CHAT_MEMORY_SCHEMA, MYSQL_CHAT_MEMORY_SCHEMA, etc.) are exported from @nestjs-ai/model-chat-memory-repository-jsdbc and can be applied through your own tooling.
Extending Dialects
To add support for a new database, extend the JsdbcChatMemoryRepositoryDialect class and implement methods for selecting, inserting, and deleting messages. You can then pass your custom dialect to the repository builder.
import { JsdbcChatMemoryRepository } from '@nestjs-ai/model-chat-memory-repository-jsdbc';
const chatMemoryRepository = await JsdbcChatMemoryRepository.builder()
.jsdbcTemplate(jsdbcTemplate)
.dialect(new MyCustomDbDialect())
.build();
MongoChatMemoryRepository
MongoChatMemoryRepository is a built-in implementation that uses MongoDB to store chat messages.
It is suitable for applications that need a flexible, document-oriented database for persistent chat memory.
Messages are retrieved in ascending timestamp order (oldest-to-newest), which is the expected format for LLM conversation history. This ordering is consistent across all chat memory repository implementations.
Install the package together with the MongoDB driver:
pnpm add @nestjs-ai/model-chat-memory-repository-mongodb mongodb
Register the module with a MongoClient, a Db, or a pre-built Collection. The module exposes CHAT_MEMORY_TOKEN, so it can be injected with @InjectChatMemory().
import { Module } from '@nestjs/common';
import { MongoChatMemoryModule } from '@nestjs-ai/model-chat-memory-repository-mongodb';
@Module({
imports: [
MongoChatMemoryModule.forFeature({
mongoClient,
createIndices: true,
ttl: 3600,
}),
],
})
export class AppModule {}
For dynamic configuration, use forFeatureAsync:
import { Module } from '@nestjs/common';
import { MongoChatMemoryModule } from '@nestjs-ai/model-chat-memory-repository-mongodb';
@Module({
imports: [
MongoChatMemoryModule.forFeatureAsync({
useFactory: () => ({
mongoClient,
createIndices: true,
ttl: 3600,
}),
}),
],
})
export class AppModule {}
If you want to create the repository manually, build it from a Collection:
import { MongoChatMemoryRepository } from '@nestjs-ai/model-chat-memory-repository-mongodb';
const chatMemoryRepository = MongoChatMemoryRepository.builder()
.collection(collection)
.build();
const chatMemory = new MessageWindowChatMemory({
chatMemoryRepository,
maxMessages: 10,
});
Module Options
| Property | Type | Default | Description |
|---|---|---|---|
|
|
|
Pre-configured MongoDB collection to use directly. |
|
|
|
MongoDB database used to resolve the collection by name. |
|
|
|
MongoDB client used to resolve the collection by name. |
|
|
|
Name of the collection used when resolving from |
|
|
|
When |
|
|
|
When set, creates or updates the TTL index on the |
Index Initialization
When createIndices is true, the module creates the compound index on conversationId and timestamp, plus a TTL index on timestamp when ttl is greater than zero.
This index initialization also causes MongoDB to create the ai_chat_memory collection if it does not already exist.
If an existing TTL index has a different expiration value, it is dropped and recreated with the configured TTL.
RedisChatMemoryRepository
RedisChatMemoryRepository is a built-in implementation that uses Redis Stack (with Redis Query Engine and RedisJSON) to store chat messages.
It is suitable for applications that require high-performance, low-latency chat memory persistence with optional TTL (time-to-live) support and advanced querying capabilities.
The repository stores messages as JSON documents and creates a search index for efficient querying.
It also provides extended query capabilities through the AdvancedRedisChatMemoryRepository interface for searching messages by content, type, time range, and metadata.
Messages are retrieved in ascending timestamp order (oldest-to-newest), which is the expected format for LLM conversation history.
Install the package:
pnpm add @nestjs-ai/model-chat-memory-repository-redis
Register the module by providing either a pre-configured RedisClientType instance or clientOptions. Note that RedisChatMemoryModule.forFeature() returns a Provider[] and must be spread into the providers array (not imports):
import { Module } from '@nestjs/common';
import { RedisChatMemoryModule } from '@nestjs-ai/model-chat-memory-repository-redis';
@Module({
providers: [
...RedisChatMemoryModule.forFeature({
clientOptions: { url: 'redis://localhost:6379' },
indexName: 'chat-memory-idx',
keyPrefix: 'chat-memory:',
initializeSchema: true,
}),
],
exports: [/* re-export the providers if other modules need them */],
})
export class AppModule {}
Use forFeatureAsync for dynamic configuration:
import { Module } from '@nestjs/common';
import { RedisChatMemoryModule } from '@nestjs-ai/model-chat-memory-repository-redis';
@Module({
providers: [
...RedisChatMemoryModule.forFeatureAsync({
useFactory: () => ({
clientOptions: { url: process.env.REDIS_URL },
timeToLive: 60 * 60 * 24, // 24 hours
initializeSchema: true,
}),
}),
],
})
export class AppModule {}
Once registered, the repository is available under CHAT_MEMORY_TOKEN and can be injected with @InjectChatMemory().
If you’d rather create the RedisChatMemoryRepository manually, you can do so by providing a Redis client through the builder:
import { createClient } from 'redis';
import { RedisChatMemoryRepository } from '@nestjs-ai/model-chat-memory-repository-redis';
const redisClient = createClient({ url: 'redis://localhost:6379' });
await redisClient.connect();
const chatMemoryRepository = await RedisChatMemoryRepository.builder()
.client(redisClient)
.indexName('my-chat-index')
.keyPrefix('my-chat:')
.ttlSeconds(24 * 60 * 60)
.build();
const chatMemory = new MessageWindowChatMemory({
chatMemoryRepository,
maxMessages: 10,
});
Module Options
| Property | Type | Default | Description |
|---|---|---|---|
|
|
— |
Pre-configured |
|
|
— |
Options forwarded to |
|
|
|
Name of the RediSearch index to create or reuse. |
|
|
|
Prefix used for all Redis keys belonging to chat memory entries. |
|
|
no expiration |
TTL applied to every message key, in seconds. Set to a positive number to expire messages. |
|
|
|
Whether to create the RediSearch index on startup. |
|
|
|
Maximum number of conversation IDs returned by |
|
|
|
Maximum number of messages returned per conversation. |
|
|
|
Custom metadata fields to index. Each entry has a |
Advanced Querying
The RedisChatMemoryRepository also implements AdvancedRedisChatMemoryRepository, which provides extended query capabilities:
import {
type AdvancedRedisChatMemoryRepository,
type MessageWithConversation,
} from '@nestjs-ai/model-chat-memory-repository-redis';
import { MessageType } from '@nestjs-ai/model';
// Cast to access advanced features
const advancedRepo = chatMemoryRepository as AdvancedRedisChatMemoryRepository;
// Find messages by type across all conversations
const userMessages: MessageWithConversation[] = await advancedRepo.findByType(
MessageType.USER,
100,
);
// Find messages containing specific content
const results = await advancedRepo.findByContent('NestJS AI', 50);
// Find messages within a time range
const recentMessages = await advancedRepo.findByTimeRange(
conversationId,
new Date(Date.now() - 60 * 60 * 1000),
new Date(),
100,
);
// Find messages by metadata
const priorityMessages = await advancedRepo.findByMetadata(
'priority',
'high',
50,
);
// Execute custom Redis queries
const customResults = await advancedRepo.executeQuery(
'@type:USER @content:Redis',
100,
);
Metadata Field Indexing
To enable efficient querying on custom metadata fields, configure metadata field definitions when registering the module:
RedisChatMemoryModule.forFeature({
clientOptions: { url: 'redis://localhost:6379' },
metadataFields: [
{ name: 'priority', type: 'tag' },
{ name: 'score', type: 'numeric' },
{ name: 'category', type: 'tag' },
],
});
Supported field types are: tag (for exact match filtering), text (for full-text search), and numeric (for range queries).
Memory in Chat Client
When using the ChatClient API, you can provide a ChatMemory implementation to maintain conversation context across multiple interactions.
NestJS AI provides a few built-in Advisors that you can use to configure the memory behavior of the ChatClient, based on your needs.
| Currently, the intermediate messages exchanged with a large-language model when performing tool calls are not stored in the memory. This is a limitation of the current implementation and will be addressed in future releases. |
-
MessageChatMemoryAdvisor. This advisor manages the conversation memory using the providedChatMemoryimplementation. On each interaction, it retrieves the conversation history from the memory and includes it in the prompt as a collection of messages. -
PromptChatMemoryAdvisor. This advisor manages the conversation memory using the providedChatMemoryimplementation. On each interaction, it retrieves the conversation history from the memory and appends it to the system prompt as plain text. -
VectorStoreChatMemoryAdvisor. This advisor manages the conversation memory using the providedVectorStoreimplementation. On each interaction, it retrieves the conversation history from the vector store and appends it to the system message as plain text.
For example, if you want to use MessageWindowChatMemory with the MessageChatMemoryAdvisor, you can configure it as follows:
import { ChatClient, MessageChatMemoryAdvisor } from '@nestjs-ai/client-chat';
import {
InMemoryChatMemoryRepository,
MessageWindowChatMemory,
} from '@nestjs-ai/model';
const chatMemory = new MessageWindowChatMemory({
chatMemoryRepository: new InMemoryChatMemoryRepository(),
});
const chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(new MessageChatMemoryAdvisor({ chatMemory }))
.build();
When performing a call to the ChatClient, the memory will be automatically managed by the MessageChatMemoryAdvisor. The conversation history will be retrieved from the memory based on the specified conversation ID:
import { ChatMemory } from '@nestjs-ai/model';
const conversationId = '007';
const content = await chatClient
.prompt()
.user('Do I have license to code?')
.advisors((a) => a.param(ChatMemory.CONVERSATION_ID, conversationId))
.call()
.content();
PromptChatMemoryAdvisor
Custom Template
The PromptChatMemoryAdvisor uses a default template to augment the system message with the retrieved conversation memory. You can customize this behavior by passing your own PromptTemplate instance through the systemPromptTemplate constructor option.
The PromptTemplate provided here customizes how the advisor merges retrieved memory with the system message. This is distinct from configuring a TemplateRenderer on the ChatClient itself, which affects the rendering of the initial user/system prompt content before the advisor runs. See ChatClient for more details on client-level template rendering.
|
The custom PromptTemplate uses the StringTemplate-based StPromptTemplate renderer by default. The important requirement is that the template must contain the following two placeholders:
-
an
instructionsplaceholder to receive the original system message. -
a
memoryplaceholder to receive the retrieved conversation memory.
import { PromptChatMemoryAdvisor } from '@nestjs-ai/client-chat';
import { PromptTemplate } from '@nestjs-ai/model';
const advisor = new PromptChatMemoryAdvisor({
chatMemory,
systemPromptTemplate: new PromptTemplate(`
{instructions}
Conversation so far:
{memory}
`),
});
VectorStoreChatMemoryAdvisor
Custom Template
The VectorStoreChatMemoryAdvisor uses a default template to augment the system message with the retrieved conversation memory. You can customize this behavior by providing your own PromptTemplate instance through the .systemPromptTemplate() builder method.
The PromptTemplate provided here customizes how the advisor merges retrieved memory with the system message. This is distinct from configuring a TemplateRenderer on the ChatClient itself, which affects the rendering of the initial user/system prompt content before the advisor runs. See ChatClient for more details on client-level template rendering.
|
The custom PromptTemplate uses the StringTemplate-based StPromptTemplate renderer by default. The important requirement is that the template must contain the following two placeholders:
-
an
instructionsplaceholder to receive the original system message. -
a
long_term_memoryplaceholder to receive the retrieved conversation memory.
import { VectorStoreChatMemoryAdvisor } from '@nestjs-ai/advisors-vector-store';
import { PromptTemplate } from '@nestjs-ai/model';
const advisor = VectorStoreChatMemoryAdvisor.builder(vectorStore)
.systemPromptTemplate(
new PromptTemplate(`
{instructions}
Relevant past conversations:
{long_term_memory}
`),
)
.build();
Memory in Chat Model
If you’re working directly with a ChatModel instead of a ChatClient, you can manage the memory explicitly:
import {
AssistantMessage,
InMemoryChatMemoryRepository,
MessageWindowChatMemory,
Prompt,
UserMessage,
} from '@nestjs-ai/model';
// Create a memory instance
const chatMemory = new MessageWindowChatMemory({
chatMemoryRepository: new InMemoryChatMemoryRepository(),
});
const conversationId = '007';
// First interaction
const userMessage1 = new UserMessage({ content: 'My name is James Bond' });
await chatMemory.add(conversationId, userMessage1);
const response1 = await chatModel.call(
new Prompt(await chatMemory.get(conversationId)),
);
await chatMemory.add(conversationId, response1.results[0].output);
// Second interaction
const userMessage2 = new UserMessage({ content: 'What is my name?' });
await chatMemory.add(conversationId, userMessage2);
const response2 = await chatModel.call(
new Prompt(await chatMemory.get(conversationId)),
);
await chatMemory.add(conversationId, response2.results[0].output);
// The response will contain "James Bond"