Skip to content

Commit 89f10da

Browse files
committed
refactor: implement chat history with redis | partial
1 parent 16e4f5d commit 89f10da

File tree

9 files changed

+93
-201
lines changed

9 files changed

+93
-201
lines changed

docker-compose.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ services:
77
dockerfile: Dockerfile
88
ports:
99
- '3000:3000'
10-
environment:
11-
- NODE_ENV=development
10+
env_file:
11+
- .env
1212
volumes:
1313
- ./src:/usr/workspace/app/src
1414
- pnpm_store:/pnpm/.pnpm-store

docs/prompts/ai-prompts.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
1+
What is this document about?
2+
13
This document is about AI and chatbot, can you summarize it for me?
4+
25
Can you list it in a bullet list form?
6+
7+
What was my first question to you?

docs/prompts/file-upload.md

Lines changed: 0 additions & 171 deletions
This file was deleted.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
"@langchain/community": "^0.3.1",
2020
"@langchain/core": "^0.3.3",
2121
"@langchain/google-genai": "^0.1.0",
22+
"@langchain/redis": "^0.1.0",
23+
"@types/redis": "^4.0.11",
2224
"dotenv": "^16.4.5",
2325
"express": "^4.21.0",
2426
"langchain": "^0.3.2",

pnpm-lock.yaml

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/modules/genai/adapters/dataproviders/redis/redis-client.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,27 @@ export class RedisClient {
88
username: process.env['REDIS_USER'],
99
password: process.env['REDIS_PASSWORD'],
1010
})
11+
12+
async initClient(): Promise<void> {
13+
this._redisClient
14+
.on('connect', () => console.log('Redis client connected'))
15+
.on('error', (err) => console.log('Redis Client Error', err))
16+
17+
await this._redisClient.connect()
18+
}
19+
20+
async closeClient(): Promise<void> {
21+
await this._redisClient.quit()
22+
}
23+
24+
async storeValue(key: string, value: object): Promise<void> {
25+
await this._redisClient.set(key, JSON.stringify(value), {
26+
EX: 300,
27+
NX: true,
28+
})
29+
}
30+
31+
getValueByKey(key: string): Promise<string | null> {
32+
return this._redisClient.get(key)
33+
}
1134
}

src/modules/genai/core/usecases/search-in-document/search-in-document.usecase.ts

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,30 @@ import {
2020
} from '@langchain/google-genai'
2121

2222
import { UseCase } from '@/common/types'
23+
import { RedisChatMessageHistory } from '@langchain/redis'
24+
import { BufferMemory } from 'langchain/memory'
25+
2326
import {
2427
CONTEXTUALIZED_SYSTEM_PROMPT,
2528
SEARCH_DOC_SYSTEM_PROMPT,
2629
} from '../../utils'
30+
2731
import { Params, Result } from './types'
2832

33+
/// TODO: delegate to RedisClient class
34+
const chatMemory = new BufferMemory({
35+
chatHistory: new RedisChatMessageHistory({
36+
sessionId: 'a168c61a-c431-4ef8-bc1c-fedd808d45ea',
37+
sessionTTL: 3600, // 300 = 5m, 3600 = 1h, null = never
38+
config: {
39+
url: process.env['REDIS_URL'],
40+
password: process.env['REDIS_PASSWORD'],
41+
},
42+
}),
43+
returnMessages: true,
44+
memoryKey: 'chat_history',
45+
})
46+
2947
export class SearchInDocumentUseCase implements UseCase<Result, Params> {
3048
async invoke({ filePath, query }: Params): Promise<Result> {
3149
const docs = await this._loadDocument(filePath)
@@ -36,7 +54,7 @@ export class SearchInDocumentUseCase implements UseCase<Result, Params> {
3654
apiKey: process.env['GOOGLE_GENAI_API_KEY'],
3755
})
3856

39-
/// context chain init
57+
/// Standalone question referencing past context
4058

4159
const contextualizedPrompt = ChatPromptTemplate.fromMessages([
4260
['system', CONTEXTUALIZED_SYSTEM_PROMPT],
@@ -50,45 +68,41 @@ export class SearchInDocumentUseCase implements UseCase<Result, Params> {
5068
new StringOutputParser(),
5169
])
5270

53-
const contextualizedQuestion = (input: Record<string, any>) => {
54-
if ('chat_history' in input) {
55-
return contextualizedQuestionChain
56-
}
57-
58-
return input['question']
59-
}
60-
61-
/// context end
71+
/// Standalone question end
6272

63-
const qaPrompt = ChatPromptTemplate.fromMessages([
73+
const questionAnsweringPrompt = ChatPromptTemplate.fromMessages([
6474
['system', SEARCH_DOC_SYSTEM_PROMPT],
6575
new MessagesPlaceholder('chat_history'),
6676
['human', '{question}'],
6777
])
6878

69-
// if exists a history, so we need to append the history prompt
70-
// to the chain
7179
const retrievalChain = RunnableSequence.from([
7280
RunnablePassthrough.assign({
73-
context: (input) => {
81+
context: (input: Record<string, any>) => {
7482
if ('chat_history' in input) {
75-
const chain = contextualizedQuestion(input)
76-
return chain.pipe(retriever).pipe(this._convertDocsToString)
83+
return RunnableSequence.from([
84+
contextualizedQuestionChain,
85+
retriever,
86+
this._convertDocsToString,
87+
])
7788
}
7889

7990
return ''
8091
},
8192
}),
82-
qaPrompt,
93+
questionAnsweringPrompt,
8394
llmModel,
8495
new StringOutputParser(),
8596
])
8697

98+
const memoryResults = await chatMemory.loadMemoryVariables({})
99+
const history = memoryResults['chat_history']
87100
const result = await retrievalChain.invoke({
88101
question: query,
89-
chat_history: [], // We need to store AI messages to the history every time = result
102+
chat_history: history,
90103
})
91104

105+
await chatMemory.saveContext({ input: query }, { output: result })
92106
return { result }
93107
}
94108

0 commit comments

Comments
 (0)