add conversational retrieval agent

This commit is contained in:
Henry
2023-08-06 19:45:21 +01:00
parent d208eae868
commit 0ae6f53295
18 changed files with 1018 additions and 103 deletions
@@ -1,6 +1,6 @@
import { ICommonObject, INode, INodeData, INodeParams, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src'
import { DynamoDBChatMessageHistory } from 'langchain/stores/message/dynamodb'
import { BufferMemory } from 'langchain/memory'
import { BufferMemory, BufferMemoryInput } from 'langchain/memory'
class DynamoDb_Memory implements INode {
label: string
@@ -51,7 +51,7 @@ class DynamoDb_Memory implements INode {
label: 'Session ID',
name: 'sessionId',
type: 'string',
description: 'if empty, chatId will be used automatically',
description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId',
default: '',
additionalParams: true,
optional: true
@@ -86,9 +86,11 @@ const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): P
const sessionId = nodeData.inputs?.sessionId as string
const region = nodeData.inputs?.region as string
const memoryKey = nodeData.inputs?.memoryKey as string
const chatId = options.chatId
let isSessionIdUsingChatMessageId = false
if (!sessionId && chatId) isSessionIdUsingChatMessageId = true
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const accessKeyId = getCredentialParam('accessKey', credentialData, nodeData)
const secretAccessKey = getCredentialParam('secretAccessKey', credentialData, nodeData)
@@ -106,12 +108,26 @@ const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): P
}
})
const memory = new BufferMemory({
const memory = new BufferMemoryExtended({
memoryKey,
chatHistory: dynamoDb,
returnMessages: true
returnMessages: true,
isSessionIdUsingChatMessageId
})
return memory
}
interface BufferMemoryExtendedInput {
isSessionIdUsingChatMessageId: boolean
}
class BufferMemoryExtended extends BufferMemory {
isSessionIdUsingChatMessageId? = false
constructor(fields: BufferMemoryInput & Partial<BufferMemoryExtendedInput>) {
super(fields)
this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId
}
}
module.exports = { nodeClass: DynamoDb_Memory }
@@ -2,6 +2,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface'
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
import { ICommonObject } from '../../../src'
import { MotorheadMemory, MotorheadMemoryInput } from 'langchain/memory'
import fetch from 'node-fetch'
class MotorMemory_Memory implements INode {
label: string
@@ -44,7 +45,7 @@ class MotorMemory_Memory implements INode {
label: 'Session Id',
name: 'sessionId',
type: 'string',
description: 'if empty, chatId will be used automatically',
description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId',
default: '',
additionalParams: true,
optional: true
@@ -77,14 +78,16 @@ const initalizeMotorhead = async (nodeData: INodeData, options: ICommonObject):
const memoryKey = nodeData.inputs?.memoryKey as string
const baseURL = nodeData.inputs?.baseURL as string
const sessionId = nodeData.inputs?.sessionId as string
const chatId = options?.chatId as string
let isSessionIdUsingChatMessageId = false
if (!sessionId && chatId) isSessionIdUsingChatMessageId = true
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const apiKey = getCredentialParam('apiKey', credentialData, nodeData)
const clientId = getCredentialParam('clientId', credentialData, nodeData)
let obj: MotorheadMemoryInput = {
let obj: MotorheadMemoryInput & Partial<MotorheadMemoryExtendedInput> = {
returnMessages: true,
sessionId: sessionId ? sessionId : chatId,
memoryKey
@@ -103,7 +106,44 @@ const initalizeMotorhead = async (nodeData: INodeData, options: ICommonObject):
}
}
return new MotorheadMemory(obj)
if (isSessionIdUsingChatMessageId) obj.isSessionIdUsingChatMessageId = true
const motorheadMemory = new MotorheadMemoryExtended(obj)
// Get messages from sessionId
await motorheadMemory.init()
return motorheadMemory
}
interface MotorheadMemoryExtendedInput {
isSessionIdUsingChatMessageId: boolean
}
class MotorheadMemoryExtended extends MotorheadMemory {
isSessionIdUsingChatMessageId? = false
constructor(fields: MotorheadMemoryInput & Partial<MotorheadMemoryExtendedInput>) {
super(fields)
this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId
}
async clear(): Promise<void> {
try {
await this.caller.call(fetch, `${this.url}/sessions/${this.sessionId}/memory`, {
//@ts-ignore
signal: this.timeout ? AbortSignal.timeout(this.timeout) : undefined,
headers: this._getHeaders() as ICommonObject,
method: 'DELETE'
})
} catch (error) {
console.error('Error deleting session: ', error)
}
// Clear the superclass's chat history
await this.chatHistory.clear()
await super.clear()
}
}
module.exports = { nodeClass: MotorMemory_Memory }
@@ -1,7 +1,7 @@
import { INode, INodeData, INodeParams } from '../../../src/Interface'
import { getBaseClasses } from '../../../src/utils'
import { ICommonObject } from '../../../src'
import { BufferMemory } from 'langchain/memory'
import { BufferMemory, BufferMemoryInput } from 'langchain/memory'
import { RedisChatMessageHistory, RedisChatMessageHistoryInput } from 'langchain/stores/message/redis'
import { createClient } from 'redis'
@@ -36,7 +36,7 @@ class RedisBackedChatMemory_Memory implements INode {
label: 'Session Id',
name: 'sessionId',
type: 'string',
description: 'if empty, chatId will be used automatically',
description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId',
default: '',
additionalParams: true,
optional: true
@@ -78,9 +78,11 @@ const initalizeRedis = (nodeData: INodeData, options: ICommonObject): BufferMemo
const sessionId = nodeData.inputs?.sessionId as string
const sessionTTL = nodeData.inputs?.sessionTTL as number
const memoryKey = nodeData.inputs?.memoryKey as string
const chatId = options?.chatId as string
let isSessionIdUsingChatMessageId = false
if (!sessionId && chatId) isSessionIdUsingChatMessageId = true
const redisClient = createClient({ url: baseURL })
let obj: RedisChatMessageHistoryInput = {
sessionId: sessionId ? sessionId : chatId,
@@ -94,10 +96,28 @@ const initalizeRedis = (nodeData: INodeData, options: ICommonObject): BufferMemo
}
}
let redisChatMessageHistory = new RedisChatMessageHistory(obj)
let redis = new BufferMemory({ memoryKey, chatHistory: redisChatMessageHistory, returnMessages: true })
const redisChatMessageHistory = new RedisChatMessageHistory(obj)
return redis
const memory = new BufferMemoryExtended({
memoryKey,
chatHistory: redisChatMessageHistory,
returnMessages: true,
isSessionIdUsingChatMessageId
})
return memory
}
interface BufferMemoryExtendedInput {
isSessionIdUsingChatMessageId: boolean
}
class BufferMemoryExtended extends BufferMemory {
isSessionIdUsingChatMessageId? = false
constructor(fields: BufferMemoryInput & Partial<BufferMemoryExtendedInput>) {
super(fields)
this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId
}
}
module.exports = { nodeClass: RedisBackedChatMemory_Memory }
@@ -50,7 +50,7 @@ class ZepMemory_Memory implements INode {
label: 'Session Id',
name: 'sessionId',
type: 'string',
description: 'if empty, chatId will be used automatically',
description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId',
default: '',
additionalParams: true,
optional: true
@@ -156,13 +156,15 @@ const initalizeZep = async (nodeData: INodeData, options: ICommonObject): Promis
const memoryKey = nodeData.inputs?.memoryKey as string
const inputKey = nodeData.inputs?.inputKey as string
const sessionId = nodeData.inputs?.sessionId as string
const chatId = options?.chatId as string
let isSessionIdUsingChatMessageId = false
if (!sessionId && chatId) isSessionIdUsingChatMessageId = true
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const apiKey = getCredentialParam('apiKey', credentialData, nodeData)
const obj: ZepMemoryInput = {
const obj: ZepMemoryInput & Partial<ZepMemoryExtendedInput> = {
baseURL,
sessionId: sessionId ? sessionId : chatId,
aiPrefix,
@@ -172,8 +174,39 @@ const initalizeZep = async (nodeData: INodeData, options: ICommonObject): Promis
inputKey
}
if (apiKey) obj.apiKey = apiKey
if (isSessionIdUsingChatMessageId) obj.isSessionIdUsingChatMessageId = true
return new ZepMemory(obj)
return new ZepMemoryExtended(obj)
}
interface ZepMemoryExtendedInput {
isSessionIdUsingChatMessageId: boolean
}
class ZepMemoryExtended extends ZepMemory {
isSessionIdUsingChatMessageId? = false
constructor(fields: ZepMemoryInput & Partial<ZepMemoryExtendedInput>) {
super(fields)
this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId
}
async clear(): Promise<void> {
// Only clear when sessionId is using chatId
// If sessionId is specified, clearing and inserting again will error because the sessionId has been soft deleted
// If using chatId, it will not be a problem because the sessionId will always be the new chatId
if (this.isSessionIdUsingChatMessageId) {
try {
await this.zepClient.deleteMemory(this.sessionId)
} catch (error) {
console.error('Error deleting session: ', error)
}
// Clear the superclass's chat history
await super.clear()
}
await this.chatHistory.clear()
}
}
module.exports = { nodeClass: ZepMemory_Memory }