mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 17:01:00 +03:00
Merge branch 'FlowiseAI:main' into main
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { initializeAgentExecutorWithOptions, AgentExecutor, InitializeAgentExecutorOptions } from 'langchain/agents'
|
||||
import { Tool } from 'langchain/tools'
|
||||
import { BaseChatMemory } from 'langchain/memory'
|
||||
import { getBaseClasses, mapChatHistory } from '../../../src/utils'
|
||||
import { BaseChatModel } from 'langchain/chat_models/base'
|
||||
import { flatten } from 'lodash'
|
||||
import { additionalCallbacks } from '../../../src/handler'
|
||||
import { AgentStep, BaseMessage, ChainValues, AIMessage, HumanMessage } from 'langchain/schema'
|
||||
import { RunnableSequence } from 'langchain/schema/runnable'
|
||||
import { getBaseClasses } from '../../../src/utils'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
|
||||
import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { AgentExecutor } from '../../../src/agents'
|
||||
import { ChatConversationalAgent } from 'langchain/agents'
|
||||
import { renderTemplate } from '@langchain/core/prompts'
|
||||
|
||||
const DEFAULT_PREFIX = `Assistant is a large language model trained by OpenAI.
|
||||
|
||||
@@ -15,6 +18,15 @@ Assistant is constantly learning and improving, and its capabilities are constan
|
||||
|
||||
Overall, Assistant is a powerful system that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether you need help with a specific question or just want to have a conversation about a particular topic, Assistant is here to assist.`
|
||||
|
||||
const TEMPLATE_TOOL_RESPONSE = `TOOL RESPONSE:
|
||||
---------------------
|
||||
{observation}
|
||||
|
||||
USER'S INPUT
|
||||
--------------------
|
||||
|
||||
Okay, so what is the response to my last comment? If using information obtained from the tools you must mention it explicitly without mentioning the tool names - I have forgotten all TOOL RESPONSES! Remember to respond with a markdown code snippet of a json blob with a single action, and NOTHING else.`
|
||||
|
||||
class ConversationalAgent_Agents implements INode {
|
||||
label: string
|
||||
name: string
|
||||
@@ -25,8 +37,9 @@ class ConversationalAgent_Agents implements INode {
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
inputs: INodeParams[]
|
||||
sessionId?: string
|
||||
|
||||
constructor() {
|
||||
constructor(fields?: { sessionId?: string }) {
|
||||
this.label = 'Conversational Agent'
|
||||
this.name = 'conversationalAgent'
|
||||
this.version = 2.0
|
||||
@@ -43,7 +56,7 @@ class ConversationalAgent_Agents implements INode {
|
||||
list: true
|
||||
},
|
||||
{
|
||||
label: 'Language Model',
|
||||
label: 'Chat Model',
|
||||
name: 'model',
|
||||
type: 'BaseChatModel'
|
||||
},
|
||||
@@ -62,52 +75,114 @@ class ConversationalAgent_Agents implements INode {
|
||||
additionalParams: true
|
||||
}
|
||||
]
|
||||
this.sessionId = fields?.sessionId
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData): Promise<any> {
|
||||
const model = nodeData.inputs?.model as BaseChatModel
|
||||
let tools = nodeData.inputs?.tools as Tool[]
|
||||
tools = flatten(tools)
|
||||
const memory = nodeData.inputs?.memory as BaseChatMemory
|
||||
const systemMessage = nodeData.inputs?.systemMessage as string
|
||||
|
||||
const obj: InitializeAgentExecutorOptions = {
|
||||
agentType: 'chat-conversational-react-description',
|
||||
verbose: process.env.DEBUG === 'true' ? true : false
|
||||
}
|
||||
|
||||
const agentArgs: any = {}
|
||||
if (systemMessage) {
|
||||
agentArgs.systemMessage = systemMessage
|
||||
}
|
||||
|
||||
if (Object.keys(agentArgs).length) obj.agentArgs = agentArgs
|
||||
|
||||
const executor = await initializeAgentExecutorWithOptions(tools, model, obj)
|
||||
executor.memory = memory
|
||||
return executor
|
||||
async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
|
||||
return prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory)
|
||||
}
|
||||
|
||||
async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<string> {
|
||||
const executor = nodeData.instance as AgentExecutor
|
||||
const memory = nodeData.inputs?.memory as BaseChatMemory
|
||||
|
||||
if (options && options.chatHistory) {
|
||||
const chatHistoryClassName = memory.chatHistory.constructor.name
|
||||
// Only replace when its In-Memory
|
||||
if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') {
|
||||
memory.chatHistory = mapChatHistory(options)
|
||||
executor.memory = memory
|
||||
}
|
||||
}
|
||||
|
||||
;(executor.memory as any).returnMessages = true // Return true for BaseChatModel
|
||||
const memory = nodeData.inputs?.memory as FlowiseMemory
|
||||
const executor = await prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory)
|
||||
|
||||
const loggerHandler = new ConsoleCallbackHandler(options.logger)
|
||||
const callbacks = await additionalCallbacks(nodeData, options)
|
||||
|
||||
const result = await executor.call({ input }, [...callbacks])
|
||||
return result?.output
|
||||
let res: ChainValues = {}
|
||||
|
||||
if (options.socketIO && options.socketIOClientId) {
|
||||
const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId)
|
||||
res = await executor.invoke({ input }, { callbacks: [loggerHandler, handler, ...callbacks] })
|
||||
} else {
|
||||
res = await executor.invoke({ input }, { callbacks: [loggerHandler, ...callbacks] })
|
||||
}
|
||||
|
||||
await memory.addChatMessages(
|
||||
[
|
||||
{
|
||||
text: input,
|
||||
type: 'userMessage'
|
||||
},
|
||||
{
|
||||
text: res?.output,
|
||||
type: 'apiMessage'
|
||||
}
|
||||
],
|
||||
this.sessionId
|
||||
)
|
||||
|
||||
return res?.output
|
||||
}
|
||||
}
|
||||
|
||||
const prepareAgent = async (
|
||||
nodeData: INodeData,
|
||||
flowObj: { sessionId?: string; chatId?: string; input?: string },
|
||||
chatHistory: IMessage[] = []
|
||||
) => {
|
||||
const model = nodeData.inputs?.model as BaseChatModel
|
||||
let tools = nodeData.inputs?.tools as Tool[]
|
||||
tools = flatten(tools)
|
||||
const memory = nodeData.inputs?.memory as FlowiseMemory
|
||||
const systemMessage = nodeData.inputs?.systemMessage as string
|
||||
const memoryKey = memory.memoryKey ? memory.memoryKey : 'chat_history'
|
||||
const inputKey = memory.inputKey ? memory.inputKey : 'input'
|
||||
|
||||
/** Bind a stop token to the model */
|
||||
const modelWithStop = model.bind({
|
||||
stop: ['\nObservation']
|
||||
})
|
||||
|
||||
const outputParser = ChatConversationalAgent.getDefaultOutputParser({
|
||||
llm: model,
|
||||
toolNames: tools.map((tool) => tool.name)
|
||||
})
|
||||
|
||||
const prompt = ChatConversationalAgent.createPrompt(tools, {
|
||||
systemMessage: systemMessage ? systemMessage : DEFAULT_PREFIX,
|
||||
outputParser
|
||||
})
|
||||
|
||||
const runnableAgent = RunnableSequence.from([
|
||||
{
|
||||
[inputKey]: (i: { input: string; steps: AgentStep[] }) => i.input,
|
||||
agent_scratchpad: async (i: { input: string; steps: AgentStep[] }) => await constructScratchPad(i.steps),
|
||||
[memoryKey]: async (_: { input: string; steps: AgentStep[] }) => {
|
||||
const messages = (await memory.getChatMessages(flowObj?.sessionId, true, chatHistory)) as BaseMessage[]
|
||||
return messages ?? []
|
||||
}
|
||||
},
|
||||
prompt,
|
||||
modelWithStop,
|
||||
outputParser
|
||||
])
|
||||
|
||||
const executor = AgentExecutor.fromAgentAndTools({
|
||||
agent: runnableAgent,
|
||||
tools,
|
||||
sessionId: flowObj?.sessionId,
|
||||
chatId: flowObj?.chatId,
|
||||
input: flowObj?.input,
|
||||
verbose: process.env.DEBUG === 'true' ? true : false
|
||||
})
|
||||
|
||||
return executor
|
||||
}
|
||||
|
||||
const constructScratchPad = async (steps: AgentStep[]): Promise<BaseMessage[]> => {
|
||||
const thoughts: BaseMessage[] = []
|
||||
for (const step of steps) {
|
||||
thoughts.push(new AIMessage(step.action.log))
|
||||
thoughts.push(
|
||||
new HumanMessage(
|
||||
renderTemplate(TEMPLATE_TOOL_RESPONSE, 'f-string', {
|
||||
observation: step.observation
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
return thoughts
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: ConversationalAgent_Agents }
|
||||
|
||||
+87
-40
@@ -1,9 +1,14 @@
|
||||
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { initializeAgentExecutorWithOptions, AgentExecutor } from 'langchain/agents'
|
||||
import { getBaseClasses, mapChatHistory } from '../../../src/utils'
|
||||
import { ChainValues, AgentStep, BaseMessage } from 'langchain/schema'
|
||||
import { flatten } from 'lodash'
|
||||
import { BaseChatMemory } from 'langchain/memory'
|
||||
import { ChatOpenAI } from 'langchain/chat_models/openai'
|
||||
import { ChatPromptTemplate, MessagesPlaceholder } from 'langchain/prompts'
|
||||
import { formatToOpenAIFunction } from 'langchain/tools'
|
||||
import { RunnableSequence } from 'langchain/schema/runnable'
|
||||
import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses } from '../../../src/utils'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
|
||||
import { OpenAIFunctionsAgentOutputParser } from 'langchain/agents/openai/output_parser'
|
||||
import { AgentExecutor, formatAgentSteps } from '../../../src/agents'
|
||||
|
||||
const defaultMessage = `Do your best to answer the questions. Feel free to use any tools available to look up relevant information, only if necessary.`
|
||||
|
||||
@@ -17,8 +22,9 @@ class ConversationalRetrievalAgent_Agents implements INode {
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
inputs: INodeParams[]
|
||||
sessionId?: string
|
||||
|
||||
constructor() {
|
||||
constructor(fields?: { sessionId?: string }) {
|
||||
this.label = 'Conversational Retrieval Agent'
|
||||
this.name = 'conversationalRetrievalAgent'
|
||||
this.version = 3.0
|
||||
@@ -54,55 +60,96 @@ class ConversationalRetrievalAgent_Agents implements INode {
|
||||
additionalParams: true
|
||||
}
|
||||
]
|
||||
this.sessionId = fields?.sessionId
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData): Promise<any> {
|
||||
const model = nodeData.inputs?.model
|
||||
const memory = nodeData.inputs?.memory as BaseChatMemory
|
||||
const systemMessage = nodeData.inputs?.systemMessage as string
|
||||
|
||||
let tools = nodeData.inputs?.tools
|
||||
tools = flatten(tools)
|
||||
|
||||
const executor = await initializeAgentExecutorWithOptions(tools, model, {
|
||||
agentType: 'openai-functions',
|
||||
verbose: process.env.DEBUG === 'true' ? true : false,
|
||||
agentArgs: {
|
||||
prefix: systemMessage ?? defaultMessage
|
||||
},
|
||||
returnIntermediateSteps: true
|
||||
})
|
||||
executor.memory = memory
|
||||
return executor
|
||||
async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
|
||||
return prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory)
|
||||
}
|
||||
|
||||
async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<string> {
|
||||
const executor = nodeData.instance as AgentExecutor
|
||||
|
||||
if (executor.memory) {
|
||||
;(executor.memory as any).memoryKey = 'chat_history'
|
||||
;(executor.memory as any).outputKey = 'output'
|
||||
;(executor.memory as any).returnMessages = true
|
||||
|
||||
const chatHistoryClassName = (executor.memory as any).chatHistory.constructor.name
|
||||
// Only replace when its In-Memory
|
||||
if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') {
|
||||
;(executor.memory as any).chatHistory = mapChatHistory(options)
|
||||
}
|
||||
}
|
||||
const memory = nodeData.inputs?.memory as FlowiseMemory
|
||||
const executor = prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory)
|
||||
|
||||
const loggerHandler = new ConsoleCallbackHandler(options.logger)
|
||||
const callbacks = await additionalCallbacks(nodeData, options)
|
||||
|
||||
let res: ChainValues = {}
|
||||
|
||||
if (options.socketIO && options.socketIOClientId) {
|
||||
const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId)
|
||||
const result = await executor.call({ input }, [loggerHandler, handler, ...callbacks])
|
||||
return result?.output
|
||||
res = await executor.invoke({ input }, { callbacks: [loggerHandler, handler, ...callbacks] })
|
||||
} else {
|
||||
const result = await executor.call({ input }, [loggerHandler, ...callbacks])
|
||||
return result?.output
|
||||
res = await executor.invoke({ input }, { callbacks: [loggerHandler, ...callbacks] })
|
||||
}
|
||||
|
||||
await memory.addChatMessages(
|
||||
[
|
||||
{
|
||||
text: input,
|
||||
type: 'userMessage'
|
||||
},
|
||||
{
|
||||
text: res?.output,
|
||||
type: 'apiMessage'
|
||||
}
|
||||
],
|
||||
this.sessionId
|
||||
)
|
||||
|
||||
return res?.output
|
||||
}
|
||||
}
|
||||
|
||||
const prepareAgent = (
|
||||
nodeData: INodeData,
|
||||
flowObj: { sessionId?: string; chatId?: string; input?: string },
|
||||
chatHistory: IMessage[] = []
|
||||
) => {
|
||||
const model = nodeData.inputs?.model as ChatOpenAI
|
||||
const memory = nodeData.inputs?.memory as FlowiseMemory
|
||||
const systemMessage = nodeData.inputs?.systemMessage as string
|
||||
let tools = nodeData.inputs?.tools
|
||||
tools = flatten(tools)
|
||||
const memoryKey = memory.memoryKey ? memory.memoryKey : 'chat_history'
|
||||
const inputKey = memory.inputKey ? memory.inputKey : 'input'
|
||||
|
||||
const prompt = ChatPromptTemplate.fromMessages([
|
||||
['ai', systemMessage ? systemMessage : defaultMessage],
|
||||
new MessagesPlaceholder(memoryKey),
|
||||
['human', `{${inputKey}}`],
|
||||
new MessagesPlaceholder('agent_scratchpad')
|
||||
])
|
||||
|
||||
const modelWithFunctions = model.bind({
|
||||
functions: [...tools.map((tool: any) => formatToOpenAIFunction(tool))]
|
||||
})
|
||||
|
||||
const runnableAgent = RunnableSequence.from([
|
||||
{
|
||||
[inputKey]: (i: { input: string; steps: AgentStep[] }) => i.input,
|
||||
agent_scratchpad: (i: { input: string; steps: AgentStep[] }) => formatAgentSteps(i.steps),
|
||||
[memoryKey]: async (_: { input: string; steps: AgentStep[] }) => {
|
||||
const messages = (await memory.getChatMessages(flowObj?.sessionId, true, chatHistory)) as BaseMessage[]
|
||||
return messages ?? []
|
||||
}
|
||||
},
|
||||
prompt,
|
||||
modelWithFunctions,
|
||||
new OpenAIFunctionsAgentOutputParser()
|
||||
])
|
||||
|
||||
const executor = AgentExecutor.fromAgentAndTools({
|
||||
agent: runnableAgent,
|
||||
tools,
|
||||
sessionId: flowObj?.sessionId,
|
||||
chatId: flowObj?.chatId,
|
||||
input: flowObj?.input,
|
||||
returnIntermediateSteps: true,
|
||||
verbose: process.env.DEBUG === 'true' ? true : false
|
||||
})
|
||||
|
||||
return executor
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: ConversationalRetrievalAgent_Agents }
|
||||
|
||||
@@ -96,45 +96,51 @@ class OpenAIAssistant_Agents implements INode {
|
||||
return null
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
memoryMethods = {
|
||||
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> {
|
||||
const selectedAssistantId = nodeData.inputs?.selectedAssistant as string
|
||||
const appDataSource = options.appDataSource as DataSource
|
||||
const databaseEntities = options.databaseEntities as IDatabaseEntity
|
||||
let sessionId = nodeData.inputs?.sessionId as string
|
||||
async clearChatMessages(nodeData: INodeData, options: ICommonObject, sessionIdObj: { type: string; id: string }): Promise<void> {
|
||||
const selectedAssistantId = nodeData.inputs?.selectedAssistant as string
|
||||
const appDataSource = options.appDataSource as DataSource
|
||||
const databaseEntities = options.databaseEntities as IDatabaseEntity
|
||||
|
||||
const assistant = await appDataSource.getRepository(databaseEntities['Assistant']).findOneBy({
|
||||
id: selectedAssistantId
|
||||
const assistant = await appDataSource.getRepository(databaseEntities['Assistant']).findOneBy({
|
||||
id: selectedAssistantId
|
||||
})
|
||||
|
||||
if (!assistant) {
|
||||
options.logger.error(`Assistant ${selectedAssistantId} not found`)
|
||||
return
|
||||
}
|
||||
|
||||
if (!sessionIdObj) return
|
||||
|
||||
let sessionId = ''
|
||||
if (sessionIdObj.type === 'chatId') {
|
||||
const chatId = sessionIdObj.id
|
||||
const chatmsg = await appDataSource.getRepository(databaseEntities['ChatMessage']).findOneBy({
|
||||
chatId
|
||||
})
|
||||
|
||||
if (!assistant) {
|
||||
options.logger.error(`Assistant ${selectedAssistantId} not found`)
|
||||
if (!chatmsg) {
|
||||
options.logger.error(`Chat Message with Chat Id: ${chatId} not found`)
|
||||
return
|
||||
}
|
||||
sessionId = chatmsg.sessionId
|
||||
} else if (sessionIdObj.type === 'threadId') {
|
||||
sessionId = sessionIdObj.id
|
||||
}
|
||||
|
||||
if (!sessionId && options.chatId) {
|
||||
const chatmsg = await appDataSource.getRepository(databaseEntities['ChatMessage']).findOneBy({
|
||||
chatId: options.chatId
|
||||
})
|
||||
if (!chatmsg) {
|
||||
options.logger.error(`Chat Message with Chat Id: ${options.chatId} not found`)
|
||||
return
|
||||
}
|
||||
sessionId = chatmsg.sessionId
|
||||
}
|
||||
const credentialData = await getCredentialData(assistant.credential ?? '', options)
|
||||
const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData)
|
||||
if (!openAIApiKey) {
|
||||
options.logger.error(`OpenAI ApiKey not found`)
|
||||
return
|
||||
}
|
||||
|
||||
const credentialData = await getCredentialData(assistant.credential ?? '', options)
|
||||
const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData)
|
||||
if (!openAIApiKey) {
|
||||
options.logger.error(`OpenAI ApiKey not found`)
|
||||
return
|
||||
}
|
||||
|
||||
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||
options.logger.info(`Clearing OpenAI Thread ${sessionId}`)
|
||||
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||
options.logger.info(`Clearing OpenAI Thread ${sessionId}`)
|
||||
try {
|
||||
if (sessionId) await openai.beta.threads.del(sessionId)
|
||||
options.logger.info(`Successfully cleared OpenAI Thread ${sessionId}`)
|
||||
} catch (e) {
|
||||
throw new Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,7 +303,11 @@ class OpenAIAssistant_Agents implements INode {
|
||||
options.socketIO.to(options.socketIOClientId).emit('tool', tool.name)
|
||||
|
||||
try {
|
||||
const toolOutput = await tool.call(actions[i].toolInput, undefined, undefined, threadId)
|
||||
const toolOutput = await tool.call(actions[i].toolInput, undefined, undefined, {
|
||||
sessionId: threadId,
|
||||
chatId: options.chatId,
|
||||
input
|
||||
})
|
||||
await analyticHandlers.onToolEnd(toolIds, toolOutput)
|
||||
submitToolOutputs.push({
|
||||
tool_call_id: actions[i].toolCallId,
|
||||
@@ -462,6 +472,7 @@ class OpenAIAssistant_Agents implements INode {
|
||||
const imageRegex = /<img[^>]*\/>/g
|
||||
let llmOutput = returnVal.replace(imageRegex, '')
|
||||
llmOutput = llmOutput.replace('<br/>', '')
|
||||
|
||||
await analyticHandlers.onLLMEnd(llmIds, llmOutput)
|
||||
await analyticHandlers.onChainEnd(parentIds, messageData, true)
|
||||
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { FlowiseMemory, ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { AgentExecutor as LCAgentExecutor, AgentExecutorInput } from 'langchain/agents'
|
||||
import { ChainValues, AgentStep, AgentFinish, AgentAction, BaseMessage, FunctionMessage, AIMessage } from 'langchain/schema'
|
||||
import { OutputParserException } from 'langchain/schema/output_parser'
|
||||
import { CallbackManagerForChainRun } from 'langchain/callbacks'
|
||||
import { formatToOpenAIFunction } from 'langchain/tools'
|
||||
import { ToolInputParsingException, Tool } from '@langchain/core/tools'
|
||||
import { ChainValues, AgentStep, BaseMessage } from 'langchain/schema'
|
||||
import { getBaseClasses } from '../../../src/utils'
|
||||
import { flatten } from 'lodash'
|
||||
import { RunnableSequence } from 'langchain/schema/runnable'
|
||||
import { formatToOpenAIFunction } from 'langchain/tools'
|
||||
import { ChatOpenAI } from 'langchain/chat_models/openai'
|
||||
import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
|
||||
import { ChatPromptTemplate, MessagesPlaceholder } from 'langchain/prompts'
|
||||
import { ChatOpenAI } from 'langchain/chat_models/openai'
|
||||
import { OpenAIFunctionsAgentOutputParser } from 'langchain/agents/openai/output_parser'
|
||||
import { AgentExecutor, formatAgentSteps } from '../../../src/agents'
|
||||
|
||||
class OpenAIFunctionAgent_Agents implements INode {
|
||||
label: string
|
||||
@@ -25,7 +22,7 @@ class OpenAIFunctionAgent_Agents implements INode {
|
||||
inputs: INodeParams[]
|
||||
sessionId?: string
|
||||
|
||||
constructor(fields: { sessionId?: string }) {
|
||||
constructor(fields?: { sessionId?: string }) {
|
||||
this.label = 'OpenAI Function Agent'
|
||||
this.name = 'openAIFunctionAgent'
|
||||
this.version = 3.0
|
||||
@@ -33,7 +30,7 @@ class OpenAIFunctionAgent_Agents implements INode {
|
||||
this.category = 'Agents'
|
||||
this.icon = 'function.svg'
|
||||
this.description = `An agent that uses Function Calling to pick the tool and args to call`
|
||||
this.baseClasses = [this.type, ...getBaseClasses(LCAgentExecutor)]
|
||||
this.baseClasses = [this.type, ...getBaseClasses(AgentExecutor)]
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Allowed Tools',
|
||||
@@ -63,19 +60,13 @@ class OpenAIFunctionAgent_Agents implements INode {
|
||||
this.sessionId = fields?.sessionId
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData): Promise<any> {
|
||||
const memory = nodeData.inputs?.memory as FlowiseMemory
|
||||
|
||||
const executor = prepareAgent(nodeData, this.sessionId)
|
||||
if (memory) executor.memory = memory
|
||||
|
||||
return executor
|
||||
async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
|
||||
return prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory)
|
||||
}
|
||||
|
||||
async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<string> {
|
||||
const memory = nodeData.inputs?.memory as FlowiseMemory
|
||||
|
||||
const executor = prepareAgent(nodeData, this.sessionId)
|
||||
const executor = prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory)
|
||||
|
||||
const loggerHandler = new ConsoleCallbackHandler(options.logger)
|
||||
const callbacks = await additionalCallbacks(nodeData, options)
|
||||
@@ -107,17 +98,11 @@ class OpenAIFunctionAgent_Agents implements INode {
|
||||
}
|
||||
}
|
||||
|
||||
const formatAgentSteps = (steps: AgentStep[]): BaseMessage[] =>
|
||||
steps.flatMap(({ action, observation }) => {
|
||||
if ('messageLog' in action && action.messageLog !== undefined) {
|
||||
const log = action.messageLog as BaseMessage[]
|
||||
return log.concat(new FunctionMessage(observation, action.tool))
|
||||
} else {
|
||||
return [new AIMessage(action.log)]
|
||||
}
|
||||
})
|
||||
|
||||
const prepareAgent = (nodeData: INodeData, sessionId?: string) => {
|
||||
const prepareAgent = (
|
||||
nodeData: INodeData,
|
||||
flowObj: { sessionId?: string; chatId?: string; input?: string },
|
||||
chatHistory: IMessage[] = []
|
||||
) => {
|
||||
const model = nodeData.inputs?.model as ChatOpenAI
|
||||
const memory = nodeData.inputs?.memory as FlowiseMemory
|
||||
const systemMessage = nodeData.inputs?.systemMessage as string
|
||||
@@ -127,7 +112,7 @@ const prepareAgent = (nodeData: INodeData, sessionId?: string) => {
|
||||
const inputKey = memory.inputKey ? memory.inputKey : 'input'
|
||||
|
||||
const prompt = ChatPromptTemplate.fromMessages([
|
||||
['ai', systemMessage ? systemMessage : `You are a helpful AI assistant.`],
|
||||
['system', systemMessage ? systemMessage : `You are a helpful AI assistant.`],
|
||||
new MessagesPlaceholder(memoryKey),
|
||||
['human', `{${inputKey}}`],
|
||||
new MessagesPlaceholder('agent_scratchpad')
|
||||
@@ -142,7 +127,7 @@ const prepareAgent = (nodeData: INodeData, sessionId?: string) => {
|
||||
[inputKey]: (i: { input: string; steps: AgentStep[] }) => i.input,
|
||||
agent_scratchpad: (i: { input: string; steps: AgentStep[] }) => formatAgentSteps(i.steps),
|
||||
[memoryKey]: async (_: { input: string; steps: AgentStep[] }) => {
|
||||
const messages = (await memory.getChatMessages(sessionId, true)) as BaseMessage[]
|
||||
const messages = (await memory.getChatMessages(flowObj?.sessionId, true, chatHistory)) as BaseMessage[]
|
||||
return messages ?? []
|
||||
}
|
||||
},
|
||||
@@ -154,231 +139,13 @@ const prepareAgent = (nodeData: INodeData, sessionId?: string) => {
|
||||
const executor = AgentExecutor.fromAgentAndTools({
|
||||
agent: runnableAgent,
|
||||
tools,
|
||||
sessionId
|
||||
sessionId: flowObj?.sessionId,
|
||||
chatId: flowObj?.chatId,
|
||||
input: flowObj?.input,
|
||||
verbose: process.env.DEBUG === 'true' ? true : false
|
||||
})
|
||||
|
||||
return executor
|
||||
}
|
||||
|
||||
type AgentExecutorOutput = ChainValues
|
||||
|
||||
class AgentExecutor extends LCAgentExecutor {
|
||||
sessionId?: string
|
||||
|
||||
static fromAgentAndTools(fields: AgentExecutorInput & { sessionId?: string }): AgentExecutor {
|
||||
const newInstance = new AgentExecutor(fields)
|
||||
if (fields.sessionId) newInstance.sessionId = fields.sessionId
|
||||
return newInstance
|
||||
}
|
||||
|
||||
shouldContinueIteration(iterations: number): boolean {
|
||||
return this.maxIterations === undefined || iterations < this.maxIterations
|
||||
}
|
||||
|
||||
async _call(inputs: ChainValues, runManager?: CallbackManagerForChainRun): Promise<AgentExecutorOutput> {
|
||||
const toolsByName = Object.fromEntries(this.tools.map((t) => [t.name.toLowerCase(), t]))
|
||||
|
||||
const steps: AgentStep[] = []
|
||||
let iterations = 0
|
||||
|
||||
const getOutput = async (finishStep: AgentFinish): Promise<AgentExecutorOutput> => {
|
||||
const { returnValues } = finishStep
|
||||
const additional = await this.agent.prepareForOutput(returnValues, steps)
|
||||
|
||||
if (this.returnIntermediateSteps) {
|
||||
return { ...returnValues, intermediateSteps: steps, ...additional }
|
||||
}
|
||||
await runManager?.handleAgentEnd(finishStep)
|
||||
return { ...returnValues, ...additional }
|
||||
}
|
||||
|
||||
while (this.shouldContinueIteration(iterations)) {
|
||||
let output
|
||||
try {
|
||||
output = await this.agent.plan(steps, inputs, runManager?.getChild())
|
||||
} catch (e) {
|
||||
if (e instanceof OutputParserException) {
|
||||
let observation
|
||||
let text = e.message
|
||||
if (this.handleParsingErrors === true) {
|
||||
if (e.sendToLLM) {
|
||||
observation = e.observation
|
||||
text = e.llmOutput ?? ''
|
||||
} else {
|
||||
observation = 'Invalid or incomplete response'
|
||||
}
|
||||
} else if (typeof this.handleParsingErrors === 'string') {
|
||||
observation = this.handleParsingErrors
|
||||
} else if (typeof this.handleParsingErrors === 'function') {
|
||||
observation = this.handleParsingErrors(e)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
output = {
|
||||
tool: '_Exception',
|
||||
toolInput: observation,
|
||||
log: text
|
||||
} as AgentAction
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
// Check if the agent has finished
|
||||
if ('returnValues' in output) {
|
||||
return getOutput(output)
|
||||
}
|
||||
|
||||
let actions: AgentAction[]
|
||||
if (Array.isArray(output)) {
|
||||
actions = output as AgentAction[]
|
||||
} else {
|
||||
actions = [output as AgentAction]
|
||||
}
|
||||
|
||||
const newSteps = await Promise.all(
|
||||
actions.map(async (action) => {
|
||||
await runManager?.handleAgentAction(action)
|
||||
const tool = action.tool === '_Exception' ? new ExceptionTool() : toolsByName[action.tool?.toLowerCase()]
|
||||
let observation
|
||||
try {
|
||||
// here we need to override Tool call method to include sessionId as parameter
|
||||
observation = tool
|
||||
? // @ts-ignore
|
||||
await tool.call(action.toolInput, runManager?.getChild(), undefined, this.sessionId)
|
||||
: `${action.tool} is not a valid tool, try another one.`
|
||||
} catch (e) {
|
||||
if (e instanceof ToolInputParsingException) {
|
||||
if (this.handleParsingErrors === true) {
|
||||
observation = 'Invalid or incomplete tool input. Please try again.'
|
||||
} else if (typeof this.handleParsingErrors === 'string') {
|
||||
observation = this.handleParsingErrors
|
||||
} else if (typeof this.handleParsingErrors === 'function') {
|
||||
observation = this.handleParsingErrors(e)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
observation = await new ExceptionTool().call(observation, runManager?.getChild())
|
||||
return { action, observation: observation ?? '' }
|
||||
}
|
||||
}
|
||||
return { action, observation: observation ?? '' }
|
||||
})
|
||||
)
|
||||
|
||||
steps.push(...newSteps)
|
||||
|
||||
const lastStep = steps[steps.length - 1]
|
||||
const lastTool = toolsByName[lastStep.action.tool?.toLowerCase()]
|
||||
|
||||
if (lastTool?.returnDirect) {
|
||||
return getOutput({
|
||||
returnValues: { [this.agent.returnValues[0]]: lastStep.observation },
|
||||
log: ''
|
||||
})
|
||||
}
|
||||
|
||||
iterations += 1
|
||||
}
|
||||
|
||||
const finish = await this.agent.returnStoppedResponse(this.earlyStoppingMethod, steps, inputs)
|
||||
|
||||
return getOutput(finish)
|
||||
}
|
||||
|
||||
async _takeNextStep(
|
||||
nameToolMap: Record<string, Tool>,
|
||||
inputs: ChainValues,
|
||||
intermediateSteps: AgentStep[],
|
||||
runManager?: CallbackManagerForChainRun
|
||||
): Promise<AgentFinish | AgentStep[]> {
|
||||
let output
|
||||
try {
|
||||
output = await this.agent.plan(intermediateSteps, inputs, runManager?.getChild())
|
||||
} catch (e) {
|
||||
if (e instanceof OutputParserException) {
|
||||
let observation
|
||||
let text = e.message
|
||||
if (this.handleParsingErrors === true) {
|
||||
if (e.sendToLLM) {
|
||||
observation = e.observation
|
||||
text = e.llmOutput ?? ''
|
||||
} else {
|
||||
observation = 'Invalid or incomplete response'
|
||||
}
|
||||
} else if (typeof this.handleParsingErrors === 'string') {
|
||||
observation = this.handleParsingErrors
|
||||
} else if (typeof this.handleParsingErrors === 'function') {
|
||||
observation = this.handleParsingErrors(e)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
output = {
|
||||
tool: '_Exception',
|
||||
toolInput: observation,
|
||||
log: text
|
||||
} as AgentAction
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
if ('returnValues' in output) {
|
||||
return output
|
||||
}
|
||||
|
||||
let actions: AgentAction[]
|
||||
if (Array.isArray(output)) {
|
||||
actions = output as AgentAction[]
|
||||
} else {
|
||||
actions = [output as AgentAction]
|
||||
}
|
||||
|
||||
const result: AgentStep[] = []
|
||||
for (const agentAction of actions) {
|
||||
let observation = ''
|
||||
if (runManager) {
|
||||
await runManager?.handleAgentAction(agentAction)
|
||||
}
|
||||
if (agentAction.tool in nameToolMap) {
|
||||
const tool = nameToolMap[agentAction.tool]
|
||||
try {
|
||||
// here we need to override Tool call method to include sessionId as parameter
|
||||
// @ts-ignore
|
||||
observation = await tool.call(agentAction.toolInput, runManager?.getChild(), undefined, this.sessionId)
|
||||
} catch (e) {
|
||||
if (e instanceof ToolInputParsingException) {
|
||||
if (this.handleParsingErrors === true) {
|
||||
observation = 'Invalid or incomplete tool input. Please try again.'
|
||||
} else if (typeof this.handleParsingErrors === 'string') {
|
||||
observation = this.handleParsingErrors
|
||||
} else if (typeof this.handleParsingErrors === 'function') {
|
||||
observation = this.handleParsingErrors(e)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
observation = await new ExceptionTool().call(observation, runManager?.getChild())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
observation = `${agentAction.tool} is not a valid tool, try another available tool: ${Object.keys(nameToolMap).join(', ')}`
|
||||
}
|
||||
result.push({
|
||||
action: agentAction,
|
||||
observation
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class ExceptionTool extends Tool {
|
||||
name = '_Exception'
|
||||
|
||||
description = 'Exception tool'
|
||||
|
||||
async _call(query: string) {
|
||||
return query
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: OpenAIFunctionAgent_Agents }
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { ConversationChain } from 'langchain/chains'
|
||||
import { getBaseClasses, mapChatHistory } from '../../../src/utils'
|
||||
import { getBaseClasses, handleEscapeCharacters } from '../../../src/utils'
|
||||
import { ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate } from 'langchain/prompts'
|
||||
import { BufferMemory } from 'langchain/memory'
|
||||
import { BaseChatModel } from 'langchain/chat_models/base'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
|
||||
import { flatten } from 'lodash'
|
||||
import { Document } from 'langchain/document'
|
||||
import { RunnableSequence } from 'langchain/schema/runnable'
|
||||
import { StringOutputParser } from 'langchain/schema/output_parser'
|
||||
import { ConsoleCallbackHandler as LCConsoleCallbackHandler } from '@langchain/core/tracers/console'
|
||||
|
||||
let systemMessage = `The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.`
|
||||
const inputKey = 'input'
|
||||
|
||||
class ConversationChain_Chains implements INode {
|
||||
label: string
|
||||
@@ -20,11 +21,12 @@ class ConversationChain_Chains implements INode {
|
||||
baseClasses: string[]
|
||||
description: string
|
||||
inputs: INodeParams[]
|
||||
sessionId?: string
|
||||
|
||||
constructor() {
|
||||
constructor(fields?: { sessionId?: string }) {
|
||||
this.label = 'Conversation Chain'
|
||||
this.name = 'conversationChain'
|
||||
this.version = 1.0
|
||||
this.version = 2.0
|
||||
this.type = 'ConversationChain'
|
||||
this.icon = 'conv.svg'
|
||||
this.category = 'Chains'
|
||||
@@ -32,7 +34,7 @@ class ConversationChain_Chains implements INode {
|
||||
this.baseClasses = [this.type, ...getBaseClasses(ConversationChain)]
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Language Model',
|
||||
label: 'Chat Model',
|
||||
name: 'model',
|
||||
type: 'BaseChatModel'
|
||||
},
|
||||
@@ -41,6 +43,14 @@ class ConversationChain_Chains implements INode {
|
||||
name: 'memory',
|
||||
type: 'BaseMemory'
|
||||
},
|
||||
{
|
||||
label: 'Chat Prompt Template',
|
||||
name: 'chatPromptTemplate',
|
||||
type: 'ChatPromptTemplate',
|
||||
description: 'Override existing prompt with Chat Prompt Template. Human Message must includes {input} variable',
|
||||
optional: true
|
||||
},
|
||||
/* Deprecated
|
||||
{
|
||||
label: 'Document',
|
||||
name: 'document',
|
||||
@@ -49,87 +59,133 @@ class ConversationChain_Chains implements INode {
|
||||
'Include whole document into the context window, if you get maximum context length error, please use model with higher context window like Claude 100k, or gpt4 32k',
|
||||
optional: true,
|
||||
list: true
|
||||
},
|
||||
},*/
|
||||
{
|
||||
label: 'System Message',
|
||||
name: 'systemMessagePrompt',
|
||||
type: 'string',
|
||||
rows: 4,
|
||||
description: 'If Chat Prompt Template is provided, this will be ignored',
|
||||
additionalParams: true,
|
||||
optional: true,
|
||||
placeholder: 'You are a helpful assistant that write codes'
|
||||
default: systemMessage,
|
||||
placeholder: systemMessage
|
||||
}
|
||||
]
|
||||
this.sessionId = fields?.sessionId
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData): Promise<any> {
|
||||
const model = nodeData.inputs?.model as BaseChatModel
|
||||
const memory = nodeData.inputs?.memory as BufferMemory
|
||||
const prompt = nodeData.inputs?.systemMessagePrompt as string
|
||||
const docs = nodeData.inputs?.document as Document[]
|
||||
|
||||
const flattenDocs = docs && docs.length ? flatten(docs) : []
|
||||
const finalDocs = []
|
||||
for (let i = 0; i < flattenDocs.length; i += 1) {
|
||||
if (flattenDocs[i] && flattenDocs[i].pageContent) {
|
||||
finalDocs.push(new Document(flattenDocs[i]))
|
||||
}
|
||||
}
|
||||
|
||||
let finalText = ''
|
||||
for (let i = 0; i < finalDocs.length; i += 1) {
|
||||
finalText += finalDocs[i].pageContent
|
||||
}
|
||||
|
||||
const replaceChar: string[] = ['{', '}']
|
||||
for (const char of replaceChar) finalText = finalText.replaceAll(char, '')
|
||||
|
||||
if (finalText) systemMessage = `${systemMessage}\nThe AI has the following context:\n${finalText}`
|
||||
|
||||
const obj: any = {
|
||||
llm: model,
|
||||
memory,
|
||||
verbose: process.env.DEBUG === 'true' ? true : false
|
||||
}
|
||||
|
||||
const chatPrompt = ChatPromptTemplate.fromMessages([
|
||||
SystemMessagePromptTemplate.fromTemplate(prompt ? `${prompt}\n${systemMessage}` : systemMessage),
|
||||
new MessagesPlaceholder(memory.memoryKey ?? 'chat_history'),
|
||||
HumanMessagePromptTemplate.fromTemplate('{input}')
|
||||
])
|
||||
obj.prompt = chatPrompt
|
||||
|
||||
const chain = new ConversationChain(obj)
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
const chain = prepareChain(nodeData, this.sessionId, options.chatHistory)
|
||||
return chain
|
||||
}
|
||||
|
||||
async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<string> {
|
||||
const chain = nodeData.instance as ConversationChain
|
||||
const memory = nodeData.inputs?.memory as BufferMemory
|
||||
memory.returnMessages = true // Return true for BaseChatModel
|
||||
|
||||
if (options && options.chatHistory) {
|
||||
const chatHistoryClassName = memory.chatHistory.constructor.name
|
||||
// Only replace when its In-Memory
|
||||
if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') {
|
||||
memory.chatHistory = mapChatHistory(options)
|
||||
}
|
||||
}
|
||||
|
||||
chain.memory = memory
|
||||
const memory = nodeData.inputs?.memory
|
||||
const chain = prepareChain(nodeData, this.sessionId, options.chatHistory)
|
||||
|
||||
const loggerHandler = new ConsoleCallbackHandler(options.logger)
|
||||
const callbacks = await additionalCallbacks(nodeData, options)
|
||||
const additionalCallback = await additionalCallbacks(nodeData, options)
|
||||
|
||||
let res = ''
|
||||
let callbacks = [loggerHandler, ...additionalCallback]
|
||||
|
||||
if (process.env.DEBUG === 'true') {
|
||||
callbacks.push(new LCConsoleCallbackHandler())
|
||||
}
|
||||
|
||||
if (options.socketIO && options.socketIOClientId) {
|
||||
const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId)
|
||||
const res = await chain.call({ input }, [loggerHandler, handler, ...callbacks])
|
||||
return res?.response
|
||||
callbacks.push(handler)
|
||||
res = await chain.invoke({ input }, { callbacks })
|
||||
} else {
|
||||
const res = await chain.call({ input }, [loggerHandler, ...callbacks])
|
||||
return res?.response
|
||||
res = await chain.invoke({ input }, { callbacks })
|
||||
}
|
||||
|
||||
await memory.addChatMessages(
|
||||
[
|
||||
{
|
||||
text: input,
|
||||
type: 'userMessage'
|
||||
},
|
||||
{
|
||||
text: res,
|
||||
type: 'apiMessage'
|
||||
}
|
||||
],
|
||||
this.sessionId
|
||||
)
|
||||
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
const prepareChatPrompt = (nodeData: INodeData) => {
|
||||
const memory = nodeData.inputs?.memory as FlowiseMemory
|
||||
const prompt = nodeData.inputs?.systemMessagePrompt as string
|
||||
const chatPromptTemplate = nodeData.inputs?.chatPromptTemplate as ChatPromptTemplate
|
||||
|
||||
if (chatPromptTemplate && chatPromptTemplate.promptMessages.length) {
|
||||
const sysPrompt = chatPromptTemplate.promptMessages[0]
|
||||
const humanPrompt = chatPromptTemplate.promptMessages[chatPromptTemplate.promptMessages.length - 1]
|
||||
const chatPrompt = ChatPromptTemplate.fromMessages([
|
||||
sysPrompt,
|
||||
new MessagesPlaceholder(memory.memoryKey ?? 'chat_history'),
|
||||
humanPrompt
|
||||
])
|
||||
|
||||
if ((chatPromptTemplate as any).promptValues) {
|
||||
// @ts-ignore
|
||||
chatPrompt.promptValues = (chatPromptTemplate as any).promptValues
|
||||
}
|
||||
|
||||
return chatPrompt
|
||||
}
|
||||
|
||||
const chatPrompt = ChatPromptTemplate.fromMessages([
|
||||
SystemMessagePromptTemplate.fromTemplate(prompt ? prompt : systemMessage),
|
||||
new MessagesPlaceholder(memory.memoryKey ?? 'chat_history'),
|
||||
HumanMessagePromptTemplate.fromTemplate(`{${inputKey}}`)
|
||||
])
|
||||
|
||||
return chatPrompt
|
||||
}
|
||||
|
||||
const prepareChain = (nodeData: INodeData, sessionId?: string, chatHistory: IMessage[] = []) => {
|
||||
const model = nodeData.inputs?.model as BaseChatModel
|
||||
const memory = nodeData.inputs?.memory as FlowiseMemory
|
||||
const memoryKey = memory.memoryKey ?? 'chat_history'
|
||||
|
||||
const chatPrompt = prepareChatPrompt(nodeData)
|
||||
let promptVariables = {}
|
||||
const promptValuesRaw = (chatPrompt as any).promptValues
|
||||
if (promptValuesRaw) {
|
||||
const promptValues = handleEscapeCharacters(promptValuesRaw, true)
|
||||
for (const val in promptValues) {
|
||||
promptVariables = {
|
||||
...promptVariables,
|
||||
[val]: () => {
|
||||
return promptValues[val]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const conversationChain = RunnableSequence.from([
|
||||
{
|
||||
[inputKey]: (input: { input: string }) => input.input,
|
||||
[memoryKey]: async () => {
|
||||
const history = await memory.getChatMessages(sessionId, true, chatHistory)
|
||||
return history
|
||||
},
|
||||
...promptVariables
|
||||
},
|
||||
chatPrompt,
|
||||
model,
|
||||
new StringOutputParser()
|
||||
])
|
||||
|
||||
return conversationChain
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: ConversationChain_Chains }
|
||||
|
||||
+275
-116
@@ -1,20 +1,26 @@
|
||||
import { BaseLanguageModel } from 'langchain/base_language'
|
||||
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, mapChatHistory } from '../../../src/utils'
|
||||
import { ConversationalRetrievalQAChain, QAChainParams } from 'langchain/chains'
|
||||
import { ConversationalRetrievalQAChain } from 'langchain/chains'
|
||||
import { BaseRetriever } from 'langchain/schema/retriever'
|
||||
import { BufferMemory, BufferMemoryInput } from 'langchain/memory'
|
||||
import { BufferMemoryInput } from 'langchain/memory'
|
||||
import { PromptTemplate } from 'langchain/prompts'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
|
||||
import {
|
||||
default_map_reduce_template,
|
||||
default_qa_template,
|
||||
qa_template,
|
||||
map_reduce_template,
|
||||
CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT,
|
||||
refine_question_template,
|
||||
refine_template
|
||||
} from './prompts'
|
||||
import { QA_TEMPLATE, REPHRASE_TEMPLATE, RESPONSE_TEMPLATE } from './prompts'
|
||||
import { Runnable, RunnableSequence, RunnableMap, RunnableBranch, RunnableLambda } from 'langchain/schema/runnable'
|
||||
import { BaseMessage, HumanMessage, AIMessage } from 'langchain/schema'
|
||||
import { StringOutputParser } from 'langchain/schema/output_parser'
|
||||
import type { Document } from 'langchain/document'
|
||||
import { ChatPromptTemplate, MessagesPlaceholder } from 'langchain/prompts'
|
||||
import { applyPatch } from 'fast-json-patch'
|
||||
import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils'
|
||||
import { ConsoleCallbackHandler, additionalCallbacks } from '../../../src/handler'
|
||||
import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, MemoryMethods } from '../../../src/Interface'
|
||||
import { ConsoleCallbackHandler as LCConsoleCallbackHandler } from '@langchain/core/tracers/console'
|
||||
|
||||
type RetrievalChainInput = {
|
||||
chat_history: string
|
||||
question: string
|
||||
}
|
||||
|
||||
const sourceRunnableName = 'FindDocs'
|
||||
|
||||
class ConversationalRetrievalQAChain_Chains implements INode {
|
||||
label: string
|
||||
@@ -26,11 +32,12 @@ class ConversationalRetrievalQAChain_Chains implements INode {
|
||||
baseClasses: string[]
|
||||
description: string
|
||||
inputs: INodeParams[]
|
||||
sessionId?: string
|
||||
|
||||
constructor() {
|
||||
constructor(fields?: { sessionId?: string }) {
|
||||
this.label = 'Conversational Retrieval QA Chain'
|
||||
this.name = 'conversationalRetrievalQAChain'
|
||||
this.version = 1.0
|
||||
this.version = 2.0
|
||||
this.type = 'ConversationalRetrievalQAChain'
|
||||
this.icon = 'qa.svg'
|
||||
this.category = 'Chains'
|
||||
@@ -38,9 +45,9 @@ class ConversationalRetrievalQAChain_Chains implements INode {
|
||||
this.baseClasses = [this.type, ...getBaseClasses(ConversationalRetrievalQAChain)]
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Language Model',
|
||||
label: 'Chat Model',
|
||||
name: 'model',
|
||||
type: 'BaseLanguageModel'
|
||||
type: 'BaseChatModel'
|
||||
},
|
||||
{
|
||||
label: 'Vector Store Retriever',
|
||||
@@ -60,6 +67,29 @@ class ConversationalRetrievalQAChain_Chains implements INode {
|
||||
type: 'boolean',
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Rephrase Prompt',
|
||||
name: 'rephrasePrompt',
|
||||
type: 'string',
|
||||
description: 'Using previous chat history, rephrase question into a standalone question',
|
||||
warning: 'Prompt must include input variables: {chat_history} and {question}',
|
||||
rows: 4,
|
||||
additionalParams: true,
|
||||
optional: true,
|
||||
default: REPHRASE_TEMPLATE
|
||||
},
|
||||
{
|
||||
label: 'Response Prompt',
|
||||
name: 'responsePrompt',
|
||||
type: 'string',
|
||||
description: 'Taking the rephrased question, search for answer from the provided context',
|
||||
warning: 'Prompt must include input variable: {context}',
|
||||
rows: 4,
|
||||
additionalParams: true,
|
||||
optional: true,
|
||||
default: RESPONSE_TEMPLATE
|
||||
}
|
||||
/** Deprecated
|
||||
{
|
||||
label: 'System Message',
|
||||
name: 'systemMessagePrompt',
|
||||
@@ -70,6 +100,7 @@ class ConversationalRetrievalQAChain_Chains implements INode {
|
||||
placeholder:
|
||||
'I want you to act as a document that I am having a conversation with. Your name is "AI Assistant". You will provide me with answers from the given info. If the answer is not included, say exactly "Hmm, I am not sure." and stop after that. Refuse to answer any question not about the info. Never break character.'
|
||||
},
|
||||
// TODO: create standalone chains for these 3 modes as they are not compatible with memory
|
||||
{
|
||||
label: 'Chain Option',
|
||||
name: 'chainOption',
|
||||
@@ -95,124 +126,252 @@ class ConversationalRetrievalQAChain_Chains implements INode {
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
}
|
||||
*/
|
||||
]
|
||||
this.sessionId = fields?.sessionId
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData): Promise<any> {
|
||||
const model = nodeData.inputs?.model as BaseLanguageModel
|
||||
const vectorStoreRetriever = nodeData.inputs?.vectorStoreRetriever as BaseRetriever
|
||||
const systemMessagePrompt = nodeData.inputs?.systemMessagePrompt as string
|
||||
const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean
|
||||
const chainOption = nodeData.inputs?.chainOption as string
|
||||
const externalMemory = nodeData.inputs?.memory
|
||||
const rephrasePrompt = nodeData.inputs?.rephrasePrompt as string
|
||||
const responsePrompt = nodeData.inputs?.responsePrompt as string
|
||||
|
||||
const obj: any = {
|
||||
verbose: process.env.DEBUG === 'true' ? true : false,
|
||||
questionGeneratorChainOptions: {
|
||||
template: CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT
|
||||
}
|
||||
let customResponsePrompt = responsePrompt
|
||||
// If the deprecated systemMessagePrompt is still exists
|
||||
if (systemMessagePrompt) {
|
||||
customResponsePrompt = `${systemMessagePrompt}\n${QA_TEMPLATE}`
|
||||
}
|
||||
|
||||
if (returnSourceDocuments) obj.returnSourceDocuments = returnSourceDocuments
|
||||
|
||||
if (chainOption === 'map_reduce') {
|
||||
obj.qaChainOptions = {
|
||||
type: 'map_reduce',
|
||||
combinePrompt: PromptTemplate.fromTemplate(
|
||||
systemMessagePrompt ? `${systemMessagePrompt}\n${map_reduce_template}` : default_map_reduce_template
|
||||
)
|
||||
} as QAChainParams
|
||||
} else if (chainOption === 'refine') {
|
||||
const qprompt = new PromptTemplate({
|
||||
inputVariables: ['context', 'question'],
|
||||
template: refine_question_template(systemMessagePrompt)
|
||||
})
|
||||
const rprompt = new PromptTemplate({
|
||||
inputVariables: ['context', 'question', 'existing_answer'],
|
||||
template: refine_template
|
||||
})
|
||||
obj.qaChainOptions = {
|
||||
type: 'refine',
|
||||
questionPrompt: qprompt,
|
||||
refinePrompt: rprompt
|
||||
} as QAChainParams
|
||||
} else {
|
||||
obj.qaChainOptions = {
|
||||
type: 'stuff',
|
||||
prompt: PromptTemplate.fromTemplate(systemMessagePrompt ? `${systemMessagePrompt}\n${qa_template}` : default_qa_template)
|
||||
} as QAChainParams
|
||||
}
|
||||
|
||||
if (externalMemory) {
|
||||
externalMemory.memoryKey = 'chat_history'
|
||||
externalMemory.inputKey = 'question'
|
||||
externalMemory.outputKey = 'text'
|
||||
externalMemory.returnMessages = true
|
||||
if (chainOption === 'refine') externalMemory.outputKey = 'output_text'
|
||||
obj.memory = externalMemory
|
||||
} else {
|
||||
const fields: BufferMemoryInput = {
|
||||
memoryKey: 'chat_history',
|
||||
inputKey: 'question',
|
||||
outputKey: 'text',
|
||||
returnMessages: true
|
||||
}
|
||||
if (chainOption === 'refine') fields.outputKey = 'output_text'
|
||||
obj.memory = new BufferMemory(fields)
|
||||
}
|
||||
|
||||
const chain = ConversationalRetrievalQAChain.fromLLM(model, vectorStoreRetriever, obj)
|
||||
return chain
|
||||
const answerChain = createChain(model, vectorStoreRetriever, rephrasePrompt, customResponsePrompt)
|
||||
return answerChain
|
||||
}
|
||||
|
||||
async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<string | ICommonObject> {
|
||||
const chain = nodeData.instance as ConversationalRetrievalQAChain
|
||||
const model = nodeData.inputs?.model as BaseLanguageModel
|
||||
const externalMemory = nodeData.inputs?.memory
|
||||
const vectorStoreRetriever = nodeData.inputs?.vectorStoreRetriever as BaseRetriever
|
||||
const systemMessagePrompt = nodeData.inputs?.systemMessagePrompt as string
|
||||
const rephrasePrompt = nodeData.inputs?.rephrasePrompt as string
|
||||
const responsePrompt = nodeData.inputs?.responsePrompt as string
|
||||
const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean
|
||||
const chainOption = nodeData.inputs?.chainOption as string
|
||||
|
||||
let model = nodeData.inputs?.model
|
||||
|
||||
// Temporary fix: https://github.com/hwchase17/langchainjs/issues/754
|
||||
model.streaming = false
|
||||
chain.questionGeneratorChain.llm = model
|
||||
|
||||
const obj = { question: input }
|
||||
|
||||
if (options && options.chatHistory && chain.memory) {
|
||||
const chatHistoryClassName = (chain.memory as any).chatHistory.constructor.name
|
||||
// Only replace when its In-Memory
|
||||
if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') {
|
||||
;(chain.memory as any).chatHistory = mapChatHistory(options)
|
||||
}
|
||||
let customResponsePrompt = responsePrompt
|
||||
// If the deprecated systemMessagePrompt is still exists
|
||||
if (systemMessagePrompt) {
|
||||
customResponsePrompt = `${systemMessagePrompt}\n${QA_TEMPLATE}`
|
||||
}
|
||||
|
||||
let memory: FlowiseMemory | undefined = externalMemory
|
||||
if (!memory) {
|
||||
memory = new BufferMemory({
|
||||
returnMessages: true,
|
||||
memoryKey: 'chat_history',
|
||||
inputKey: 'input'
|
||||
})
|
||||
}
|
||||
|
||||
const answerChain = createChain(model, vectorStoreRetriever, rephrasePrompt, customResponsePrompt)
|
||||
|
||||
const history = ((await memory.getChatMessages(this.sessionId, false, options.chatHistory)) as IMessage[]) ?? []
|
||||
|
||||
const loggerHandler = new ConsoleCallbackHandler(options.logger)
|
||||
const callbacks = await additionalCallbacks(nodeData, options)
|
||||
const additionalCallback = await additionalCallbacks(nodeData, options)
|
||||
|
||||
if (options.socketIO && options.socketIOClientId) {
|
||||
const handler = new CustomChainHandler(
|
||||
options.socketIO,
|
||||
options.socketIOClientId,
|
||||
chainOption === 'refine' ? 4 : undefined,
|
||||
returnSourceDocuments
|
||||
)
|
||||
const res = await chain.call(obj, [loggerHandler, handler, ...callbacks])
|
||||
if (chainOption === 'refine') {
|
||||
if (res.output_text && res.sourceDocuments) {
|
||||
return {
|
||||
text: res.output_text,
|
||||
sourceDocuments: res.sourceDocuments
|
||||
}
|
||||
}
|
||||
return res?.output_text
|
||||
}
|
||||
if (res.text && res.sourceDocuments) return res
|
||||
return res?.text
|
||||
} else {
|
||||
const res = await chain.call(obj, [loggerHandler, ...callbacks])
|
||||
if (res.text && res.sourceDocuments) return res
|
||||
return res?.text
|
||||
let callbacks = [loggerHandler, ...additionalCallback]
|
||||
|
||||
if (process.env.DEBUG === 'true') {
|
||||
callbacks.push(new LCConsoleCallbackHandler())
|
||||
}
|
||||
|
||||
const stream = answerChain.streamLog(
|
||||
{ question: input, chat_history: history },
|
||||
{ callbacks },
|
||||
{
|
||||
includeNames: [sourceRunnableName]
|
||||
}
|
||||
)
|
||||
|
||||
let streamedResponse: Record<string, any> = {}
|
||||
let sourceDocuments: ICommonObject[] = []
|
||||
let text = ''
|
||||
let isStreamingStarted = false
|
||||
const isStreamingEnabled = options.socketIO && options.socketIOClientId
|
||||
|
||||
for await (const chunk of stream) {
|
||||
streamedResponse = applyPatch(streamedResponse, chunk.ops).newDocument
|
||||
|
||||
if (streamedResponse.final_output) {
|
||||
text = streamedResponse.final_output?.output
|
||||
if (isStreamingEnabled) options.socketIO.to(options.socketIOClientId).emit('end')
|
||||
if (Array.isArray(streamedResponse?.logs?.[sourceRunnableName]?.final_output?.output)) {
|
||||
sourceDocuments = streamedResponse?.logs?.[sourceRunnableName]?.final_output?.output
|
||||
if (isStreamingEnabled && returnSourceDocuments)
|
||||
options.socketIO.to(options.socketIOClientId).emit('sourceDocuments', sourceDocuments)
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
Array.isArray(streamedResponse?.streamed_output) &&
|
||||
streamedResponse?.streamed_output.length &&
|
||||
!streamedResponse.final_output
|
||||
) {
|
||||
const token = streamedResponse.streamed_output[streamedResponse.streamed_output.length - 1]
|
||||
|
||||
if (!isStreamingStarted) {
|
||||
isStreamingStarted = true
|
||||
if (isStreamingEnabled) options.socketIO.to(options.socketIOClientId).emit('start', token)
|
||||
}
|
||||
if (isStreamingEnabled) options.socketIO.to(options.socketIOClientId).emit('token', token)
|
||||
}
|
||||
}
|
||||
|
||||
await memory.addChatMessages(
|
||||
[
|
||||
{
|
||||
text: input,
|
||||
type: 'userMessage'
|
||||
},
|
||||
{
|
||||
text: text,
|
||||
type: 'apiMessage'
|
||||
}
|
||||
],
|
||||
this.sessionId
|
||||
)
|
||||
|
||||
if (returnSourceDocuments) return { text, sourceDocuments }
|
||||
else return { text }
|
||||
}
|
||||
}
|
||||
|
||||
const createRetrieverChain = (llm: BaseLanguageModel, retriever: Runnable, rephrasePrompt: string) => {
|
||||
// Small speed/accuracy optimization: no need to rephrase the first question
|
||||
// since there shouldn't be any meta-references to prior chat history
|
||||
const CONDENSE_QUESTION_PROMPT = PromptTemplate.fromTemplate(rephrasePrompt)
|
||||
const condenseQuestionChain = RunnableSequence.from([CONDENSE_QUESTION_PROMPT, llm, new StringOutputParser()]).withConfig({
|
||||
runName: 'CondenseQuestion'
|
||||
})
|
||||
|
||||
const hasHistoryCheckFn = RunnableLambda.from((input: RetrievalChainInput) => input.chat_history.length > 0).withConfig({
|
||||
runName: 'HasChatHistoryCheck'
|
||||
})
|
||||
|
||||
const conversationChain = condenseQuestionChain.pipe(retriever).withConfig({
|
||||
runName: 'RetrievalChainWithHistory'
|
||||
})
|
||||
|
||||
const basicRetrievalChain = RunnableLambda.from((input: RetrievalChainInput) => input.question)
|
||||
.withConfig({
|
||||
runName: 'Itemgetter:question'
|
||||
})
|
||||
.pipe(retriever)
|
||||
.withConfig({ runName: 'RetrievalChainWithNoHistory' })
|
||||
|
||||
return RunnableBranch.from([[hasHistoryCheckFn, conversationChain], basicRetrievalChain]).withConfig({ runName: sourceRunnableName })
|
||||
}
|
||||
|
||||
const formatDocs = (docs: Document[]) => {
|
||||
return docs.map((doc, i) => `<doc id='${i}'>${doc.pageContent}</doc>`).join('\n')
|
||||
}
|
||||
|
||||
const formatChatHistoryAsString = (history: BaseMessage[]) => {
|
||||
return history.map((message) => `${message._getType()}: ${message.content}`).join('\n')
|
||||
}
|
||||
|
||||
const serializeHistory = (input: any) => {
|
||||
const chatHistory: IMessage[] = input.chat_history || []
|
||||
const convertedChatHistory = []
|
||||
for (const message of chatHistory) {
|
||||
if (message.type === 'userMessage') {
|
||||
convertedChatHistory.push(new HumanMessage({ content: message.message }))
|
||||
}
|
||||
if (message.type === 'apiMessage') {
|
||||
convertedChatHistory.push(new AIMessage({ content: message.message }))
|
||||
}
|
||||
}
|
||||
return convertedChatHistory
|
||||
}
|
||||
|
||||
const createChain = (
|
||||
llm: BaseLanguageModel,
|
||||
retriever: Runnable,
|
||||
rephrasePrompt = REPHRASE_TEMPLATE,
|
||||
responsePrompt = RESPONSE_TEMPLATE
|
||||
) => {
|
||||
const retrieverChain = createRetrieverChain(llm, retriever, rephrasePrompt)
|
||||
|
||||
const context = RunnableMap.from({
|
||||
context: RunnableSequence.from([
|
||||
({ question, chat_history }) => ({
|
||||
question,
|
||||
chat_history: formatChatHistoryAsString(chat_history)
|
||||
}),
|
||||
retrieverChain,
|
||||
RunnableLambda.from(formatDocs).withConfig({
|
||||
runName: 'FormatDocumentChunks'
|
||||
})
|
||||
]),
|
||||
question: RunnableLambda.from((input: RetrievalChainInput) => input.question).withConfig({
|
||||
runName: 'Itemgetter:question'
|
||||
}),
|
||||
chat_history: RunnableLambda.from((input: RetrievalChainInput) => input.chat_history).withConfig({
|
||||
runName: 'Itemgetter:chat_history'
|
||||
})
|
||||
}).withConfig({ tags: ['RetrieveDocs'] })
|
||||
|
||||
const prompt = ChatPromptTemplate.fromMessages([
|
||||
['system', responsePrompt],
|
||||
new MessagesPlaceholder('chat_history'),
|
||||
['human', `{question}`]
|
||||
])
|
||||
|
||||
const responseSynthesizerChain = RunnableSequence.from([prompt, llm, new StringOutputParser()]).withConfig({
|
||||
tags: ['GenerateResponse']
|
||||
})
|
||||
|
||||
const conversationalQAChain = RunnableSequence.from([
|
||||
{
|
||||
question: RunnableLambda.from((input: RetrievalChainInput) => input.question).withConfig({
|
||||
runName: 'Itemgetter:question'
|
||||
}),
|
||||
chat_history: RunnableLambda.from(serializeHistory).withConfig({
|
||||
runName: 'SerializeHistory'
|
||||
})
|
||||
},
|
||||
context,
|
||||
responseSynthesizerChain
|
||||
])
|
||||
|
||||
return conversationalQAChain
|
||||
}
|
||||
|
||||
class BufferMemory extends FlowiseMemory implements MemoryMethods {
|
||||
constructor(fields: BufferMemoryInput) {
|
||||
super(fields)
|
||||
}
|
||||
|
||||
async getChatMessages(_?: string, returnBaseMessages = false, prevHistory: IMessage[] = []): Promise<IMessage[] | BaseMessage[]> {
|
||||
await this.chatHistory.clear()
|
||||
|
||||
for (const msg of prevHistory) {
|
||||
if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message)
|
||||
else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message)
|
||||
}
|
||||
|
||||
const memoryResult = await this.loadMemoryVariables({})
|
||||
const baseMessages = memoryResult[this.memoryKey ?? 'chat_history']
|
||||
return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages)
|
||||
}
|
||||
|
||||
async addChatMessages(): Promise<void> {
|
||||
// adding chat messages will be done on the fly in getChatMessages()
|
||||
return
|
||||
}
|
||||
|
||||
async clearChatMessages(): Promise<void> {
|
||||
await this.clear()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,64 +1,27 @@
|
||||
export const default_qa_template = `Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer.
|
||||
|
||||
{context}
|
||||
|
||||
Question: {question}
|
||||
Helpful Answer:`
|
||||
|
||||
export const qa_template = `Use the following pieces of context to answer the question at the end.
|
||||
|
||||
{context}
|
||||
|
||||
Question: {question}
|
||||
Helpful Answer:`
|
||||
|
||||
export const default_map_reduce_template = `Given the following extracted parts of a long document and a question, create a final answer.
|
||||
If you don't know the answer, just say that you don't know. Don't try to make up an answer.
|
||||
|
||||
{summaries}
|
||||
|
||||
Question: {question}
|
||||
Helpful Answer:`
|
||||
|
||||
export const map_reduce_template = `Given the following extracted parts of a long document and a question, create a final answer.
|
||||
|
||||
{summaries}
|
||||
|
||||
Question: {question}
|
||||
Helpful Answer:`
|
||||
|
||||
export const refine_question_template = (sysPrompt?: string) => {
|
||||
let returnPrompt = ''
|
||||
if (sysPrompt)
|
||||
returnPrompt = `Context information is below.
|
||||
---------------------
|
||||
{context}
|
||||
---------------------
|
||||
Given the context information and not prior knowledge, ${sysPrompt}
|
||||
Answer the question: {question}.
|
||||
Answer:`
|
||||
if (!sysPrompt)
|
||||
returnPrompt = `Context information is below.
|
||||
---------------------
|
||||
{context}
|
||||
---------------------
|
||||
Given the context information and not prior knowledge, answer the question: {question}.
|
||||
Answer:`
|
||||
return returnPrompt
|
||||
}
|
||||
|
||||
export const refine_template = `The original question is as follows: {question}
|
||||
We have provided an existing answer: {existing_answer}
|
||||
We have the opportunity to refine the existing answer (only if needed) with some more context below.
|
||||
------------
|
||||
{context}
|
||||
------------
|
||||
Given the new context, refine the original answer to better answer the question.
|
||||
If you can't find answer from the context, return the original answer.`
|
||||
|
||||
export const CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT = `Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, answer in the same language as the follow up question. include it in the standalone question.
|
||||
|
||||
Chat History:
|
||||
{chat_history}
|
||||
Follow Up Input: {question}
|
||||
Standalone question:`
|
||||
|
||||
export const RESPONSE_TEMPLATE = `I want you to act as a document that I am having a conversation with. Your name is "AI Assistant". Using the provided context, answer the user's question to the best of your ability using the resources provided.
|
||||
If there is nothing in the context relevant to the question at hand, just say "Hmm, I'm not sure" and stop after that. Refuse to answer any question not about the info. Never break character.
|
||||
------------
|
||||
{context}
|
||||
------------
|
||||
REMEMBER: If there is no relevant information within the context, just say "Hmm, I'm not sure". Don't try to make up an answer. Never break character.`
|
||||
|
||||
export const QA_TEMPLATE = `Use the following pieces of context to answer the question at the end.
|
||||
|
||||
{context}
|
||||
|
||||
Question: {question}
|
||||
Helpful Answer:`
|
||||
|
||||
export const REPHRASE_TEMPLATE = `Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.
|
||||
|
||||
Chat History:
|
||||
{chat_history}
|
||||
Follow Up Input: {question}
|
||||
Standalone Question:`
|
||||
|
||||
@@ -82,7 +82,7 @@ class LLMChain_Chains implements INode {
|
||||
const model = nodeData.inputs?.model as BaseLanguageModel
|
||||
const prompt = nodeData.inputs?.prompt
|
||||
const output = nodeData.outputs?.output as string
|
||||
const promptValues = prompt.promptValues as ICommonObject
|
||||
let promptValues: ICommonObject | undefined = nodeData.inputs?.prompt.promptValues as ICommonObject
|
||||
const llmOutputParser = nodeData.inputs?.outputParser as BaseOutputParser
|
||||
this.outputParser = llmOutputParser
|
||||
if (llmOutputParser) {
|
||||
@@ -107,17 +107,24 @@ class LLMChain_Chains implements INode {
|
||||
verbose: process.env.DEBUG === 'true'
|
||||
})
|
||||
const inputVariables = chain.prompt.inputVariables as string[] // ["product"]
|
||||
promptValues = injectOutputParser(this.outputParser, chain, promptValues)
|
||||
const res = await runPrediction(inputVariables, chain, input, promptValues, options, nodeData)
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('\x1b[92m\x1b[1m\n*****OUTPUT PREDICTION*****\n\x1b[0m\x1b[0m')
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(res)
|
||||
|
||||
let finalRes = res
|
||||
if (this.outputParser && typeof res === 'object' && Object.prototype.hasOwnProperty.call(res, 'json')) {
|
||||
finalRes = (res as ICommonObject).json
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply string transformation to convert special chars:
|
||||
* FROM: hello i am ben\n\n\thow are you?
|
||||
* TO: hello i am benFLOWISE_NEWLINEFLOWISE_NEWLINEFLOWISE_TABhow are you?
|
||||
*/
|
||||
return handleEscapeCharacters(res, false)
|
||||
return handleEscapeCharacters(finalRes, false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,22 +69,23 @@ class VectaraChain_Chains implements INode {
|
||||
options: [
|
||||
{
|
||||
label: 'vectara-summary-ext-v1.2.0 (gpt-3.5-turbo)',
|
||||
name: 'vectara-summary-ext-v1.2.0'
|
||||
name: 'vectara-summary-ext-v1.2.0',
|
||||
description: 'base summarizer, available to all Vectara users'
|
||||
},
|
||||
{
|
||||
label: 'vectara-experimental-summary-ext-2023-10-23-small (gpt-3.5-turbo)',
|
||||
name: 'vectara-experimental-summary-ext-2023-10-23-small',
|
||||
description: 'In beta, available to both Growth and Scale Vectara users'
|
||||
description: `In beta, available to both Growth and <a target="_blank" href="https://vectara.com/pricing/">Scale</a> Vectara users`
|
||||
},
|
||||
{
|
||||
label: 'vectara-summary-ext-v1.3.0 (gpt-4.0)',
|
||||
name: 'vectara-summary-ext-v1.3.0',
|
||||
description: 'Only available to paying Scale Vectara users'
|
||||
description: 'Only available to <a target="_blank" href="https://vectara.com/pricing/">Scale</a> Vectara users'
|
||||
},
|
||||
{
|
||||
label: 'vectara-experimental-summary-ext-2023-10-23-med (gpt-4.0)',
|
||||
name: 'vectara-experimental-summary-ext-2023-10-23-med',
|
||||
description: 'In beta, only available to paying Scale Vectara users'
|
||||
description: `In beta, only available to <a target="_blank" href="https://vectara.com/pricing/">Scale</a> Vectara users`
|
||||
}
|
||||
],
|
||||
default: 'vectara-summary-ext-v1.2.0'
|
||||
@@ -228,7 +229,7 @@ class VectaraChain_Chains implements INode {
|
||||
|
||||
async run(nodeData: INodeData, input: string): Promise<object> {
|
||||
const vectorStore = nodeData.inputs?.vectaraStore as VectaraStore
|
||||
const responseLang = (nodeData.inputs?.responseLang as string) ?? 'auto'
|
||||
const responseLang = (nodeData.inputs?.responseLang as string) ?? 'eng'
|
||||
const summarizerPromptName = nodeData.inputs?.summarizerPromptName as string
|
||||
const maxSummarizedResultsStr = nodeData.inputs?.maxSummarizedResults as string
|
||||
const maxSummarizedResults = maxSummarizedResultsStr ? parseInt(maxSummarizedResultsStr, 10) : 7
|
||||
@@ -247,17 +248,31 @@ class VectaraChain_Chains implements INode {
|
||||
lexicalInterpolationConfig: { lambda: vectaraFilter?.lambda ?? 0.025 }
|
||||
}))
|
||||
|
||||
// Vectara reranker ID for MMR (https://docs.vectara.com/docs/api-reference/search-apis/reranking#maximal-marginal-relevance-mmr-reranker)
|
||||
const mmrRerankerId = 272725718
|
||||
const mmrEnabled = vectaraFilter?.mmrConfig?.enabled
|
||||
|
||||
const data = {
|
||||
query: [
|
||||
{
|
||||
query: input,
|
||||
start: 0,
|
||||
numResults: topK,
|
||||
numResults: mmrEnabled ? vectaraFilter?.mmrTopK : topK,
|
||||
corpusKey: corpusKeys,
|
||||
contextConfig: {
|
||||
sentencesAfter: vectaraFilter?.contextConfig?.sentencesAfter ?? 2,
|
||||
sentencesBefore: vectaraFilter?.contextConfig?.sentencesBefore ?? 2
|
||||
},
|
||||
corpusKey: corpusKeys,
|
||||
...(mmrEnabled
|
||||
? {
|
||||
rerankingConfig: {
|
||||
rerankerId: mmrRerankerId,
|
||||
mmrConfig: {
|
||||
diversityBias: vectaraFilter?.mmrConfig.diversityBias
|
||||
}
|
||||
}
|
||||
}
|
||||
: {}),
|
||||
summary: [
|
||||
{
|
||||
summarizerPromptName,
|
||||
@@ -285,6 +300,14 @@ class VectaraChain_Chains implements INode {
|
||||
const documents = result.responseSet[0].document
|
||||
let rawSummarizedText = ''
|
||||
|
||||
// remove responses that are not in the topK (in case of MMR)
|
||||
// Note that this does not really matter functionally due to the reorder citations, but it is more efficient
|
||||
const maxResponses = mmrEnabled ? Math.min(responses.length, topK) : responses.length
|
||||
if (responses.length > maxResponses) {
|
||||
responses.splice(0, maxResponses)
|
||||
}
|
||||
|
||||
// Add metadata to each text response given its corresponding document metadata
|
||||
for (let i = 0; i < responses.length; i += 1) {
|
||||
const responseMetadata = responses[i].metadata
|
||||
const documentMetadata = documents[responses[i].documentIndex].metadata
|
||||
@@ -301,13 +324,13 @@ class VectaraChain_Chains implements INode {
|
||||
responses[i].metadata = combinedMetadata
|
||||
}
|
||||
|
||||
// Create the summarization response
|
||||
const summaryStatus = result.responseSet[0].summary[0].status
|
||||
if (summaryStatus.length > 0 && summaryStatus[0].code === 'BAD_REQUEST') {
|
||||
throw new Error(
|
||||
`BAD REQUEST: Too much text for the summarizer to summarize. Please try reducing the number of search results to summarize, or the context of each result by adjusting the 'summary_num_sentences', and 'summary_num_results' parameters respectively.`
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
summaryStatus.length > 0 &&
|
||||
summaryStatus[0].code === 'NOT_FOUND' &&
|
||||
@@ -316,8 +339,8 @@ class VectaraChain_Chains implements INode {
|
||||
throw new Error(`BAD REQUEST: summarizer ${summarizerPromptName} is invalid for this account.`)
|
||||
}
|
||||
|
||||
// Reorder citations in summary and create the list of returned source documents
|
||||
rawSummarizedText = result.responseSet[0].summary[0]?.text
|
||||
|
||||
let summarizedText = reorderCitations(rawSummarizedText)
|
||||
let summaryResponses = applyCitationOrder(responses, rawSummarizedText)
|
||||
|
||||
|
||||
+90
-3
@@ -1,7 +1,9 @@
|
||||
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { convertMultiOptionsToStringArray, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { BaseCache } from 'langchain/schema'
|
||||
import { ChatGoogleGenerativeAI } from '@langchain/google-genai'
|
||||
import { ChatGoogleGenerativeAI, GoogleGenerativeAIChatInput } from '@langchain/google-genai'
|
||||
import { HarmBlockThreshold, HarmCategory } from '@google/generative-ai'
|
||||
import type { SafetySetting } from '@google/generative-ai'
|
||||
|
||||
class GoogleGenerativeAI_ChatModels implements INode {
|
||||
label: string
|
||||
@@ -74,6 +76,73 @@ class GoogleGenerativeAI_ChatModels implements INode {
|
||||
step: 0.1,
|
||||
optional: true,
|
||||
additionalParams: true
|
||||
},
|
||||
{
|
||||
label: 'Top Next Highest Probability Tokens',
|
||||
name: 'topK',
|
||||
type: 'number',
|
||||
description: `Decode using top-k sampling: consider the set of top_k most probable tokens. Must be positive`,
|
||||
step: 1,
|
||||
optional: true,
|
||||
additionalParams: true
|
||||
},
|
||||
{
|
||||
label: 'Harm Category',
|
||||
name: 'harmCategory',
|
||||
type: 'multiOptions',
|
||||
description:
|
||||
'Refer to <a target="_blank" href="https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/configure-safety-attributes#safety_attribute_definitions">official guide</a> on how to use Harm Category',
|
||||
options: [
|
||||
{
|
||||
label: 'Dangerous',
|
||||
name: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT
|
||||
},
|
||||
{
|
||||
label: 'Harassment',
|
||||
name: HarmCategory.HARM_CATEGORY_HARASSMENT
|
||||
},
|
||||
{
|
||||
label: 'Hate Speech',
|
||||
name: HarmCategory.HARM_CATEGORY_HATE_SPEECH
|
||||
},
|
||||
{
|
||||
label: 'Sexually Explicit',
|
||||
name: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT
|
||||
}
|
||||
],
|
||||
optional: true,
|
||||
additionalParams: true
|
||||
},
|
||||
{
|
||||
label: 'Harm Block Threshold',
|
||||
name: 'harmBlockThreshold',
|
||||
type: 'multiOptions',
|
||||
description:
|
||||
'Refer to <a target="_blank" href="https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/configure-safety-attributes#safety_setting_thresholds">official guide</a> on how to use Harm Block Threshold',
|
||||
options: [
|
||||
{
|
||||
label: 'Low and Above',
|
||||
name: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE
|
||||
},
|
||||
{
|
||||
label: 'Medium and Above',
|
||||
name: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE
|
||||
},
|
||||
{
|
||||
label: 'None',
|
||||
name: HarmBlockThreshold.BLOCK_NONE
|
||||
},
|
||||
{
|
||||
label: 'Only High',
|
||||
name: HarmBlockThreshold.BLOCK_ONLY_HIGH
|
||||
},
|
||||
{
|
||||
label: 'Threshold Unspecified',
|
||||
name: HarmBlockThreshold.HARM_BLOCK_THRESHOLD_UNSPECIFIED
|
||||
}
|
||||
],
|
||||
optional: true,
|
||||
additionalParams: true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -86,9 +155,12 @@ class GoogleGenerativeAI_ChatModels implements INode {
|
||||
const modelName = nodeData.inputs?.modelName as string
|
||||
const maxOutputTokens = nodeData.inputs?.maxOutputTokens as string
|
||||
const topP = nodeData.inputs?.topP as string
|
||||
const topK = nodeData.inputs?.topK as string
|
||||
const harmCategory = nodeData.inputs?.harmCategory as string
|
||||
const harmBlockThreshold = nodeData.inputs?.harmBlockThreshold as string
|
||||
const cache = nodeData.inputs?.cache as BaseCache
|
||||
|
||||
const obj = {
|
||||
const obj: Partial<GoogleGenerativeAIChatInput> = {
|
||||
apiKey: apiKey,
|
||||
modelName: modelName,
|
||||
maxOutputTokens: 2048
|
||||
@@ -98,8 +170,23 @@ class GoogleGenerativeAI_ChatModels implements INode {
|
||||
|
||||
const model = new ChatGoogleGenerativeAI(obj)
|
||||
if (topP) model.topP = parseFloat(topP)
|
||||
if (topK) model.topK = parseFloat(topK)
|
||||
if (cache) model.cache = cache
|
||||
if (temperature) model.temperature = parseFloat(temperature)
|
||||
|
||||
// Safety Settings
|
||||
let harmCategories: string[] = convertMultiOptionsToStringArray(harmCategory)
|
||||
let harmBlockThresholds: string[] = convertMultiOptionsToStringArray(harmBlockThreshold)
|
||||
if (harmCategories.length != harmBlockThresholds.length)
|
||||
throw new Error(`Harm Category & Harm Block Threshold are not the same length`)
|
||||
const safetySettings: SafetySetting[] = harmCategories.map((harmCategory, index) => {
|
||||
return {
|
||||
category: harmCategory as HarmCategory,
|
||||
threshold: harmBlockThresholds[index] as HarmBlockThreshold
|
||||
}
|
||||
})
|
||||
if (safetySettings.length > 0) model.safetySettings = safetySettings
|
||||
|
||||
return model
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses } from '../../../src/utils'
|
||||
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { OpenAIChat } from 'langchain/llms/openai'
|
||||
import { OpenAIChatInput } from 'langchain/chat_models/openai'
|
||||
import { BaseCache } from 'langchain/schema'
|
||||
@@ -14,6 +14,7 @@ class ChatLocalAI_ChatModels implements INode {
|
||||
category: string
|
||||
description: string
|
||||
baseClasses: string[]
|
||||
credential: INodeParams
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
@@ -25,6 +26,13 @@ class ChatLocalAI_ChatModels implements INode {
|
||||
this.category = 'Chat Models'
|
||||
this.description = 'Use local LLMs like llama.cpp, gpt4all using LocalAI'
|
||||
this.baseClasses = [this.type, 'BaseChatModel', ...getBaseClasses(OpenAIChat)]
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['localAIApi'],
|
||||
optional: true
|
||||
}
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Cache',
|
||||
@@ -79,13 +87,16 @@ class ChatLocalAI_ChatModels implements INode {
|
||||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData): Promise<any> {
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
const temperature = nodeData.inputs?.temperature as string
|
||||
const modelName = nodeData.inputs?.modelName as string
|
||||
const maxTokens = nodeData.inputs?.maxTokens as string
|
||||
const topP = nodeData.inputs?.topP as string
|
||||
const timeout = nodeData.inputs?.timeout as string
|
||||
const basePath = nodeData.inputs?.basePath as string
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const localAIApiKey = getCredentialParam('localAIApiKey', credentialData, nodeData)
|
||||
|
||||
const cache = nodeData.inputs?.cache as BaseCache
|
||||
|
||||
const obj: Partial<OpenAIChatInput> & BaseLLMParams & { openAIApiKey?: string } = {
|
||||
@@ -98,6 +109,7 @@ class ChatLocalAI_ChatModels implements INode {
|
||||
if (topP) obj.topP = parseFloat(topP)
|
||||
if (timeout) obj.timeout = parseInt(timeout, 10)
|
||||
if (cache) obj.cache = cache
|
||||
if (localAIApiKey) obj.openAIApiKey = localAIApiKey
|
||||
|
||||
const model = new OpenAIChat(obj, { basePath })
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getCredentialData, getCredentialParam } from '../../../src'
|
||||
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { FigmaFileLoader, FigmaLoaderParams } from 'langchain/document_loaders/web/figma'
|
||||
import { TextSplitter } from 'langchain/text_splitter'
|
||||
|
||||
class Figma_DocumentLoaders implements INode {
|
||||
label: string
|
||||
@@ -71,6 +72,8 @@ class Figma_DocumentLoaders implements INode {
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
const nodeIds = (nodeData.inputs?.nodeIds as string)?.trim().split(',') || []
|
||||
const fileKey = nodeData.inputs?.fileKey as string
|
||||
const textSplitter = nodeData.inputs?.textSplitter as TextSplitter
|
||||
const metadata = nodeData.inputs?.metadata
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const accessToken = getCredentialParam('accessToken', credentialData, nodeData)
|
||||
@@ -82,7 +85,21 @@ class Figma_DocumentLoaders implements INode {
|
||||
}
|
||||
|
||||
const loader = new FigmaFileLoader(figmaOptions)
|
||||
const docs = await loader.load()
|
||||
|
||||
const docs = textSplitter ? await loader.loadAndSplit() : await loader.load()
|
||||
|
||||
if (metadata) {
|
||||
const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata)
|
||||
return docs.map((doc) => {
|
||||
return {
|
||||
...doc,
|
||||
metadata: {
|
||||
...doc.metadata,
|
||||
...parsedMetadata
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return docs
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { OpenAIEmbeddings, OpenAIEmbeddingsParams } from 'langchain/embeddings/openai'
|
||||
|
||||
class LocalAIEmbedding_Embeddings implements INode {
|
||||
@@ -10,6 +11,7 @@ class LocalAIEmbedding_Embeddings implements INode {
|
||||
category: string
|
||||
description: string
|
||||
baseClasses: string[]
|
||||
credential: INodeParams
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
@@ -21,6 +23,13 @@ class LocalAIEmbedding_Embeddings implements INode {
|
||||
this.category = 'Embeddings'
|
||||
this.description = 'Use local embeddings models like llama.cpp'
|
||||
this.baseClasses = [this.type, 'Embeddings']
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['localAIApi'],
|
||||
optional: true
|
||||
}
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Base Path',
|
||||
@@ -37,15 +46,20 @@ class LocalAIEmbedding_Embeddings implements INode {
|
||||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData): Promise<any> {
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
const modelName = nodeData.inputs?.modelName as string
|
||||
const basePath = nodeData.inputs?.basePath as string
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const localAIApiKey = getCredentialParam('localAIApiKey', credentialData, nodeData)
|
||||
|
||||
const obj: Partial<OpenAIEmbeddingsParams> & { openAIApiKey?: string } = {
|
||||
modelName,
|
||||
openAIApiKey: 'sk-'
|
||||
}
|
||||
|
||||
if (localAIApiKey) obj.openAIApiKey = localAIApiKey
|
||||
|
||||
const model = new OpenAIEmbeddings(obj, { basePath })
|
||||
|
||||
return model
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FlowiseMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface'
|
||||
import { FlowiseMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods } from '../../../src/Interface'
|
||||
import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils'
|
||||
import { BufferMemory, BufferMemoryInput } from 'langchain/memory'
|
||||
import { BaseMessage } from 'langchain/schema'
|
||||
@@ -55,36 +55,27 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods {
|
||||
super(fields)
|
||||
}
|
||||
|
||||
async getChatMessages(_?: string, returnBaseMessages = false): Promise<IMessage[] | BaseMessage[]> {
|
||||
async getChatMessages(_?: string, returnBaseMessages = false, prevHistory: IMessage[] = []): Promise<IMessage[] | BaseMessage[]> {
|
||||
await this.chatHistory.clear()
|
||||
|
||||
for (const msg of prevHistory) {
|
||||
if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message)
|
||||
else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message)
|
||||
}
|
||||
|
||||
const memoryResult = await this.loadMemoryVariables({})
|
||||
const baseMessages = memoryResult[this.memoryKey ?? 'chat_history']
|
||||
return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages)
|
||||
}
|
||||
|
||||
async addChatMessages(msgArray: { text: string; type: MessageType }[]): Promise<void> {
|
||||
const input = msgArray.find((msg) => msg.type === 'userMessage')
|
||||
const output = msgArray.find((msg) => msg.type === 'apiMessage')
|
||||
|
||||
const inputValues = { [this.inputKey ?? 'input']: input?.text }
|
||||
const outputValues = { output: output?.text }
|
||||
|
||||
await this.saveContext(inputValues, outputValues)
|
||||
async addChatMessages(): Promise<void> {
|
||||
// adding chat messages will be done on the fly in getChatMessages()
|
||||
return
|
||||
}
|
||||
|
||||
async clearChatMessages(): Promise<void> {
|
||||
await this.clear()
|
||||
}
|
||||
|
||||
async resumeMessages(messages: IMessage[]): Promise<void> {
|
||||
// Clear existing chatHistory to avoid duplication
|
||||
if (messages.length) await this.clear()
|
||||
|
||||
// Insert into chatHistory
|
||||
for (const msg of messages) {
|
||||
if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message)
|
||||
else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: BufferMemory_Memory }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FlowiseWindowMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface'
|
||||
import { FlowiseWindowMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods } from '../../../src/Interface'
|
||||
import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils'
|
||||
import { BufferWindowMemory, BufferWindowMemoryInput } from 'langchain/memory'
|
||||
import { BaseMessage } from 'langchain/schema'
|
||||
@@ -67,36 +67,28 @@ class BufferWindowMemoryExtended extends FlowiseWindowMemory implements MemoryMe
|
||||
super(fields)
|
||||
}
|
||||
|
||||
async getChatMessages(_?: string, returnBaseMessages = false): Promise<IMessage[] | BaseMessage[]> {
|
||||
async getChatMessages(_?: string, returnBaseMessages = false, prevHistory: IMessage[] = []): Promise<IMessage[] | BaseMessage[]> {
|
||||
await this.chatHistory.clear()
|
||||
|
||||
// Insert into chatHistory
|
||||
for (const msg of prevHistory) {
|
||||
if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message)
|
||||
else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message)
|
||||
}
|
||||
|
||||
const memoryResult = await this.loadMemoryVariables({})
|
||||
const baseMessages = memoryResult[this.memoryKey ?? 'chat_history']
|
||||
return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages)
|
||||
}
|
||||
|
||||
async addChatMessages(msgArray: { text: string; type: MessageType }[]): Promise<void> {
|
||||
const input = msgArray.find((msg) => msg.type === 'userMessage')
|
||||
const output = msgArray.find((msg) => msg.type === 'apiMessage')
|
||||
|
||||
const inputValues = { [this.inputKey ?? 'input']: input?.text }
|
||||
const outputValues = { output: output?.text }
|
||||
|
||||
await this.saveContext(inputValues, outputValues)
|
||||
async addChatMessages(): Promise<void> {
|
||||
// adding chat messages will be done on the fly in getChatMessages()
|
||||
return
|
||||
}
|
||||
|
||||
async clearChatMessages(): Promise<void> {
|
||||
await this.clear()
|
||||
}
|
||||
|
||||
async resumeMessages(messages: IMessage[]): Promise<void> {
|
||||
// Clear existing chatHistory to avoid duplication
|
||||
if (messages.length) await this.clear()
|
||||
|
||||
// Insert into chatHistory
|
||||
for (const msg of messages) {
|
||||
if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message)
|
||||
else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: BufferWindowMemory_Memory }
|
||||
|
||||
+17
-25
@@ -1,4 +1,4 @@
|
||||
import { FlowiseSummaryMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface'
|
||||
import { FlowiseSummaryMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods } from '../../../src/Interface'
|
||||
import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils'
|
||||
import { ConversationSummaryMemory, ConversationSummaryMemoryInput } from 'langchain/memory'
|
||||
import { BaseLanguageModel } from 'langchain/base_language'
|
||||
@@ -66,40 +66,32 @@ class ConversationSummaryMemoryExtended extends FlowiseSummaryMemory implements
|
||||
super(fields)
|
||||
}
|
||||
|
||||
async getChatMessages(_?: string, returnBaseMessages = false): Promise<IMessage[] | BaseMessage[]> {
|
||||
async getChatMessages(_?: string, returnBaseMessages = false, prevHistory: IMessage[] = []): Promise<IMessage[] | BaseMessage[]> {
|
||||
await this.chatHistory.clear()
|
||||
this.buffer = ''
|
||||
|
||||
for (const msg of prevHistory) {
|
||||
if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message)
|
||||
else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message)
|
||||
}
|
||||
|
||||
// Get summary
|
||||
const chatMessages = await this.chatHistory.getMessages()
|
||||
this.buffer = chatMessages.length ? await this.predictNewSummary(chatMessages.slice(-2), this.buffer) : ''
|
||||
|
||||
const memoryResult = await this.loadMemoryVariables({})
|
||||
const baseMessages = memoryResult[this.memoryKey ?? 'chat_history']
|
||||
return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages)
|
||||
}
|
||||
|
||||
async addChatMessages(msgArray: { text: string; type: MessageType }[]): Promise<void> {
|
||||
const input = msgArray.find((msg) => msg.type === 'userMessage')
|
||||
const output = msgArray.find((msg) => msg.type === 'apiMessage')
|
||||
|
||||
const inputValues = { [this.inputKey ?? 'input']: input?.text }
|
||||
const outputValues = { output: output?.text }
|
||||
|
||||
await this.saveContext(inputValues, outputValues)
|
||||
async addChatMessages(): Promise<void> {
|
||||
// adding chat messages will be done on the fly in getChatMessages()
|
||||
return
|
||||
}
|
||||
|
||||
async clearChatMessages(): Promise<void> {
|
||||
await this.clear()
|
||||
}
|
||||
|
||||
async resumeMessages(messages: IMessage[]): Promise<void> {
|
||||
// Clear existing chatHistory to avoid duplication
|
||||
if (messages.length) await this.clear()
|
||||
|
||||
// Insert into chatHistory
|
||||
for (const msg of messages) {
|
||||
if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message)
|
||||
else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message)
|
||||
}
|
||||
|
||||
// Replace buffer
|
||||
const chatMessages = await this.chatHistory.getMessages()
|
||||
this.buffer = await this.predictNewSummary(chatMessages.slice(-2), this.buffer)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: ConversationSummaryMemory_Memory }
|
||||
|
||||
@@ -12,13 +12,7 @@ import {
|
||||
import { DynamoDBChatMessageHistory } from 'langchain/stores/message/dynamodb'
|
||||
import { BufferMemory, BufferMemoryInput } from 'langchain/memory'
|
||||
import { mapStoredMessageToChatMessage, AIMessage, HumanMessage, StoredMessage, BaseMessage } from 'langchain/schema'
|
||||
import {
|
||||
convertBaseMessagetoIMessage,
|
||||
getBaseClasses,
|
||||
getCredentialData,
|
||||
getCredentialParam,
|
||||
serializeChatHistory
|
||||
} from '../../../src/utils'
|
||||
import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface'
|
||||
|
||||
class DynamoDb_Memory implements INode {
|
||||
@@ -70,7 +64,8 @@ class DynamoDb_Memory implements INode {
|
||||
label: 'Session ID',
|
||||
name: 'sessionId',
|
||||
type: 'string',
|
||||
description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId',
|
||||
description:
|
||||
'If not specified, a random id will be used. Learn <a target="_blank" href="https://docs.flowiseai.com/memory/long-term-memory#ui-and-embedded-chat">more</a>',
|
||||
default: '',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
@@ -88,25 +83,6 @@ class DynamoDb_Memory implements INode {
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
return initalizeDynamoDB(nodeData, options)
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
memoryMethods = {
|
||||
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> {
|
||||
const dynamodbMemory = await initalizeDynamoDB(nodeData, options)
|
||||
const sessionId = nodeData.inputs?.sessionId as string
|
||||
const chatId = options?.chatId as string
|
||||
options.logger.info(`Clearing DynamoDb memory session ${sessionId ? sessionId : chatId}`)
|
||||
await dynamodbMemory.clear()
|
||||
options.logger.info(`Successfully cleared DynamoDb memory session ${sessionId ? sessionId : chatId}`)
|
||||
},
|
||||
async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise<string> {
|
||||
const memoryKey = nodeData.inputs?.memoryKey as string
|
||||
const dynamodbMemory = await initalizeDynamoDB(nodeData, options)
|
||||
const key = memoryKey ?? 'chat_history'
|
||||
const memoryResult = await dynamodbMemory.loadMemoryVariables({})
|
||||
return serializeChatHistory(memoryResult[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): Promise<BufferMemory> => {
|
||||
@@ -114,17 +90,7 @@ const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): P
|
||||
const partitionKey = nodeData.inputs?.partitionKey as string
|
||||
const region = nodeData.inputs?.region as string
|
||||
const memoryKey = nodeData.inputs?.memoryKey as string
|
||||
const chatId = options.chatId
|
||||
|
||||
let isSessionIdUsingChatMessageId = false
|
||||
let sessionId = ''
|
||||
|
||||
if (!nodeData.inputs?.sessionId && chatId) {
|
||||
isSessionIdUsingChatMessageId = true
|
||||
sessionId = chatId
|
||||
} else {
|
||||
sessionId = nodeData.inputs?.sessionId
|
||||
}
|
||||
const sessionId = nodeData.inputs?.sessionId as string
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const accessKeyId = getCredentialParam('accessKey', credentialData, nodeData)
|
||||
@@ -150,7 +116,6 @@ const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): P
|
||||
const memory = new BufferMemoryExtended({
|
||||
memoryKey: memoryKey ?? 'chat_history',
|
||||
chatHistory: dynamoDb,
|
||||
isSessionIdUsingChatMessageId,
|
||||
sessionId,
|
||||
dynamodbClient: client
|
||||
})
|
||||
@@ -158,7 +123,6 @@ const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): P
|
||||
}
|
||||
|
||||
interface BufferMemoryExtendedInput {
|
||||
isSessionIdUsingChatMessageId: boolean
|
||||
dynamodbClient: DynamoDBClient
|
||||
sessionId: string
|
||||
}
|
||||
@@ -178,7 +142,6 @@ interface DynamoDBSerializedChatMessage {
|
||||
}
|
||||
|
||||
class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods {
|
||||
isSessionIdUsingChatMessageId = false
|
||||
sessionId = ''
|
||||
dynamodbClient: DynamoDBClient
|
||||
|
||||
@@ -306,10 +269,6 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods {
|
||||
await this.dynamodbClient.send(new DeleteItemCommand(params))
|
||||
await this.clear()
|
||||
}
|
||||
|
||||
async resumeMessages(): Promise<void> {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: DynamoDb_Memory }
|
||||
|
||||
@@ -2,13 +2,7 @@ import { MongoClient, Collection, Document } from 'mongodb'
|
||||
import { MongoDBChatMessageHistory } from 'langchain/stores/message/mongodb'
|
||||
import { BufferMemory, BufferMemoryInput } from 'langchain/memory'
|
||||
import { mapStoredMessageToChatMessage, AIMessage, HumanMessage, BaseMessage } from 'langchain/schema'
|
||||
import {
|
||||
convertBaseMessagetoIMessage,
|
||||
getBaseClasses,
|
||||
getCredentialData,
|
||||
getCredentialParam,
|
||||
serializeChatHistory
|
||||
} from '../../../src/utils'
|
||||
import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface'
|
||||
|
||||
class MongoDB_Memory implements INode {
|
||||
@@ -55,7 +49,8 @@ class MongoDB_Memory implements INode {
|
||||
label: 'Session Id',
|
||||
name: 'sessionId',
|
||||
type: 'string',
|
||||
description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId',
|
||||
description:
|
||||
'If not specified, a random id will be used. Learn <a target="_blank" href="https://docs.flowiseai.com/memory/long-term-memory#ui-and-embedded-chat">more</a>',
|
||||
default: '',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
@@ -73,42 +68,13 @@ class MongoDB_Memory implements INode {
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
return initializeMongoDB(nodeData, options)
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
memoryMethods = {
|
||||
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> {
|
||||
const mongodbMemory = await initializeMongoDB(nodeData, options)
|
||||
const sessionId = nodeData.inputs?.sessionId as string
|
||||
const chatId = options?.chatId as string
|
||||
options.logger.info(`Clearing MongoDB memory session ${sessionId ? sessionId : chatId}`)
|
||||
await mongodbMemory.clear()
|
||||
options.logger.info(`Successfully cleared MongoDB memory session ${sessionId ? sessionId : chatId}`)
|
||||
},
|
||||
async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise<string> {
|
||||
const memoryKey = nodeData.inputs?.memoryKey as string
|
||||
const mongodbMemory = await initializeMongoDB(nodeData, options)
|
||||
const key = memoryKey ?? 'chat_history'
|
||||
const memoryResult = await mongodbMemory.loadMemoryVariables({})
|
||||
return serializeChatHistory(memoryResult[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const initializeMongoDB = async (nodeData: INodeData, options: ICommonObject): Promise<BufferMemory> => {
|
||||
const databaseName = nodeData.inputs?.databaseName as string
|
||||
const collectionName = nodeData.inputs?.collectionName as string
|
||||
const memoryKey = nodeData.inputs?.memoryKey as string
|
||||
const chatId = options?.chatId as string
|
||||
|
||||
let isSessionIdUsingChatMessageId = false
|
||||
let sessionId = ''
|
||||
|
||||
if (!nodeData.inputs?.sessionId && chatId) {
|
||||
isSessionIdUsingChatMessageId = true
|
||||
sessionId = chatId
|
||||
} else {
|
||||
sessionId = nodeData.inputs?.sessionId
|
||||
}
|
||||
const sessionId = nodeData.inputs?.sessionId as string
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const mongoDBConnectUrl = getCredentialParam('mongoDBConnectUrl', credentialData, nodeData)
|
||||
@@ -149,14 +115,12 @@ const initializeMongoDB = async (nodeData: INodeData, options: ICommonObject): P
|
||||
return new BufferMemoryExtended({
|
||||
memoryKey: memoryKey ?? 'chat_history',
|
||||
chatHistory: mongoDBChatMessageHistory,
|
||||
isSessionIdUsingChatMessageId,
|
||||
sessionId,
|
||||
collection
|
||||
})
|
||||
}
|
||||
|
||||
interface BufferMemoryExtendedInput {
|
||||
isSessionIdUsingChatMessageId: boolean
|
||||
collection: Collection<Document>
|
||||
sessionId: string
|
||||
}
|
||||
@@ -164,7 +128,6 @@ interface BufferMemoryExtendedInput {
|
||||
class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods {
|
||||
sessionId = ''
|
||||
collection: Collection<Document>
|
||||
isSessionIdUsingChatMessageId? = false
|
||||
|
||||
constructor(fields: BufferMemoryInput & BufferMemoryExtendedInput) {
|
||||
super(fields)
|
||||
@@ -221,10 +184,6 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods {
|
||||
await this.collection.deleteOne({ sessionId: id })
|
||||
await this.clear()
|
||||
}
|
||||
|
||||
async resumeMessages(): Promise<void> {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: MongoDB_Memory }
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface'
|
||||
import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { ICommonObject } from '../../../src'
|
||||
import { MotorheadMemory, MotorheadMemoryInput, InputValues, MemoryVariables, OutputValues, getBufferString } from 'langchain/memory'
|
||||
import { MotorheadMemory, MotorheadMemoryInput, InputValues, OutputValues } from 'langchain/memory'
|
||||
import fetch from 'node-fetch'
|
||||
import { BaseMessage } from 'langchain/schema'
|
||||
import { AIMessage, BaseMessage, ChatMessage, HumanMessage } from 'langchain/schema'
|
||||
|
||||
type MotorheadMessage = {
|
||||
content: string
|
||||
role: 'Human' | 'AI'
|
||||
}
|
||||
|
||||
class MotorMemory_Memory implements INode {
|
||||
label: string
|
||||
@@ -46,7 +51,8 @@ class MotorMemory_Memory implements INode {
|
||||
label: 'Session Id',
|
||||
name: 'sessionId',
|
||||
type: 'string',
|
||||
description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId',
|
||||
description:
|
||||
'If not specified, a random id will be used. Learn <a target="_blank" href="https://docs.flowiseai.com/memory/long-term-memory#ui-and-embedded-chat">more</a>',
|
||||
default: '',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
@@ -64,49 +70,19 @@ class MotorMemory_Memory implements INode {
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
return initalizeMotorhead(nodeData, options)
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
memoryMethods = {
|
||||
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> {
|
||||
const motorhead = await initalizeMotorhead(nodeData, options)
|
||||
const sessionId = nodeData.inputs?.sessionId as string
|
||||
const chatId = options?.chatId as string
|
||||
options.logger.info(`Clearing Motorhead memory session ${sessionId ? sessionId : chatId}`)
|
||||
await motorhead.clear()
|
||||
options.logger.info(`Successfully cleared Motorhead memory session ${sessionId ? sessionId : chatId}`)
|
||||
},
|
||||
async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise<string> {
|
||||
const memoryKey = nodeData.inputs?.memoryKey as string
|
||||
const motorhead = await initalizeMotorhead(nodeData, options)
|
||||
const key = memoryKey ?? 'chat_history'
|
||||
const memoryResult = await motorhead.loadMemoryVariables({})
|
||||
return getBufferString(memoryResult[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const initalizeMotorhead = async (nodeData: INodeData, options: ICommonObject): Promise<MotorheadMemory> => {
|
||||
const memoryKey = nodeData.inputs?.memoryKey as string
|
||||
const baseURL = nodeData.inputs?.baseURL as string
|
||||
const chatId = options?.chatId as string
|
||||
|
||||
let isSessionIdUsingChatMessageId = false
|
||||
let sessionId = ''
|
||||
|
||||
if (!nodeData.inputs?.sessionId && chatId) {
|
||||
isSessionIdUsingChatMessageId = true
|
||||
sessionId = chatId
|
||||
} else {
|
||||
sessionId = nodeData.inputs?.sessionId
|
||||
}
|
||||
const sessionId = nodeData.inputs?.sessionId as string
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const apiKey = getCredentialParam('apiKey', credentialData, nodeData)
|
||||
const clientId = getCredentialParam('clientId', credentialData, nodeData)
|
||||
|
||||
let obj: MotorheadMemoryInput & MotorheadMemoryExtendedInput = {
|
||||
let obj: MotorheadMemoryInput = {
|
||||
returnMessages: true,
|
||||
isSessionIdUsingChatMessageId,
|
||||
sessionId,
|
||||
memoryKey
|
||||
}
|
||||
@@ -132,23 +108,9 @@ const initalizeMotorhead = async (nodeData: INodeData, options: ICommonObject):
|
||||
return motorheadMemory
|
||||
}
|
||||
|
||||
interface MotorheadMemoryExtendedInput {
|
||||
isSessionIdUsingChatMessageId: boolean
|
||||
}
|
||||
|
||||
class MotorheadMemoryExtended extends MotorheadMemory implements MemoryMethods {
|
||||
isSessionIdUsingChatMessageId? = false
|
||||
|
||||
constructor(fields: MotorheadMemoryInput & MotorheadMemoryExtendedInput) {
|
||||
constructor(fields: MotorheadMemoryInput) {
|
||||
super(fields)
|
||||
this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId
|
||||
}
|
||||
|
||||
async loadMemoryVariables(values: InputValues, overrideSessionId = ''): Promise<MemoryVariables> {
|
||||
if (overrideSessionId) {
|
||||
this.sessionId = overrideSessionId
|
||||
}
|
||||
return super.loadMemoryVariables({ values })
|
||||
}
|
||||
|
||||
async saveContext(inputValues: InputValues, outputValues: OutputValues, overrideSessionId = ''): Promise<void> {
|
||||
@@ -180,9 +142,33 @@ class MotorheadMemoryExtended extends MotorheadMemory implements MemoryMethods {
|
||||
|
||||
async getChatMessages(overrideSessionId = '', returnBaseMessages = false): Promise<IMessage[] | BaseMessage[]> {
|
||||
const id = overrideSessionId ?? this.sessionId
|
||||
const memoryVariables = await this.loadMemoryVariables({}, id)
|
||||
const baseMessages = memoryVariables[this.memoryKey]
|
||||
return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages)
|
||||
try {
|
||||
const resp = await this.caller.call(fetch, `${this.url}/sessions/${id}/memory`, {
|
||||
//@ts-ignore
|
||||
signal: this.timeout ? AbortSignal.timeout(this.timeout) : undefined,
|
||||
headers: this._getHeaders() as ICommonObject,
|
||||
method: 'GET'
|
||||
})
|
||||
const data = await resp.json()
|
||||
const rawStoredMessages: MotorheadMessage[] = data?.data?.messages ?? []
|
||||
|
||||
const baseMessages = rawStoredMessages.reverse().map((message) => {
|
||||
const { content, role } = message
|
||||
if (role === 'Human') {
|
||||
return new HumanMessage(content)
|
||||
} else if (role === 'AI') {
|
||||
return new AIMessage(content)
|
||||
} else {
|
||||
// default to generic ChatMessage
|
||||
return new ChatMessage(content, role)
|
||||
}
|
||||
})
|
||||
|
||||
return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages)
|
||||
} catch (error) {
|
||||
console.error('Error getting session: ', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId = ''): Promise<void> {
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import { INode, INodeData, INodeParams, ICommonObject, IMessage, MessageType, FlowiseMemory, MemoryMethods } from '../../../src/Interface'
|
||||
import {
|
||||
convertBaseMessagetoIMessage,
|
||||
getBaseClasses,
|
||||
getCredentialData,
|
||||
getCredentialParam,
|
||||
serializeChatHistory
|
||||
} from '../../../src/utils'
|
||||
import { Redis } from 'ioredis'
|
||||
import { BufferMemory, BufferMemoryInput } from 'langchain/memory'
|
||||
import { RedisChatMessageHistory, RedisChatMessageHistoryInput } from 'langchain/stores/message/ioredis'
|
||||
import { mapStoredMessageToChatMessage, BaseMessage, AIMessage, HumanMessage } from 'langchain/schema'
|
||||
import { Redis } from 'ioredis'
|
||||
import { INode, INodeData, INodeParams, ICommonObject, MessageType, IMessage, MemoryMethods, FlowiseMemory } from '../../../src/Interface'
|
||||
import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
|
||||
class RedisBackedChatMemory_Memory implements INode {
|
||||
label: string
|
||||
@@ -44,7 +38,8 @@ class RedisBackedChatMemory_Memory implements INode {
|
||||
label: 'Session Id',
|
||||
name: 'sessionId',
|
||||
type: 'string',
|
||||
description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId',
|
||||
description:
|
||||
'If not specified, a random id will be used. Learn <a target="_blank" href="https://docs.flowiseai.com/memory/long-term-memory#ui-and-embedded-chat">more</a>',
|
||||
default: '',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
@@ -78,47 +73,19 @@ class RedisBackedChatMemory_Memory implements INode {
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
return await initalizeRedis(nodeData, options)
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
memoryMethods = {
|
||||
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> {
|
||||
const redis = await initalizeRedis(nodeData, options)
|
||||
const sessionId = nodeData.inputs?.sessionId as string
|
||||
const chatId = options?.chatId as string
|
||||
options.logger.info(`Clearing Redis memory session ${sessionId ? sessionId : chatId}`)
|
||||
await redis.clear()
|
||||
options.logger.info(`Successfully cleared Redis memory session ${sessionId ? sessionId : chatId}`)
|
||||
},
|
||||
async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise<string> {
|
||||
const memoryKey = nodeData.inputs?.memoryKey as string
|
||||
const redis = await initalizeRedis(nodeData, options)
|
||||
const key = memoryKey ?? 'chat_history'
|
||||
const memoryResult = await redis.loadMemoryVariables({})
|
||||
return serializeChatHistory(memoryResult[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Promise<BufferMemory> => {
|
||||
const sessionTTL = nodeData.inputs?.sessionTTL as number
|
||||
const memoryKey = nodeData.inputs?.memoryKey as string
|
||||
const sessionId = nodeData.inputs?.sessionId as string
|
||||
const windowSize = nodeData.inputs?.windowSize as number
|
||||
const chatId = options?.chatId as string
|
||||
|
||||
let isSessionIdUsingChatMessageId = false
|
||||
let sessionId = ''
|
||||
|
||||
if (!nodeData.inputs?.sessionId && chatId) {
|
||||
isSessionIdUsingChatMessageId = true
|
||||
sessionId = chatId
|
||||
} else {
|
||||
sessionId = nodeData.inputs?.sessionId
|
||||
}
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const redisUrl = getCredentialParam('redisUrl', credentialData, nodeData)
|
||||
|
||||
let client: Redis
|
||||
|
||||
if (!redisUrl || redisUrl === '') {
|
||||
const username = getCredentialParam('redisCacheUser', credentialData, nodeData)
|
||||
const password = getCredentialParam('redisCachePwd', credentialData, nodeData)
|
||||
@@ -153,7 +120,7 @@ const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Prom
|
||||
|
||||
const redisChatMessageHistory = new RedisChatMessageHistory(obj)
|
||||
|
||||
redisChatMessageHistory.getMessages = async (): Promise<BaseMessage[]> => {
|
||||
/*redisChatMessageHistory.getMessages = async (): Promise<BaseMessage[]> => {
|
||||
const rawStoredMessages = await client.lrange((redisChatMessageHistory as any).sessionId, windowSize ? -windowSize : 0, -1)
|
||||
const orderedMessages = rawStoredMessages.reverse().map((message) => JSON.parse(message))
|
||||
return orderedMessages.map(mapStoredMessageToChatMessage)
|
||||
@@ -169,44 +136,45 @@ const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Prom
|
||||
|
||||
redisChatMessageHistory.clear = async (): Promise<void> => {
|
||||
await client.del((redisChatMessageHistory as any).sessionId)
|
||||
}
|
||||
}*/
|
||||
|
||||
const memory = new BufferMemoryExtended({
|
||||
memoryKey: memoryKey ?? 'chat_history',
|
||||
chatHistory: redisChatMessageHistory,
|
||||
isSessionIdUsingChatMessageId,
|
||||
sessionId,
|
||||
windowSize,
|
||||
redisClient: client
|
||||
})
|
||||
|
||||
return memory
|
||||
}
|
||||
|
||||
interface BufferMemoryExtendedInput {
|
||||
isSessionIdUsingChatMessageId: boolean
|
||||
redisClient: Redis
|
||||
sessionId: string
|
||||
windowSize?: number
|
||||
}
|
||||
|
||||
class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods {
|
||||
isSessionIdUsingChatMessageId? = false
|
||||
sessionId = ''
|
||||
redisClient: Redis
|
||||
windowSize?: number
|
||||
|
||||
constructor(fields: BufferMemoryInput & BufferMemoryExtendedInput) {
|
||||
super(fields)
|
||||
this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId
|
||||
this.sessionId = fields.sessionId
|
||||
this.redisClient = fields.redisClient
|
||||
this.windowSize = fields.windowSize
|
||||
}
|
||||
|
||||
async getChatMessages(overrideSessionId = '', returnBaseMessage = false): Promise<IMessage[] | BaseMessage[]> {
|
||||
async getChatMessages(overrideSessionId = '', returnBaseMessages = false): Promise<IMessage[] | BaseMessage[]> {
|
||||
if (!this.redisClient) return []
|
||||
|
||||
const id = overrideSessionId ?? this.sessionId
|
||||
const rawStoredMessages = await this.redisClient.lrange(id, 0, -1)
|
||||
const rawStoredMessages = await this.redisClient.lrange(id, this.windowSize ? this.windowSize * -1 : 0, -1)
|
||||
const orderedMessages = rawStoredMessages.reverse().map((message) => JSON.parse(message))
|
||||
const baseMessages = orderedMessages.map(mapStoredMessageToChatMessage)
|
||||
return returnBaseMessage ? baseMessages : convertBaseMessagetoIMessage(baseMessages)
|
||||
return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages)
|
||||
}
|
||||
|
||||
async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId = ''): Promise<void> {
|
||||
@@ -236,10 +204,6 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods {
|
||||
await this.redisClient.del(id)
|
||||
await this.clear()
|
||||
}
|
||||
|
||||
async resumeMessages(): Promise<void> {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: RedisBackedChatMemory_Memory }
|
||||
|
||||
+4
-45
@@ -3,13 +3,7 @@ import { BufferMemory, BufferMemoryInput } from 'langchain/memory'
|
||||
import { UpstashRedisChatMessageHistory } from 'langchain/stores/message/upstash_redis'
|
||||
import { mapStoredMessageToChatMessage, AIMessage, HumanMessage, StoredMessage, BaseMessage } from 'langchain/schema'
|
||||
import { FlowiseMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface'
|
||||
import {
|
||||
convertBaseMessagetoIMessage,
|
||||
getBaseClasses,
|
||||
getCredentialData,
|
||||
getCredentialParam,
|
||||
serializeChatHistory
|
||||
} from '../../../src/utils'
|
||||
import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { ICommonObject } from '../../../src/Interface'
|
||||
|
||||
class UpstashRedisBackedChatMemory_Memory implements INode {
|
||||
@@ -51,7 +45,8 @@ class UpstashRedisBackedChatMemory_Memory implements INode {
|
||||
label: 'Session Id',
|
||||
name: 'sessionId',
|
||||
type: 'string',
|
||||
description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId',
|
||||
description:
|
||||
'If not specified, a random id will be used. Learn <a target="_blank" href="https://docs.flowiseai.com/memory/long-term-memory#ui-and-embedded-chat">more</a>',
|
||||
default: '',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
@@ -70,40 +65,12 @@ class UpstashRedisBackedChatMemory_Memory implements INode {
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
return initalizeUpstashRedis(nodeData, options)
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
memoryMethods = {
|
||||
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> {
|
||||
const redis = await initalizeUpstashRedis(nodeData, options)
|
||||
const sessionId = nodeData.inputs?.sessionId as string
|
||||
const chatId = options?.chatId as string
|
||||
options.logger.info(`Clearing Upstash Redis memory session ${sessionId ? sessionId : chatId}`)
|
||||
await redis.clear()
|
||||
options.logger.info(`Successfully cleared Upstash Redis memory session ${sessionId ? sessionId : chatId}`)
|
||||
},
|
||||
async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise<string> {
|
||||
const redis = await initalizeUpstashRedis(nodeData, options)
|
||||
const key = 'chat_history'
|
||||
const memoryResult = await redis.loadMemoryVariables({})
|
||||
return serializeChatHistory(memoryResult[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const initalizeUpstashRedis = async (nodeData: INodeData, options: ICommonObject): Promise<BufferMemory> => {
|
||||
const baseURL = nodeData.inputs?.baseURL as string
|
||||
const sessionTTL = nodeData.inputs?.sessionTTL as string
|
||||
const chatId = options?.chatId as string
|
||||
|
||||
let isSessionIdUsingChatMessageId = false
|
||||
let sessionId = ''
|
||||
|
||||
if (!nodeData.inputs?.sessionId && chatId) {
|
||||
isSessionIdUsingChatMessageId = true
|
||||
sessionId = chatId
|
||||
} else {
|
||||
sessionId = nodeData.inputs?.sessionId
|
||||
}
|
||||
const sessionId = nodeData.inputs?.sessionId as string
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const upstashRestToken = getCredentialParam('upstashRestToken', credentialData, nodeData)
|
||||
@@ -122,7 +89,6 @@ const initalizeUpstashRedis = async (nodeData: INodeData, options: ICommonObject
|
||||
const memory = new BufferMemoryExtended({
|
||||
memoryKey: 'chat_history',
|
||||
chatHistory: redisChatMessageHistory,
|
||||
isSessionIdUsingChatMessageId,
|
||||
sessionId,
|
||||
redisClient: client
|
||||
})
|
||||
@@ -131,19 +97,16 @@ const initalizeUpstashRedis = async (nodeData: INodeData, options: ICommonObject
|
||||
}
|
||||
|
||||
interface BufferMemoryExtendedInput {
|
||||
isSessionIdUsingChatMessageId: boolean
|
||||
redisClient: Redis
|
||||
sessionId: string
|
||||
}
|
||||
|
||||
class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods {
|
||||
isSessionIdUsingChatMessageId? = false
|
||||
sessionId = ''
|
||||
redisClient: Redis
|
||||
|
||||
constructor(fields: BufferMemoryInput & BufferMemoryExtendedInput) {
|
||||
super(fields)
|
||||
this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId
|
||||
this.sessionId = fields.sessionId
|
||||
this.redisClient = fields.redisClient
|
||||
}
|
||||
@@ -186,10 +149,6 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods {
|
||||
await this.redisClient.del(id)
|
||||
await this.clear()
|
||||
}
|
||||
|
||||
async resumeMessages(): Promise<void> {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: UpstashRedisBackedChatMemory_Memory }
|
||||
|
||||
@@ -2,7 +2,7 @@ import { IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } f
|
||||
import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep'
|
||||
import { ICommonObject } from '../../../src'
|
||||
import { InputValues, MemoryVariables, OutputValues, getBufferString } from 'langchain/memory'
|
||||
import { InputValues, MemoryVariables, OutputValues } from 'langchain/memory'
|
||||
import { BaseMessage } from 'langchain/schema'
|
||||
|
||||
class ZepMemory_Memory implements INode {
|
||||
@@ -55,10 +55,9 @@ class ZepMemory_Memory implements INode {
|
||||
label: 'Size',
|
||||
name: 'k',
|
||||
type: 'number',
|
||||
placeholder: '10',
|
||||
default: '10',
|
||||
description: 'Window of size k to surface the last k back-and-forth to use as memory.',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
additionalParams: true
|
||||
},
|
||||
{
|
||||
label: 'AI Prefix',
|
||||
@@ -101,27 +100,6 @@ class ZepMemory_Memory implements INode {
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
return await initalizeZep(nodeData, options)
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
memoryMethods = {
|
||||
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> {
|
||||
const zep = await initalizeZep(nodeData, options)
|
||||
const sessionId = nodeData.inputs?.sessionId as string
|
||||
const chatId = options?.chatId as string
|
||||
options.logger.info(`Clearing Zep memory session ${sessionId ? sessionId : chatId}`)
|
||||
await zep.clear()
|
||||
options.logger.info(`Successfully cleared Zep memory session ${sessionId ? sessionId : chatId}`)
|
||||
},
|
||||
async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise<string> {
|
||||
const memoryKey = nodeData.inputs?.memoryKey as string
|
||||
const aiPrefix = nodeData.inputs?.aiPrefix as string
|
||||
const humanPrefix = nodeData.inputs?.humanPrefix as string
|
||||
const zep = await initalizeZep(nodeData, options)
|
||||
const key = memoryKey ?? 'chat_history'
|
||||
const memoryResult = await zep.loadMemoryVariables({})
|
||||
return getBufferString(memoryResult[key], humanPrefix, aiPrefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const initalizeZep = async (nodeData: INodeData, options: ICommonObject): Promise<ZepMemory> => {
|
||||
@@ -131,30 +109,19 @@ const initalizeZep = async (nodeData: INodeData, options: ICommonObject): Promis
|
||||
const memoryKey = nodeData.inputs?.memoryKey as string
|
||||
const inputKey = nodeData.inputs?.inputKey as string
|
||||
const k = nodeData.inputs?.k as string
|
||||
const chatId = options?.chatId as string
|
||||
|
||||
let isSessionIdUsingChatMessageId = false
|
||||
let sessionId = ''
|
||||
|
||||
if (!nodeData.inputs?.sessionId && chatId) {
|
||||
isSessionIdUsingChatMessageId = true
|
||||
sessionId = chatId
|
||||
} else {
|
||||
sessionId = nodeData.inputs?.sessionId
|
||||
}
|
||||
const sessionId = nodeData.inputs?.sessionId as string
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const apiKey = getCredentialParam('apiKey', credentialData, nodeData)
|
||||
|
||||
const obj: ZepMemoryInput & ZepMemoryExtendedInput = {
|
||||
baseURL,
|
||||
sessionId,
|
||||
aiPrefix,
|
||||
humanPrefix,
|
||||
returnMessages: true,
|
||||
memoryKey,
|
||||
inputKey,
|
||||
isSessionIdUsingChatMessageId,
|
||||
sessionId,
|
||||
k: k ? parseInt(k, 10) : undefined
|
||||
}
|
||||
if (apiKey) obj.apiKey = apiKey
|
||||
@@ -163,17 +130,14 @@ const initalizeZep = async (nodeData: INodeData, options: ICommonObject): Promis
|
||||
}
|
||||
|
||||
interface ZepMemoryExtendedInput {
|
||||
isSessionIdUsingChatMessageId: boolean
|
||||
k?: number
|
||||
}
|
||||
|
||||
class ZepMemoryExtended extends ZepMemory implements MemoryMethods {
|
||||
isSessionIdUsingChatMessageId? = false
|
||||
lastN?: number
|
||||
|
||||
constructor(fields: ZepMemoryInput & ZepMemoryExtendedInput) {
|
||||
super(fields)
|
||||
this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId
|
||||
this.lastN = fields.k
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg width="32" height="32" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M11.776 18.304c.64 0 1.92-.032 3.712-.768 2.08-.864 6.176-2.4 9.152-4 2.08-1.12 2.976-2.592 2.976-4.576 0-2.72-2.208-4.96-4.96-4.96h-11.52A7.143 7.143 0 0 0 4 11.136c0 3.936 3.008 7.168 7.776 7.168Z" fill="#39594D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M13.728 23.2c0-1.92 1.152-3.68 2.944-4.416l3.616-1.504C23.968 15.776 28 18.464 28 22.432A5.572 5.572 0 0 1 22.432 28h-3.936c-2.624 0-4.768-2.144-4.768-4.8Z" fill="#D18EE2"/><path d="M8.128 19.232A4.138 4.138 0 0 0 4 23.36v.544C4 26.144 5.856 28 8.128 28a4.138 4.138 0 0 0 4.128-4.128v-.544c-.032-2.24-1.856-4.096-4.128-4.096Z" fill="#FF7759"/></svg>
|
||||
|
After Width: | Height: | Size: 738 B |
@@ -0,0 +1,55 @@
|
||||
import { Callbacks } from 'langchain/callbacks'
|
||||
import { Document } from 'langchain/document'
|
||||
import { BaseDocumentCompressor } from 'langchain/retrievers/document_compressors'
|
||||
import axios from 'axios'
|
||||
export class CohereRerank extends BaseDocumentCompressor {
|
||||
private cohereAPIKey: any
|
||||
private COHERE_API_URL = 'https://api.cohere.ai/v1/rerank'
|
||||
private readonly model: string
|
||||
private readonly k: number
|
||||
private readonly maxChunksPerDoc: number
|
||||
constructor(cohereAPIKey: string, model: string, k: number, maxChunksPerDoc: number) {
|
||||
super()
|
||||
this.cohereAPIKey = cohereAPIKey
|
||||
this.model = model
|
||||
this.k = k
|
||||
this.maxChunksPerDoc = maxChunksPerDoc
|
||||
}
|
||||
async compressDocuments(
|
||||
documents: Document<Record<string, any>>[],
|
||||
query: string,
|
||||
_?: Callbacks | undefined
|
||||
): Promise<Document<Record<string, any>>[]> {
|
||||
// avoid empty api call
|
||||
if (documents.length === 0) {
|
||||
return []
|
||||
}
|
||||
const config = {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.cohereAPIKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json'
|
||||
}
|
||||
}
|
||||
const data = {
|
||||
model: this.model,
|
||||
topN: this.k,
|
||||
max_chunks_per_doc: this.maxChunksPerDoc,
|
||||
query: query,
|
||||
return_documents: false,
|
||||
documents: documents.map((doc) => doc.pageContent)
|
||||
}
|
||||
try {
|
||||
let returnedDocs = await axios.post(this.COHERE_API_URL, data, config)
|
||||
const finalResults: Document<Record<string, any>>[] = []
|
||||
returnedDocs.data.results.forEach((result: any) => {
|
||||
const doc = documents[result.index]
|
||||
doc.metadata.relevance_score = result.relevance_score
|
||||
finalResults.push(doc)
|
||||
})
|
||||
return finalResults
|
||||
} catch (error) {
|
||||
return documents
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
||||
import { BaseRetriever } from 'langchain/schema/retriever'
|
||||
import { ContextualCompressionRetriever } from 'langchain/retrievers/contextual_compression'
|
||||
import { getCredentialData, getCredentialParam, handleEscapeCharacters } from '../../../src'
|
||||
import { CohereRerank } from './CohereRerank'
|
||||
import { VectorStoreRetriever } from 'langchain/vectorstores/base'
|
||||
|
||||
class CohereRerankRetriever_Retrievers implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
description: string
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
inputs: INodeParams[]
|
||||
credential: INodeParams
|
||||
badge: string
|
||||
outputs: INodeOutputsValue[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Cohere Rerank Retriever'
|
||||
this.name = 'cohereRerankRetriever'
|
||||
this.version = 1.0
|
||||
this.type = 'Cohere Rerank Retriever'
|
||||
this.icon = 'Cohere.svg'
|
||||
this.category = 'Retrievers'
|
||||
this.badge = 'NEW'
|
||||
this.description = 'Cohere Rerank indexes the documents from most to least semantically relevant to the query.'
|
||||
this.baseClasses = [this.type, 'BaseRetriever']
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['cohereApi']
|
||||
}
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Vector Store Retriever',
|
||||
name: 'baseRetriever',
|
||||
type: 'VectorStoreRetriever'
|
||||
},
|
||||
{
|
||||
label: 'Model Name',
|
||||
name: 'model',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
label: 'rerank-english-v2.0',
|
||||
name: 'rerank-english-v2.0'
|
||||
},
|
||||
{
|
||||
label: 'rerank-multilingual-v2.0',
|
||||
name: 'rerank-multilingual-v2.0'
|
||||
}
|
||||
],
|
||||
default: 'rerank-english-v2.0',
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
description: 'Query to retrieve documents from retriever. If not specified, user question will be used',
|
||||
optional: true,
|
||||
acceptVariable: true
|
||||
},
|
||||
{
|
||||
label: 'Top K',
|
||||
name: 'topK',
|
||||
description: 'Number of top results to fetch. Default to the TopK of the Base Retriever',
|
||||
placeholder: '4',
|
||||
type: 'number',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Max Chunks Per Doc',
|
||||
name: 'maxChunksPerDoc',
|
||||
description: 'The maximum number of chunks to produce internally from a document. Default to 10',
|
||||
placeholder: '10',
|
||||
type: 'number',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
this.outputs = [
|
||||
{
|
||||
label: 'Cohere Rerank Retriever',
|
||||
name: 'retriever',
|
||||
baseClasses: this.baseClasses
|
||||
},
|
||||
{
|
||||
label: 'Document',
|
||||
name: 'document',
|
||||
baseClasses: ['Document']
|
||||
},
|
||||
{
|
||||
label: 'Text',
|
||||
name: 'text',
|
||||
baseClasses: ['string', 'json']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
|
||||
const baseRetriever = nodeData.inputs?.baseRetriever as BaseRetriever
|
||||
const model = nodeData.inputs?.model as string
|
||||
const query = nodeData.inputs?.query as string
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const cohereApiKey = getCredentialParam('cohereApiKey', credentialData, nodeData)
|
||||
const topK = nodeData.inputs?.topK as string
|
||||
const k = topK ? parseFloat(topK) : (baseRetriever as VectorStoreRetriever).k ?? 4
|
||||
const maxChunksPerDoc = nodeData.inputs?.maxChunksPerDoc as string
|
||||
const max_chunks_per_doc = maxChunksPerDoc ? parseFloat(maxChunksPerDoc) : 10
|
||||
const output = nodeData.outputs?.output as string
|
||||
|
||||
const cohereCompressor = new CohereRerank(cohereApiKey, model, k, max_chunks_per_doc)
|
||||
|
||||
const retriever = new ContextualCompressionRetriever({
|
||||
baseCompressor: cohereCompressor,
|
||||
baseRetriever: baseRetriever
|
||||
})
|
||||
|
||||
if (output === 'retriever') return retriever
|
||||
else if (output === 'document') return await retriever.getRelevantDocuments(query ? query : input)
|
||||
else if (output === 'text') {
|
||||
let finaltext = ''
|
||||
|
||||
const docs = await retriever.getRelevantDocuments(query ? query : input)
|
||||
|
||||
for (const doc of docs) finaltext += `${doc.pageContent}\n`
|
||||
|
||||
return handleEscapeCharacters(finaltext, false)
|
||||
}
|
||||
|
||||
return retriever
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: CohereRerankRetriever_Retrievers }
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
||||
import { BaseRetriever } from 'langchain/schema/retriever'
|
||||
import { Embeddings } from 'langchain/embeddings/base'
|
||||
import { ContextualCompressionRetriever } from 'langchain/retrievers/contextual_compression'
|
||||
import { EmbeddingsFilter } from 'langchain/retrievers/document_compressors/embeddings_filter'
|
||||
import { handleEscapeCharacters } from '../../../src/utils'
|
||||
|
||||
class EmbeddingsFilterRetriever_Retrievers implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
description: string
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
inputs: INodeParams[]
|
||||
outputs: INodeOutputsValue[]
|
||||
badge: string
|
||||
|
||||
constructor() {
|
||||
this.label = 'Embeddings Filter Retriever'
|
||||
this.name = 'embeddingsFilterRetriever'
|
||||
this.version = 1.0
|
||||
this.type = 'EmbeddingsFilterRetriever'
|
||||
this.icon = 'compressionRetriever.svg'
|
||||
this.category = 'Retrievers'
|
||||
this.badge = 'NEW'
|
||||
this.description = 'A document compressor that uses embeddings to drop documents unrelated to the query'
|
||||
this.baseClasses = [this.type, 'BaseRetriever']
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Vector Store Retriever',
|
||||
name: 'baseRetriever',
|
||||
type: 'VectorStoreRetriever'
|
||||
},
|
||||
{
|
||||
label: 'Embeddings',
|
||||
name: 'embeddings',
|
||||
type: 'Embeddings'
|
||||
},
|
||||
{
|
||||
label: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
description: 'Query to retrieve documents from retriever. If not specified, user question will be used',
|
||||
optional: true,
|
||||
acceptVariable: true
|
||||
},
|
||||
{
|
||||
label: 'Similarity Threshold',
|
||||
name: 'similarityThreshold',
|
||||
description:
|
||||
'Threshold for determining when two documents are similar enough to be considered redundant. Must be specified if `k` is not set',
|
||||
type: 'number',
|
||||
default: 0.8,
|
||||
step: 0.1,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'K',
|
||||
name: 'k',
|
||||
description:
|
||||
'The number of relevant documents to return. Can be explicitly set to undefined, in which case similarity_threshold must be specified. Defaults to 20',
|
||||
type: 'number',
|
||||
default: 20,
|
||||
step: 1,
|
||||
optional: true,
|
||||
additionalParams: true
|
||||
}
|
||||
]
|
||||
this.outputs = [
|
||||
{
|
||||
label: 'Embeddings Filter Retriever',
|
||||
name: 'retriever',
|
||||
baseClasses: this.baseClasses
|
||||
},
|
||||
{
|
||||
label: 'Document',
|
||||
name: 'document',
|
||||
baseClasses: ['Document']
|
||||
},
|
||||
{
|
||||
label: 'Text',
|
||||
name: 'text',
|
||||
baseClasses: ['string', 'json']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, input: string): Promise<any> {
|
||||
const baseRetriever = nodeData.inputs?.baseRetriever as BaseRetriever
|
||||
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
||||
const query = nodeData.inputs?.query as string
|
||||
const similarityThreshold = nodeData.inputs?.similarityThreshold as string
|
||||
const k = nodeData.inputs?.k as string
|
||||
const output = nodeData.outputs?.output as string
|
||||
|
||||
if (k === undefined && similarityThreshold === undefined) {
|
||||
throw new Error(`Must specify one of "k" or "similarity_threshold".`)
|
||||
}
|
||||
|
||||
const similarityThresholdNumber = similarityThreshold ? parseFloat(similarityThreshold) : 0.8
|
||||
const kNumber = k ? parseFloat(k) : undefined
|
||||
|
||||
const baseCompressor = new EmbeddingsFilter({
|
||||
embeddings: embeddings,
|
||||
similarityThreshold: similarityThresholdNumber,
|
||||
k: kNumber
|
||||
})
|
||||
|
||||
const retriever = new ContextualCompressionRetriever({
|
||||
baseCompressor,
|
||||
baseRetriever: baseRetriever
|
||||
})
|
||||
|
||||
if (output === 'retriever') return retriever
|
||||
else if (output === 'document') return await retriever.getRelevantDocuments(query ? query : input)
|
||||
else if (output === 'text') {
|
||||
let finaltext = ''
|
||||
|
||||
const docs = await retriever.getRelevantDocuments(query ? query : input)
|
||||
|
||||
for (const doc of docs) finaltext += `${doc.pageContent}\n`
|
||||
|
||||
return handleEscapeCharacters(finaltext, false)
|
||||
}
|
||||
|
||||
return retriever
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: EmbeddingsFilterRetriever_Retrievers }
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chart-bar" 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 d="M3 12m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v6a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" />
|
||||
<path d="M9 8m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v10a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" />
|
||||
<path d="M15 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" />
|
||||
<path d="M4 20l14 0" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 600 B |
@@ -1,8 +1,9 @@
|
||||
import { VectorStore } from 'langchain/vectorstores/base'
|
||||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
||||
import { HydeRetriever, HydeRetrieverOptions, PromptKey } from 'langchain/retrievers/hyde'
|
||||
import { BaseLanguageModel } from 'langchain/base_language'
|
||||
import { PromptTemplate } from 'langchain/prompts'
|
||||
import { handleEscapeCharacters } from '../../../src/utils'
|
||||
|
||||
class HydeRetriever_Retrievers implements INode {
|
||||
label: string
|
||||
@@ -14,11 +15,12 @@ class HydeRetriever_Retrievers implements INode {
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
inputs: INodeParams[]
|
||||
outputs: INodeOutputsValue[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Hyde Retriever'
|
||||
this.label = 'HyDE Retriever'
|
||||
this.name = 'HydeRetriever'
|
||||
this.version = 2.0
|
||||
this.version = 3.0
|
||||
this.type = 'HydeRetriever'
|
||||
this.icon = 'hyderetriever.svg'
|
||||
this.category = 'Retrievers'
|
||||
@@ -35,6 +37,14 @@ class HydeRetriever_Retrievers implements INode {
|
||||
name: 'vectorStore',
|
||||
type: 'VectorStore'
|
||||
},
|
||||
{
|
||||
label: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
description: 'Query to retrieve documents from retriever. If not specified, user question will be used',
|
||||
optional: true,
|
||||
acceptVariable: true
|
||||
},
|
||||
{
|
||||
label: 'Select Defined Prompt',
|
||||
name: 'promptKey',
|
||||
@@ -121,15 +131,34 @@ Passage:`
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
this.outputs = [
|
||||
{
|
||||
label: 'HyDE Retriever',
|
||||
name: 'retriever',
|
||||
baseClasses: this.baseClasses
|
||||
},
|
||||
{
|
||||
label: 'Document',
|
||||
name: 'document',
|
||||
baseClasses: ['Document']
|
||||
},
|
||||
{
|
||||
label: 'Text',
|
||||
name: 'text',
|
||||
baseClasses: ['string', 'json']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData): Promise<any> {
|
||||
async init(nodeData: INodeData, input: string): Promise<any> {
|
||||
const llm = nodeData.inputs?.model as BaseLanguageModel
|
||||
const vectorStore = nodeData.inputs?.vectorStore as VectorStore
|
||||
const promptKey = nodeData.inputs?.promptKey as PromptKey
|
||||
const customPrompt = nodeData.inputs?.customPrompt as string
|
||||
const query = nodeData.inputs?.query as string
|
||||
const topK = nodeData.inputs?.topK as string
|
||||
const k = topK ? parseFloat(topK) : 4
|
||||
const output = nodeData.outputs?.output as string
|
||||
|
||||
const obj: HydeRetrieverOptions<any> = {
|
||||
llm,
|
||||
@@ -141,6 +170,19 @@ Passage:`
|
||||
else if (promptKey) obj.promptTemplate = promptKey
|
||||
|
||||
const retriever = new HydeRetriever(obj)
|
||||
|
||||
if (output === 'retriever') return retriever
|
||||
else if (output === 'document') return await retriever.getRelevantDocuments(query ? query : input)
|
||||
else if (output === 'text') {
|
||||
let finaltext = ''
|
||||
|
||||
const docs = await retriever.getRelevantDocuments(query ? query : input)
|
||||
|
||||
for (const doc of docs) finaltext += `${doc.pageContent}\n`
|
||||
|
||||
return handleEscapeCharacters(finaltext, false)
|
||||
}
|
||||
|
||||
return retriever
|
||||
}
|
||||
}
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
||||
import { BaseRetriever } from 'langchain/schema/retriever'
|
||||
import { ContextualCompressionRetriever } from 'langchain/retrievers/contextual_compression'
|
||||
import { BaseLanguageModel } from 'langchain/base_language'
|
||||
import { LLMChainExtractor } from 'langchain/retrievers/document_compressors/chain_extract'
|
||||
import { handleEscapeCharacters } from '../../../src/utils'
|
||||
|
||||
class LLMFilterCompressionRetriever_Retrievers implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
description: string
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
inputs: INodeParams[]
|
||||
outputs: INodeOutputsValue[]
|
||||
badge: string
|
||||
|
||||
constructor() {
|
||||
this.label = 'LLM Filter Retriever'
|
||||
this.name = 'llmFilterRetriever'
|
||||
this.version = 1.0
|
||||
this.type = 'LLMFilterRetriever'
|
||||
this.icon = 'llmFilterRetriever.svg'
|
||||
this.category = 'Retrievers'
|
||||
this.badge = 'NEW'
|
||||
this.description =
|
||||
'Iterate over the initially returned documents and extract, from each, only the content that is relevant to the query'
|
||||
this.baseClasses = [this.type, 'BaseRetriever']
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Vector Store Retriever',
|
||||
name: 'baseRetriever',
|
||||
type: 'VectorStoreRetriever'
|
||||
},
|
||||
{
|
||||
label: 'Language Model',
|
||||
name: 'model',
|
||||
type: 'BaseLanguageModel'
|
||||
},
|
||||
{
|
||||
label: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
description: 'Query to retrieve documents from retriever. If not specified, user question will be used',
|
||||
optional: true,
|
||||
acceptVariable: true
|
||||
}
|
||||
]
|
||||
this.outputs = [
|
||||
{
|
||||
label: 'LLM Filter Retriever',
|
||||
name: 'retriever',
|
||||
baseClasses: this.baseClasses
|
||||
},
|
||||
{
|
||||
label: 'Document',
|
||||
name: 'document',
|
||||
baseClasses: ['Document']
|
||||
},
|
||||
{
|
||||
label: 'Text',
|
||||
name: 'text',
|
||||
baseClasses: ['string', 'json']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, input: string): Promise<any> {
|
||||
const baseRetriever = nodeData.inputs?.baseRetriever as BaseRetriever
|
||||
const model = nodeData.inputs?.model as BaseLanguageModel
|
||||
const query = nodeData.inputs?.query as string
|
||||
const output = nodeData.outputs?.output as string
|
||||
|
||||
if (!model) throw new Error('There must be a LLM model connected to LLM Filter Retriever')
|
||||
|
||||
const retriever = new ContextualCompressionRetriever({
|
||||
baseCompressor: LLMChainExtractor.fromLLM(model),
|
||||
baseRetriever: baseRetriever
|
||||
})
|
||||
|
||||
if (output === 'retriever') return retriever
|
||||
else if (output === 'document') return await retriever.getRelevantDocuments(query ? query : input)
|
||||
else if (output === 'text') {
|
||||
let finaltext = ''
|
||||
|
||||
const docs = await retriever.getRelevantDocuments(query ? query : input)
|
||||
|
||||
for (const doc of docs) finaltext += `${doc.pageContent}\n`
|
||||
|
||||
return handleEscapeCharacters(finaltext, false)
|
||||
}
|
||||
|
||||
return retriever
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: LLMFilterCompressionRetriever_Retrievers }
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-filter-check" 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 d="M11.18 20.274l-2.18 .726v-8.5l-4.48 -4.928a2 2 0 0 1 -.52 -1.345v-2.227h16v2.172a2 2 0 0 1 -.586 1.414l-4.414 4.414v3" /><path d="M15 19l2 2l4 -4" /></svg>
|
||||
|
After Width: | Height: | Size: 446 B |
@@ -0,0 +1,136 @@
|
||||
import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
||||
import { BaseLanguageModel } from 'langchain/base_language'
|
||||
import { ContextualCompressionRetriever } from 'langchain/retrievers/contextual_compression'
|
||||
import { BaseRetriever } from 'langchain/schema/retriever'
|
||||
import { ReciprocalRankFusion } from './ReciprocalRankFusion'
|
||||
import { VectorStoreRetriever } from 'langchain/vectorstores/base'
|
||||
import { handleEscapeCharacters } from '../../../src/utils'
|
||||
|
||||
class RRFRetriever_Retrievers implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
description: string
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
inputs: INodeParams[]
|
||||
badge: string
|
||||
outputs: INodeOutputsValue[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Reciprocal Rank Fusion Retriever'
|
||||
this.name = 'RRFRetriever'
|
||||
this.version = 1.0
|
||||
this.type = 'RRFRetriever'
|
||||
this.badge = 'NEW'
|
||||
this.icon = 'rrfRetriever.svg'
|
||||
this.category = 'Retrievers'
|
||||
this.description = 'Reciprocal Rank Fusion to re-rank search results by multiple query generation.'
|
||||
this.baseClasses = [this.type, 'BaseRetriever']
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Vector Store Retriever',
|
||||
name: 'baseRetriever',
|
||||
type: 'VectorStoreRetriever'
|
||||
},
|
||||
{
|
||||
label: 'Language Model',
|
||||
name: 'model',
|
||||
type: 'BaseLanguageModel'
|
||||
},
|
||||
{
|
||||
label: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
description: 'Query to retrieve documents from retriever. If not specified, user question will be used',
|
||||
optional: true,
|
||||
acceptVariable: true
|
||||
},
|
||||
{
|
||||
label: 'Query Count',
|
||||
name: 'queryCount',
|
||||
description: 'Number of synthetic queries to generate. Default to 4',
|
||||
placeholder: '4',
|
||||
type: 'number',
|
||||
default: 4,
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Top K',
|
||||
name: 'topK',
|
||||
description: 'Number of top results to fetch. Default to the TopK of the Base Retriever',
|
||||
placeholder: '0',
|
||||
type: 'number',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Constant',
|
||||
name: 'c',
|
||||
description:
|
||||
'A constant added to the rank, controlling the balance between the importance of high-ranked items and the consideration given to lower-ranked items.\n' +
|
||||
'Default is 60',
|
||||
placeholder: '60',
|
||||
type: 'number',
|
||||
default: 60,
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
this.outputs = [
|
||||
{
|
||||
label: 'Reciprocal Rank Fusion Retriever',
|
||||
name: 'retriever',
|
||||
baseClasses: this.baseClasses
|
||||
},
|
||||
{
|
||||
label: 'Document',
|
||||
name: 'document',
|
||||
baseClasses: ['Document']
|
||||
},
|
||||
{
|
||||
label: 'Text',
|
||||
name: 'text',
|
||||
baseClasses: ['string', 'json']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, input: string): Promise<any> {
|
||||
const llm = nodeData.inputs?.model as BaseLanguageModel
|
||||
const baseRetriever = nodeData.inputs?.baseRetriever as BaseRetriever
|
||||
const query = nodeData.inputs?.query as string
|
||||
const queryCount = nodeData.inputs?.queryCount as string
|
||||
const q = queryCount ? parseFloat(queryCount) : 4
|
||||
const topK = nodeData.inputs?.topK as string
|
||||
const k = topK ? parseFloat(topK) : (baseRetriever as VectorStoreRetriever).k ?? 4
|
||||
const constantC = nodeData.inputs?.c as string
|
||||
const c = topK ? parseFloat(constantC) : 60
|
||||
const output = nodeData.outputs?.output as string
|
||||
|
||||
const ragFusion = new ReciprocalRankFusion(llm, baseRetriever as VectorStoreRetriever, q, k, c)
|
||||
const retriever = new ContextualCompressionRetriever({
|
||||
baseCompressor: ragFusion,
|
||||
baseRetriever: baseRetriever
|
||||
})
|
||||
|
||||
if (output === 'retriever') return retriever
|
||||
else if (output === 'document') return await retriever.getRelevantDocuments(query ? query : input)
|
||||
else if (output === 'text') {
|
||||
let finaltext = ''
|
||||
|
||||
const docs = await retriever.getRelevantDocuments(query ? query : input)
|
||||
|
||||
for (const doc of docs) finaltext += `${doc.pageContent}\n`
|
||||
|
||||
return handleEscapeCharacters(finaltext, false)
|
||||
}
|
||||
|
||||
return retriever
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: RRFRetriever_Retrievers }
|
||||
@@ -0,0 +1,96 @@
|
||||
import { BaseDocumentCompressor } from 'langchain/retrievers/document_compressors'
|
||||
import { Document } from 'langchain/document'
|
||||
import { Callbacks } from 'langchain/callbacks'
|
||||
import { BaseLanguageModel } from 'langchain/base_language'
|
||||
import { ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate } from 'langchain/prompts'
|
||||
import { LLMChain } from 'langchain/chains'
|
||||
import { VectorStoreRetriever } from 'langchain/vectorstores/base'
|
||||
|
||||
export class ReciprocalRankFusion extends BaseDocumentCompressor {
|
||||
private readonly llm: BaseLanguageModel
|
||||
private readonly queryCount: number
|
||||
private readonly topK: number
|
||||
private readonly c: number
|
||||
private baseRetriever: VectorStoreRetriever
|
||||
constructor(llm: BaseLanguageModel, baseRetriever: VectorStoreRetriever, queryCount: number, topK: number, c: number) {
|
||||
super()
|
||||
this.queryCount = queryCount
|
||||
this.llm = llm
|
||||
this.baseRetriever = baseRetriever
|
||||
this.topK = topK
|
||||
this.c = c
|
||||
}
|
||||
async compressDocuments(
|
||||
documents: Document<Record<string, any>>[],
|
||||
query: string,
|
||||
_?: Callbacks | undefined
|
||||
): Promise<Document<Record<string, any>>[]> {
|
||||
// avoid empty api call
|
||||
if (documents.length === 0) {
|
||||
return []
|
||||
}
|
||||
const chatPrompt = ChatPromptTemplate.fromMessages([
|
||||
SystemMessagePromptTemplate.fromTemplate(
|
||||
'You are a helpful assistant that generates multiple search queries based on a single input query.'
|
||||
),
|
||||
HumanMessagePromptTemplate.fromTemplate(
|
||||
'Generate multiple search queries related to: {input}. Provide these alternative questions separated by newlines, do not add any numbers.'
|
||||
),
|
||||
HumanMessagePromptTemplate.fromTemplate('OUTPUT (' + this.queryCount + ' queries):')
|
||||
])
|
||||
const llmChain = new LLMChain({
|
||||
llm: this.llm,
|
||||
prompt: chatPrompt
|
||||
})
|
||||
const multipleQueries = await llmChain.call({ input: query })
|
||||
const queries = []
|
||||
queries.push(query)
|
||||
multipleQueries.text.split('\n').map((q: string) => {
|
||||
queries.push(q)
|
||||
})
|
||||
const docList: Document<Record<string, any>>[][] = []
|
||||
for (let i = 0; i < queries.length; i++) {
|
||||
const resultOne = await this.baseRetriever.vectorStore.similaritySearch(queries[i], 5)
|
||||
const docs: any[] = []
|
||||
resultOne.forEach((doc) => {
|
||||
docs.push(doc)
|
||||
})
|
||||
docList.push(docs)
|
||||
}
|
||||
|
||||
return this.reciprocalRankFunction(docList, this.c)
|
||||
}
|
||||
|
||||
reciprocalRankFunction(docList: Document<Record<string, any>>[][], k: number): Document<Record<string, any>>[] {
|
||||
docList.forEach((docs: Document<Record<string, any>>[]) => {
|
||||
docs.forEach((doc: any, index: number) => {
|
||||
let rank = index + 1
|
||||
if (doc.metadata.relevancy_score) {
|
||||
doc.metadata.relevancy_score += 1 / (rank + k)
|
||||
} else {
|
||||
doc.metadata.relevancy_score = 1 / (rank + k)
|
||||
}
|
||||
})
|
||||
})
|
||||
const scoreArray: any[] = []
|
||||
docList.forEach((docs: Document<Record<string, any>>[]) => {
|
||||
docs.forEach((doc: any) => {
|
||||
scoreArray.push(doc.metadata.relevancy_score)
|
||||
})
|
||||
})
|
||||
scoreArray.sort((a, b) => b - a)
|
||||
const rerankedDocuments: Document<Record<string, any>>[] = []
|
||||
const seenScores: any[] = []
|
||||
scoreArray.forEach((score) => {
|
||||
docList.forEach((docs) => {
|
||||
docs.forEach((doc: any) => {
|
||||
if (doc.metadata.relevancy_score === score && seenScores.indexOf(score) === -1) {
|
||||
rerankedDocuments.push(doc)
|
||||
seenScores.push(doc.metadata.relevancy_score)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
return rerankedDocuments.splice(0, this.topK)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-math-x-divide-y-2" 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 d="M3 21l18 -18" /><path d="M15 14l3 4.5" /><path d="M21 14l-4.5 7" /><path d="M3 4l6 6" /><path d="M3 10l6 -6" /></svg>
|
||||
|
After Width: | Height: | Size: 413 B |
+16
-5
@@ -18,7 +18,7 @@ class SimilarityThresholdRetriever_Retrievers implements INode {
|
||||
constructor() {
|
||||
this.label = 'Similarity Score Threshold Retriever'
|
||||
this.name = 'similarityThresholdRetriever'
|
||||
this.version = 1.0
|
||||
this.version = 2.0
|
||||
this.type = 'SimilarityThresholdRetriever'
|
||||
this.icon = 'similaritythreshold.svg'
|
||||
this.category = 'Retrievers'
|
||||
@@ -30,6 +30,14 @@ class SimilarityThresholdRetriever_Retrievers implements INode {
|
||||
name: 'vectorStore',
|
||||
type: 'VectorStore'
|
||||
},
|
||||
{
|
||||
label: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
description: 'Query to retrieve documents from retriever. If not specified, user question will be used',
|
||||
optional: true,
|
||||
acceptVariable: true
|
||||
},
|
||||
{
|
||||
label: 'Minimum Similarity Score (%)',
|
||||
name: 'minSimilarityScore',
|
||||
@@ -44,7 +52,8 @@ class SimilarityThresholdRetriever_Retrievers implements INode {
|
||||
description: `The maximum number of results to fetch`,
|
||||
type: 'number',
|
||||
default: 20,
|
||||
step: 1
|
||||
step: 1,
|
||||
additionalParams: true
|
||||
},
|
||||
{
|
||||
label: 'K Increment',
|
||||
@@ -52,7 +61,8 @@ class SimilarityThresholdRetriever_Retrievers implements INode {
|
||||
description: `How much to increase K by each time. It'll fetch N results, then N + kIncrement, then N + kIncrement * 2, etc.`,
|
||||
type: 'number',
|
||||
default: 2,
|
||||
step: 1
|
||||
step: 1,
|
||||
additionalParams: true
|
||||
}
|
||||
]
|
||||
this.outputs = [
|
||||
@@ -77,6 +87,7 @@ class SimilarityThresholdRetriever_Retrievers implements INode {
|
||||
async init(nodeData: INodeData, input: string): Promise<any> {
|
||||
const vectorStore = nodeData.inputs?.vectorStore as VectorStore
|
||||
const minSimilarityScore = nodeData.inputs?.minSimilarityScore as number
|
||||
const query = nodeData.inputs?.query as string
|
||||
const maxK = nodeData.inputs?.maxK as string
|
||||
const kIncrement = nodeData.inputs?.kIncrement as string
|
||||
|
||||
@@ -89,11 +100,11 @@ class SimilarityThresholdRetriever_Retrievers implements INode {
|
||||
})
|
||||
|
||||
if (output === 'retriever') return retriever
|
||||
else if (output === 'document') return await retriever.getRelevantDocuments(input)
|
||||
else if (output === 'document') return await retriever.getRelevantDocuments(query ? query : input)
|
||||
else if (output === 'text') {
|
||||
let finaltext = ''
|
||||
|
||||
const docs = await retriever.getRelevantDocuments(input)
|
||||
const docs = await retriever.getRelevantDocuments(query ? query : input)
|
||||
|
||||
for (const doc of docs) finaltext += `${doc.pageContent}\n`
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../src/Interface'
|
||||
import { convertSchemaToZod, getBaseClasses } from '../../../src/utils'
|
||||
import { convertSchemaToZod, getBaseClasses, getVars } from '../../../src/utils'
|
||||
import { DynamicStructuredTool } from './core'
|
||||
import { z } from 'zod'
|
||||
import { DataSource } from 'typeorm'
|
||||
@@ -60,7 +60,7 @@ class CustomTool_Tools implements INode {
|
||||
}
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
const selectedToolId = nodeData.inputs?.selectedTool as string
|
||||
const customToolFunc = nodeData.inputs?.customToolFunc as string
|
||||
|
||||
@@ -81,29 +81,9 @@ class CustomTool_Tools implements INode {
|
||||
}
|
||||
if (customToolFunc) obj.code = customToolFunc
|
||||
|
||||
const variables = await appDataSource.getRepository(databaseEntities['Variable']).find()
|
||||
const variables = await getVars(appDataSource, databaseEntities, nodeData)
|
||||
|
||||
// override variables defined in overrideConfig
|
||||
// nodeData.inputs.variables is an Object, check each property and override the variable
|
||||
if (nodeData?.inputs?.vars) {
|
||||
for (const propertyName of Object.getOwnPropertyNames(nodeData.inputs.vars)) {
|
||||
const foundVar = variables.find((v) => v.name === propertyName)
|
||||
if (foundVar) {
|
||||
// even if the variable was defined as runtime, we override it with static value
|
||||
foundVar.type = 'static'
|
||||
foundVar.value = nodeData.inputs.vars[propertyName]
|
||||
} else {
|
||||
// add it the variables, if not found locally in the db
|
||||
variables.push({ name: propertyName, type: 'static', value: nodeData.inputs.vars[propertyName] })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const flow = {
|
||||
chatId: options.chatId, // id is uppercase (I)
|
||||
chatflowId: options.chatflowid, // id is lowercase (i)
|
||||
input
|
||||
}
|
||||
const flow = { chatflowId: options.chatflowid }
|
||||
|
||||
let dynamicStructuredTool = new DynamicStructuredTool(obj)
|
||||
dynamicStructuredTool.setVariables(variables)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from 'zod'
|
||||
import { NodeVM } from 'vm2'
|
||||
import { availableDependencies } from '../../../src/utils'
|
||||
import { availableDependencies, defaultAllowBuiltInDep, prepareSandboxVars } from '../../../src/utils'
|
||||
import { RunnableConfig } from '@langchain/core/runnables'
|
||||
import { StructuredTool, ToolParams } from '@langchain/core/tools'
|
||||
import { CallbackManagerForToolRun, Callbacks, CallbackManager, parseCallbackConfigArg } from '@langchain/core/callbacks/manager'
|
||||
@@ -55,7 +55,12 @@ export class DynamicStructuredTool<
|
||||
this.schema = fields.schema
|
||||
}
|
||||
|
||||
async call(arg: z.output<T>, configArg?: RunnableConfig | Callbacks, tags?: string[], overrideSessionId?: string): Promise<string> {
|
||||
async call(
|
||||
arg: z.output<T>,
|
||||
configArg?: RunnableConfig | Callbacks,
|
||||
tags?: string[],
|
||||
flowConfig?: { sessionId?: string; chatId?: string; input?: string }
|
||||
): Promise<string> {
|
||||
const config = parseCallbackConfigArg(configArg)
|
||||
if (config.runName === undefined) {
|
||||
config.runName = this.name
|
||||
@@ -86,7 +91,7 @@ export class DynamicStructuredTool<
|
||||
)
|
||||
let result
|
||||
try {
|
||||
result = await this._call(parsed, runManager, overrideSessionId)
|
||||
result = await this._call(parsed, runManager, flowConfig)
|
||||
} catch (e) {
|
||||
await runManager?.handleToolError(e)
|
||||
throw e
|
||||
@@ -95,7 +100,11 @@ export class DynamicStructuredTool<
|
||||
return result
|
||||
}
|
||||
|
||||
protected async _call(arg: z.output<T>, _?: CallbackManagerForToolRun, overrideSessionId?: string): Promise<string> {
|
||||
protected async _call(
|
||||
arg: z.output<T>,
|
||||
_?: CallbackManagerForToolRun,
|
||||
flowConfig?: { sessionId?: string; chatId?: string; input?: string }
|
||||
): Promise<string> {
|
||||
let sandbox: any = {}
|
||||
if (typeof arg === 'object' && Object.keys(arg).length) {
|
||||
for (const item in arg) {
|
||||
@@ -103,48 +112,13 @@ export class DynamicStructuredTool<
|
||||
}
|
||||
}
|
||||
|
||||
// inject variables
|
||||
let vars = {}
|
||||
if (this.variables) {
|
||||
for (const item of this.variables) {
|
||||
let value = item.value
|
||||
|
||||
// read from .env file
|
||||
if (item.type === 'runtime') {
|
||||
value = process.env[item.name]
|
||||
}
|
||||
|
||||
Object.defineProperty(vars, item.name, {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: value
|
||||
})
|
||||
}
|
||||
}
|
||||
sandbox['$vars'] = vars
|
||||
sandbox['$vars'] = prepareSandboxVars(this.variables)
|
||||
|
||||
// inject flow properties
|
||||
if (this.flowObj) {
|
||||
sandbox['$flow'] = { ...this.flowObj, sessionId: overrideSessionId }
|
||||
sandbox['$flow'] = { ...this.flowObj, ...flowConfig }
|
||||
}
|
||||
|
||||
const defaultAllowBuiltInDep = [
|
||||
'assert',
|
||||
'buffer',
|
||||
'crypto',
|
||||
'events',
|
||||
'http',
|
||||
'https',
|
||||
'net',
|
||||
'path',
|
||||
'querystring',
|
||||
'timers',
|
||||
'tls',
|
||||
'url',
|
||||
'zlib'
|
||||
]
|
||||
|
||||
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
|
||||
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
|
||||
: defaultAllowBuiltInDep
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
||||
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
||||
import { NodeVM } from 'vm2'
|
||||
import { availableDependencies, handleEscapeCharacters } from '../../../src/utils'
|
||||
import { DataSource } from 'typeorm'
|
||||
import { availableDependencies, defaultAllowBuiltInDep, getVars, handleEscapeCharacters, prepareSandboxVars } from '../../../src/utils'
|
||||
|
||||
class CustomFunction_Utilities implements INode {
|
||||
label: string
|
||||
@@ -55,9 +56,19 @@ class CustomFunction_Utilities implements INode {
|
||||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, input: string): Promise<any> {
|
||||
async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
|
||||
const javascriptFunction = nodeData.inputs?.javascriptFunction as string
|
||||
const functionInputVariablesRaw = nodeData.inputs?.functionInputVariables
|
||||
const appDataSource = options.appDataSource as DataSource
|
||||
const databaseEntities = options.databaseEntities as IDatabaseEntity
|
||||
|
||||
const variables = await getVars(appDataSource, databaseEntities, nodeData)
|
||||
const flow = {
|
||||
chatflowId: options.chatflowid,
|
||||
sessionId: options.sessionId,
|
||||
chatId: options.chatId,
|
||||
input
|
||||
}
|
||||
|
||||
let inputVars: ICommonObject = {}
|
||||
if (functionInputVariablesRaw) {
|
||||
@@ -69,29 +80,30 @@ class CustomFunction_Utilities implements INode {
|
||||
}
|
||||
}
|
||||
|
||||
let sandbox: any = { $input: input }
|
||||
|
||||
if (Object.keys(inputVars).length) {
|
||||
for (const item in inputVars) {
|
||||
sandbox[`$${item}`] = inputVars[item]
|
||||
// Some values might be a stringified JSON, parse it
|
||||
for (const key in inputVars) {
|
||||
if (typeof inputVars[key] === 'string' && inputVars[key].startsWith('{') && inputVars[key].endsWith('}')) {
|
||||
try {
|
||||
inputVars[key] = JSON.parse(inputVars[key])
|
||||
} catch (e) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const defaultAllowBuiltInDep = [
|
||||
'assert',
|
||||
'buffer',
|
||||
'crypto',
|
||||
'events',
|
||||
'http',
|
||||
'https',
|
||||
'net',
|
||||
'path',
|
||||
'querystring',
|
||||
'timers',
|
||||
'tls',
|
||||
'url',
|
||||
'zlib'
|
||||
]
|
||||
let sandbox: any = { $input: input }
|
||||
sandbox['$vars'] = prepareSandboxVars(variables)
|
||||
sandbox['$flow'] = flow
|
||||
|
||||
if (Object.keys(inputVars).length) {
|
||||
for (const item in inputVars) {
|
||||
let value = inputVars[item]
|
||||
if (typeof value === 'string') {
|
||||
value = handleEscapeCharacters(value, true)
|
||||
}
|
||||
sandbox[`$${item}`] = value
|
||||
}
|
||||
}
|
||||
|
||||
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
|
||||
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
||||
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
||||
import { NodeVM } from 'vm2'
|
||||
import { availableDependencies } from '../../../src/utils'
|
||||
import { DataSource } from 'typeorm'
|
||||
import { availableDependencies, defaultAllowBuiltInDep, getVars, handleEscapeCharacters, prepareSandboxVars } from '../../../src/utils'
|
||||
|
||||
class IfElseFunction_Utilities implements INode {
|
||||
label: string
|
||||
@@ -73,10 +74,20 @@ class IfElseFunction_Utilities implements INode {
|
||||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, input: string): Promise<any> {
|
||||
async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
|
||||
const ifFunction = nodeData.inputs?.ifFunction as string
|
||||
const elseFunction = nodeData.inputs?.elseFunction as string
|
||||
const functionInputVariablesRaw = nodeData.inputs?.functionInputVariables
|
||||
const appDataSource = options.appDataSource as DataSource
|
||||
const databaseEntities = options.databaseEntities as IDatabaseEntity
|
||||
|
||||
const variables = await getVars(appDataSource, databaseEntities, nodeData)
|
||||
const flow = {
|
||||
chatflowId: options.chatflowid,
|
||||
sessionId: options.sessionId,
|
||||
chatId: options.chatId,
|
||||
input
|
||||
}
|
||||
|
||||
let inputVars: ICommonObject = {}
|
||||
if (functionInputVariablesRaw) {
|
||||
@@ -84,34 +95,35 @@ class IfElseFunction_Utilities implements INode {
|
||||
inputVars =
|
||||
typeof functionInputVariablesRaw === 'object' ? functionInputVariablesRaw : JSON.parse(functionInputVariablesRaw)
|
||||
} catch (exception) {
|
||||
throw new Error("Invalid JSON in the PromptTemplate's promptValues: " + exception)
|
||||
throw new Error("Invalid JSON in the IfElse's Input Variables: " + exception)
|
||||
}
|
||||
}
|
||||
|
||||
// Some values might be a stringified JSON, parse it
|
||||
for (const key in inputVars) {
|
||||
if (typeof inputVars[key] === 'string' && inputVars[key].startsWith('{') && inputVars[key].endsWith('}')) {
|
||||
try {
|
||||
inputVars[key] = JSON.parse(inputVars[key])
|
||||
} catch (e) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let sandbox: any = { $input: input }
|
||||
sandbox['$vars'] = prepareSandboxVars(variables)
|
||||
sandbox['$flow'] = flow
|
||||
|
||||
if (Object.keys(inputVars).length) {
|
||||
for (const item in inputVars) {
|
||||
sandbox[`$${item}`] = inputVars[item]
|
||||
let value = inputVars[item]
|
||||
if (typeof value === 'string') {
|
||||
value = handleEscapeCharacters(value, true)
|
||||
}
|
||||
sandbox[`$${item}`] = value
|
||||
}
|
||||
}
|
||||
|
||||
const defaultAllowBuiltInDep = [
|
||||
'assert',
|
||||
'buffer',
|
||||
'crypto',
|
||||
'events',
|
||||
'http',
|
||||
'https',
|
||||
'net',
|
||||
'path',
|
||||
'querystring',
|
||||
'timers',
|
||||
'tls',
|
||||
'url',
|
||||
'zlib'
|
||||
]
|
||||
|
||||
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
|
||||
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
|
||||
: defaultAllowBuiltInDep
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { INode, INodeParams } from '../../../src/Interface'
|
||||
|
||||
class StickyNote implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
description: string
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Sticky Note'
|
||||
this.name = 'stickyNote'
|
||||
this.version = 1.0
|
||||
this.type = 'StickyNote'
|
||||
this.icon = 'stickyNote.svg'
|
||||
this.category = 'Utilities'
|
||||
this.description = 'Add a sticky note'
|
||||
this.inputs = [
|
||||
{
|
||||
label: '',
|
||||
name: 'note',
|
||||
type: 'string',
|
||||
rows: 1,
|
||||
placeholder: 'Type something here',
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
this.baseClasses = [this.type]
|
||||
}
|
||||
|
||||
async init(): Promise<any> {
|
||||
return new StickyNote()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: StickyNote }
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M15.5 3H5a2 2 0 0 0-2 2v14c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2V8.5L15.5 3Z"/>
|
||||
<path d="M15 3v6h6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 305 B |
@@ -0,0 +1,182 @@
|
||||
import { flatten } from 'lodash'
|
||||
import { Embeddings } from 'langchain/embeddings/base'
|
||||
import { Document } from 'langchain/document'
|
||||
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, getCredentialData } from '../../../src/utils'
|
||||
import { AstraDBVectorStore, AstraLibArgs } from '@langchain/community/vectorstores/astradb'
|
||||
import { addMMRInputParams, resolveVectorStoreOrRetriever } from '../VectorStoreUtils'
|
||||
|
||||
class Astra_VectorStores implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
description: string
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
badge: string
|
||||
baseClasses: string[]
|
||||
inputs: INodeParams[]
|
||||
credential: INodeParams
|
||||
outputs: INodeOutputsValue[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Astra'
|
||||
this.name = 'Astra'
|
||||
this.version = 1.0
|
||||
this.type = 'Astra'
|
||||
this.icon = 'astra.svg'
|
||||
this.category = 'Vector Stores'
|
||||
this.description = `Upsert embedded data and perform similarity or mmr search upon query using DataStax Astra DB, a serverless vector database that’s perfect for managing mission-critical AI workloads`
|
||||
this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever']
|
||||
this.badge = 'NEW'
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['AstraDBApi']
|
||||
}
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Document',
|
||||
name: 'document',
|
||||
type: 'Document',
|
||||
list: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Embeddings',
|
||||
name: 'embeddings',
|
||||
type: 'Embeddings'
|
||||
},
|
||||
{
|
||||
label: 'Vector Dimension',
|
||||
name: 'vectorDimension',
|
||||
type: 'number',
|
||||
placeholder: '1536',
|
||||
optional: true,
|
||||
description: 'Dimension used for storing vector embedding'
|
||||
},
|
||||
{
|
||||
label: 'Similarity Metric',
|
||||
name: 'similarityMetric',
|
||||
type: 'string',
|
||||
placeholder: 'cosine',
|
||||
optional: true,
|
||||
description: 'cosine | euclidean | dot_product'
|
||||
},
|
||||
{
|
||||
label: 'Top K',
|
||||
name: 'topK',
|
||||
description: 'Number of top results to fetch. Default to 4',
|
||||
placeholder: '4',
|
||||
type: 'number',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
addMMRInputParams(this.inputs)
|
||||
this.outputs = [
|
||||
{
|
||||
label: 'Astra Retriever',
|
||||
name: 'retriever',
|
||||
baseClasses: this.baseClasses
|
||||
},
|
||||
{
|
||||
label: 'Astra Vector Store',
|
||||
name: 'vectorStore',
|
||||
baseClasses: [this.type, ...getBaseClasses(AstraDBVectorStore)]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
vectorStoreMethods = {
|
||||
async upsert(nodeData: INodeData, options: ICommonObject): Promise<void> {
|
||||
const docs = nodeData.inputs?.document as Document[]
|
||||
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
||||
const vectorDimension = nodeData.inputs?.vectorDimension as number
|
||||
const similarityMetric = nodeData.inputs?.similarityMetric as 'cosine' | 'euclidean' | 'dot_product' | undefined
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
|
||||
const expectedSimilarityMetric = ['cosine', 'euclidean', 'dot_product']
|
||||
if (similarityMetric && !expectedSimilarityMetric.includes(similarityMetric)) {
|
||||
throw new Error(`Invalid Similarity Metric should be one of 'cosine' | 'euclidean' | 'dot_product'`)
|
||||
}
|
||||
|
||||
const clientConfig = {
|
||||
token: credentialData?.applicationToken,
|
||||
endpoint: credentialData?.dbEndPoint
|
||||
}
|
||||
|
||||
const astraConfig: AstraLibArgs = {
|
||||
...clientConfig,
|
||||
collection: credentialData.collectionName ?? 'flowise_test',
|
||||
collectionOptions: {
|
||||
vector: {
|
||||
dimension: vectorDimension ?? 1536,
|
||||
metric: similarityMetric ?? 'cosine'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const flattenDocs = docs && docs.length ? flatten(docs) : []
|
||||
const finalDocs = []
|
||||
for (let i = 0; i < flattenDocs.length; i += 1) {
|
||||
if (flattenDocs[i] && flattenDocs[i].pageContent) {
|
||||
finalDocs.push(new Document(flattenDocs[i]))
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await AstraDBVectorStore.fromDocuments(finalDocs, embeddings, astraConfig)
|
||||
} catch (e) {
|
||||
throw new Error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
const docs = nodeData.inputs?.document as Document[]
|
||||
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
||||
const vectorDimension = nodeData.inputs?.vectorDimension as number
|
||||
const similarityMetric = nodeData.inputs?.similarityMetric as 'cosine' | 'euclidean' | 'dot_product' | undefined
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
|
||||
const expectedSimilarityMetric = ['cosine', 'euclidean', 'dot_product']
|
||||
if (similarityMetric && !expectedSimilarityMetric.includes(similarityMetric)) {
|
||||
throw new Error(`Invalid Similarity Metric should be one of 'cosine' | 'euclidean' | 'dot_product'`)
|
||||
}
|
||||
|
||||
const clientConfig = {
|
||||
token: credentialData?.applicationToken,
|
||||
endpoint: credentialData?.dbEndPoint
|
||||
}
|
||||
|
||||
const astraConfig: AstraLibArgs = {
|
||||
...clientConfig,
|
||||
collection: credentialData.collectionName ?? 'flowise_test',
|
||||
collectionOptions: {
|
||||
vector: {
|
||||
dimension: vectorDimension ?? 1536,
|
||||
metric: similarityMetric ?? 'cosine'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const flattenDocs = docs && docs.length ? flatten(docs) : []
|
||||
const finalDocs = []
|
||||
for (let i = 0; i < flattenDocs.length; i += 1) {
|
||||
if (flattenDocs[i] && flattenDocs[i].pageContent) {
|
||||
finalDocs.push(new Document(flattenDocs[i]))
|
||||
}
|
||||
}
|
||||
|
||||
const vectorStore = await AstraDBVectorStore.fromExistingIndex(embeddings, astraConfig)
|
||||
|
||||
return resolveVectorStoreOrRetriever(nodeData, vectorStore)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: Astra_VectorStores }
|
||||
@@ -0,0 +1,12 @@
|
||||
<svg width="1200" height="1200" viewBox="0 0 1200 1200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="1200" height="1200" fill="black"/>
|
||||
<g clip-path="url(#clip0_102_1968)">
|
||||
<path d="M508.819 464.97H267.001V737.697H508.819L569.566 690.526V512.14L508.819 464.97ZM313.864 512.14H522.703V690.575H313.864V512.14Z" fill="white"/>
|
||||
<path d="M917.531 514.121V468H696.425L636.389 514.121V577.447L696.425 623.568H889.124V688.545H648.348V734.667H875.409L935.444 688.545V623.568L875.409 577.447H682.709V514.121H917.531Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_102_1968">
|
||||
<rect width="668.444" height="266.667" fill="white" transform="translate(267 468)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 694 B |
@@ -65,6 +65,14 @@ class Milvus_VectorStores implements INode {
|
||||
name: 'milvusCollection',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
label: 'Milvus Text Field',
|
||||
name: 'milvusTextField',
|
||||
type: 'string',
|
||||
placeholder: 'langchain_text',
|
||||
optional: true,
|
||||
additionalParams: true
|
||||
},
|
||||
{
|
||||
label: 'Milvus Filter',
|
||||
name: 'milvusFilter',
|
||||
@@ -150,6 +158,7 @@ class Milvus_VectorStores implements INode {
|
||||
const address = nodeData.inputs?.milvusServerUrl as string
|
||||
const collectionName = nodeData.inputs?.milvusCollection as string
|
||||
const milvusFilter = nodeData.inputs?.milvusFilter as string
|
||||
const textField = nodeData.inputs?.milvusTextField as string
|
||||
|
||||
// embeddings
|
||||
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
||||
@@ -169,7 +178,8 @@ class Milvus_VectorStores implements INode {
|
||||
// init MilvusLibArgs
|
||||
const milVusArgs: MilvusLibArgs = {
|
||||
url: address,
|
||||
collectionName: collectionName
|
||||
collectionName: collectionName,
|
||||
textField: textField
|
||||
}
|
||||
|
||||
if (milvusUser) milVusArgs.username = milvusUser
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Embeddings } from 'langchain/embeddings/base'
|
||||
import { Document } from 'langchain/document'
|
||||
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { addMMRInputParams, resolveVectorStoreOrRetriever } from '../VectorStoreUtils'
|
||||
|
||||
class MongoDBAtlas_VectorStores implements INode {
|
||||
label: string
|
||||
@@ -24,7 +25,7 @@ class MongoDBAtlas_VectorStores implements INode {
|
||||
this.label = 'MongoDB Atlas'
|
||||
this.name = 'mongoDBAtlas'
|
||||
this.version = 1.0
|
||||
this.description = `Upsert embedded data and perform similarity search upon query using MongoDB Atlas, a managed cloud mongodb database`
|
||||
this.description = `Upsert embedded data and perform similarity or mmr search upon query using MongoDB Atlas, a managed cloud mongodb database`
|
||||
this.type = 'MongoDB Atlas'
|
||||
this.icon = 'mongodb.svg'
|
||||
this.category = 'Vector Stores'
|
||||
@@ -95,6 +96,7 @@ class MongoDBAtlas_VectorStores implements INode {
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
addMMRInputParams(this.inputs)
|
||||
this.outputs = [
|
||||
{
|
||||
label: 'MongoDB Retriever',
|
||||
@@ -162,9 +164,6 @@ class MongoDBAtlas_VectorStores implements INode {
|
||||
let textKey = nodeData.inputs?.textKey as string
|
||||
let embeddingKey = nodeData.inputs?.embeddingKey as string
|
||||
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
||||
const topK = nodeData.inputs?.topK as string
|
||||
const k = topK ? parseFloat(topK) : 4
|
||||
const output = nodeData.outputs?.output as string
|
||||
|
||||
let mongoDBConnectUrl = getCredentialParam('mongoDBConnectUrl', credentialData, nodeData)
|
||||
|
||||
@@ -181,13 +180,7 @@ class MongoDBAtlas_VectorStores implements INode {
|
||||
embeddingKey
|
||||
})
|
||||
|
||||
if (output === 'retriever') {
|
||||
return vectorStore.asRetriever(k)
|
||||
} else if (output === 'vectorStore') {
|
||||
;(vectorStore as any).k = k
|
||||
return vectorStore
|
||||
}
|
||||
return vectorStore
|
||||
return resolveVectorStoreOrRetriever(nodeData, vectorStore)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Embeddings } from 'langchain/embeddings/base'
|
||||
import { Document } from 'langchain/document'
|
||||
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { addMMRInputParams, resolveVectorStoreOrRetriever } from '../VectorStoreUtils'
|
||||
|
||||
class Pinecone_VectorStores implements INode {
|
||||
label: string
|
||||
@@ -23,11 +24,11 @@ class Pinecone_VectorStores implements INode {
|
||||
constructor() {
|
||||
this.label = 'Pinecone'
|
||||
this.name = 'pinecone'
|
||||
this.version = 1.0
|
||||
this.version = 2.0
|
||||
this.type = 'Pinecone'
|
||||
this.icon = 'pinecone.svg'
|
||||
this.category = 'Vector Stores'
|
||||
this.description = `Upsert embedded data and perform similarity search upon query using Pinecone, a leading fully managed hosted vector database`
|
||||
this.description = `Upsert embedded data and perform similarity or mmr search using Pinecone, a leading fully managed hosted vector database`
|
||||
this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever']
|
||||
this.badge = 'NEW'
|
||||
this.credential = {
|
||||
@@ -79,6 +80,7 @@ class Pinecone_VectorStores implements INode {
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
addMMRInputParams(this.inputs)
|
||||
this.outputs = [
|
||||
{
|
||||
label: 'Pinecone Retriever',
|
||||
@@ -106,8 +108,7 @@ class Pinecone_VectorStores implements INode {
|
||||
const pineconeEnv = getCredentialParam('pineconeEnv', credentialData, nodeData)
|
||||
|
||||
const client = new Pinecone({
|
||||
apiKey: pineconeApiKey,
|
||||
environment: pineconeEnv
|
||||
apiKey: pineconeApiKey
|
||||
})
|
||||
|
||||
const pineconeIndex = client.Index(index)
|
||||
@@ -140,17 +141,13 @@ class Pinecone_VectorStores implements INode {
|
||||
const pineconeMetadataFilter = nodeData.inputs?.pineconeMetadataFilter
|
||||
const docs = nodeData.inputs?.document as Document[]
|
||||
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
||||
const output = nodeData.outputs?.output as string
|
||||
const topK = nodeData.inputs?.topK as string
|
||||
const k = topK ? parseFloat(topK) : 4
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData)
|
||||
const pineconeEnv = getCredentialParam('pineconeEnv', credentialData, nodeData)
|
||||
|
||||
const client = new Pinecone({
|
||||
apiKey: pineconeApiKey,
|
||||
environment: pineconeEnv
|
||||
apiKey: pineconeApiKey
|
||||
})
|
||||
|
||||
const pineconeIndex = client.Index(index)
|
||||
@@ -175,14 +172,7 @@ class Pinecone_VectorStores implements INode {
|
||||
|
||||
const vectorStore = await PineconeStore.fromExistingIndex(embeddings, obj)
|
||||
|
||||
if (output === 'retriever') {
|
||||
const retriever = vectorStore.asRetriever(k)
|
||||
return retriever
|
||||
} else if (output === 'vectorStore') {
|
||||
;(vectorStore as any).k = k
|
||||
return vectorStore
|
||||
}
|
||||
return vectorStore
|
||||
return resolveVectorStoreOrRetriever(nodeData, vectorStore)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -95,11 +95,9 @@ class Pinecone_Existing_VectorStores implements INode {
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData)
|
||||
const pineconeEnv = getCredentialParam('pineconeEnv', credentialData, nodeData)
|
||||
|
||||
const client = new Pinecone({
|
||||
apiKey: pineconeApiKey,
|
||||
environment: pineconeEnv
|
||||
apiKey: pineconeApiKey
|
||||
})
|
||||
|
||||
const pineconeIndex = client.Index(index)
|
||||
|
||||
@@ -96,11 +96,9 @@ class PineconeUpsert_VectorStores implements INode {
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData)
|
||||
const pineconeEnv = getCredentialParam('pineconeEnv', credentialData, nodeData)
|
||||
|
||||
const client = new Pinecone({
|
||||
apiKey: pineconeApiKey,
|
||||
environment: pineconeEnv
|
||||
apiKey: pineconeApiKey
|
||||
})
|
||||
|
||||
const pineconeIndex = client.Index(index)
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Embeddings } from 'langchain/embeddings/base'
|
||||
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { SupabaseLibArgs, SupabaseVectorStore } from 'langchain/vectorstores/supabase'
|
||||
import { addMMRInputParams, resolveVectorStoreOrRetriever } from '../VectorStoreUtils'
|
||||
|
||||
class Supabase_VectorStores implements INode {
|
||||
label: string
|
||||
@@ -23,11 +24,11 @@ class Supabase_VectorStores implements INode {
|
||||
constructor() {
|
||||
this.label = 'Supabase'
|
||||
this.name = 'supabase'
|
||||
this.version = 1.0
|
||||
this.version = 2.0
|
||||
this.type = 'Supabase'
|
||||
this.icon = 'supabase.svg'
|
||||
this.category = 'Vector Stores'
|
||||
this.description = 'Upsert embedded data and perform similarity search upon query using Supabase via pgvector extension'
|
||||
this.description = 'Upsert embedded data and perform similarity or mmr search upon query using Supabase via pgvector extension'
|
||||
this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever']
|
||||
this.badge = 'NEW'
|
||||
this.credential = {
|
||||
@@ -81,6 +82,7 @@ class Supabase_VectorStores implements INode {
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
addMMRInputParams(this.inputs)
|
||||
this.outputs = [
|
||||
{
|
||||
label: 'Supabase Retriever',
|
||||
@@ -135,9 +137,6 @@ class Supabase_VectorStores implements INode {
|
||||
const queryName = nodeData.inputs?.queryName as string
|
||||
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
||||
const supabaseMetadataFilter = nodeData.inputs?.supabaseMetadataFilter
|
||||
const output = nodeData.outputs?.output as string
|
||||
const topK = nodeData.inputs?.topK as string
|
||||
const k = topK ? parseFloat(topK) : 4
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const supabaseApiKey = getCredentialParam('supabaseApiKey', credentialData, nodeData)
|
||||
@@ -157,14 +156,7 @@ class Supabase_VectorStores implements INode {
|
||||
|
||||
const vectorStore = await SupabaseVectorStore.fromExistingIndex(embeddings, obj)
|
||||
|
||||
if (output === 'retriever') {
|
||||
const retriever = vectorStore.asRetriever(k)
|
||||
return retriever
|
||||
} else if (output === 'vectorStore') {
|
||||
;(vectorStore as any).k = k
|
||||
return vectorStore
|
||||
}
|
||||
return vectorStore
|
||||
return resolveVectorStoreOrRetriever(nodeData, vectorStore)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { flatten } from 'lodash'
|
||||
import { VectaraStore, VectaraLibArgs, VectaraFilter, VectaraContextConfig, VectaraFile } from 'langchain/vectorstores/vectara'
|
||||
import { VectaraStore, VectaraLibArgs, VectaraFilter, VectaraContextConfig, VectaraFile, MMRConfig } from 'langchain/vectorstores/vectara'
|
||||
import { Document } from 'langchain/document'
|
||||
import { Embeddings } from 'langchain/embeddings/base'
|
||||
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
||||
@@ -22,7 +22,7 @@ class Vectara_VectorStores implements INode {
|
||||
constructor() {
|
||||
this.label = 'Vectara'
|
||||
this.name = 'vectara'
|
||||
this.version = 1.0
|
||||
this.version = 2.0
|
||||
this.type = 'Vectara'
|
||||
this.icon = 'vectara.png'
|
||||
this.category = 'Vector Stores'
|
||||
@@ -82,7 +82,9 @@ class Vectara_VectorStores implements INode {
|
||||
label: 'Lambda',
|
||||
name: 'lambda',
|
||||
description:
|
||||
'Improves retrieval accuracy by adjusting the balance (from 0 to 1) between neural search and keyword-based search factors.',
|
||||
'Enable hybrid search to improve retrieval accuracy by adjusting the balance (from 0 to 1) between neural search and keyword-based search factors.' +
|
||||
'A value of 0.0 means that only neural search is used, while a value of 1.0 means that only keyword-based search is used. Defaults to 0.0 (neural only).',
|
||||
default: 0.0,
|
||||
type: 'number',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
@@ -90,8 +92,30 @@ class Vectara_VectorStores implements INode {
|
||||
{
|
||||
label: 'Top K',
|
||||
name: 'topK',
|
||||
description: 'Number of top results to fetch. Defaults to 4',
|
||||
placeholder: '4',
|
||||
description: 'Number of top results to fetch. Defaults to 5',
|
||||
placeholder: '5',
|
||||
type: 'number',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'MMR K',
|
||||
name: 'mmrK',
|
||||
description: 'Number of top results to fetch for MMR. Defaults to 50',
|
||||
placeholder: '50',
|
||||
type: 'number',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'MMR diversity bias',
|
||||
name: 'mmrDiversityBias',
|
||||
step: 0.1,
|
||||
description:
|
||||
'The diversity bias to use for MMR. This is a value between 0.0 and 1.0' +
|
||||
'Values closer to 1.0 optimize for the most diverse results.' +
|
||||
'Defaults to 0 (MMR disabled)',
|
||||
placeholder: '0.0',
|
||||
type: 'number',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
@@ -191,7 +215,9 @@ class Vectara_VectorStores implements INode {
|
||||
const lambda = nodeData.inputs?.lambda as number
|
||||
const output = nodeData.outputs?.output as string
|
||||
const topK = nodeData.inputs?.topK as string
|
||||
const k = topK ? parseFloat(topK) : 4
|
||||
const k = topK ? parseFloat(topK) : 5
|
||||
const mmrK = nodeData.inputs?.mmrK as number
|
||||
const mmrDiversityBias = nodeData.inputs?.mmrDiversityBias as number
|
||||
|
||||
const vectaraArgs: VectaraLibArgs = {
|
||||
apiKey: apiKey,
|
||||
@@ -208,6 +234,11 @@ class Vectara_VectorStores implements INode {
|
||||
if (sentencesBefore) vectaraContextConfig.sentencesBefore = sentencesBefore
|
||||
if (sentencesAfter) vectaraContextConfig.sentencesAfter = sentencesAfter
|
||||
vectaraFilter.contextConfig = vectaraContextConfig
|
||||
const mmrConfig: MMRConfig = {}
|
||||
mmrConfig.enabled = mmrDiversityBias > 0
|
||||
mmrConfig.mmrTopK = mmrK
|
||||
mmrConfig.diversityBias = mmrDiversityBias
|
||||
vectaraFilter.mmrConfig = mmrConfig
|
||||
|
||||
const vectorStore = new VectaraStore(vectaraArgs)
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { INodeData } from '../../src'
|
||||
|
||||
export const resolveVectorStoreOrRetriever = (nodeData: INodeData, vectorStore: any) => {
|
||||
const output = nodeData.outputs?.output as string
|
||||
const searchType = nodeData.outputs?.searchType as string
|
||||
const topK = nodeData.inputs?.topK as string
|
||||
const k = topK ? parseFloat(topK) : 4
|
||||
|
||||
if (output === 'retriever') {
|
||||
if ('mmr' === searchType) {
|
||||
const fetchK = nodeData.inputs?.fetchK as string
|
||||
const lambda = nodeData.inputs?.lambda as string
|
||||
const f = fetchK ? parseInt(fetchK) : 20
|
||||
const l = lambda ? parseFloat(lambda) : 0.5
|
||||
return vectorStore.asRetriever({
|
||||
searchType: 'mmr',
|
||||
k: k,
|
||||
searchKwargs: {
|
||||
fetchK: f,
|
||||
lambda: l
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// "searchType" is "similarity"
|
||||
return vectorStore.asRetriever(k)
|
||||
}
|
||||
} else if (output === 'vectorStore') {
|
||||
;(vectorStore as any).k = k
|
||||
return vectorStore
|
||||
}
|
||||
}
|
||||
|
||||
export const addMMRInputParams = (inputs: any[]) => {
|
||||
const mmrInputParams = [
|
||||
{
|
||||
label: 'Search Type',
|
||||
name: 'searchType',
|
||||
type: 'options',
|
||||
default: 'similarity',
|
||||
options: [
|
||||
{
|
||||
label: 'Similarity',
|
||||
name: 'similarity'
|
||||
},
|
||||
{
|
||||
label: 'Max Marginal Relevance',
|
||||
name: 'mmr'
|
||||
}
|
||||
],
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Fetch K (for MMR Search)',
|
||||
name: 'fetchK',
|
||||
description: 'Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR',
|
||||
placeholder: '20',
|
||||
type: 'number',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Lambda (for MMR Search)',
|
||||
name: 'lambda',
|
||||
description:
|
||||
'Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR',
|
||||
placeholder: '0.5',
|
||||
type: 'number',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
|
||||
inputs.push(...mmrInputParams)
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { Document } from 'langchain/document'
|
||||
import { Embeddings } from 'langchain/embeddings/base'
|
||||
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { addMMRInputParams, resolveVectorStoreOrRetriever } from '../VectorStoreUtils'
|
||||
|
||||
class Weaviate_VectorStores implements INode {
|
||||
label: string
|
||||
@@ -23,12 +24,12 @@ class Weaviate_VectorStores implements INode {
|
||||
constructor() {
|
||||
this.label = 'Weaviate'
|
||||
this.name = 'weaviate'
|
||||
this.version = 1.0
|
||||
this.version = 2.0
|
||||
this.type = 'Weaviate'
|
||||
this.icon = 'weaviate.png'
|
||||
this.category = 'Vector Stores'
|
||||
this.description =
|
||||
'Upsert embedded data and perform similarity search upon query using Weaviate, a scalable open-source vector database'
|
||||
'Upsert embedded data and perform similarity or mmr search using Weaviate, a scalable open-source vector database'
|
||||
this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever']
|
||||
this.badge = 'NEW'
|
||||
this.credential = {
|
||||
@@ -107,6 +108,7 @@ class Weaviate_VectorStores implements INode {
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
addMMRInputParams(this.inputs)
|
||||
this.outputs = [
|
||||
{
|
||||
label: 'Weaviate Retriever',
|
||||
@@ -174,9 +176,6 @@ class Weaviate_VectorStores implements INode {
|
||||
const weaviateTextKey = nodeData.inputs?.weaviateTextKey as string
|
||||
const weaviateMetadataKeys = nodeData.inputs?.weaviateMetadataKeys as string
|
||||
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
||||
const output = nodeData.outputs?.output as string
|
||||
const topK = nodeData.inputs?.topK as string
|
||||
const k = topK ? parseFloat(topK) : 4
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const weaviateApiKey = getCredentialParam('weaviateApiKey', credentialData, nodeData)
|
||||
@@ -199,14 +198,7 @@ class Weaviate_VectorStores implements INode {
|
||||
|
||||
const vectorStore = await WeaviateStore.fromExistingIndex(embeddings, obj)
|
||||
|
||||
if (output === 'retriever') {
|
||||
const retriever = vectorStore.asRetriever(k)
|
||||
return retriever
|
||||
} else if (output === 'vectorStore') {
|
||||
;(vectorStore as any).k = k
|
||||
return vectorStore
|
||||
}
|
||||
return vectorStore
|
||||
return resolveVectorStoreOrRetriever(nodeData, vectorStore)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Embeddings } from 'langchain/embeddings/base'
|
||||
import { Document } from 'langchain/document'
|
||||
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { addMMRInputParams, resolveVectorStoreOrRetriever } from '../VectorStoreUtils'
|
||||
|
||||
class Zep_VectorStores implements INode {
|
||||
label: string
|
||||
@@ -23,12 +24,12 @@ class Zep_VectorStores implements INode {
|
||||
constructor() {
|
||||
this.label = 'Zep'
|
||||
this.name = 'zep'
|
||||
this.version = 1.0
|
||||
this.version = 2.0
|
||||
this.type = 'Zep'
|
||||
this.icon = 'zep.svg'
|
||||
this.category = 'Vector Stores'
|
||||
this.description =
|
||||
'Upsert embedded data and perform similarity search upon query using Zep, a fast and scalable building block for LLM apps'
|
||||
'Upsert embedded data and perform similarity or mmr search upon query using Zep, a fast and scalable building block for LLM apps'
|
||||
this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever']
|
||||
this.badge = 'NEW'
|
||||
this.credential = {
|
||||
@@ -88,6 +89,7 @@ class Zep_VectorStores implements INode {
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
addMMRInputParams(this.inputs)
|
||||
this.outputs = [
|
||||
{
|
||||
label: 'Zep Retriever',
|
||||
@@ -144,9 +146,6 @@ class Zep_VectorStores implements INode {
|
||||
const zepMetadataFilter = nodeData.inputs?.zepMetadataFilter
|
||||
const dimension = nodeData.inputs?.dimension as number
|
||||
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
||||
const output = nodeData.outputs?.output as string
|
||||
const topK = nodeData.inputs?.topK as string
|
||||
const k = topK ? parseFloat(topK) : 4
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const apiKey = getCredentialParam('apiKey', credentialData, nodeData)
|
||||
@@ -165,14 +164,7 @@ class Zep_VectorStores implements INode {
|
||||
|
||||
const vectorStore = await ZepExistingVS.fromExistingIndex(embeddings, zepConfig)
|
||||
|
||||
if (output === 'retriever') {
|
||||
const retriever = vectorStore.asRetriever(k)
|
||||
return retriever
|
||||
} else if (output === 'vectorStore') {
|
||||
;(vectorStore as any).k = k
|
||||
return vectorStore
|
||||
}
|
||||
return vectorStore
|
||||
return resolveVectorStoreOrRetriever(nodeData, vectorStore)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +202,7 @@ class ZepExistingVS extends ZepVectorStore {
|
||||
this.args = args
|
||||
}
|
||||
|
||||
async initalizeCollection(args: IZepConfig & Partial<ZepFilter>) {
|
||||
async initializeCollection(args: IZepConfig & Partial<ZepFilter>) {
|
||||
this.client = await ZepClient.init(args.apiUrl, args.apiKey)
|
||||
try {
|
||||
this.collection = await this.client.document.getCollection(args.collectionName)
|
||||
@@ -259,7 +251,7 @@ class ZepExistingVS extends ZepVectorStore {
|
||||
const newfilter = {
|
||||
where: { and: ANDFilters }
|
||||
}
|
||||
await this.initalizeCollection(this.args!).catch((err) => {
|
||||
await this.initializeCollection(this.args!).catch((err) => {
|
||||
console.error('Error initializing collection:', err)
|
||||
throw err
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user