Merge pull request #284 from wangerzi/feature/SupportZepMemoryAndAutoSummary

feature/SupportZepMemoryAndAutoSummary
This commit is contained in:
Henry Heng
2023-06-12 22:33:46 +01:00
committed by GitHub
8 changed files with 181 additions and 6 deletions
@@ -0,0 +1,140 @@
import { SystemChatMessage } from 'langchain/schema'
import { INode, INodeData, INodeParams } from '../../../src/Interface'
import { getBaseClasses } from '../../../src/utils'
import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep'
import { ICommonObject } from '../../../src'
class ZepMemory_Memory implements INode {
label: string
name: string
description: string
type: string
icon: string
category: string
baseClasses: string[]
inputs: INodeParams[]
constructor() {
this.label = 'Zep Memory'
this.name = 'ZepMemory'
this.type = 'ZepMemory'
this.icon = 'memory.svg'
this.category = 'Memory'
this.description = 'Summarizes the conversation and stores the memory in zep server'
this.baseClasses = [this.type, ...getBaseClasses(ZepMemory)]
this.inputs = [
{
label: 'Base URL',
name: 'baseURL',
type: 'string',
default: 'http://127.0.0.1:8000'
},
{
label: 'Auto Summary',
name: 'autoSummary',
type: 'boolean',
default: true
},
{
label: 'Session Id',
name: 'sessionId',
type: 'string',
description: 'if empty, chatId will be used automatically',
default: '',
additionalParams: true
},
{
label: 'Auto Summary Template',
name: 'autoSummaryTemplate',
type: 'string',
default: 'This is the summary of the following conversation:\n{summary}',
additionalParams: true
},
{
label: 'AI Prefix',
name: 'aiPrefix',
type: 'string',
default: 'ai',
additionalParams: true
},
{
label: 'Human Prefix',
name: 'humanPrefix',
type: 'string',
default: 'human',
additionalParams: true
},
{
label: 'Memory Key',
name: 'memoryKey',
type: 'string',
default: 'chat_history',
additionalParams: true
},
{
label: 'Input Key',
name: 'inputKey',
type: 'string',
default: 'input',
additionalParams: true
},
{
label: 'Output Key',
name: 'outputKey',
type: 'string',
default: 'text',
additionalParams: true
}
]
}
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
const baseURL = nodeData.inputs?.baseURL as string
const aiPrefix = nodeData.inputs?.aiPrefix as string
const humanPrefix = nodeData.inputs?.humanPrefix as string
const memoryKey = nodeData.inputs?.memoryKey as string
const inputKey = nodeData.inputs?.inputKey as string
const autoSummaryTemplate = nodeData.inputs?.autoSummaryTemplate as string
const autoSummary = nodeData.inputs?.autoSummary as boolean
const sessionId = nodeData.inputs?.sessionId as string
const chatId = options?.chatId as string
const obj: ZepMemoryInput = {
baseURL,
sessionId: sessionId ? sessionId : chatId,
aiPrefix,
humanPrefix,
returnMessages: true,
memoryKey,
inputKey
}
let zep = new ZepMemory(obj)
// hack to support summary
let tmpFunc = zep.loadMemoryVariables
zep.loadMemoryVariables = async (values) => {
let data = await tmpFunc.bind(zep, values)()
if (autoSummary && zep.returnMessages && data[zep.memoryKey] && data[zep.memoryKey].length) {
const memory = await zep.zepClient.getMemory(zep.sessionId, 10)
if (memory?.summary) {
let summary = autoSummaryTemplate.replace(/{summary}/g, memory.summary.content)
// eslint-disable-next-line no-console
console.log('[ZepMemory] auto summary:', summary)
data[zep.memoryKey].unshift(new SystemChatMessage(summary))
}
}
// for langchain zep memory compatibility, or we will get "Missing value for input variable chat_history"
if (data instanceof Array) {
data = {
[zep.memoryKey]: data
}
}
return data
}
return zep
}
}
module.exports = { nodeClass: ZepMemory_Memory }
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-book" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M3 19a9 9 0 0 1 9 0a9 9 0 0 1 9 0"></path>
<path d="M3 6a9 9 0 0 1 9 0a9 9 0 0 1 9 0"></path>
<path d="M3 6l0 13"></path>
<path d="M12 6l0 13"></path>
<path d="M21 6l0 13"></path>
</svg>

After

Width:  |  Height:  |  Size: 495 B

