{"record":{"id":"64e81c3991ec5461","repo":"n8n-io/n8n","slug":"filterablekeys-must-contain-at-least-one-key","errorCode":null,"errorMessage":"filterableKeys must contain at least one key","messagePattern":"filterableKeys must contain at least one key","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@n8n/agents/src/sdk/vector-store-filter.ts","lineNumber":78,"sourceCode":"\t}\n\n\t// eq, ne\n\tif (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean') {\n\t\tthrow new Error(\n\t\t\t`Filter operator \"${operator}\" on key \"${key}\" requires a string, number, or boolean value.`,\n\t\t);\n\t}\n}\n\nfunction isNonEmptyArray(arr: string[]): arr is [string, ...string[]] {\n\treturn arr.length > 0;\n}\n\n/** Builds the zod schema for the model-facing `filter` tool input, scoped to `keys` (key -> description). */\nexport function buildFilterInputSchema(keys: Record<string, string>) {\n\tconst keyNames = Object.keys(keys);\n\tif (!isNonEmptyArray(keyNames)) {\n\t\tthrow new Error('filterableKeys must contain at least one key');\n\t}\n\n\tconst keyDescriptions = keyNames.map((key) => `- ${key}: ${keys[key]}`).join('\\n');\n\n\t// Discriminated by `operator` so the model-facing schema rejects the same\n\t// operator/value mismatches the runtime validator does (e.g. an array for\n\t// \"eq\", or a scalar for \"in\"), instead of failing later during search.\n\treturn z\n\t\t.array(\n\t\t\tz.discriminatedUnion('operator', [\n\t\t\t\tz.object({\n\t\t\t\t\tkey: z.enum(keyNames),\n\t\t\t\t\toperator: z.enum(SCALAR_OPERATORS),\n\t\t\t\t\tvalue: z.union([z.string(), z.number(), z.boolean()]),\n\t\t\t\t}),\n\t\t\t\tz.object({\n\t\t\t\t\tkey: z.enum(keyNames),\n\t\t\t\t\toperator: z.enum(ARRAY_OPERATORS),","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/agents/src/sdk/vector-store-filter.ts#L60-L96","documentation":"buildFilterInputSchema(keys) constructs the model-facing filter tool input from a keys map (key name -> human description for the LLM). It requires at least one key, verified by the isNonEmptyArray type guard on Object.keys(keys). An empty keys object would produce a schema that allows no filterable dimensions, making the filter tool useless and confusing the model — rejected up front.","triggerScenarios":"Calling VectorStore.asTool({ filterableKeys: {} }) or directly invoking buildFilterInputSchema({}). The error fires before the zod discriminated-union schema is constructed.","commonSituations":"Dynamically deriving filterableKeys from metadata that ended up empty; passing an empty object as a placeholder during development; refactoring that clears the keys map; conditionally building keys and the condition produced nothing.","solutions":["Provide at least one key: filterableKeys: { category: 'Document category' }.","If you have no filterable metadata, omit filterableKeys entirely — asTool() then builds a simpler query-only tool without the filter input.","When keys come from external data, guard with Object.keys(keys).length > 0 before passing filterableKeys."],"exampleFix":"// before — throws\nstore.asTool({ filterableKeys: {} });\n\n// after — provide keys\nstore.asTool({ filterableKeys: { category: 'Document category', source: 'Source system' } });\n// or omit filterableKeys entirely\nstore.asTool();","handlingStrategy":"validation","validationCode":"function asToolWithFilters(store: VectorStore, keys: Record<string, string>) {\n  if (Object.keys(keys).length === 0) {\n    return store.asTool(); // omit filterableKeys entirely\n  }\n  return store.asTool({ filterableKeys: keys });\n}","typeGuard":"function hasAtLeastOneKey(keys: Record<string, string>): keys is Record<string, string> {\n  return Object.keys(keys).length > 0;\n}","tryCatchPattern":null,"preventionTips":["Skip filterableKeys entirely when no metadata dimensions are filterable.","Derive filterableKeys from a non-empty metadata schema so it can never be empty by construction.","Guard with Object.keys(keys).length > 0 before passing the option."],"tags":["vector-store","filter","as-tool","validation","configuration"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}