Skip to content

Commit 16e4f5d

Browse files
committed
refactor: add message history context | partial
1 parent 1f8e0b4 commit 16e4f5d

File tree

9 files changed

+168
-22
lines changed

9 files changed

+168
-22
lines changed

.env-example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
LANGCHAIN_API_KEY=
22
LANGCHAIN_TRACING_V2=true
33
LANGCHAIN_CALLBACKS_BACKGROUND=true
4+
45
GOOGLE_GENAI_API_KEY=
6+
7+
REDIS_ENDPOINT_URI=localhost:6379
8+
REDIS_PASSWORD=redis@dev

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"cSpell.words": [
3+
"dataproviders",
34
"gemini",
45
"nodenext",
56
"usecases"

docs/prompts/ai-prompts.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
This document is about AI and chatbot, can you summarize it for me?
2+
Can you list it in a bullet list form?

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"express": "^4.21.0",
2424
"langchain": "^0.3.2",
2525
"multer": "1.4.5-lts.1",
26-
"pdf-parse": "^1.1.1"
26+
"pdf-parse": "^1.1.1",
27+
"redis": "^4.7.0"
2728
},
2829
"devDependencies": {
2930
"@types/cors": "^2.8.17",

pnpm-lock.yaml

Lines changed: 90 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { createClient } from 'redis'
2+
3+
export class RedisClient {
4+
constructor() {}
5+
6+
_redisClient = createClient({
7+
url: process.env['REDIS_ENDPOINT_URI'],
8+
username: process.env['REDIS_USER'],
9+
password: process.env['REDIS_PASSWORD'],
10+
})
11+
}

src/modules/genai/adapters/inde.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './dataproviders/redis/redis-client'

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

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import {
77
MessagesPlaceholder,
88
} from '@langchain/core/prompts'
99

10-
import { RunnableSequence } from '@langchain/core/runnables'
10+
import {
11+
RunnablePassthrough,
12+
RunnableSequence,
13+
} from '@langchain/core/runnables'
1114
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter'
1215
import { MemoryVectorStore } from 'langchain/vectorstores/memory'
1316

@@ -17,7 +20,10 @@ import {
1720
} from '@langchain/google-genai'
1821

1922
import { UseCase } from '@/common/types'
20-
import { SEARCH_DOC_SYSTEM_PROMPT } from '../../utils'
23+
import {
24+
CONTEXTUALIZED_SYSTEM_PROMPT,
25+
SEARCH_DOC_SYSTEM_PROMPT,
26+
} from '../../utils'
2127
import { Params, Result } from './types'
2228

2329
export class SearchInDocumentUseCase implements UseCase<Result, Params> {
@@ -30,30 +36,57 @@ export class SearchInDocumentUseCase implements UseCase<Result, Params> {
3036
apiKey: process.env['GOOGLE_GENAI_API_KEY'],
3137
})
3238

33-
const documentRetrievalChain = RunnableSequence.from([
34-
(input) => input.question,
35-
retriever,
36-
this._convertDocsToString,
39+
/// context chain init
40+
41+
const contextualizedPrompt = ChatPromptTemplate.fromMessages([
42+
['system', CONTEXTUALIZED_SYSTEM_PROMPT],
43+
new MessagesPlaceholder('chat_history'),
44+
['human', '{question}'],
3745
])
3846

39-
const answerGenerationPrompt = ChatPromptTemplate.fromMessages([
47+
const contextualizedQuestionChain = RunnableSequence.from([
48+
contextualizedPrompt,
49+
llmModel,
50+
new StringOutputParser(),
51+
])
52+
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
62+
63+
const qaPrompt = ChatPromptTemplate.fromMessages([
4064
['system', SEARCH_DOC_SYSTEM_PROMPT],
4165
new MessagesPlaceholder('chat_history'),
4266
['human', '{question}'],
4367
])
4468

69+
// if exists a history, so we need to append the history prompt
70+
// to the chain
4571
const retrievalChain = RunnableSequence.from([
46-
{
47-
context: documentRetrievalChain,
48-
question: (input) => input.question,
49-
},
50-
answerGenerationPrompt,
72+
RunnablePassthrough.assign({
73+
context: (input) => {
74+
if ('chat_history' in input) {
75+
const chain = contextualizedQuestion(input)
76+
return chain.pipe(retriever).pipe(this._convertDocsToString)
77+
}
78+
79+
return ''
80+
},
81+
}),
82+
qaPrompt,
5183
llmModel,
5284
new StringOutputParser(),
5385
])
5486

5587
const result = await retrievalChain.invoke({
5688
question: query,
89+
chat_history: [], // We need to store AI messages to the history every time = result
5790
})
5891

5992
return { result }
@@ -77,14 +110,14 @@ export class SearchInDocumentUseCase implements UseCase<Result, Params> {
77110
chunkOverlap: 128,
78111
})
79112

80-
const embeddings = new GoogleGenerativeAIEmbeddings({
81-
apiKey: process.env['GOOGLE_GENAI_API_KEY'],
82-
model: 'text-embedding-004',
83-
})
84-
85113
const splitDocs = await splitter.splitDocuments(docs)
86-
const vectorstore = new MemoryVectorStore(embeddings)
87-
await vectorstore.addDocuments(splitDocs)
114+
const vectorstore = await MemoryVectorStore.fromDocuments(
115+
splitDocs,
116+
new GoogleGenerativeAIEmbeddings({
117+
apiKey: process.env['GOOGLE_GENAI_API_KEY'],
118+
model: 'text-embedding-004',
119+
}),
120+
)
88121

89122
return vectorstore
90123
}

src/modules/genai/core/utils/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,8 @@ If you don't know the answer, just say that you don't know. Be verbose!
88

99
export const REPHRASE_QUESTION_SYSTEM_TEMPLATE = `
1010
Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.`
11+
12+
export const CONTEXTUALIZED_SYSTEM_PROMPT = `Given a chat history and the latest user question
13+
which might reference context in the chat history, formulate a standalone question
14+
which can be understood without the chat history. Do NOT answer the question,
15+
just reformulate it if needed and otherwise return it as is.`

0 commit comments

Comments
 (0)