forked from langchain-ai/langchainjs
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding Redis Memory Support (langchain-ai#951)
* Merge post upstream sync (#1) * Added RedisMemory with tests and docs * fixed Redis test file name * TODOs for quick refactor * updated memory to accept a client * Added TODOS * ongoing testing * Updated tests * Updated tests for memory return option * finalized tests + updated docs * Adding docs * Readded init and cleaned up func * fixed test typing * redo yarn lock * remove yarn.lock * updated yarn lock and namespaced redis * fix merge conflict * updated BaseChatMemoryInput * Updated with lint fixes * Fixed docs to match memory instantiation * yarn format docs * Merging fixes to address ForkPR comments (langchain-ai#2) * updating for pr * Removed redis_memory in favor of chat memory * Fixed tests and updated docs * Bump docs * lint results * fixes from lint and format --------- Co-authored-by: Chris Toomey <[email protected]> * Update RedisChatMessageHistory class to initialize a client internally * Update Redis chat message history docs to reflect the fact that Redis is a peer dependency, allow direct passing of Redis client * Patch typing issues with passing node-redis client directly into a RedisChatMessageHistory instance --------- Co-authored-by: Chris Toomey <[email protected]> Co-authored-by: Jacob Lee <[email protected]>
- Loading branch information
1 parent
dad9643
commit ace3ee7
Showing
13 changed files
with
386 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
--- | ||
hide_table_of_contents: true | ||
--- | ||
|
||
import CodeBlock from "@theme/CodeBlock"; | ||
|
||
# Redis-Backed 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 [Redis](https://redis.io/) instance. | ||
|
||
## Setup | ||
|
||
You will need to install [node-redis](https://github.com/redis/node-redis) in your project: | ||
|
||
```bash npm2yarn | ||
npm install redis | ||
``` | ||
|
||
You will also need a Redis instance to connect to. See instructions on [the official Redis website](https://redis.io/docs/getting-started/) for running the server locally. | ||
|
||
## Usage | ||
|
||
Each chat history session stored in Redis must have a unique id. You can provide an optional `sessionTTL` to make sessions expire after a give number of seconds. | ||
The `config` parameter is passed directly into the `createClient` method of [node-redis](https://github.com/redis/node-redis), and takes all the same arguments. | ||
|
||
import Example from "@examples/memory/redis.ts"; | ||
|
||
<CodeBlock language="typescript">{Example}</CodeBlock> | ||
|
||
## Advanced Usage | ||
|
||
You can also directly pass in a previously created [node-redis](https://github.com/redis/node-redis) client instance: | ||
|
||
import AdvancedExample from "@examples/memory/redis-advanced.ts"; | ||
|
||
<CodeBlock language="typescript">{AdvancedExample}</CodeBlock> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { createClient } from "redis"; | ||
import { BufferMemory } from "langchain/memory"; | ||
import { RedisChatMessageHistory } from "langchain/stores/message/redis"; | ||
import { ChatOpenAI } from "langchain/chat_models/openai"; | ||
import { ConversationChain } from "langchain/chains"; | ||
|
||
const client = createClient({ | ||
url: "redis://localhost:6379", | ||
}); | ||
|
||
const memory = new BufferMemory({ | ||
chatHistory: new RedisChatMessageHistory({ | ||
sessionId: new Date().toISOString(), | ||
sessionTTL: 300, | ||
client, | ||
}), | ||
}); | ||
|
||
const model = new ChatOpenAI({ | ||
modelName: "gpt-3.5-turbo", | ||
temperature: 0, | ||
}); | ||
|
||
const chain = new ConversationChain({ llm: model, memory }); | ||
|
||
const res1 = await chain.call({ 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.call({ input: "What did I just say my name was?" }); | ||
console.log({ res2 }); | ||
|
||
/* | ||
{ | ||
res1: { | ||
text: "You said your name was Jim." | ||
} | ||
} | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { BufferMemory } from "langchain/memory"; | ||
import { RedisChatMessageHistory } from "langchain/stores/message/redis"; | ||
import { ChatOpenAI } from "langchain/chat_models/openai"; | ||
import { ConversationChain } from "langchain/chains"; | ||
|
||
const memory = new BufferMemory({ | ||
chatHistory: new RedisChatMessageHistory({ | ||
sessionId: new Date().toISOString(), // Or some other unique identifier for the conversation | ||
sessionTTL: 300, // 5 minutes, omit this parameter to make sessions never expire | ||
config: { | ||
url: "redis://localhost:6379", // Default value, override with your own instance's URL | ||
}, | ||
}), | ||
}); | ||
|
||
const model = new ChatOpenAI({ | ||
modelName: "gpt-3.5-turbo", | ||
temperature: 0, | ||
}); | ||
|
||
const chain = new ConversationChain({ llm: model, memory }); | ||
|
||
const res1 = await chain.call({ 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.call({ input: "What did I just say my name was?" }); | ||
console.log({ res2 }); | ||
|
||
/* | ||
{ | ||
res1: { | ||
text: "You said your name was Jim." | ||
} | ||
} | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { | ||
createClient, | ||
RedisClientOptions, | ||
RedisClientType, | ||
RedisModules, | ||
RedisFunctions, | ||
RedisScripts, | ||
} from "redis"; | ||
import { | ||
BaseChatMessage, | ||
BaseListChatMessageHistory, | ||
} from "../../schema/index.js"; | ||
import { | ||
StoredMessage, | ||
mapChatMessagesToStoredMessages, | ||
mapStoredMessagesToChatMessages, | ||
} from "./utils.js"; | ||
|
||
export type RedisChatMessageHistoryInput = { | ||
sessionId: string; | ||
sessionTTL?: number; | ||
config?: RedisClientOptions; | ||
// Typing issues with createClient output: https://github.com/redis/node-redis/issues/1865 | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
client?: any; | ||
}; | ||
|
||
export class RedisChatMessageHistory extends BaseListChatMessageHistory { | ||
public client: RedisClientType<RedisModules, RedisFunctions, RedisScripts>; | ||
|
||
private sessionId: string; | ||
|
||
private sessionTTL?: number; | ||
|
||
constructor(fields: RedisChatMessageHistoryInput) { | ||
const { sessionId, sessionTTL, config, client } = fields; | ||
super(); | ||
this.client = (client ?? createClient(config ?? {})) as RedisClientType< | ||
RedisModules, | ||
RedisFunctions, | ||
RedisScripts | ||
>; | ||
this.sessionId = sessionId; | ||
this.sessionTTL = sessionTTL; | ||
} | ||
|
||
async ensureReadiness() { | ||
if (!this.client.isReady) { | ||
await this.client.connect(); | ||
} | ||
return true; | ||
} | ||
|
||
async getMessages(): Promise<BaseChatMessage[]> { | ||
await this.ensureReadiness(); | ||
const rawStoredMessages = await this.client.lRange(this.sessionId, 0, -1); | ||
const orderedMessages = rawStoredMessages | ||
.reverse() | ||
.map((message) => JSON.parse(message)); | ||
const previousMessages = orderedMessages | ||
.map((item) => ({ | ||
type: item.type, | ||
role: item.role, | ||
text: item.text, | ||
})) | ||
.filter( | ||
(x): x is StoredMessage => x.type !== undefined && x.text !== undefined | ||
); | ||
return mapStoredMessagesToChatMessages(previousMessages); | ||
} | ||
|
||
async addMessage(message: BaseChatMessage): Promise<void> { | ||
await this.ensureReadiness(); | ||
const messageToAdd = mapChatMessagesToStoredMessages([message]); | ||
await this.client.lPush(this.sessionId, JSON.stringify(messageToAdd[0])); | ||
if (this.sessionTTL) { | ||
await this.client.expire(this.sessionId, this.sessionTTL); | ||
} | ||
} | ||
|
||
async clear(): Promise<void> { | ||
await this.ensureReadiness(); | ||
await this.client.del(this.sessionId); | ||
} | ||
} |
Oops, something went wrong.