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

initializeSchema

boolean

false

When true, runs the vendor-specific SPRING_AI_CHAT_MEMORY schema script on startup against the JSDBC DataSource.

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();

CassandraChatMemoryRepository

Cassandra-backed chat memory is not yet available in NestJS AI.

Neo4j ChatMemoryRepository

Neo4j-backed chat memory is not yet available in NestJS AI.

CosmosDBChatMemoryRepository

Azure Cosmos DB-backed chat memory is not yet available in NestJS AI.

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

collection

Collection

null

Pre-configured MongoDB collection to use directly.

db

Db

null

MongoDB database used to resolve the collection by name.

mongoClient

MongoClient

null

MongoDB client used to resolve the collection by name.

collectionName

string

ai_chat_memory

Name of the collection used when resolving from db or mongoClient.

createIndices

boolean

false

When true, creates the chat memory indexes on startup.

ttl

number

null

When set, creates or updates the TTL index on the timestamp field.

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

client

RedisClientType

Pre-configured redis client. If supplied and not already connected, the module calls connect() for you.

clientOptions

RedisClientOptions

Options forwarded to createClient when client is not provided.

indexName

string

chat-memory-idx

Name of the RediSearch index to create or reuse.

keyPrefix

string

chat-memory:

Prefix used for all Redis keys belonging to chat memory entries.

timeToLive

number | null

no expiration

TTL applied to every message key, in seconds. Set to a positive number to expire messages.

initializeSchema

boolean

true

Whether to create the RediSearch index on startup.

maxConversationIds

number

1000

Maximum number of conversation IDs returned by findConversationIds.

maxMessagesPerConversation

number

1000

Maximum number of messages returned per conversation.

metadataFields

RedisChatMemoryMetadataField[]

[]

Custom metadata fields to index. Each entry has a name and an optional type (text, numeric, or tag).

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).

Schema Initialization

When initializeSchema is true (the default), the repository creates the RediSearch index on startup if it does not already exist. Set it to false if you manage the index yourself.

Requirements

  • Redis Stack 7.0 or higher (includes Redis Query Engine and RedisJSON modules)

  • The redis Node.js client library, version 5 or higher (declared as a peer dependency)

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 provided ChatMemory implementation. 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 provided ChatMemory implementation. 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 provided VectorStore implementation. 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 instructions placeholder to receive the original system message.

  • a memory placeholder 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 instructions placeholder to receive the original system message.

  • a long_term_memory placeholder 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"