{"record":{"id":"07c4b0d2056cb367","repo":"n8n-io/n8n","slug":"filter-operator-operator-on-key-key-does","errorCode":null,"errorMessage":"Filter operator \"${operator}\" on key \"${key}\" does not support float values: Qdrant match only supports strings, integers, and booleans.","messagePattern":"Filter operator \"(.+?)\" on key \"(.+?)\" does not support float values: Qdrant match only supports strings, integers, and booleans\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/@n8n/agents/src/vector-stores/qdrant.ts","lineNumber":160,"sourceCode":"\t\tscore: point.score,\n\t};\n}\n\n/** Negations are expressed as nested `must_not` filters so both `and`/`or` combinators work uniformly. */\nfunction buildQdrantFilter(filter: VectorFilter): Schemas['Filter'] {\n\tconst conditions = filter.conditions.map(buildCondition);\n\treturn filter.combineWith === 'or' ? { should: conditions } : { must: conditions };\n}\n\nfunction buildCondition(condition: FilterCondition): Schemas['Condition'] {\n\tconst { key, operator, value } = condition;\n\tconst payloadKey = `metadata.${key}`;\n\n\tswitch (operator) {\n\t\tcase 'eq':\n\t\tcase 'ne': {\n\t\t\tif (typeof value === 'number' && !Number.isInteger(value)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Filter operator \"${operator}\" on key \"${key}\" does not support float values: Qdrant match only supports strings, integers, and booleans.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst match: Schemas['Condition'] = { key: payloadKey, match: { value } };\n\t\t\treturn operator === 'eq' ? match : { must_not: [match] };\n\t\t}\n\t\tcase 'in':\n\t\tcase 'nin': {\n\t\t\tif (!Array.isArray(value) || value.length === 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Filter operator \"${operator}\" on key \"${key}\" requires a non-empty array value.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst allStrings = value.every((v) => typeof v === 'string');\n\t\t\tconst allIntegers = value.every((v) => typeof v === 'number' && Number.isInteger(v));\n\t\t\tif (!allStrings && !allIntegers) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Filter operator \"${operator}\" on key \"${key}\" requires all array elements to be strings or all to be integers: Qdrant match does not support mixed-type or float values.`,","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/agents/src/vector-stores/qdrant.ts#L142-L178","documentation":"Thrown by buildCondition in the Qdrant backend when an `eq`/`ne` condition's value is a non-integer number (a float). Qdrant's `match` filter only accepts strings, integers, and booleans — floats have no exact representation in its match index, so they are rejected before the request. Operator and key are named in the message.","triggerScenarios":"Filtering with `{ key: 'price', operator: 'eq', value: 9.99 }`; `{ key: 'score', operator: 'ne', value: 1.5 }`; any numeric metadata that is a float used in an eq/ne filter.","commonSituations":"Numeric metadata stored as floats (price, rating, geo coords) and filtered with eq/ne; reading filter values from JSON config that parses to floats; comparing against a computed float.","solutions":["Don't use eq/ne for float metadata — Qdrant match cannot represent it. Store floats only for retrieval, not exact-match filtering.","If exact filtering is required, store a coarser integer/string representation (e.g. price_cents as integer, or a price bucket string) and filter on that.","For range semantics, note the SDK filter API has no range operator; consider a backend that supports it (Postgres with jsonb) or pre-bucket the value."],"exampleFix":"// before\nawait store.search('q', {\n  filter: { conditions: [{ key: 'price', operator: 'eq', value: 9.99 }] },\n});\n\n// after — store and filter on an integer/string bucket instead\nawait store.addDocuments([\n  { content: 'item', metadata: { price_cents: 999, price_band: '0-10' } },\n]);\nawait store.search('q', {\n  filter: { conditions: [{ key: 'price_band', operator: 'eq', value: '0-10' }] },\n});","handlingStrategy":"validation","validationCode":"function assertQdrantEqValue(key: string, value: unknown): void {\n  if (typeof value === 'number' && !Number.isInteger(value)) {\n    throw new Error(`Qdrant eq/ne on \"${key}\" cannot take a float (${value}); use a string/int/bool bucket`);\n  }\n}\n\nconditions.forEach((c) => {\n  if (c.operator === 'eq' || c.operator === 'ne') assertQdrantEqValue(c.key, c.value);\n});","typeGuard":"function isQdrantMatchValue(v: unknown): v is string | number | boolean {\n  return typeof v === 'string' || typeof v === 'boolean' || (typeof v === 'number' && Number.isInteger(v));\n}","tryCatchPattern":null,"preventionTips":["Don't use eq/ne for float metadata on Qdrant — Qdrant match can't represent floats.","Store a coarse integer/string bucket (price_cents, price_band) for filterable numeric fields.","Validate eq/ne values against the string|int|boolean shape before building a Qdrant filter."],"tags":["qdrant","filter","validation","vector-store","float"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}