+1
View File
@@ -17,6 +17,7 @@
"license": "SEE LICENSE IN LICENSE.md",
"dependencies": {
"@dqbd/tiktoken": "^1.0.7",
"@getzep/zep-js": "^0.3.1",
"@huggingface/inference": "1",
"@pinecone-database/pinecone": "^0.0.12",
"@supabase/supabase-js": "^2.21.0",
+2 -1
View File
@@ -23,7 +23,7 @@ export class ChildProcess {
await sendToParentProcess('start', '_')
// Create a Queue and add our initial node in it
const { endingNodeData, chatflow, incomingInput, componentNodes } = messageValue
const { endingNodeData, chatflow, chatId, incomingInput, componentNodes } = messageValue
let nodeToExecuteData: INodeData
let addToChatFlowPool: any = {}
@@ -83,6 +83,7 @@ export class ChildProcess {
depthQueue,
componentNodes,
incomingInput.question,
chatId,
incomingInput?.overrideConfig
)
+1
View File
@@ -143,6 +143,7 @@ export interface IDatabaseExport {
export interface IRunChatflowMessageValue {
chatflow: IChatFlow
chatId: string
incomingInput: IncomingInput
componentNodes: IComponentNodes
endingNodeData?: INodeData
+25 -3
View File
@@ -432,7 +432,7 @@ export class App {
* @param {IncomingInput} incomingInput
* @param {INodeData} endingNodeData
*/
async startChildProcess(chatflow: ChatFlow, incomingInput: IncomingInput, endingNodeData?: INodeData) {
async startChildProcess(chatflow: ChatFlow, chatId: string, incomingInput: IncomingInput, endingNodeData?: INodeData) {
try {
const controller = new AbortController()
const { signal } = controller
@@ -444,6 +444,7 @@ export class App {
const value = {
chatflow,
chatId,
incomingInput,
componentNodes: cloneDeep(this.nodesPool.componentNodes),
endingNodeData
@@ -506,6 +507,9 @@ export class App {
})
if (!chatflow) return res.status(404).send(`Chatflow ${chatflowid} not found`)
const chatId = await getChatId(chatflow.id)
if (!chatId) return res.status(500).send(`Chatflow ${chatflowid} first message not found`)
if (!isInternal) {
await this.validateKey(req, res, chatflow)
}
@@ -557,7 +561,7 @@ export class App {
if (isRebuildNeeded()) {
nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData
try {
const result = await this.startChildProcess(chatflow, incomingInput, nodeToExecuteData)
const result = await this.startChildProcess(chatflow, chatId, incomingInput, nodeToExecuteData)
return res.json(result)
} catch (error) {
@@ -565,7 +569,7 @@ export class App {
}
} else {
try {
const result = await this.startChildProcess(chatflow, incomingInput)
const result = await this.startChildProcess(chatflow, chatId, incomingInput)
return res.json(result)
} catch (error) {
return res.status(500).send(error)
@@ -618,6 +622,7 @@ export class App {
depthQueue,
this.nodesPool.componentNodes,
incomingInput.question,
chatId,
incomingInput?.overrideConfig
)
@@ -661,6 +666,23 @@ export class App {
}
}
/**
* Get first chat message id
* @param {string} chatflowid
* @returns {string}
*/
export async function getChatId(chatflowid: string) {
// first chatmessage id as the unique chat id
const firstChatMessage = await getDataSource()
.getRepository(ChatMessage)
.createQueryBuilder('cm')
.select('cm.id')
.where('chatflowid = :chatflowid', { chatflowid })
.orderBy('cm.createdDate', 'ASC')
.getOne()
return firstChatMessage ? firstChatMessage.id : ''
}
let serverApp: App | undefined
export async function start(): Promise<void> {
+2 -1
View File
@@ -182,6 +182,7 @@ export const buildLangchain = async (
depthQueue: IDepthQueue,
componentNodes: IComponentNodes,
question: string,
chatId: string,
overrideConfig?: ICommonObject
) => {
const flowNodes = cloneDeep(reactFlowNodes)
@@ -214,7 +215,7 @@ export const buildLangchain = async (
if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig)
const reactFlowNodeData: INodeData = resolveVariables(flowNodeData, flowNodes, question)
flowNodes[nodeIndex].data.instance = await newNodeInstance.init(reactFlowNodeData, question)
flowNodes[nodeIndex].data.instance = await newNodeInstance.init(reactFlowNodeData, question, { chatId })
} catch (e: any) {
console.error(e)
throw new Error(e)
@@ -118,7 +118,8 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
setLoading(true)
setMessages((prevMessages) => [...prevMessages, { message: userInput, type: 'userMessage' }])
addChatMessage(userInput, 'userMessage')
// waiting for first chatmessage saved, the first chatmessage will be used in sendMessageAndGetPrediction
await addChatMessage(userInput, 'userMessage')
// Send user question and history to API
try {