feat(milvus): make distance metric type configurable#6579
Conversation
The Milvus node hardcoded the index metric_type to L2 when creating the collection index on upsert, while the similarity-search path already read the metric from indexCreateParams. As a result, collections using COSINE or IP could not be created from Flowise and queries against them failed with metric-type mismatch errors. Add a 'Metric Type' option (L2 / COSINE / IP, default L2) and thread the selected metric into index creation on upsert and into the search-side indexCreateParams so score normalization matches. L2 remains the default, so existing flows are unchanged. Closes FlowiseAI#6033
There was a problem hiding this comment.
Code Review
This pull request introduces support for configuring the distance metric type (L2, COSINE, or IP) in the Milvus vector store node, along with corresponding unit tests. The feedback suggests initializing indexCreateParams safely using object spread if it is undefined to ensure the user-selected metric type is not silently ignored during upsert and search paths. Additionally, casting the metric type to the MetricType enum is recommended to prevent TypeScript compilation errors.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| if (vectorStore.indexCreateParams) { | ||
| vectorStore.indexCreateParams.metric_type = metricType | ||
| } |
There was a problem hiding this comment.
If vectorStore.indexCreateParams is undefined, the check if (vectorStore.indexCreateParams) will prevent setting the metric type, causing it to silently fall back to the default (L2) and ignoring the user's configuration. It is safer to initialize indexCreateParams if it is not already defined. Spreading undefined is safe and does not require a fallback.
| if (vectorStore.indexCreateParams) { | |
| vectorStore.indexCreateParams.metric_type = metricType | |
| } | |
| vectorStore.indexCreateParams = { | |
| ...vectorStore.indexCreateParams, | |
| metric_type: metricType | |
| } |
References
- Spreading
nullorundefinedwithin an object literal is safe and does not require a?? {}fallback, as it evaluates to an empty object.
There was a problem hiding this comment.
Addressed in 5e153b0: indexCreateParams is now built with object spread so the selected metric is always applied.
| if (vectorStore.indexCreateParams) { | ||
| vectorStore.indexCreateParams.metric_type = metricType | ||
| } |
There was a problem hiding this comment.
Similarly, if vectorStore.indexCreateParams is undefined, the user-selected metric type will be ignored during the search path initialization. Initializing it safely ensures the metric type is always applied. Spreading undefined is safe and does not require a fallback.
| if (vectorStore.indexCreateParams) { | |
| vectorStore.indexCreateParams.metric_type = metricType | |
| } | |
| vectorStore.indexCreateParams = { | |
| ...vectorStore.indexCreateParams, | |
| metric_type: metricType | |
| } |
References
- Spreading
nullorundefinedwithin an object literal is safe and does not require a?? {}fallback, as it evaluates to an empty object.
There was a problem hiding this comment.
Addressed in 5e153b0: same spread fix applied on the search path so the metric is never silently dropped.
| index_name: `myindex_${Date.now().toString()}`, | ||
| index_type: IndexType.AUTOINDEX, | ||
| metric_type: MetricType.L2 | ||
| metric_type: this.indexCreateParams?.metric_type ?? MetricType.L2 |
There was a problem hiding this comment.
In TypeScript, this.indexCreateParams?.metric_type is typed as a string (or 'L2' | 'COSINE' | 'IP'), whereas createIndex from @zilliz/milvus2-sdk-node expects the MetricType enum. To prevent potential compilation errors under strict type-checking configurations, cast the value to MetricType.
| metric_type: this.indexCreateParams?.metric_type ?? MetricType.L2 | |
| metric_type: (this.indexCreateParams?.metric_type as MetricType) ?? MetricType.L2 |
There was a problem hiding this comment.
Addressed in 5e153b0: cast the value to MetricType at the createIndex call site for strict type-checking. Thanks for the review.
Address review feedback: build indexCreateParams with object spread so the selected metric type is always applied even if the property is initially undefined (instead of being silently dropped by a truthiness guard), and cast the metric to MetricType at the createIndex call site for strict type-checking configurations.
Summary
The Milvus vector store node hardcoded the index
metric_typetoL2when itcreated the collection index during upsert, even though the similarity-search
path already read the metric from
indexCreateParams.metric_type(and applied ametric-specific score normalization). Because of this, collections that use
COSINEorIPcould not be created from Flowise, and querying a collectionconfigured with a non-L2 metric produced metric-type mismatch errors.
This PR adds a Metric Type option to the node (L2 / COSINE / IP, default L2)
and threads the selected metric into:
MilvusUpsert.addVectorsnow usesindexCreateParams.metric_typeinstead of a hardcodedMetricType.L2), andinitsetsindexCreateParams.metric_type) so scorenormalization matches the chosen metric.
L2remains the default and the new input is optional, so existing flows keeptheir current behavior.
A small exported helper
resolveMilvusMetricTypenormalizes the input value(case-insensitive, L2 fallback for empty/unknown), which keeps the mapping
unit-testable without a live Milvus server.
Verification
packages/components/nodes/vectorstores/Milvus/Milvus.test.ts(jest):resolveMilvusMetricTypemapsL2/COSINE/IP(any case) correctly andfalls back to
L2for empty/undefined/unknown input.metricTypeoptions input withL2/COSINE/IPand anL2default, and the input is optional.jest(targeted): 6 passed, 6 total.eslintandprettier --checkon the changed files: clean.Closes #6033