From b16b8c62655392dba1912d75e653c3949ac7dd82 Mon Sep 17 00:00:00 2001 From: Vikram Segta Date: Thu, 15 Jun 2023 01:16:51 +0530 Subject: [PATCH 01/11] bug/Chat multiline fix --- packages/ui/src/views/chatmessage/ChatMessage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index 5021cd9b..f73a2ba0 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -366,6 +366,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { value={userInput} onChange={onChange} multiline={true} + maxRows={2} endAdornment={ From 92bbc938c914b8c4717b179ad9bfa5df2e43c010 Mon Sep 17 00:00:00 2001 From: Vikram Segta Date: Thu, 15 Jun 2023 10:41:06 +0530 Subject: [PATCH 02/11] bug/Chat input fix --- packages/ui/src/views/chatmessage/ChatExpandDialog.js | 2 +- packages/ui/src/views/chatmessage/ChatMessage.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/views/chatmessage/ChatExpandDialog.js b/packages/ui/src/views/chatmessage/ChatExpandDialog.js index aa5cd504..1b2037a8 100644 --- a/packages/ui/src/views/chatmessage/ChatExpandDialog.js +++ b/packages/ui/src/views/chatmessage/ChatExpandDialog.js @@ -43,7 +43,7 @@ const ChatExpandDialog = ({ show, dialogProps, onClear, onCancel }) => { )} - + diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index f73a2ba0..f37abe64 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -366,7 +366,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { value={userInput} onChange={onChange} multiline={true} - maxRows={2} + maxRows={isDialog ? 7 : 2} endAdornment={ From 56973754f6554079f428cfce3d01640b7107d3b4 Mon Sep 17 00:00:00 2001 From: Vikram Segta Date: Sat, 8 Jul 2023 15:00:47 +0530 Subject: [PATCH 03/11] bug/chat input issue --- packages/ui/src/views/chatmessage/ChatMessage.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.css b/packages/ui/src/views/chatmessage/ChatMessage.css index ef7f1e93..3b006c1d 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.css +++ b/packages/ui/src/views/chatmessage/ChatMessage.css @@ -127,7 +127,8 @@ .cloud-dialog { width: 100%; - height: calc(100vh - 230px); + height: 100vh; + overflow-y: scroll; border-radius: 0.5rem; display: flex; justify-content: center; From eb19c206cfddc9d3f62e98312a6bee7641ed84ce Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 11 Jul 2023 01:53:22 +0100 Subject: [PATCH 04/11] add logs to component chains/agents --- .../nodes/agents/AutoGPT/AutoGPT.ts | 1 - .../OpenAIFunctionAgent.ts | 9 +- .../nodes/chains/ApiChain/GETApiChain.ts | 9 +- .../nodes/chains/ApiChain/OpenAPIChain.ts | 9 +- .../nodes/chains/ApiChain/POSTApiChain.ts | 9 +- .../ConversationChain/ConversationChain.ts | 9 +- .../ConversationalRetrievalQAChain.ts | 9 +- .../nodes/chains/LLMChain/LLMChain.ts | 40 ++-- .../MultiPromptChain/MultiPromptChain.ts | 9 +- .../MultiRetrievalQAChain.ts | 8 +- .../RetrievalQAChain/RetrievalQAChain.ts | 8 +- .../SqlDatabaseChain/SqlDatabaseChain.ts | 9 +- .../chains/VectorDBQAChain/VectorDBQAChain.ts | 9 +- packages/components/src/handler.ts | 180 ++++++++++++++++++ packages/components/src/utils.ts | 48 +---- packages/server/.env.example | 3 +- packages/server/src/ChatflowPool.ts | 4 + packages/server/src/ChildProcess.ts | 176 +++++++++-------- packages/server/src/commands/start.ts | 2 + packages/server/src/index.ts | 36 +++- packages/server/src/utils/config.ts | 4 +- packages/server/src/utils/index.ts | 8 +- packages/server/src/utils/logger.ts | 14 +- 23 files changed, 414 insertions(+), 199 deletions(-) create mode 100644 packages/components/src/handler.ts diff --git a/packages/components/nodes/agents/AutoGPT/AutoGPT.ts b/packages/components/nodes/agents/AutoGPT/AutoGPT.ts index ca118500..044b6f7b 100644 --- a/packages/components/nodes/agents/AutoGPT/AutoGPT.ts +++ b/packages/components/nodes/agents/AutoGPT/AutoGPT.ts @@ -90,7 +90,6 @@ class AutoGPT_Agents implements INode { const res = await executor.run([input]) return res || 'I have completed all my tasks.' } catch (e) { - console.error(e) throw new Error(e) } } diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index 43de74cb..f4d065d9 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -1,10 +1,11 @@ import { ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' import { initializeAgentExecutorWithOptions, AgentExecutor } from 'langchain/agents' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { BaseLanguageModel } from 'langchain/base_language' import { flatten } from 'lodash' import { BaseChatMemory, ChatMessageHistory } from 'langchain/memory' import { AIMessage, HumanMessage } from 'langchain/schema' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class OpenAIFunctionAgent_Agents implements INode { label: string @@ -93,12 +94,14 @@ class OpenAIFunctionAgent_Agents implements INode { executor.memory = memory } + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - const result = await executor.run(input, [handler]) + const result = await executor.run(input, [loggerHandler, handler]) return result } else { - const result = await executor.run(input) + const result = await executor.run(input, [loggerHandler]) return result } } diff --git a/packages/components/nodes/chains/ApiChain/GETApiChain.ts b/packages/components/nodes/chains/ApiChain/GETApiChain.ts index 8e657749..373d0462 100644 --- a/packages/components/nodes/chains/ApiChain/GETApiChain.ts +++ b/packages/components/nodes/chains/ApiChain/GETApiChain.ts @@ -1,8 +1,9 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { APIChain } from 'langchain/chains' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { BaseLanguageModel } from 'langchain/base_language' import { PromptTemplate } from 'langchain/prompts' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' export const API_URL_RAW_PROMPT_TEMPLATE = `You are given the below API Documentation: {api_docs} @@ -95,12 +96,14 @@ class GETApiChain_Chains implements INode { const ansPrompt = nodeData.inputs?.ansPrompt as string const chain = await getAPIChain(apiDocs, model, headers, urlPrompt, ansPrompt) + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) - const res = await chain.run(input, [handler]) + const res = await chain.run(input, [loggerHandler, handler]) return res } else { - const res = await chain.run(input) + const res = await chain.run(input, [loggerHandler]) return res } } diff --git a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts index ae1ae3c0..a231e80a 100644 --- a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts +++ b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts @@ -1,7 +1,8 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { APIChain, createOpenAPIChain } from 'langchain/chains' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { ChatOpenAI } from 'langchain/chat_models/openai' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class OpenApiChain_Chains implements INode { label: string @@ -57,12 +58,14 @@ class OpenApiChain_Chains implements INode { async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const chain = await initChain(nodeData) + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - const res = await chain.run(input, [handler]) + const res = await chain.run(input, [loggerHandler, handler]) return res } else { - const res = await chain.run(input) + const res = await chain.run(input, [loggerHandler]) return res } } diff --git a/packages/components/nodes/chains/ApiChain/POSTApiChain.ts b/packages/components/nodes/chains/ApiChain/POSTApiChain.ts index 3c6ea677..7189f1ad 100644 --- a/packages/components/nodes/chains/ApiChain/POSTApiChain.ts +++ b/packages/components/nodes/chains/ApiChain/POSTApiChain.ts @@ -1,8 +1,9 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { BaseLanguageModel } from 'langchain/base_language' import { PromptTemplate } from 'langchain/prompts' import { API_RESPONSE_RAW_PROMPT_TEMPLATE, API_URL_RAW_PROMPT_TEMPLATE, APIChain } from './postCore' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class POSTApiChain_Chains implements INode { label: string @@ -84,12 +85,14 @@ class POSTApiChain_Chains implements INode { const ansPrompt = nodeData.inputs?.ansPrompt as string const chain = await getAPIChain(apiDocs, model, headers, urlPrompt, ansPrompt) + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) - const res = await chain.run(input, [handler]) + const res = await chain.run(input, [loggerHandler, handler]) return res } else { - const res = await chain.run(input) + const res = await chain.run(input, [loggerHandler]) return res } } diff --git a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts index 27af5b8e..f1df0183 100644 --- a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts +++ b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts @@ -1,10 +1,11 @@ import { ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' import { ConversationChain } from 'langchain/chains' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate } from 'langchain/prompts' import { BufferMemory, ChatMessageHistory } from 'langchain/memory' import { BaseChatModel } from 'langchain/chat_models/base' import { AIMessage, HumanMessage } from 'langchain/schema' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' const 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.` @@ -90,12 +91,14 @@ class ConversationChain_Chains implements INode { chain.memory = memory } + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - const res = await chain.call({ input }, [handler]) + const res = await chain.call({ input }, [loggerHandler, handler]) return res?.response } else { - const res = await chain.call({ input }) + const res = await chain.call({ input }, [loggerHandler]) return res?.response } } diff --git a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts index 2dccd012..24b40d48 100644 --- a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts +++ b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts @@ -1,10 +1,11 @@ import { BaseLanguageModel } from 'langchain/base_language' import { ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { ConversationalRetrievalQAChain } from 'langchain/chains' import { AIMessage, BaseRetriever, HumanMessage } from 'langchain/schema' import { BaseChatMemory, BufferMemory, ChatMessageHistory } from 'langchain/memory' import { PromptTemplate } from 'langchain/prompts' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' const default_qa_template = `Use the following pieces of context to answer the question at the end, in its original language. If you don't know the answer, just say that you don't know in its original language, don't try to make up an answer. @@ -175,13 +176,15 @@ class ConversationalRetrievalQAChain_Chains implements INode { chain.memory = memory } + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, undefined, returnSourceDocuments) - const res = await chain.call(obj, [handler]) + const res = await chain.call(obj, [loggerHandler, handler]) if (res.text && res.sourceDocuments) return res return res?.text } else { - const res = await chain.call(obj) + const res = await chain.call(obj, [loggerHandler]) if (res.text && res.sourceDocuments) return res return res?.text } diff --git a/packages/components/nodes/chains/LLMChain/LLMChain.ts b/packages/components/nodes/chains/LLMChain/LLMChain.ts index 67c21ce4..1d0ccb92 100644 --- a/packages/components/nodes/chains/LLMChain/LLMChain.ts +++ b/packages/components/nodes/chains/LLMChain/LLMChain.ts @@ -1,7 +1,8 @@ import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { LLMChain } from 'langchain/chains' import { BaseLanguageModel } from 'langchain/base_language' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class LLMChain_Chains implements INode { label: string @@ -55,7 +56,7 @@ class LLMChain_Chains implements INode { ] } - async init(nodeData: INodeData, input: string): Promise { + async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { const model = nodeData.inputs?.model as BaseLanguageModel const prompt = nodeData.inputs?.prompt const output = nodeData.outputs?.output as string @@ -67,7 +68,7 @@ class LLMChain_Chains implements INode { } else if (output === 'outputPrediction') { const chain = new LLMChain({ llm: model, prompt, verbose: process.env.DEBUG === 'true' ? true : false }) const inputVariables = chain.prompt.inputVariables as string[] // ["product"] - const res = await runPrediction(inputVariables, chain, input, promptValues) + const res = await runPrediction(inputVariables, chain, input, promptValues, options) // 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 @@ -81,9 +82,7 @@ class LLMChain_Chains implements INode { const chain = nodeData.instance as LLMChain const promptValues = nodeData.inputs?.prompt.promptValues as ICommonObject - const res = options.socketIO - ? await runPrediction(inputVariables, chain, input, promptValues, true, options.socketIO, options.socketIOClientId) - : await runPrediction(inputVariables, chain, input, promptValues) + const res = await runPrediction(inputVariables, chain, input, promptValues, options) // eslint-disable-next-line no-console console.log('\x1b[93m\x1b[1m\n*****FINAL RESULT*****\n\x1b[0m\x1b[0m') // eslint-disable-next-line no-console @@ -97,17 +96,20 @@ const runPrediction = async ( chain: LLMChain, input: string, promptValues: ICommonObject, - isStreaming?: boolean, - socketIO?: any, - socketIOClientId = '' + options: ICommonObject ) => { + const loggerHandler = new ConsoleCallbackHandler(options.logger) + const isStreaming = options.socketIO && options.socketIOClientId + const socketIO = isStreaming ? options.socketIO : undefined + const socketIOClientId = isStreaming ? options.socketIOClientId : '' + if (inputVariables.length === 1) { if (isStreaming) { const handler = new CustomChainHandler(socketIO, socketIOClientId) - const res = await chain.run(input, [handler]) + const res = await chain.run(input, [loggerHandler, handler]) return res } else { - const res = await chain.run(input) + const res = await chain.run(input, [loggerHandler]) return res } } else if (inputVariables.length > 1) { @@ -122,15 +124,13 @@ const runPrediction = async ( if (seen.length === 0) { // All inputVariables have fixed values specified - const options = { - ...promptValues - } + const options = { ...promptValues } if (isStreaming) { const handler = new CustomChainHandler(socketIO, socketIOClientId) - const res = await chain.call(options, [handler]) + const res = await chain.call(options, [loggerHandler, handler]) return res?.text } else { - const res = await chain.call(options) + const res = await chain.call(options, [loggerHandler]) return res?.text } } else if (seen.length === 1) { @@ -143,10 +143,10 @@ const runPrediction = async ( } if (isStreaming) { const handler = new CustomChainHandler(socketIO, socketIOClientId) - const res = await chain.call(options, [handler]) + const res = await chain.call(options, [loggerHandler, handler]) return res?.text } else { - const res = await chain.call(options) + const res = await chain.call(options, [loggerHandler]) return res?.text } } else { @@ -155,10 +155,10 @@ const runPrediction = async ( } else { if (isStreaming) { const handler = new CustomChainHandler(socketIO, socketIOClientId) - const res = await chain.run(input, [handler]) + const res = await chain.run(input, [loggerHandler, handler]) return res } else { - const res = await chain.run(input) + const res = await chain.run(input, [loggerHandler]) return res } } diff --git a/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts b/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts index 189f41f7..e9783639 100644 --- a/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts +++ b/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts @@ -1,7 +1,8 @@ import { BaseLanguageModel } from 'langchain/base_language' import { ICommonObject, INode, INodeData, INodeParams, PromptRetriever } from '../../../src/Interface' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { MultiPromptChain } from 'langchain/chains' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class MultiPromptChain_Chains implements INode { label: string @@ -63,12 +64,14 @@ class MultiPromptChain_Chains implements INode { const chain = nodeData.instance as MultiPromptChain const obj = { input } + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) - const res = await chain.call(obj, [handler]) + const res = await chain.call(obj, [loggerHandler, handler]) return res?.text } else { - const res = await chain.call(obj) + const res = await chain.call(obj, [loggerHandler]) return res?.text } } diff --git a/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts b/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts index b3575a93..a1947faa 100644 --- a/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts +++ b/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts @@ -1,7 +1,8 @@ import { BaseLanguageModel } from 'langchain/base_language' import { ICommonObject, INode, INodeData, INodeParams, VectorStoreRetriever } from '../../../src/Interface' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { MultiRetrievalQAChain } from 'langchain/chains' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class MultiRetrievalQAChain_Chains implements INode { label: string @@ -71,14 +72,15 @@ class MultiRetrievalQAChain_Chains implements INode { const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean const obj = { input } + const loggerHandler = new ConsoleCallbackHandler(options.logger) if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2, returnSourceDocuments) - const res = await chain.call(obj, [handler]) + const res = await chain.call(obj, [loggerHandler, handler]) if (res.text && res.sourceDocuments) return res return res?.text } else { - const res = await chain.call(obj) + const res = await chain.call(obj, [loggerHandler]) if (res.text && res.sourceDocuments) return res return res?.text } diff --git a/packages/components/nodes/chains/RetrievalQAChain/RetrievalQAChain.ts b/packages/components/nodes/chains/RetrievalQAChain/RetrievalQAChain.ts index 97fa51a1..eaf5a0a9 100644 --- a/packages/components/nodes/chains/RetrievalQAChain/RetrievalQAChain.ts +++ b/packages/components/nodes/chains/RetrievalQAChain/RetrievalQAChain.ts @@ -1,8 +1,9 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { RetrievalQAChain } from 'langchain/chains' import { BaseRetriever } from 'langchain/schema' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { BaseLanguageModel } from 'langchain/base_language' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class RetrievalQAChain_Chains implements INode { label: string @@ -49,13 +50,14 @@ class RetrievalQAChain_Chains implements INode { const obj = { query: input } + const loggerHandler = new ConsoleCallbackHandler(options.logger) if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - const res = await chain.call(obj, [handler]) + const res = await chain.call(obj, [loggerHandler, handler]) return res?.text } else { - const res = await chain.call(obj) + const res = await chain.call(obj, [loggerHandler]) return res?.text } } diff --git a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts index 08d3eee5..5817264d 100644 --- a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts +++ b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts @@ -1,9 +1,10 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { SqlDatabaseChain, SqlDatabaseChainInput } from 'langchain/chains/sql_db' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { DataSource } from 'typeorm' import { SqlDatabase } from 'langchain/sql_db' import { BaseLanguageModel } from 'langchain/base_language' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class SqlDatabaseChain_Chains implements INode { label: string @@ -65,12 +66,14 @@ class SqlDatabaseChain_Chains implements INode { const dbFilePath = nodeData.inputs?.dbFilePath const chain = await getSQLDBChain(databaseType, dbFilePath, model) + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) - const res = await chain.run(input, [handler]) + const res = await chain.run(input, [loggerHandler, handler]) return res } else { - const res = await chain.run(input) + const res = await chain.run(input, [loggerHandler]) return res } } diff --git a/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts b/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts index 5850ed7b..abe7aab3 100644 --- a/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts +++ b/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts @@ -1,8 +1,9 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { VectorDBQAChain } from 'langchain/chains' import { BaseLanguageModel } from 'langchain/base_language' import { VectorStore } from 'langchain/vectorstores' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class VectorDBQAChain_Chains implements INode { label: string @@ -53,12 +54,14 @@ class VectorDBQAChain_Chains implements INode { query: input } + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - const res = await chain.call(obj, [handler]) + const res = await chain.call(obj, [loggerHandler, handler]) return res?.text } else { - const res = await chain.call(obj) + const res = await chain.call(obj, [loggerHandler]) return res?.text } } diff --git a/packages/components/src/handler.ts b/packages/components/src/handler.ts new file mode 100644 index 00000000..8e363361 --- /dev/null +++ b/packages/components/src/handler.ts @@ -0,0 +1,180 @@ +import { BaseTracer, Run, BaseCallbackHandler } from 'langchain/callbacks' +import { AgentAction, ChainValues } from 'langchain/schema' +import { Logger } from 'winston' +import { Server } from 'socket.io' + +interface AgentRun extends Run { + actions: AgentAction[] +} + +function tryJsonStringify(obj: unknown, fallback: string) { + try { + return JSON.stringify(obj, null, 2) + } catch (err) { + return fallback + } +} + +function elapsed(run: Run): string { + if (!run.end_time) return '' + const elapsed = run.end_time - run.start_time + if (elapsed < 1000) { + return `${elapsed}ms` + } + return `${(elapsed / 1000).toFixed(2)}s` +} + +export class ConsoleCallbackHandler extends BaseTracer { + name = 'console_callback_handler' as const + logger: Logger + + protected persistRun(_run: Run) { + return Promise.resolve() + } + + constructor(logger: Logger) { + super() + this.logger = logger + } + + // utility methods + + getParents(run: Run) { + const parents: Run[] = [] + let currentRun = run + while (currentRun.parent_run_id) { + const parent = this.runMap.get(currentRun.parent_run_id) + if (parent) { + parents.push(parent) + currentRun = parent + } else { + break + } + } + return parents + } + + getBreadcrumbs(run: Run) { + const parents = this.getParents(run).reverse() + const string = [...parents, run] + .map((parent) => { + const name = `${parent.execution_order}:${parent.run_type}:${parent.name}` + return name + }) + .join(' > ') + return string + } + + // logging methods + + onChainStart(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose(`[chain/start] [${crumbs}] Entering Chain run with input: ${tryJsonStringify(run.inputs, '[inputs]')}`) + } + + onChainEnd(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose( + `[chain/end] [${crumbs}] [${elapsed(run)}] Exiting Chain run with output: ${tryJsonStringify(run.outputs, '[outputs]')}` + ) + } + + onChainError(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose( + `[chain/error] [${crumbs}] [${elapsed(run)}] Chain run errored with error: ${tryJsonStringify(run.error, '[error]')}` + ) + } + + onLLMStart(run: Run) { + const crumbs = this.getBreadcrumbs(run) + const inputs = 'prompts' in run.inputs ? { prompts: (run.inputs.prompts as string[]).map((p) => p.trim()) } : run.inputs + this.logger.verbose(`[llm/start] [${crumbs}] Entering LLM run with input: ${tryJsonStringify(inputs, '[inputs]')}`) + } + + onLLMEnd(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose( + `[llm/end] [${crumbs}] [${elapsed(run)}] Exiting LLM run with output: ${tryJsonStringify(run.outputs, '[response]')}` + ) + } + + onLLMError(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose( + `[llm/error] [${crumbs}] [${elapsed(run)}] LLM run errored with error: ${tryJsonStringify(run.error, '[error]')}` + ) + } + + onToolStart(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose(`[tool/start] [${crumbs}] Entering Tool run with input: "${run.inputs.input?.trim()}"`) + } + + onToolEnd(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose(`[tool/end] [${crumbs}] [${elapsed(run)}] Exiting Tool run with output: "${run.outputs?.output?.trim()}"`) + } + + onToolError(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose( + `[tool/error] [${crumbs}] [${elapsed(run)}] Tool run errored with error: ${tryJsonStringify(run.error, '[error]')}` + ) + } + + onAgentAction(run: Run) { + const agentRun = run as AgentRun + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose( + `[agent/action] [${crumbs}] Agent selected action: ${tryJsonStringify( + agentRun.actions[agentRun.actions.length - 1], + '[action]' + )}` + ) + } +} + +/** + * Custom chain handler class + */ +export class CustomChainHandler extends BaseCallbackHandler { + name = 'custom_chain_handler' + isLLMStarted = false + socketIO: Server + socketIOClientId = '' + skipK = 0 // Skip streaming for first K numbers of handleLLMStart + returnSourceDocuments = false + + constructor(socketIO: Server, socketIOClientId: string, skipK?: number, returnSourceDocuments?: boolean) { + super() + this.socketIO = socketIO + this.socketIOClientId = socketIOClientId + this.skipK = skipK ?? this.skipK + this.returnSourceDocuments = returnSourceDocuments ?? this.returnSourceDocuments + } + + handleLLMStart() { + if (this.skipK > 0) this.skipK -= 1 + } + + handleLLMNewToken(token: string) { + if (this.skipK === 0) { + if (!this.isLLMStarted) { + this.isLLMStarted = true + this.socketIO.to(this.socketIOClientId).emit('start', token) + } + this.socketIO.to(this.socketIOClientId).emit('token', token) + } + } + + handleLLMEnd() { + this.socketIO.to(this.socketIOClientId).emit('end') + } + + handleChainEnd(outputs: ChainValues): void | Promise { + if (this.returnSourceDocuments) { + this.socketIO.to(this.socketIOClientId).emit('sourceDocuments', outputs?.sourceDocuments) + } + } +} diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index e1399404..027ec8db 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -3,9 +3,6 @@ import { load } from 'cheerio' import * as fs from 'fs' import * as path from 'path' import { JSDOM } from 'jsdom' -import { BaseCallbackHandler } from 'langchain/callbacks' -import { Server } from 'socket.io' -import { ChainValues } from 'langchain/dist/schema' export const numberOrExpressionRegex = '^(\\d+\\.?\\d*|{{.*}})$' //return true if string consists only numbers OR expression {{}} export const notEmptyRegex = '(.|\\s)*\\S(.|\\s)*' //return true if string is not empty or blank @@ -350,50 +347,9 @@ export const getEnvironmentVariable = (name: string): string | undefined => { } } -/** - * Custom chain handler class +/* + * List of dependencies allowed to be import in vm2 */ -export class CustomChainHandler extends BaseCallbackHandler { - name = 'custom_chain_handler' - isLLMStarted = false - socketIO: Server - socketIOClientId = '' - skipK = 0 // Skip streaming for first K numbers of handleLLMStart - returnSourceDocuments = false - - constructor(socketIO: Server, socketIOClientId: string, skipK?: number, returnSourceDocuments?: boolean) { - super() - this.socketIO = socketIO - this.socketIOClientId = socketIOClientId - this.skipK = skipK ?? this.skipK - this.returnSourceDocuments = returnSourceDocuments ?? this.returnSourceDocuments - } - - handleLLMStart() { - if (this.skipK > 0) this.skipK -= 1 - } - - handleLLMNewToken(token: string) { - if (this.skipK === 0) { - if (!this.isLLMStarted) { - this.isLLMStarted = true - this.socketIO.to(this.socketIOClientId).emit('start', token) - } - this.socketIO.to(this.socketIOClientId).emit('token', token) - } - } - - handleLLMEnd() { - this.socketIO.to(this.socketIOClientId).emit('end') - } - - handleChainEnd(outputs: ChainValues): void | Promise { - if (this.returnSourceDocuments) { - this.socketIO.to(this.socketIOClientId).emit('sourceDocuments', outputs?.sourceDocuments) - } - } -} - export const availableDependencies = [ '@dqbd/tiktoken', '@getzep/zep-js', diff --git a/packages/server/.env.example b/packages/server/.env.example index 262e08a6..d9b2da76 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -5,4 +5,5 @@ PORT=3000 # DATABASE_PATH=/your_database_path/.flowise # APIKEY_PATH=/your_api_key_path/.flowise # LOG_PATH=/your_log_path/logs -# EXECUTION_MODE=child or main +# LOG_LEVEL=debug (error | warn | info | verbose | debug) +# EXECUTION_MODE=main (child | main) diff --git a/packages/server/src/ChatflowPool.ts b/packages/server/src/ChatflowPool.ts index 35b0d947..d296dcfe 100644 --- a/packages/server/src/ChatflowPool.ts +++ b/packages/server/src/ChatflowPool.ts @@ -1,5 +1,6 @@ import { ICommonObject } from 'flowise-components' import { IActiveChatflows, INodeData, IReactFlowNode } from './Interface' +import logger from './utils/logger' /** * This pool is to keep track of active chatflow pools @@ -22,6 +23,7 @@ export class ChatflowPool { inSync: true } if (overrideConfig) this.activeChatflows[chatflowid].overrideConfig = overrideConfig + logger.info(`[server]: Chatflow ${chatflowid} added into ChatflowPool`) } /** @@ -32,6 +34,7 @@ export class ChatflowPool { updateInSync(chatflowid: string, inSync: boolean) { if (Object.prototype.hasOwnProperty.call(this.activeChatflows, chatflowid)) { this.activeChatflows[chatflowid].inSync = inSync + logger.info(`[server]: Chatflow ${chatflowid} updated inSync=${inSync} in ChatflowPool`) } } @@ -42,6 +45,7 @@ export class ChatflowPool { async remove(chatflowid: string) { if (Object.prototype.hasOwnProperty.call(this.activeChatflows, chatflowid)) { delete this.activeChatflows[chatflowid] + logger.info(`[server]: Chatflow ${chatflowid} removed from ChatflowPool`) } } } diff --git a/packages/server/src/ChildProcess.ts b/packages/server/src/ChildProcess.ts index e8aeaff2..27629480 100644 --- a/packages/server/src/ChildProcess.ts +++ b/packages/server/src/ChildProcess.ts @@ -5,6 +5,7 @@ import { DataSource } from 'typeorm' import { ChatFlow } from './entity/ChatFlow' import { ChatMessage } from './entity/ChatMessage' import { Tool } from './entity/Tool' +import logger from './utils/logger' export class ChildProcess { /** @@ -27,99 +28,112 @@ export class ChildProcess { await sendToParentProcess('start', '_') - const childAppDataSource = await initDB() + try { + const childAppDataSource = await initDB() - // Create a Queue and add our initial node in it - const { endingNodeData, chatflow, chatId, incomingInput, componentNodes } = messageValue + // Create a Queue and add our initial node in it + const { endingNodeData, chatflow, chatId, incomingInput, componentNodes } = messageValue - let nodeToExecuteData: INodeData - let addToChatFlowPool: any = {} + let nodeToExecuteData: INodeData + let addToChatFlowPool: any = {} - /* Don't rebuild the flow (to avoid duplicated upsert, recomputation) when all these conditions met: - * - Node Data already exists in pool - * - Still in sync (i.e the flow has not been modified since) - * - Existing overrideConfig and new overrideConfig are the same - * - Flow doesn't start with nodes that depend on incomingInput.question - ***/ - if (endingNodeData) { - nodeToExecuteData = endingNodeData - } else { - /*** Get chatflows and prepare data ***/ - const flowData = chatflow.flowData - const parsedFlowData: IReactFlowObject = JSON.parse(flowData) - const nodes = parsedFlowData.nodes - const edges = parsedFlowData.edges + /* Don't rebuild the flow (to avoid duplicated upsert, recomputation) when all these conditions met: + * - Node Data already exists in pool + * - Still in sync (i.e the flow has not been modified since) + * - Existing overrideConfig and new overrideConfig are the same + * - Flow doesn't start with nodes that depend on incomingInput.question + ***/ + if (endingNodeData) { + nodeToExecuteData = endingNodeData + } else { + /*** Get chatflows and prepare data ***/ + const flowData = chatflow.flowData + const parsedFlowData: IReactFlowObject = JSON.parse(flowData) + const nodes = parsedFlowData.nodes + const edges = parsedFlowData.edges - /*** Get Ending Node with Directed Graph ***/ - const { graph, nodeDependencies } = constructGraphs(nodes, edges) - const directedGraph = graph - const endingNodeId = getEndingNode(nodeDependencies, directedGraph) - if (!endingNodeId) { - await sendToParentProcess('error', `Ending node must be either a Chain or Agent`) - return - } + /*** Get Ending Node with Directed Graph ***/ + const { graph, nodeDependencies } = constructGraphs(nodes, edges) + const directedGraph = graph + const endingNodeId = getEndingNode(nodeDependencies, directedGraph) + if (!endingNodeId) { + await sendToParentProcess('error', `Ending node ${endingNodeId} not found`) + return + } - const endingNodeData = nodes.find((nd) => nd.id === endingNodeId)?.data - if (!endingNodeData) { - await sendToParentProcess('error', `Ending node must be either a Chain or Agent`) - return - } + const endingNodeData = nodes.find((nd) => nd.id === endingNodeId)?.data + if (!endingNodeData) { + await sendToParentProcess('error', `Ending node ${endingNodeId} data not found`) + return + } - if ( - endingNodeData.outputs && - Object.keys(endingNodeData.outputs).length && - !Object.values(endingNodeData.outputs).includes(endingNodeData.name) - ) { - await sendToParentProcess( - 'error', - `Output of ${endingNodeData.label} (${endingNodeData.id}) must be ${endingNodeData.label}, can't be an Output Prediction` + if (endingNodeData && endingNodeData.category !== 'Chains' && endingNodeData.category !== 'Agents') { + await sendToParentProcess('error', `Ending node must be either a Chain or Agent`) + return + } + + if ( + endingNodeData.outputs && + Object.keys(endingNodeData.outputs).length && + !Object.values(endingNodeData.outputs).includes(endingNodeData.name) + ) { + await sendToParentProcess( + 'error', + `Output of ${endingNodeData.label} (${endingNodeData.id}) must be ${endingNodeData.label}, can't be an Output Prediction` + ) + return + } + + /*** Get Starting Nodes with Non-Directed Graph ***/ + const constructedObj = constructGraphs(nodes, edges, true) + const nonDirectedGraph = constructedObj.graph + const { startingNodeIds, depthQueue } = getStartingNodes(nonDirectedGraph, endingNodeId) + + logger.debug(`[server] [mode:child]: Start building chatflow ${chatflow.id}`) + /*** BFS to traverse from Starting Nodes to Ending Node ***/ + const reactFlowNodes = await buildLangchain( + startingNodeIds, + nodes, + graph, + depthQueue, + componentNodes, + incomingInput.question, + chatId, + childAppDataSource, + incomingInput?.overrideConfig ) - return + + const nodeToExecute = reactFlowNodes.find((node: IReactFlowNode) => node.id === endingNodeId) + if (!nodeToExecute) { + await sendToParentProcess('error', `Node ${endingNodeId} not found`) + return + } + + const reactFlowNodeData: INodeData = resolveVariables(nodeToExecute.data, reactFlowNodes, incomingInput.question) + nodeToExecuteData = reactFlowNodeData + + const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.id)) + addToChatFlowPool = { + chatflowid: chatflow.id, + nodeToExecuteData, + startingNodes, + overrideConfig: incomingInput?.overrideConfig + } } - /*** Get Starting Nodes with Non-Directed Graph ***/ - const constructedObj = constructGraphs(nodes, edges, true) - const nonDirectedGraph = constructedObj.graph - const { startingNodeIds, depthQueue } = getStartingNodes(nonDirectedGraph, endingNodeId) + const nodeInstanceFilePath = componentNodes[nodeToExecuteData.name].filePath as string + const nodeModule = await import(nodeInstanceFilePath) + const nodeInstance = new nodeModule.nodeClass() - /*** BFS to traverse from Starting Nodes to Ending Node ***/ - const reactFlowNodes = await buildLangchain( - startingNodeIds, - nodes, - graph, - depthQueue, - componentNodes, - incomingInput.question, - chatId, - childAppDataSource, - incomingInput?.overrideConfig - ) + logger.debug(`[server] [mode:child]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) + const result = await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history }) + logger.debug(`[server] [mode:child]: Finished running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) - const nodeToExecute = reactFlowNodes.find((node: IReactFlowNode) => node.id === endingNodeId) - if (!nodeToExecute) { - await sendToParentProcess('error', `Node ${endingNodeId} not found`) - return - } - - const reactFlowNodeData: INodeData = resolveVariables(nodeToExecute.data, reactFlowNodes, incomingInput.question) - nodeToExecuteData = reactFlowNodeData - - const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.id)) - addToChatFlowPool = { - chatflowid: chatflow.id, - nodeToExecuteData, - startingNodes, - overrideConfig: incomingInput?.overrideConfig - } + await sendToParentProcess('finish', { result, addToChatFlowPool }) + } catch (e: any) { + await sendToParentProcess('error', e.message) + logger.error('[server] [mode:child]: Error:', e) } - - const nodeInstanceFilePath = componentNodes[nodeToExecuteData.name].filePath as string - const nodeModule = await import(nodeInstanceFilePath) - const nodeInstance = new nodeModule.nodeClass() - - const result = await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history }) - - await sendToParentProcess('finish', { result, addToChatFlowPool }) } } diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index c05c042a..276a3036 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -23,6 +23,7 @@ export default class Start extends Command { DATABASE_PATH: Flags.string(), APIKEY_PATH: Flags.string(), LOG_PATH: Flags.string(), + LOG_LEVEL: Flags.string(), EXECUTION_MODE: Flags.string() } @@ -61,6 +62,7 @@ export default class Start extends Command { if (flags.DATABASE_PATH) process.env.DATABASE_PATH = flags.DATABASE_PATH if (flags.APIKEY_PATH) process.env.APIKEY_PATH = flags.APIKEY_PATH if (flags.LOG_PATH) process.env.LOG_PATH = flags.LOG_PATH + if (flags.LOG_LEVEL) process.env.LOG_LEVEL = flags.LOG_LEVEL if (flags.EXECUTION_MODE) process.env.EXECUTION_MODE = flags.EXECUTION_MODE if (flags.DEBUG) process.env.DEBUG = flags.DEBUG diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 0f87aeba..74c4d07e 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -283,10 +283,16 @@ export class App { const nodes = parsedFlowData.nodes const edges = parsedFlowData.edges const { graph, nodeDependencies } = constructGraphs(nodes, edges) + const endingNodeId = getEndingNode(nodeDependencies, graph) - if (!endingNodeId) return res.status(500).send(`Ending node must be either a Chain or Agent`) + if (!endingNodeId) return res.status(500).send(`Ending node ${endingNodeId} not found`) + const endingNodeData = nodes.find((nd) => nd.id === endingNodeId)?.data - if (!endingNodeData) return res.status(500).send(`Ending node must be either a Chain or Agent`) + if (!endingNodeData) return res.status(500).send(`Ending node ${endingNodeId} data not found`) + + if (endingNodeData && endingNodeData.category !== 'Chains' && endingNodeData.category !== 'Agents') { + return res.status(500).send(`Ending node must be either a Chain or Agent`) + } const obj = { isStreaming: isFlowValidForStream(nodes, endingNodeData) @@ -638,7 +644,7 @@ export class App { }) }) } catch (err) { - logger.error(err) + logger.error('[server] [mode:child]: Error:', err) } } @@ -714,9 +720,11 @@ export class App { if (process.env.EXECUTION_MODE === 'child') { if (isFlowReusable()) { nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData + logger.debug( + `[server] [mode:child]: Reuse existing chatflow ${chatflowid} with ending node ${nodeToExecuteData.label} (${nodeToExecuteData.id})` + ) try { const result = await this.startChildProcess(chatflow, chatId, incomingInput, nodeToExecuteData) - return res.json(result) } catch (error) { return res.status(500).send(error) @@ -739,15 +747,22 @@ export class App { if (isFlowReusable()) { nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData isStreamValid = isFlowValidForStream(nodes, nodeToExecuteData) + logger.debug( + `[server]: Reuse existing chatflow ${chatflowid} with ending node ${nodeToExecuteData.label} (${nodeToExecuteData.id})` + ) } else { /*** Get Ending Node with Directed Graph ***/ const { graph, nodeDependencies } = constructGraphs(nodes, edges) const directedGraph = graph const endingNodeId = getEndingNode(nodeDependencies, directedGraph) - if (!endingNodeId) return res.status(500).send(`Ending node must be either a Chain or Agent`) + if (!endingNodeId) return res.status(500).send(`Ending node ${endingNodeId} not found`) const endingNodeData = nodes.find((nd) => nd.id === endingNodeId)?.data - if (!endingNodeData) return res.status(500).send(`Ending node must be either a Chain or Agent`) + if (!endingNodeData) return res.status(500).send(`Ending node ${endingNodeId} data not found`) + + if (endingNodeData && endingNodeData.category !== 'Chains' && endingNodeData.category !== 'Agents') { + return res.status(500).send(`Ending node must be either a Chain or Agent`) + } if ( endingNodeData.outputs && @@ -768,6 +783,7 @@ export class App { const nonDirectedGraph = constructedObj.graph const { startingNodeIds, depthQueue } = getStartingNodes(nonDirectedGraph, endingNodeId) + logger.debug(`[server]: Start building chatflow ${chatflowid}`) /*** BFS to traverse from Starting Nodes to Ending Node ***/ const reactFlowNodes = await buildLangchain( startingNodeIds, @@ -796,17 +812,21 @@ export class App { const nodeInstance = new nodeModule.nodeClass() isStreamValid = isStreamValid && !isVectorStoreFaiss(nodeToExecuteData) + logger.debug(`[server]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) const result = isStreamValid ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history, socketIO, - socketIOClientId: incomingInput.socketIOClientId + socketIOClientId: incomingInput.socketIOClientId, + logger }) - : await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history }) + : await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history, logger }) + logger.debug(`[server]: Finished running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) return res.json(result) } } catch (e: any) { + logger.error('[server]: Error:', e) return res.status(500).send(e.message) } } diff --git a/packages/server/src/utils/config.ts b/packages/server/src/utils/config.ts index c38d5a0c..8540b3b1 100644 --- a/packages/server/src/utils/config.ts +++ b/packages/server/src/utils/config.ts @@ -9,12 +9,12 @@ dotenv.config({ path: path.join(__dirname, '..', '..', '.env'), override: true } const loggingConfig = { dir: process.env.LOG_PATH ?? path.join(__dirname, '..', '..', '..', '..', 'logs'), server: { - level: 'info', + level: process.env.LOG_LEVEL ?? 'info', filename: 'server.log', errorFilename: 'server-error.log' }, express: { - level: 'info', + level: process.env.LOG_LEVEL ?? 'info', format: 'jsonl', // can't be changed currently filename: 'server-requests.log.jsonl' // should end with .jsonl } diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index bc4d8188..b67f2796 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -180,6 +180,9 @@ export const getEndingNode = (nodeDependencies: INodeDependencies, graph: INodeD * @param {IDepthQueue} depthQueue * @param {IComponentNodes} componentNodes * @param {string} question + * @param {string} chatId + * @param {DataSource} appDataSource + * @param {ICommonObject} overrideConfig */ export const buildLangchain = async ( startingNodeIds: string[], @@ -222,11 +225,14 @@ export const buildLangchain = async ( if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig) const reactFlowNodeData: INodeData = resolveVariables(flowNodeData, flowNodes, question) + logger.debug(`[server]: Initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`) flowNodes[nodeIndex].data.instance = await newNodeInstance.init(reactFlowNodeData, question, { chatId, appDataSource, - databaseEntities + databaseEntities, + logger }) + logger.debug(`[server]: Finished initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`) } catch (e: any) { logger.error(e) throw new Error(e) diff --git a/packages/server/src/utils/logger.ts b/packages/server/src/utils/logger.ts index 1c28b173..5d7ffedf 100644 --- a/packages/server/src/utils/logger.ts +++ b/packages/server/src/utils/logger.ts @@ -4,7 +4,7 @@ import config from './config' // should be replaced by node-config or similar import { createLogger, transports, format } from 'winston' import { NextFunction, Request, Response } from 'express' -const { combine, timestamp, printf } = format +const { combine, timestamp, printf, errors } = format // expect the log dir be relative to the projects root const logDir = config.logging.dir @@ -18,9 +18,11 @@ const logger = createLogger({ format: combine( timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), format.json(), - printf(({ level, message, timestamp }) => { - return `${timestamp} [${level.toUpperCase()}]: ${message}` - }) + printf(({ level, message, timestamp, stack }) => { + const text = `${timestamp} [${level.toUpperCase()}]: ${message}` + return stack ? text + '\n' + stack : text + }), + errors({ stack: true }) ), defaultMeta: { package: 'server' @@ -56,7 +58,7 @@ const logger = createLogger({ */ export function expressRequestLogger(req: Request, res: Response, next: NextFunction): void { const fileLogger = createLogger({ - format: combine(timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), format.json()), + format: combine(timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), format.json(), errors({ stack: true })), defaultMeta: { package: 'server', request: { @@ -71,7 +73,7 @@ export function expressRequestLogger(req: Request, res: Response, next: NextFunc transports: [ new transports.File({ filename: path.join(logDir, config.logging.express.filename ?? 'server-requests.log.jsonl'), - level: 'debug' + level: config.logging.express.level ?? 'debug' }) ] }) From 8ad870ba261c3c8a3493f2cc43e6e0ee9f5e4afe Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 11 Jul 2023 02:12:54 +0100 Subject: [PATCH 05/11] update README --- README.md | 22 ++++++++++++++++++++++ packages/server/README.md | 22 ++++++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4613e19f..7792b969 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,28 @@ FLOWISE_USERNAME=user FLOWISE_PASSWORD=1234 ``` +## 🌱 Env Variables + +Flowise support different environment variables to configure your instance. You can specify the following variables in the `.env` file inside `packages/server` folder. + +| Variable | Description | Type | Default | +| ---------------- | ---------------------------------------------------------------- | ------------------------------------------------ | ----------------------------------- | +| PORT | The HTTP port Flowise runs on | Number | 3000 | +| FLOWISE_USERNAME | Username to login | String | +| FLOWISE_PASSWORD | Password to login | String | +| DEBUG | Print logs from components | Boolean | +| LOG_PATH | Location where log files are stored | String | `your-path/Flowise/logs` | +| LOG_LEVEL | Different levels of logs | Enum String: `error`, `info`, `verbose`, `debug` | `info` | +| DATABASE_PATH | Location where database is saved | String | `your-home-dir/.flowise` | +| APIKEY_PATH | Location where api keys are saved | String | `your-path/Flowise/packages/server` | +| EXECUTION_MODE | Whether predictions run in their own process or the main process | Enum String: `child`, `main` | `main` | + +You can also specify the env variables when using `npx`. For example: + +``` +npx flowise start --PORT=3000 --DEBUG=true +``` + ## 📖 Documentation [Flowise Docs](https://docs.flowiseai.com/) diff --git a/packages/server/README.md b/packages/server/README.md index 7895bd90..fb3a0c12 100644 --- a/packages/server/README.md +++ b/packages/server/README.md @@ -29,9 +29,27 @@ FLOWISE_USERNAME=user FLOWISE_PASSWORD=1234 ``` -## 🔎 Debugging +## 🌱 Env Variables -You can set `DEBUG=true` to the `.env` file. Refer [here](https://docs.flowiseai.com/environment-variables) for full list of env variables +Flowise support different environment variables to configure your instance. You can specify the following variables in the `.env` file inside `packages/server` folder. + +| Variable | Description | Type | Default | +| ---------------- | ---------------------------------------------------------------- | ------------------------------------------------ | ----------------------------------- | +| PORT | The HTTP port Flowise runs on | Number | 3000 | +| FLOWISE_USERNAME | Username to login | String | +| FLOWISE_PASSWORD | Password to login | String | +| DEBUG | Print logs from components | Boolean | +| LOG_PATH | Location where log files are stored | String | `your-path/Flowise/logs` | +| LOG_LEVEL | Different levels of logs | Enum String: `error`, `info`, `verbose`, `debug` | `info` | +| DATABASE_PATH | Location where database is saved | String | `your-home-dir/.flowise` | +| APIKEY_PATH | Location where api keys are saved | String | `your-path/Flowise/packages/server` | +| EXECUTION_MODE | Whether predictions run in their own process or the main process | Enum String: `child`, `main` | `main` | + +You can also specify the env variables when using `npx`. For example: + +``` +npx flowise start --PORT=3000 --DEBUG=true +``` ## 📖 Documentation From 4c47beabbcaffba93a1c9c873cb59899531f0292 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 12 Jul 2023 01:11:02 +0100 Subject: [PATCH 06/11] update claude v2 --- .../ConversationChain/ConversationChain.ts | 26 ++++++++++++++++++- .../chatmodels/ChatAnthropic/ChatAnthropic.ts | 10 +++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts index f1df0183..7b6f002d 100644 --- a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts +++ b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts @@ -6,8 +6,10 @@ import { BufferMemory, ChatMessageHistory } from 'langchain/memory' import { BaseChatModel } from 'langchain/chat_models/base' import { AIMessage, HumanMessage } from 'langchain/schema' import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' +import { flatten } from 'lodash' +import { Document } from 'langchain/document' -const 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.` +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.` class ConversationChain_Chains implements INode { label: string @@ -38,6 +40,14 @@ class ConversationChain_Chains implements INode { name: 'memory', type: 'BaseMemory' }, + { + label: 'Document', + name: 'document', + type: 'Document', + description: 'Include whole document into the context window', + optional: true, + list: true + }, { label: 'System Message', name: 'systemMessagePrompt', @@ -54,6 +64,20 @@ class ConversationChain_Chains implements INode { 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) { + finalDocs.push(new Document(flattenDocs[i])) + } + + let finalText = '' + for (let i = 0; i < finalDocs.length; i += 1) { + finalText += finalDocs[i].pageContent + } + + if (finalText) systemMessage = `${systemMessage}\nThe AI has the following context:\n${finalText}` const obj: any = { llm: model, diff --git a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts index 3d861d24..b65c7bd8 100644 --- a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts +++ b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts @@ -31,6 +31,16 @@ class ChatAnthropic_ChatModels implements INode { name: 'modelName', type: 'options', options: [ + { + label: 'claude-2', + name: 'claude-2', + description: 'Claude 2 latest major version, automatically get updates to the model as they are released' + }, + { + label: 'claude-instant-1', + name: 'claude-instant-1', + description: 'Claude Instant latest major version, automatically get updates to the model as they are released' + }, { label: 'claude-v1', name: 'claude-v1' From f558c0374473d55e023aaf2b8b74dd5c7f684020 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 12 Jul 2023 01:31:01 +0100 Subject: [PATCH 07/11] update marketplace --- .../chatflows/Simple Conversation Chain.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/server/marketplaces/chatflows/Simple Conversation Chain.json b/packages/server/marketplaces/chatflows/Simple Conversation Chain.json index f7e654db..04009123 100644 --- a/packages/server/marketplaces/chatflows/Simple Conversation Chain.json +++ b/packages/server/marketplaces/chatflows/Simple Conversation Chain.json @@ -249,6 +249,15 @@ "name": "memory", "type": "BaseMemory", "id": "conversationChain_0-input-memory-BaseMemory" + }, + { + "label": "Document", + "name": "document", + "type": "Document", + "description": "Include whole document into the context window", + "optional": true, + "list": true, + "id": "conversationChain_0-input-document-Document" } ], "inputs": { From d023042158a26bd8b2c07d37f14d020b57c9f12b Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 12 Jul 2023 02:38:59 +0100 Subject: [PATCH 08/11] =?UTF-8?q?=F0=9F=A5=B3=20flowise-components@1.2.16?= =?UTF-8?q?=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index 32a84610..3459a372 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.2.15", + "version": "1.2.16", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From 59a3f7ab66190f5d4dfbdff2d8110ebf865558b8 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 12 Jul 2023 02:40:46 +0100 Subject: [PATCH 09/11] =?UTF-8?q?=F0=9F=A5=B3=20flowise-ui@1.2.14=20releas?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 2cadc89d..0df27569 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.2.13", + "version": "1.2.14", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { From f8945c7fabd8a11668b105f3e28eaa432458489b Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 12 Jul 2023 02:41:09 +0100 Subject: [PATCH 10/11] =?UTF-8?q?=F0=9F=A5=B3=20flowise@1.2.15=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 61ea436b..9d0d9d1a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.14", + "version": "1.2.15", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index 44b7e991..daed16d8 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.14", + "version": "1.2.15", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From ab0534169ce01c406217b9fb22f9b7f2dada5e3f Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Thu, 13 Jul 2023 19:03:36 +0100 Subject: [PATCH 11/11] update README Railway Deploy --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7792b969..5023a1b7 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ npx flowise start --PORT=3000 --DEBUG=true ### [Railway](https://docs.flowiseai.com/deployment/railway) -[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/YK7J0v) +[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/pn4G8S?referralCode=WVNPD9) ### [Render](https://docs.flowiseai.com/deployment/render)