From 2c26ddfdb0ec74f62905426fd1a178521cd23c60 Mon Sep 17 00:00:00 2001 From: jun4027 Date: Tue, 19 May 2026 10:59:06 +0900 Subject: [PATCH 1/3] fix: fix hybrid search issues and add a separate Weaviate retriever --- .../HybridSearchRetriever.ts | 110 ++++++++++++++ .../WeaviateRetriever/WeaviateRetriever.ts | 139 +++++++++++++++++ .../WeaviateRetriever/weaviateRetriever.png | Bin 0 -> 55728 bytes .../nodes/vectorstores/Weaviate/Weaviate.ts | 143 +++++++++++++++--- 4 files changed, 375 insertions(+), 17 deletions(-) create mode 100644 packages/components/nodes/retrievers/WeaviateRetriever/HybridSearchRetriever.ts create mode 100644 packages/components/nodes/retrievers/WeaviateRetriever/WeaviateRetriever.ts create mode 100644 packages/components/nodes/retrievers/WeaviateRetriever/weaviateRetriever.png diff --git a/packages/components/nodes/retrievers/WeaviateRetriever/HybridSearchRetriever.ts b/packages/components/nodes/retrievers/WeaviateRetriever/HybridSearchRetriever.ts new file mode 100644 index 00000000000..2f680d7f0d4 --- /dev/null +++ b/packages/components/nodes/retrievers/WeaviateRetriever/HybridSearchRetriever.ts @@ -0,0 +1,110 @@ +import { WeaviateStore } from '@langchain/weaviate' +import { VectorStoreRetriever, VectorStoreRetrieverInput } from '@langchain/core/vectorstores' +import { Document } from '@langchain/core/documents' +import { get } from 'lodash' + +type WeaviateHybridInput = Omit, 'k'> & { + alpha: number + topK: number + resultFormat?: string + fusionType?: 'RankedFusion' | 'RelativeScoreFusion' +} + +export class HybridSearchRetriever extends VectorStoreRetriever { + resultFormat: string + alpha: number + topK: number + fusionType: string + + constructor(input: WeaviateHybridInput) { + super(input) + this.vectorStore = input.vectorStore + this.alpha = input.alpha + this.topK = input.topK + this.fusionType = input.fusionType ? this.fusionType : 'RankedFusion' + } + + async _getRelevantDocuments(query: string): Promise { + const results = await this.vectorStore.hybridSearch(query, { + limit: this.topK, + alpha: this.alpha, + filters: this.filter + }) + if (this.resultFormat != undefined) { + return results.map((doc) => { + let resContent = this.resultFormat.replace(/{{context}}/g, doc.pageContent) + resContent = replaceMetadata(resContent, doc.metadata) + + return new Document({ + pageContent: resContent, + metadata: doc.metadata + }) + }) + } else { + return results + } + } + + static fromVectorStore(vectorStore: V, options: Omit, 'vectorStore'>) { + return new this({ ...options, vectorStore }) + } +} + +function replaceMetadata(template: string, metadata: Record): string { + const metadataRegex = /{{metadata\.([\w.]+)}}/g + return template.replace(metadataRegex, (match, path) => { + const value = get(metadata, path) + return value !== undefined ? String(value) : match + }) +} + +export const processSearchFilter = (filterInput: any, client: any, indexName: string) => { + if (!filterInput) return undefined + let rawFilter = filterInput?.where ?? filterInput + + if (rawFilter.operator === 'And' || rawFilter.operator === 'Or') { + const subFilters = rawFilter.operands?.map((operand: any) => processSearchFilter(operand, client, indexName)).filter(Boolean) + + if (!subFilters?.length) return undefined + + return rawFilter.operator === 'And' + ? subFilters.reduce((acc: any, f: any) => acc.and(f)) + : subFilters.reduce((acc: any, f: any) => acc.or(f)) + } + + if (rawFilter?.path && rawFilter?.operator) { + const propName = Array.isArray(rawFilter.path) ? rawFilter.path[0] : rawFilter.path + const operator = rawFilter.operator + const propValue = + rawFilter.valueText ?? + rawFilter.valueString ?? + rawFilter.valueInt ?? + rawFilter.valueNumber ?? + rawFilter.valueBoolean ?? + rawFilter.valueDate ?? + rawFilter.valueTextArray ?? + rawFilter.valueStringArray ?? + rawFilter.valueIntArray ?? + rawFilter.valueNumberArray ?? + rawFilter.valueBooleanArray ?? + rawFilter.valueDateArray + + const filter = client.collections.get(indexName).filter.byProperty(propName) + + const operatorMap: Record any> = { + Equal: (v) => filter.equal(v), + NotEqual: (v) => filter.notEqual(v), + GreaterThan: (v) => filter.greaterThan(v), + GreaterThanEqual: (v) => filter.greaterOrEqual(v), + LessThan: (v) => filter.lessThan(v), + LessThanEqual: (v) => filter.lessOrEqual(v), + Like: (v) => filter.like(v), + ContainsAny: (v) => filter.containsAny(v), + ContainsAll: (v) => filter.containsAll(v) + } + + return operatorMap[operator]?.(propValue) + } + + return undefined +} diff --git a/packages/components/nodes/retrievers/WeaviateRetriever/WeaviateRetriever.ts b/packages/components/nodes/retrievers/WeaviateRetriever/WeaviateRetriever.ts new file mode 100644 index 00000000000..1160174d8dc --- /dev/null +++ b/packages/components/nodes/retrievers/WeaviateRetriever/WeaviateRetriever.ts @@ -0,0 +1,139 @@ +import { WeaviateStore } from '@langchain/weaviate' +import { INode, INodeData, INodeParams, INodeOutputsValue } from '../../../src/Interface' +import { handleEscapeCharacters } from '../../../src' +import { HybridSearchRetriever } from './HybridSearchRetriever' + +const defaultReturnFormat = '{{context}}\nSource: {{metadata.source}}' + +class WeaviateRetriever_Retrievers implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Weaviate Retriever' + this.name = 'weaviateRetriever' + this.version = 1.0 + this.type = 'WeaviateRetriever' + this.icon = 'weaviateRetriever.png' + this.category = 'Retrievers' + this.description = 'Weaviate hybrid search combining vector similarity and BM25 keyword search' + this.baseClasses = [this.type, 'BaseRetriever'] + this.inputs = [ + { + label: 'Weaviate Vector Store', + name: 'vectorStore', + type: 'VectorStore' + }, + { + label: 'Query', + name: 'query', + type: 'string', + description: 'Query to retrieve documents from retriever. If not specified, user question will be used', + optional: true, + acceptVariable: true + }, + { + label: 'Result Format', + name: 'resultFormat', + type: 'string', + rows: 4, + description: + 'Format to return the results in. Use {{context}} to insert the pageContent of the document and {{metadata.key}} to insert metadata values.', + default: defaultReturnFormat + }, + { + label: 'Alpha', + name: 'alpha', + type: 'number', + description: + 'Number between 0 and 1 that determines the weighting of keyword (BM25) portion of the hybrid search. A value of 1 is a pure vector search, while 0 is a pure keyword search.', + default: 0.5, + step: 0.1, + optional: true + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to vector store topK', + placeholder: '4', + type: 'number', + optional: true + }, + { + label: 'fusionType', + name: 'fusionType', + type: 'options', + default: 'RankedFusion', + description: + "Method to merge results: 'RankedFusion' combines by document rank, while 'RelativeScoreFusion' combines by normalized scores.", + options: [ + { + label: 'RankedFusion', + name: 'RankedFusion' + }, + { + label: 'RelativeScoreFusion', + name: 'RelativeScoreFusion' + } + ], + optional: true + } + ] + this.outputs = [ + { + label: 'Weaviate Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Document', + name: 'document', + description: 'Array of document objects containing metadata and pageContent', + baseClasses: ['Document', 'json'] + }, + { + label: 'Text', + name: 'text', + description: 'Concatenated string from pageContent of documents', + baseClasses: ['string', 'json'] + } + ] + } + + async init(nodeData: INodeData, input: string): Promise { + const vectorStore = nodeData.inputs?.vectorStore as WeaviateStore + const query = nodeData.inputs?.query as string + const topK = nodeData.inputs?.topK as string + const alpha = nodeData.inputs?.alpha as string + const resultFormat = nodeData.inputs?.resultFormat as string + const output = nodeData.outputs?.output as string + + const retriever = HybridSearchRetriever.fromVectorStore(vectorStore, { + resultFormat, + alpha: alpha ? parseFloat(alpha) : 0.5, + topK: topK ? parseInt(topK, 10) : 4 + }) + + const searchPath = query ? query : input + + if (output === 'retriever') return retriever + else if (output === 'document') return await retriever._getRelevantDocuments(searchPath) + else if (output === 'text') { + const docs = await retriever._getRelevantDocuments(searchPath) + const finaltext = docs.map((doc) => doc.pageContent).join('\n') + return handleEscapeCharacters(finaltext, false) + } + + return retriever + } +} + +module.exports = { nodeClass: WeaviateRetriever_Retrievers } diff --git a/packages/components/nodes/retrievers/WeaviateRetriever/weaviateRetriever.png b/packages/components/nodes/retrievers/WeaviateRetriever/weaviateRetriever.png new file mode 100644 index 0000000000000000000000000000000000000000..25a39e33894ee2702ff1e569cf14971eb2bf45f3 GIT binary patch literal 55728 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 D(1YVr literal 0 HcmV?d00001 diff --git a/packages/components/nodes/vectorstores/Weaviate/Weaviate.ts b/packages/components/nodes/vectorstores/Weaviate/Weaviate.ts index ac88610038e..2e0c48f076e 100644 --- a/packages/components/nodes/vectorstores/Weaviate/Weaviate.ts +++ b/packages/components/nodes/vectorstores/Weaviate/Weaviate.ts @@ -5,9 +5,8 @@ import { Document } from '@langchain/core/documents' import { Embeddings } from '@langchain/core/embeddings' import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams, IndexingResult } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam, normalizeKeysRecursively, parseJsonBody } from '../../../src/utils' -import { addMMRInputParams, resolveVectorStoreOrRetriever } from '../VectorStoreUtils' import { index } from '../../../src/indexing' -import { VectorStore } from '@langchain/core/vectorstores' +import { HybridSearchRetriever, processSearchFilter } from '../../retrievers/WeaviateRetriever/HybridSearchRetriever' /** * Parses a host string into host and optional port. @@ -231,19 +230,92 @@ class Weaviate_VectorStores implements INode { additionalParams: true, optional: true, acceptVariable: true + }, + { + label: 'Search Type', + name: 'searchType', + type: 'options', + default: 'similarity', + options: [ + { + label: 'Similarity', + name: 'similarity' + }, + { + label: 'Max Marginal Relevance', + name: 'mmr' + }, + { + label: 'Hybrid Search', + name: 'hybrid' + } + ], + additionalParams: true, + optional: true + }, + { + label: 'Fetch K', + name: 'fetchK', + description: 'Number of initial documents to fetch for MMR reranking. Default to 20. Used only when the search type is MMR', + placeholder: '20', + type: 'number', + additionalParams: true, + optional: true, + show: { + searchType: ['mmr'] + } + }, + { + label: 'Lambda', + name: 'lambda', + description: + 'Number between 0 and 1 that determines the degree of diversity among the results, where 0 corresponds to maximum diversity and 1 to minimum diversity. Used only when the search type is MMR', + placeholder: '0.5', + type: 'number', + additionalParams: true, + optional: true, + show: { + searchType: ['mmr'] + } + }, + { + label: 'Alpha', + name: 'alpha', + description: + 'Number between 0 and 1 that determines the weighting of keyword (BM25) portion of the hybrid search. A value of 1 is a pure vector search, while 0 is a pure keyword search.', + placeholder: '1', + type: 'number', + additionalParams: true, + optional: true, + show: { + searchType: ['hybrid'] + } + }, + { + label: 'fusionType', + name: 'fusionType', + type: 'options', + default: 'RankedFusion', + description: + "Method to merge results: 'RankedFusion' combines by document rank, while 'RelativeScoreFusion' combines by normalized scores.", + options: [ + { + label: 'RankedFusion', + name: 'RankedFusion' + }, + { + label: 'RelativeScoreFusion', + name: 'RelativeScoreFusion' + } + ], + additionalParams: true, + optional: true, + show: { + searchType: ['hybrid'] + } } ] - addMMRInputParams(this.inputs) - this.inputs.push({ - label: 'Alpha (for Hybrid Search)', - name: 'alpha', - description: - 'Number between 0 and 1 that determines the weighting of keyword (BM25) portion of the hybrid search. A value of 1 is a pure vector search, while 0 is a pure keyword search.', - placeholder: '1', - type: 'number', - additionalParams: true, - optional: true - }) + this.outputs = [ { label: 'Weaviate Retriever', @@ -307,7 +379,7 @@ class Weaviate_VectorStores implements INode { try { if (recordManager) { - const vectorStore = (await WeaviateStore.fromExistingIndex(embeddings, obj)) as unknown as VectorStore + const vectorStore = (await WeaviateStore.fromExistingIndex(embeddings, obj)) as unknown as WeaviateStore await recordManager.createSchema() const res = await index({ docsSource: finalDocs, @@ -394,6 +466,11 @@ class Weaviate_VectorStores implements INode { const weaviateIndex = nodeData.inputs?.weaviateIndex as string const weaviateTextKey = nodeData.inputs?.weaviateTextKey as string const weaviateMetadataKeys = nodeData.inputs?.weaviateMetadataKeys as string + const output = nodeData.outputs?.output as string + const searchType = nodeData.inputs?.searchType as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseFloat(topK) : 4 + const alpha = nodeData.inputs?.alpha const embeddings = nodeData.inputs?.embeddings as Embeddings let weaviateFilter = nodeData.inputs?.weaviateFilter @@ -418,12 +495,44 @@ class Weaviate_VectorStores implements INode { if (weaviateTextKey) obj.textKey = weaviateTextKey if (weaviateMetadataKeys) obj.metadataKeys = JSON.parse(weaviateMetadataKeys.replace(/\s/g, '')) if (weaviateFilter) { - weaviateFilter = typeof weaviateFilter === 'object' ? weaviateFilter : parseJsonBody(weaviateFilter) + const rawFilter = typeof weaviateFilter === 'object' ? weaviateFilter : parseJsonBody(weaviateFilter) + weaviateFilter = processSearchFilter(rawFilter, client, weaviateIndex) } - const vectorStore = (await WeaviateStore.fromExistingIndex(embeddings, obj)) as unknown as VectorStore + const vectorStore = (await WeaviateStore.fromExistingIndex(embeddings, obj)) as unknown as WeaviateStore - return resolveVectorStoreOrRetriever(nodeData, vectorStore, weaviateFilter) + if (output === 'retriever') { + if ('mmr' === searchType) { + const fetchK = nodeData.inputs?.fetchK as string + const lambda = nodeData.inputs?.lambda as string + const f = fetchK ? parseInt(fetchK) : 20 + const l = lambda ? parseFloat(lambda) : 0.5 + return vectorStore.asRetriever({ + searchType: 'mmr', + k: k, + filter: weaviateFilter, + searchKwargs: { + fetchK: f, + lambda: l + } + }) + } else if ('hybrid' === searchType) { + return new HybridSearchRetriever({ + vectorStore: vectorStore, + alpha: alpha, + topK: k, + filter: weaviateFilter + }) + } else { + return vectorStore.asRetriever({ + k: k, + filter: weaviateFilter + }) + } + } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k + return vectorStore + } } } From c5061bbfb014dbd4c291d2b6745464ac2c492cd9 Mon Sep 17 00:00:00 2001 From: jun4027 Date: Tue, 19 May 2026 11:11:38 +0900 Subject: [PATCH 2/3] Fix Weaviate metadata filter handling in Retriever Tool --- .../nodes/tools/RetrieverTool/RetrieverTool.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/tools/RetrieverTool/RetrieverTool.ts b/packages/components/nodes/tools/RetrieverTool/RetrieverTool.ts index 61ac1dfaa1f..0973bf3fa52 100644 --- a/packages/components/nodes/tools/RetrieverTool/RetrieverTool.ts +++ b/packages/components/nodes/tools/RetrieverTool/RetrieverTool.ts @@ -7,6 +7,7 @@ import { getBaseClasses, resolveFlowObjValue, parseWithTypeConversion } from '.. import { SOURCE_DOCUMENTS_PREFIX } from '../../../src/agents' import { RunnableConfig } from '@langchain/core/runnables' import { VectorStoreRetriever } from '@langchain/core/vectorstores' +import { processSearchFilter } from '../../retrievers/WeaviateRetriever/HybridSearchRetriever' const howToUse = `Add additional filters to vector store. You can also filter with flow config, including the current "state": - \`$flow.sessionId\` @@ -203,7 +204,17 @@ class Retriever_Tools implements INode { if (newMetadataFilter && typeof newMetadataFilter === 'object' && Object.keys(newMetadataFilter).length > 0) { const vectorStore = (retriever as VectorStoreRetriever).vectorStore - vectorStore.filter = newMetadataFilter + if (vectorStore.constructor.name === 'WeaviateStore' || vectorStore.lc_namespace?.includes('weaviate')) { + const client = (vectorStore as any).client + const indexName = (vectorStore as any).indexName + if (client && indexName) { + const newWeaviateMetadataFilter = processSearchFilter(newMetadataFilter, client, indexName) + const weaviateRetriever = retriever as VectorStoreRetriever + weaviateRetriever.filter = newWeaviateMetadataFilter + } + } else { + vectorStore.filter = newMetadataFilter + } } } const docs = await retriever.invoke(input) From 94221ae5f19ec0533df1eca91865e2c7ed8af1a0 Mon Sep 17 00:00:00 2001 From: jun4027 Date: Tue, 19 May 2026 15:57:12 +0900 Subject: [PATCH 3/3] fix hybrid search fusionType processing --- .../WeaviateRetriever/HybridSearchRetriever.ts | 5 +++-- .../WeaviateRetriever/WeaviateRetriever.ts | 16 +++++++++------- .../nodes/vectorstores/Weaviate/Weaviate.ts | 18 ++++++++++-------- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/packages/components/nodes/retrievers/WeaviateRetriever/HybridSearchRetriever.ts b/packages/components/nodes/retrievers/WeaviateRetriever/HybridSearchRetriever.ts index 2f680d7f0d4..cedabb26ed0 100644 --- a/packages/components/nodes/retrievers/WeaviateRetriever/HybridSearchRetriever.ts +++ b/packages/components/nodes/retrievers/WeaviateRetriever/HybridSearchRetriever.ts @@ -7,7 +7,7 @@ type WeaviateHybridInput = Omit extends VectorStoreRetriever { @@ -21,13 +21,14 @@ export class HybridSearchRetriever extends VectorStoreR this.vectorStore = input.vectorStore this.alpha = input.alpha this.topK = input.topK - this.fusionType = input.fusionType ? this.fusionType : 'RankedFusion' + this.fusionType = input.fusionType ?? 'RelativeScore' } async _getRelevantDocuments(query: string): Promise { const results = await this.vectorStore.hybridSearch(query, { limit: this.topK, alpha: this.alpha, + fusionType: this.fusionType, filters: this.filter }) if (this.resultFormat != undefined) { diff --git a/packages/components/nodes/retrievers/WeaviateRetriever/WeaviateRetriever.ts b/packages/components/nodes/retrievers/WeaviateRetriever/WeaviateRetriever.ts index 1160174d8dc..d08a3b4f551 100644 --- a/packages/components/nodes/retrievers/WeaviateRetriever/WeaviateRetriever.ts +++ b/packages/components/nodes/retrievers/WeaviateRetriever/WeaviateRetriever.ts @@ -71,17 +71,17 @@ class WeaviateRetriever_Retrievers implements INode { label: 'fusionType', name: 'fusionType', type: 'options', - default: 'RankedFusion', + default: 'RelativeScore', description: - "Method to merge results: 'RankedFusion' combines by document rank, while 'RelativeScoreFusion' combines by normalized scores.", + "Method to merge results: 'Ranked' combines by document rank, while 'RelativeScore' combines by normalized scores.", options: [ { - label: 'RankedFusion', - name: 'RankedFusion' + label: 'RelativeScore', + name: 'RelativeScore' }, { - label: 'RelativeScoreFusion', - name: 'RelativeScoreFusion' + label: 'Ranked', + name: 'Ranked' } ], optional: true @@ -114,12 +114,14 @@ class WeaviateRetriever_Retrievers implements INode { const topK = nodeData.inputs?.topK as string const alpha = nodeData.inputs?.alpha as string const resultFormat = nodeData.inputs?.resultFormat as string + const fusionType = nodeData.inputs?.fusionType as string const output = nodeData.outputs?.output as string const retriever = HybridSearchRetriever.fromVectorStore(vectorStore, { resultFormat, alpha: alpha ? parseFloat(alpha) : 0.5, - topK: topK ? parseInt(topK, 10) : 4 + topK: topK ? parseInt(topK, 10) : 4, + fusionType: fusionType ?? 'RelativeScore' }) const searchPath = query ? query : input diff --git a/packages/components/nodes/vectorstores/Weaviate/Weaviate.ts b/packages/components/nodes/vectorstores/Weaviate/Weaviate.ts index 2e0c48f076e..2e7e8285c75 100644 --- a/packages/components/nodes/vectorstores/Weaviate/Weaviate.ts +++ b/packages/components/nodes/vectorstores/Weaviate/Weaviate.ts @@ -295,17 +295,17 @@ class Weaviate_VectorStores implements INode { label: 'fusionType', name: 'fusionType', type: 'options', - default: 'RankedFusion', + default: 'RelativeScore', description: - "Method to merge results: 'RankedFusion' combines by document rank, while 'RelativeScoreFusion' combines by normalized scores.", + "Method to merge results: 'Ranked' combines by document rank, while 'RelativeScore' combines by normalized scores.", options: [ { - label: 'RankedFusion', - name: 'RankedFusion' + label: 'RelativeScore', + name: 'RelativeScore' }, { - label: 'RelativeScoreFusion', - name: 'RelativeScoreFusion' + label: 'Ranked', + name: 'Ranked' } ], additionalParams: true, @@ -468,8 +468,9 @@ class Weaviate_VectorStores implements INode { const weaviateMetadataKeys = nodeData.inputs?.weaviateMetadataKeys as string const output = nodeData.outputs?.output as string const searchType = nodeData.inputs?.searchType as string + const fusionType = nodeData.inputs?.fusionType as string const topK = nodeData.inputs?.topK as string - const k = topK ? parseFloat(topK) : 4 + const k = topK ? parseInt(topK, 10) : 4 const alpha = nodeData.inputs?.alpha const embeddings = nodeData.inputs?.embeddings as Embeddings let weaviateFilter = nodeData.inputs?.weaviateFilter @@ -519,8 +520,9 @@ class Weaviate_VectorStores implements INode { } else if ('hybrid' === searchType) { return new HybridSearchRetriever({ vectorStore: vectorStore, - alpha: alpha, + alpha: alpha ? parseFloat(alpha) : 1, topK: k, + fusionType: fusionType ?? 'RelativeScore', filter: weaviateFilter }) } else {