diff --git a/packages/components/nodes/agentflow/Agent/Agent.ts b/packages/components/nodes/agentflow/Agent/Agent.ts index 9ee90a4770d..21f495d641c 100644 --- a/packages/components/nodes/agentflow/Agent/Agent.ts +++ b/packages/components/nodes/agentflow/Agent/Agent.ts @@ -19,7 +19,7 @@ import { Tool } from '@langchain/core/tools' import { ARTIFACTS_PREFIX, SOURCE_DOCUMENTS_PREFIX, TOOL_ARGS_PREFIX } from '../../../src/agents' import { flatten } from 'lodash' import zodToJsonSchema from 'zod-to-json-schema' -import { getErrorMessage } from '../../../src/error' +import { DocumentStoreError, getErrorMessage } from '../../../src/error' import { DataSource } from 'typeorm' import { getPastChatHistoryImageMessages, @@ -573,64 +573,83 @@ class Agent_Agentflow implements INode { const knowledgeBases = nodeData.inputs?.agentKnowledgeDocumentStores as IKnowledgeBase[] if (knowledgeBases && knowledgeBases.length > 0) { for (const knowledgeBase of knowledgeBases) { - const nodeInstanceFilePath = options.componentNodes['retrieverTool'].filePath as string - const nodeModule = await import(nodeInstanceFilePath) - const newRetrieverToolNodeInstance = new nodeModule.nodeClass() - const [storeId, storeName] = knowledgeBase.documentStore.split(':') - - const docStoreVectorInstanceFilePath = options.componentNodes['documentStoreVS'].filePath as string - const docStoreVectorModule = await import(docStoreVectorInstanceFilePath) - const newDocStoreVectorInstance = new docStoreVectorModule.nodeClass() - const docStoreVectorInstance = await newDocStoreVectorInstance.init( - { + try { + const nodeInstanceFilePath = options.componentNodes['retrieverTool'].filePath as string + const nodeModule = await import(nodeInstanceFilePath) + const newRetrieverToolNodeInstance = new nodeModule.nodeClass() + const [storeId, storeName] = knowledgeBase.documentStore.split(':') + + const docStoreVectorInstanceFilePath = options.componentNodes['documentStoreVS'].filePath as string + const docStoreVectorModule = await import(docStoreVectorInstanceFilePath) + const newDocStoreVectorInstance = new docStoreVectorModule.nodeClass() + let docStoreVectorInstance + try { + docStoreVectorInstance = await newDocStoreVectorInstance.init( + { + ...nodeData, + inputs: { + ...nodeData.inputs, + selectedStore: storeId + }, + outputs: { + output: 'retriever' + } + }, + '', + options + ) + } catch (error) { + if (error instanceof DocumentStoreError) { + throw error + } + throw new DocumentStoreError( + error.message, + storeId, + error instanceof Error ? { cause: error.cause } : undefined + ) + } + const newRetrieverToolNodeData = { ...nodeData, inputs: { ...nodeData.inputs, - selectedStore: storeId - }, - outputs: { - output: 'retriever' + name: storeName + .toLowerCase() + .replace(/ /g, '_') + .replace(/[^a-z0-9_-]/g, ''), + description: knowledgeBase.docStoreDescription, + retriever: docStoreVectorInstance, + returnSourceDocuments: knowledgeBase.returnSourceDocuments } - }, - '', - options - ) - - const newRetrieverToolNodeData = { - ...nodeData, - inputs: { - ...nodeData.inputs, + } + const retrieverToolInstance = await newRetrieverToolNodeInstance.init(newRetrieverToolNodeData, '', options) + toolsInstance.push(retrieverToolInstance as Tool) + const jsonSchema = zodToJsonSchema(retrieverToolInstance.schema) + if (jsonSchema.$schema) { + delete jsonSchema.$schema + } + const componentNode = options.componentNodes['retrieverTool'] + availableTools.push({ name: storeName .toLowerCase() .replace(/ /g, '_') .replace(/[^a-z0-9_-]/g, ''), description: knowledgeBase.docStoreDescription, - retriever: docStoreVectorInstance, - returnSourceDocuments: knowledgeBase.returnSourceDocuments + schema: jsonSchema, + toolNode: { + label: componentNode?.label || retrieverToolInstance.name, + name: componentNode?.name || retrieverToolInstance.name + } + }) + } catch (error) { + if (error instanceof DocumentStoreError) { + console.warn( + `Failed to initialize document store ${knowledgeBase.documentStore}, skipping:`, + getErrorMessage(error) + ) + continue } + throw error } - const retrieverToolInstance = await newRetrieverToolNodeInstance.init(newRetrieverToolNodeData, '', options) - - toolsInstance.push(retrieverToolInstance as Tool) - - const jsonSchema = zodToJsonSchema(retrieverToolInstance.schema) - if (jsonSchema.$schema) { - delete jsonSchema.$schema - } - const componentNode = options.componentNodes['retrieverTool'] - - availableTools.push({ - name: storeName - .toLowerCase() - .replace(/ /g, '_') - .replace(/[^a-z0-9_-]/g, ''), - description: knowledgeBase.docStoreDescription, - schema: jsonSchema, - toolNode: { - label: componentNode?.label || retrieverToolInstance.name, - name: componentNode?.name || retrieverToolInstance.name - } - }) } } diff --git a/packages/components/nodes/agentflow/Retriever/Retriever.ts b/packages/components/nodes/agentflow/Retriever/Retriever.ts index b9af63b766c..26f0a65f853 100644 --- a/packages/components/nodes/agentflow/Retriever/Retriever.ts +++ b/packages/components/nodes/agentflow/Retriever/Retriever.ts @@ -11,6 +11,7 @@ import { updateFlowState } from '../utils' import { DataSource } from 'typeorm' import { BaseRetriever } from '@langchain/core/retrievers' import { Document } from '@langchain/core/documents' +import { DocumentStoreError } from '../../../src/error' interface IKnowledgeBase { documentStore: string @@ -152,27 +153,46 @@ class Retriever_Agentflow implements INode { const knowledgeBases = nodeData.inputs?.retrieverKnowledgeDocumentStores as IKnowledgeBase[] if (knowledgeBases && knowledgeBases.length > 0) { for (const knowledgeBase of knowledgeBases) { - const [storeId, _] = knowledgeBase.documentStore.split(':') - - const docStoreVectorInstanceFilePath = options.componentNodes['documentStoreVS'].filePath as string - const docStoreVectorModule = await import(docStoreVectorInstanceFilePath) - const newDocStoreVectorInstance = new docStoreVectorModule.nodeClass() - const docStoreVectorInstance = (await newDocStoreVectorInstance.init( - { - ...nodeData, - inputs: { - ...nodeData.inputs, - selectedStore: storeId - }, - outputs: { - output: 'retriever' + try { + const [storeId, _] = knowledgeBase.documentStore.split(':') + + const docStoreVectorInstanceFilePath = options.componentNodes['documentStoreVS'].filePath as string + const docStoreVectorModule = await import(docStoreVectorInstanceFilePath) + const newDocStoreVectorInstance = new docStoreVectorModule.nodeClass() + let docStoreVectorInstance + try { + docStoreVectorInstance = (await newDocStoreVectorInstance.init( + { + ...nodeData, + inputs: { + ...nodeData.inputs, + selectedStore: storeId + }, + outputs: { + output: 'retriever' + } + }, + '', + options + )) as BaseRetriever + } catch (error) { + if (error instanceof DocumentStoreError) { + throw error } - }, - '', - options - )) as BaseRetriever - - docs = await docStoreVectorInstance.invoke(retrieverQuery || input, { signal: abortController?.signal }) + throw new DocumentStoreError(error.message, storeId, error instanceof Error ? { cause: error.cause } : undefined) + } + const storeDocs = await docStoreVectorInstance.invoke(retrieverQuery || input, { signal: abortController?.signal }) + docs.push(...storeDocs) + } catch (error) { + if (error instanceof DocumentStoreError) { + console.warn( + `Document store ${knowledgeBase.documentStore} unavailable, continuing with other stores:`, + error.message + ) + continue + } + throw error + } } } diff --git a/packages/components/nodes/tools/RetrieverTool/RetrieverTool.ts b/packages/components/nodes/tools/RetrieverTool/RetrieverTool.ts index 0010bce9c50..966bd997751 100644 --- a/packages/components/nodes/tools/RetrieverTool/RetrieverTool.ts +++ b/packages/components/nodes/tools/RetrieverTool/RetrieverTool.ts @@ -7,6 +7,7 @@ import { getBaseClasses, resolveFlowObjValue } from '../../../src/utils' import { SOURCE_DOCUMENTS_PREFIX } from '../../../src/agents' import { RunnableConfig } from '@langchain/core/runnables' import { VectorStoreRetriever } from '@langchain/core/vectorstores' +import { getErrorMessage } from '../../../src/error' const howToUse = `Add additional filters to vector store. You can also filter with flow config, including the current "state": - \`$flow.sessionId\` @@ -193,20 +194,32 @@ class Retriever_Tools implements INode { const flow = { chatflowId: options.chatflowid } const func = async ({ input }: { input: string }, _?: CallbackManagerForToolRun, flowConfig?: IFlowConfig) => { - if (retrieverToolMetadataFilter) { - const flowObj = flowConfig - - const metadatafilter = - typeof retrieverToolMetadataFilter === 'object' ? retrieverToolMetadataFilter : JSON.parse(retrieverToolMetadataFilter) - const newMetadataFilter = resolveFlowObjValue(metadatafilter, flowObj) - - const vectorStore = (retriever as VectorStoreRetriever).vectorStore - vectorStore.filter = newMetadataFilter + try { + if (retrieverToolMetadataFilter) { + const flowObj = flowConfig + const metadatafilter = + typeof retrieverToolMetadataFilter === 'object' + ? retrieverToolMetadataFilter + : JSON.parse(retrieverToolMetadataFilter) + const newMetadataFilter = resolveFlowObjValue(metadatafilter, flowObj) + const vectorStore = (retriever as VectorStoreRetriever).vectorStore + vectorStore.filter = newMetadataFilter + } + const docs = await retriever.invoke(input) + const content = docs.map((doc) => doc.pageContent).join('\n\n') + const sourceDocuments = JSON.stringify(docs) + return returnSourceDocuments ? content + SOURCE_DOCUMENTS_PREFIX + sourceDocuments : content + } catch (error) { + const errorMessage = getErrorMessage(error) + const isDocStoreError = + errorMessage && + (errorMessage.includes('document store') || errorMessage.includes('vector store') || errorMessage.includes('retriever')) + if (isDocStoreError) { + console.warn('Document store retrieval failed, returning fallback response:', getErrorMessage(error)) + return 'Knowledge base temporarily unavailable. Proceeding with general knowledge.' + } + throw error } - const docs = await retriever.invoke(input) - const content = docs.map((doc) => doc.pageContent).join('\n\n') - const sourceDocuments = JSON.stringify(docs) - return returnSourceDocuments ? content + SOURCE_DOCUMENTS_PREFIX + sourceDocuments : content } const schema = z.object({ diff --git a/packages/components/nodes/vectorstores/DocumentStoreVS/DocStoreVector.ts b/packages/components/nodes/vectorstores/DocumentStoreVS/DocStoreVector.ts index 0f228d1fb79..f16e2785be7 100644 --- a/packages/components/nodes/vectorstores/DocumentStoreVS/DocStoreVector.ts +++ b/packages/components/nodes/vectorstores/DocumentStoreVS/DocStoreVector.ts @@ -1,5 +1,6 @@ import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOptionsValue, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { DataSource } from 'typeorm' +import { DocumentStoreError, getErrorMessage } from '../../../src/error' class DocStore_VectorStores implements INode { label: string @@ -73,45 +74,56 @@ class DocStore_VectorStores implements INode { } async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { - const selectedStore = nodeData.inputs?.selectedStore as string - const appDataSource = options.appDataSource as DataSource - const databaseEntities = options.databaseEntities as IDatabaseEntity - const output = nodeData.outputs?.output as string - - const entity = await appDataSource.getRepository(databaseEntities['DocumentStore']).findOneBy({ id: selectedStore }) - if (!entity) { - return { error: 'Store not found' } - } - const data: ICommonObject = {} - data.output = output - - // Prepare Embeddings Instance - const embeddingConfig = JSON.parse(entity.embeddingConfig) - data.embeddingName = embeddingConfig.name - data.embeddingConfig = embeddingConfig.config - let embeddingObj = await _createEmbeddingsObject(options.componentNodes, data, options) - if (!embeddingObj) { - return { error: 'Failed to create EmbeddingObj' } - } + try { + const selectedStore = nodeData.inputs?.selectedStore as string + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity + const output = nodeData.outputs?.output as string - // Prepare Vector Store Instance - const vsConfig = JSON.parse(entity.vectorStoreConfig) - data.vectorStoreName = vsConfig.name - data.vectorStoreConfig = vsConfig.config - if (data.inputs) { - data.vectorStoreConfig = { ...vsConfig.config, ...data.inputs } - } + const entity = await appDataSource.getRepository(databaseEntities['DocumentStore']).findOneBy({ id: selectedStore }) + if (!entity) { + throw new DocumentStoreError('Store not found', selectedStore) + } + const data: ICommonObject = {} + data.output = output + + // Prepare Embeddings Instance + const embeddingConfig = JSON.parse(entity.embeddingConfig) + data.embeddingName = embeddingConfig.name + data.embeddingConfig = embeddingConfig.config + let embeddingObj = await _createEmbeddingsObject(options.componentNodes, data, options) + if (!embeddingObj) { + throw new DocumentStoreError('Failed to create EmbeddingObj', selectedStore) + } + + // Prepare Vector Store Instance + const vsConfig = JSON.parse(entity.vectorStoreConfig) + data.vectorStoreName = vsConfig.name + data.vectorStoreConfig = vsConfig.config + if (data.inputs) { + data.vectorStoreConfig = { ...vsConfig.config, ...data.inputs } + } - // Prepare Vector Store Node Data - const vStoreNodeData = _createVectorStoreNodeData(options.componentNodes, data, embeddingObj) + // Prepare Vector Store Node Data + const vStoreNodeData = _createVectorStoreNodeData(options.componentNodes, data, embeddingObj) - // Finally create the Vector Store or Retriever object (data.output) - const vectorStoreObj = await _createVectorStoreObject(options.componentNodes, data) - const retrieverOrVectorStore = await vectorStoreObj.init(vStoreNodeData, '', options) - if (!retrieverOrVectorStore) { - return { error: 'Failed to create vectorStore' } + // Finally create the Vector Store or Retriever object (data.output) + const vectorStoreObj = await _createVectorStoreObject(options.componentNodes, data) + const retrieverOrVectorStore = await vectorStoreObj.init(vStoreNodeData, '', options) + if (!retrieverOrVectorStore) { + throw new DocumentStoreError('Failed to create vectorStore', selectedStore) + } + return retrieverOrVectorStore + } catch (error) { + if (error instanceof DocumentStoreError) { + throw error + } + throw new DocumentStoreError( + getErrorMessage(error), + nodeData.inputs?.selectedStore, + error instanceof Error ? { cause: error.cause } : undefined + ) } - return retrieverOrVectorStore } } diff --git a/packages/components/src/error.ts b/packages/components/src/error.ts index 12ba0a67099..2d3d8c2a21e 100644 --- a/packages/components/src/error.ts +++ b/packages/components/src/error.ts @@ -23,3 +23,12 @@ const toErrorWithMessage = (maybeError: unknown): ErrorWithMessage => { export const getErrorMessage = (error: unknown) => { return toErrorWithMessage(error).message } + +export class DocumentStoreError extends Error { + public storeId?: string + constructor(message: string, storeId?: string, errorOptions?: ErrorOptions) { + super(`Document store error: ${message}`, errorOptions) + this.name = 'DocumentStoreError' + this.storeId = storeId + } +} diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json index edac0ceea87..c9e13de51c7 100644 --- a/packages/components/tsconfig.json +++ b/packages/components/tsconfig.json @@ -1,9 +1,9 @@ { "compilerOptions": { - "lib": ["ES2020", "ES2021.String"], + "lib": ["ES2022"], "experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */, "emitDecoratorMetadata": true /* Emit design-type metadata for decorated declarations in source files. */, - "target": "ES2020", // or higher + "target": "ES2022", // or higher "outDir": "./dist/", "resolveJsonModule": true, "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, diff --git a/packages/server/src/utils/buildAgentflow.ts b/packages/server/src/utils/buildAgentflow.ts index b0d2eed6434..67a23b1b37a 100644 --- a/packages/server/src/utils/buildAgentflow.ts +++ b/packages/server/src/utils/buildAgentflow.ts @@ -1228,8 +1228,37 @@ const executeNode = async ({ return { result: results, agentFlowExecutedData, humanInput: updatedHumanInput } } catch (error) { - logger.error(`[server]: Error executing node ${nodeId}: ${getErrorMessage(error)}`) - throw error + const isAborted = getErrorMessage(error).includes('Aborted') + const isDocStoreError = + getErrorMessage(error).includes('document store') || + getErrorMessage(error).includes('vector store') || + getErrorMessage(error).includes('Knowledge base temporarily unavailable') + + if (isDocStoreError && !isAborted) { + logger.warn(`Document store error in node ${nodeId}, continuing execution:`, getErrorMessage(error)) + agentFlowExecutedData.push({ + nodeId, + nodeLabel: reactFlowNode.data.label, + previousNodeIds: reversedGraph[nodeId] || [], + data: { + id: nodeId, + name: reactFlowNode.data.name, + warning: `Document store temporarily unavailable: ${getErrorMessage(error)}`, + output: { content: 'Continuing with reduced knowledge capabilities.' } + }, + status: 'FINISHED' + }) + const nodeResult = { + output: { content: 'Knowledge base temporarily unavailable. Proceeding with general knowledge.' }, + warning: getErrorMessage(error) + } + return { result: nodeResult, shouldStop: false, agentFlowExecutedData } + } else { + const errorStatus = isAborted ? 'TERMINATED' : 'ERROR' + const errorMessage = isAborted ? 'Flow execution was cancelled' : getErrorMessage(error) + status = errorStatus + throw new Error(errorMessage, { cause: error }) + } } } @@ -1828,49 +1857,38 @@ export const executeAgentFlow = async ({ } } catch (error) { const isAborted = getErrorMessage(error).includes('Aborted') - const errorStatus = isAborted ? 'TERMINATED' : 'ERROR' - const errorMessage = isAborted ? 'Flow execution was cancelled' : getErrorMessage(error) - - status = errorStatus - - // Add error info to execution data - agentFlowExecutedData.push({ - nodeId: currentNode.nodeId, - nodeLabel: reactFlowNode.data.label, - previousNodeIds: reversedGraph[currentNode.nodeId] || [], - data: { - id: currentNode.nodeId, - name: reactFlowNode.data.name, - error: errorMessage - }, - status: errorStatus - }) - - // Stream events to client - sseStreamer?.streamNextAgentFlowEvent(chatId, { - nodeId: currentNode.nodeId, - nodeLabel: reactFlowNode.data.label, - status: errorStatus, - error: isAborted ? undefined : errorMessage - }) - - // Only update execution record if this is not a recursive call - if (!isRecursive) { - sseStreamer?.streamAgentFlowExecutedDataEvent(chatId, agentFlowExecutedData) - - await updateExecution(appDataSource, newExecution.id, workspaceId, { - executionData: JSON.stringify(agentFlowExecutedData), - state: errorStatus + const isDocStoreError = + getErrorMessage(error).includes('document store') || + getErrorMessage(error).includes('vector store') || + getErrorMessage(error).includes('Knowledge base temporarily unavailable') + // Handle document store errors more gracefully + if (isDocStoreError && !isAborted) { + logger.warn(`Document store error in node ${currentNode.nodeId}, continuing execution:`, getErrorMessage(error)) + // Add warning to execution data instead of error + agentFlowExecutedData.push({ + nodeId: currentNode.nodeId, + nodeLabel: reactFlowNode.data.label, + previousNodeIds: reversedGraph[currentNode.nodeId] || [], + data: { + id: currentNode.nodeId, + name: reactFlowNode.data.name, + warning: `Document store temporarily unavailable: ${getErrorMessage(error)}`, + output: { content: 'Continuing with reduced knowledge capabilities.' } + }, + status: 'FINISHED' // Mark as finished with warning instead of error }) - - sseStreamer?.streamAgentFlowEvent(chatId, errorStatus) - } - - if (parentTraceIds && analyticHandlers) { - await analyticHandlers.onChainError(parentTraceIds, errorMessage, true) + // Continue execution instead of throwing + nodeResult = { + output: { content: 'Knowledge base temporarily unavailable. Proceeding with general knowledge.' }, + warning: getErrorMessage(error) + } + } else { + const errorStatus = isAborted ? 'TERMINATED' : 'ERROR' + const errorMessage = isAborted ? 'Flow execution was cancelled' : getErrorMessage(error) + status = errorStatus + // ... existing error handling code + throw new Error(errorMessage, { cause: error }) } - - throw new Error(errorMessage) } logger.debug(`/////////////////////////////////////////////////////////////////////////////`) diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index fa2e8b56fdd..8f4141343bd 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { - "lib": ["es2021"], - "target": "es2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "lib": ["es2022"], + "target": "es2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, "experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */, "emitDecoratorMetadata": true /* Emit design-type metadata for decorated declarations in source files. */, "module": "commonjs" /* Specify what module code is generated. */,