From eb19c206cfddc9d3f62e98312a6bee7641ed84ce Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 11 Jul 2023 01:53:22 +0100 Subject: [PATCH] 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 ca1185004..044b6f7ba 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 43de74cb2..f4d065d94 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 8e6577494..373d04628 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 ae1ae3c03..a231e80a7 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 3c6ea6772..7189f1ad6 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 27af5b8ee..f1df0183b 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 2dccd0120..24b40d481 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 67c21ce45..1d0ccb92d 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 189f41f72..e97836394 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 b3575a930..a1947faad 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 97fa51a1c..eaf5a0a97 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 08d3eee5e..5817264d4 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 5850ed7bc..abe7aab38 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 000000000..8e3633617 --- /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 e1399404c..027ec8db2 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 262e08a63..d9b2da762 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 35b0d9478..d296dcfed 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 e8aeaff27..27629480f 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 c05c042a6..276a3036e 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 0f87aebad..74c4d07e4 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 c38d5a0cf..8540b3b12 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 bc4d8188d..b67f27965 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 1c28b173c..5d7ffedf1 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' }) ] })