Skip to content

Commit 3dcb2ec

Browse files
committed
feat: "fault tolerance" prevent agent execution error on store statues changes or failed retreivers
1 parent 768de61 commit 3dcb2ec

File tree

6 files changed

+239
-158
lines changed

6 files changed

+239
-158
lines changed

packages/components/nodes/agentflow/Agent/Agent.ts

Lines changed: 60 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
replaceBase64ImagesWithFileReferences,
2929
updateFlowState
3030
} from '../utils'
31+
import { DocumentStoreError } from '../../../src/error'
3132

3233
interface ITool {
3334
agentSelectedTool: string
@@ -544,64 +545,76 @@ class Agent_Agentflow implements INode {
544545
const knowledgeBases = nodeData.inputs?.agentKnowledgeDocumentStores as IKnowledgeBase[]
545546
if (knowledgeBases && knowledgeBases.length > 0) {
546547
for (const knowledgeBase of knowledgeBases) {
547-
const nodeInstanceFilePath = options.componentNodes['retrieverTool'].filePath as string
548-
const nodeModule = await import(nodeInstanceFilePath)
549-
const newRetrieverToolNodeInstance = new nodeModule.nodeClass()
550-
const [storeId, storeName] = knowledgeBase.documentStore.split(':')
551-
552-
const docStoreVectorInstanceFilePath = options.componentNodes['documentStoreVS'].filePath as string
553-
const docStoreVectorModule = await import(docStoreVectorInstanceFilePath)
554-
const newDocStoreVectorInstance = new docStoreVectorModule.nodeClass()
555-
const docStoreVectorInstance = await newDocStoreVectorInstance.init(
556-
{
548+
try {
549+
const nodeInstanceFilePath = options.componentNodes['retrieverTool'].filePath as string
550+
const nodeModule = await import(nodeInstanceFilePath)
551+
const newRetrieverToolNodeInstance = new nodeModule.nodeClass()
552+
const [storeId, storeName] = knowledgeBase.documentStore.split(':')
553+
554+
const docStoreVectorInstanceFilePath = options.componentNodes['documentStoreVS'].filePath as string
555+
const docStoreVectorModule = await import(docStoreVectorInstanceFilePath)
556+
const newDocStoreVectorInstance = new docStoreVectorModule.nodeClass()
557+
let docStoreVectorInstance
558+
try {
559+
docStoreVectorInstance = await newDocStoreVectorInstance.init(
560+
{
561+
...nodeData,
562+
inputs: {
563+
...nodeData.inputs,
564+
selectedStore: storeId
565+
},
566+
outputs: {
567+
output: 'retriever'
568+
}
569+
},
570+
'',
571+
options
572+
)
573+
} catch (error) {
574+
if (error instanceof DocumentStoreError) {
575+
throw error
576+
}
577+
throw new DocumentStoreError(error.message, storeId)
578+
}
579+
const newRetrieverToolNodeData = {
557580
...nodeData,
558581
inputs: {
559582
...nodeData.inputs,
560-
selectedStore: storeId
561-
},
562-
outputs: {
563-
output: 'retriever'
583+
name: storeName
584+
.toLowerCase()
585+
.replace(/ /g, '_')
586+
.replace(/[^a-z0-9_-]/g, ''),
587+
description: knowledgeBase.docStoreDescription,
588+
retriever: docStoreVectorInstance,
589+
returnSourceDocuments: knowledgeBase.returnSourceDocuments
564590
}
565-
},
566-
'',
567-
options
568-
)
569-
570-
const newRetrieverToolNodeData = {
571-
...nodeData,
572-
inputs: {
573-
...nodeData.inputs,
591+
}
592+
const retrieverToolInstance = await newRetrieverToolNodeInstance.init(newRetrieverToolNodeData, '', options)
593+
toolsInstance.push(retrieverToolInstance as Tool)
594+
const jsonSchema = zodToJsonSchema(retrieverToolInstance.schema)
595+
if (jsonSchema.$schema) {
596+
delete jsonSchema.$schema
597+
}
598+
const componentNode = options.componentNodes['retrieverTool']
599+
availableTools.push({
574600
name: storeName
575601
.toLowerCase()
576602
.replace(/ /g, '_')
577603
.replace(/[^a-z0-9_-]/g, ''),
578604
description: knowledgeBase.docStoreDescription,
579-
retriever: docStoreVectorInstance,
580-
returnSourceDocuments: knowledgeBase.returnSourceDocuments
605+
schema: jsonSchema,
606+
toolNode: {
607+
label: componentNode?.label || retrieverToolInstance.name,
608+
name: componentNode?.name || retrieverToolInstance.name
609+
}
610+
})
611+
} catch (error) {
612+
if (error instanceof DocumentStoreError) {
613+
console.warn(`Failed to initialize document store ${knowledgeBase.documentStore}, skipping:`, error.message)
614+
continue
581615
}
616+
throw error
582617
}
583-
const retrieverToolInstance = await newRetrieverToolNodeInstance.init(newRetrieverToolNodeData, '', options)
584-
585-
toolsInstance.push(retrieverToolInstance as Tool)
586-
587-
const jsonSchema = zodToJsonSchema(retrieverToolInstance.schema)
588-
if (jsonSchema.$schema) {
589-
delete jsonSchema.$schema
590-
}
591-
const componentNode = options.componentNodes['retrieverTool']
592-
593-
availableTools.push({
594-
name: storeName
595-
.toLowerCase()
596-
.replace(/ /g, '_')
597-
.replace(/[^a-z0-9_-]/g, ''),
598-
description: knowledgeBase.docStoreDescription,
599-
schema: jsonSchema,
600-
toolNode: {
601-
label: componentNode?.label || retrieverToolInstance.name,
602-
name: componentNode?.name || retrieverToolInstance.name
603-
}
604-
})
605618
}
606619
}
607620

packages/components/nodes/agentflow/Retriever/Retriever.ts

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { updateFlowState } from '../utils'
1111
import { DataSource } from 'typeorm'
1212
import { BaseRetriever } from '@langchain/core/retrievers'
1313
import { Document } from '@langchain/core/documents'
14+
import { DocumentStoreError } from '../../../src/error'
1415

1516
interface IKnowledgeBase {
1617
documentStore: string
@@ -152,27 +153,46 @@ class Retriever_Agentflow implements INode {
152153
const knowledgeBases = nodeData.inputs?.retrieverKnowledgeDocumentStores as IKnowledgeBase[]
153154
if (knowledgeBases && knowledgeBases.length > 0) {
154155
for (const knowledgeBase of knowledgeBases) {
155-
const [storeId, _] = knowledgeBase.documentStore.split(':')
156-
157-
const docStoreVectorInstanceFilePath = options.componentNodes['documentStoreVS'].filePath as string
158-
const docStoreVectorModule = await import(docStoreVectorInstanceFilePath)
159-
const newDocStoreVectorInstance = new docStoreVectorModule.nodeClass()
160-
const docStoreVectorInstance = (await newDocStoreVectorInstance.init(
161-
{
162-
...nodeData,
163-
inputs: {
164-
...nodeData.inputs,
165-
selectedStore: storeId
166-
},
167-
outputs: {
168-
output: 'retriever'
156+
try {
157+
const [storeId, _] = knowledgeBase.documentStore.split(':')
158+
159+
const docStoreVectorInstanceFilePath = options.componentNodes['documentStoreVS'].filePath as string
160+
const docStoreVectorModule = await import(docStoreVectorInstanceFilePath)
161+
const newDocStoreVectorInstance = new docStoreVectorModule.nodeClass()
162+
let docStoreVectorInstance
163+
try {
164+
docStoreVectorInstance = (await newDocStoreVectorInstance.init(
165+
{
166+
...nodeData,
167+
inputs: {
168+
...nodeData.inputs,
169+
selectedStore: storeId
170+
},
171+
outputs: {
172+
output: 'retriever'
173+
}
174+
},
175+
'',
176+
options
177+
)) as BaseRetriever
178+
} catch (error) {
179+
if (error instanceof DocumentStoreError) {
180+
throw error
169181
}
170-
},
171-
'',
172-
options
173-
)) as BaseRetriever
174-
175-
docs = await docStoreVectorInstance.invoke(retrieverQuery || input, { signal: abortController?.signal })
182+
throw new DocumentStoreError(error.message, storeId)
183+
}
184+
const storeDocs = await docStoreVectorInstance.invoke(retrieverQuery || input, { signal: abortController?.signal })
185+
docs.push(...storeDocs)
186+
} catch (error) {
187+
if (error instanceof DocumentStoreError) {
188+
console.warn(
189+
`Document store ${knowledgeBase.documentStore} unavailable, continuing with other stores:`,
190+
error.message
191+
)
192+
continue
193+
}
194+
throw error
195+
}
176196
}
177197
}
178198

packages/components/nodes/tools/RetrieverTool/RetrieverTool.ts

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -193,20 +193,33 @@ class Retriever_Tools implements INode {
193193
const flow = { chatflowId: options.chatflowid }
194194

195195
const func = async ({ input }: { input: string }, _?: CallbackManagerForToolRun, flowConfig?: IFlowConfig) => {
196-
if (retrieverToolMetadataFilter) {
197-
const flowObj = flowConfig
198-
199-
const metadatafilter =
200-
typeof retrieverToolMetadataFilter === 'object' ? retrieverToolMetadataFilter : JSON.parse(retrieverToolMetadataFilter)
201-
const newMetadataFilter = resolveFlowObjValue(metadatafilter, flowObj)
202-
203-
const vectorStore = (retriever as VectorStoreRetriever<any>).vectorStore
204-
vectorStore.filter = newMetadataFilter
196+
try {
197+
if (retrieverToolMetadataFilter) {
198+
const flowObj = flowConfig
199+
const metadatafilter =
200+
typeof retrieverToolMetadataFilter === 'object'
201+
? retrieverToolMetadataFilter
202+
: JSON.parse(retrieverToolMetadataFilter)
203+
const newMetadataFilter = resolveFlowObjValue(metadatafilter, flowObj)
204+
const vectorStore = (retriever as VectorStoreRetriever<any>).vectorStore
205+
vectorStore.filter = newMetadataFilter
206+
}
207+
const docs = await retriever.invoke(input)
208+
const content = docs.map((doc) => doc.pageContent).join('\n\n')
209+
const sourceDocuments = JSON.stringify(docs)
210+
return returnSourceDocuments ? content + SOURCE_DOCUMENTS_PREFIX + sourceDocuments : content
211+
} catch (error) {
212+
const isDocStoreError =
213+
error.message &&
214+
(error.message.includes('document store') ||
215+
error.message.includes('vector store') ||
216+
error.message.includes('retriever'))
217+
if (isDocStoreError) {
218+
console.warn('Document store retrieval failed, returning fallback response:', error.message)
219+
return 'Knowledge base temporarily unavailable. Proceeding with general knowledge.'
220+
}
221+
throw error
205222
}
206-
const docs = await retriever.invoke(input)
207-
const content = docs.map((doc) => doc.pageContent).join('\n\n')
208-
const sourceDocuments = JSON.stringify(docs)
209-
return returnSourceDocuments ? content + SOURCE_DOCUMENTS_PREFIX + sourceDocuments : content
210223
}
211224

212225
const schema = z.object({

packages/components/nodes/vectorstores/DocumentStoreVS/DocStoreVector.ts

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOptionsValue, INodeOutputsValue, INodeParams } from '../../../src/Interface'
22
import { DataSource } from 'typeorm'
3+
import { DocumentStoreError } from '../../../src/error'
34

45
class DocStore_VectorStores implements INode {
56
label: string
@@ -73,45 +74,52 @@ class DocStore_VectorStores implements INode {
7374
}
7475

7576
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
76-
const selectedStore = nodeData.inputs?.selectedStore as string
77-
const appDataSource = options.appDataSource as DataSource
78-
const databaseEntities = options.databaseEntities as IDatabaseEntity
79-
const output = nodeData.outputs?.output as string
80-
81-
const entity = await appDataSource.getRepository(databaseEntities['DocumentStore']).findOneBy({ id: selectedStore })
82-
if (!entity) {
83-
return { error: 'Store not found' }
84-
}
85-
const data: ICommonObject = {}
86-
data.output = output
87-
88-
// Prepare Embeddings Instance
89-
const embeddingConfig = JSON.parse(entity.embeddingConfig)
90-
data.embeddingName = embeddingConfig.name
91-
data.embeddingConfig = embeddingConfig.config
92-
let embeddingObj = await _createEmbeddingsObject(options.componentNodes, data, options)
93-
if (!embeddingObj) {
94-
return { error: 'Failed to create EmbeddingObj' }
95-
}
77+
try {
78+
const selectedStore = nodeData.inputs?.selectedStore as string
79+
const appDataSource = options.appDataSource as DataSource
80+
const databaseEntities = options.databaseEntities as IDatabaseEntity
81+
const output = nodeData.outputs?.output as string
9682

97-
// Prepare Vector Store Instance
98-
const vsConfig = JSON.parse(entity.vectorStoreConfig)
99-
data.vectorStoreName = vsConfig.name
100-
data.vectorStoreConfig = vsConfig.config
101-
if (data.inputs) {
102-
data.vectorStoreConfig = { ...vsConfig.config, ...data.inputs }
103-
}
83+
const entity = await appDataSource.getRepository(databaseEntities['DocumentStore']).findOneBy({ id: selectedStore })
84+
if (!entity) {
85+
throw new DocumentStoreError('Store not found', selectedStore)
86+
}
87+
const data: ICommonObject = {}
88+
data.output = output
89+
90+
// Prepare Embeddings Instance
91+
const embeddingConfig = JSON.parse(entity.embeddingConfig)
92+
data.embeddingName = embeddingConfig.name
93+
data.embeddingConfig = embeddingConfig.config
94+
let embeddingObj = await _createEmbeddingsObject(options.componentNodes, data, options)
95+
if (!embeddingObj) {
96+
throw new DocumentStoreError('Failed to create EmbeddingObj', selectedStore)
97+
}
98+
99+
// Prepare Vector Store Instance
100+
const vsConfig = JSON.parse(entity.vectorStoreConfig)
101+
data.vectorStoreName = vsConfig.name
102+
data.vectorStoreConfig = vsConfig.config
103+
if (data.inputs) {
104+
data.vectorStoreConfig = { ...vsConfig.config, ...data.inputs }
105+
}
104106

105-
// Prepare Vector Store Node Data
106-
const vStoreNodeData = _createVectorStoreNodeData(options.componentNodes, data, embeddingObj)
107+
// Prepare Vector Store Node Data
108+
const vStoreNodeData = _createVectorStoreNodeData(options.componentNodes, data, embeddingObj)
107109

108-
// Finally create the Vector Store or Retriever object (data.output)
109-
const vectorStoreObj = await _createVectorStoreObject(options.componentNodes, data)
110-
const retrieverOrVectorStore = await vectorStoreObj.init(vStoreNodeData, '', options)
111-
if (!retrieverOrVectorStore) {
112-
return { error: 'Failed to create vectorStore' }
110+
// Finally create the Vector Store or Retriever object (data.output)
111+
const vectorStoreObj = await _createVectorStoreObject(options.componentNodes, data)
112+
const retrieverOrVectorStore = await vectorStoreObj.init(vStoreNodeData, '', options)
113+
if (!retrieverOrVectorStore) {
114+
throw new DocumentStoreError('Failed to create vectorStore', selectedStore)
115+
}
116+
return retrieverOrVectorStore
117+
} catch (error) {
118+
if (error instanceof DocumentStoreError) {
119+
throw error
120+
}
121+
throw new DocumentStoreError(error.message, nodeData.inputs?.selectedStore)
113122
}
114-
return retrieverOrVectorStore
115123
}
116124
}
117125

packages/components/src/error.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,12 @@ const toErrorWithMessage = (maybeError: unknown): ErrorWithMessage => {
2323
export const getErrorMessage = (error: unknown) => {
2424
return toErrorWithMessage(error).message
2525
}
26+
27+
export class DocumentStoreError extends Error {
28+
public storeId?: string
29+
constructor(message: string, storeId?: string) {
30+
super(`Document store error: ${message}`)
31+
this.name = 'DocumentStoreError'
32+
this.storeId = storeId
33+
}
34+
}

0 commit comments

Comments
 (0)