{"record":{"id":"a753c3cfd5e3c674","repo":"chroma-core/chroma","slug":"rrf-requires-at-least-one-rank-expression","errorCode":null,"errorMessage":"Rrf requires at least one rank expression","messagePattern":"Rrf requires at least one rank expression","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"clients/new-js/packages/chromadb/src/execution/expression/rank.ts","lineNumber":465,"sourceCode":"\nexport interface RrfOptions {\n  ranks: RankInput[];\n  k?: number;\n  weights?: number[];\n  normalize?: boolean;\n}\n\nexport const Rrf = ({\n  ranks,\n  k = 60,\n  weights,\n  normalize = false,\n}: RrfOptions): RankExpression => {\n  if (!Number.isInteger(k) || k <= 0) {\n    throw new TypeError(\"Rrf k must be a positive integer\");\n  }\n  if (!Array.isArray(ranks) || ranks.length === 0) {\n    throw new TypeError(\"Rrf requires at least one rank expression\");\n  }\n\n  const expressions = ranks.map((rank, index) =>\n    requireRank(rank, `ranks[${index}]`),\n  );\n\n  let weightValues = weights\n    ? weights.slice()\n    : new Array(expressions.length).fill(1);\n  if (weightValues.length !== expressions.length) {\n    throw new Error(\"Number of weights must match number of ranks\");\n  }\n  if (weightValues.some((value) => typeof value !== \"number\" || value < 0)) {\n    throw new TypeError(\"Weights must be non-negative numbers\");\n  }\n\n  if (normalize) {\n    const total = weightValues.reduce((sum, value) => sum + value, 0);","sourceCodeStart":447,"sourceCodeEnd":483,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/clients/new-js/packages/chromadb/src/execution/expression/rank.ts#L447-L483","documentation":"Thrown by Rrf (rank.ts:465) when its `ranks` option is not a non-empty array. RRF fuses two or more ranked lists, so at least one rank expression is structurally required. The check is Array.isArray(ranks) && ranks.length > 0; a single bare expression object or an empty array both throw. Remember k is validated first, so an invalid k will surface before this error.","triggerScenarios":"Calling Rrf({ ranks: [] }) — e.g. the list of candidate retrievers was empty at runtime; Rrf({ ranks: Val(1) }) — passing one expression directly instead of wrapping it in an array; Rrf({ ranks: denseRank }) where a variable holding a single expression was not arrayed.","commonSituations":"Hybrid search pipelines that fuse 'all available retrievers'; user deselects every retriever in a UI so the fusion list becomes empty; conditional branches that push retrievers only when features are enabled; refactor from multiple named params to a ranks array where one call site was missed.","solutions":["Ensure ranks is an array with at least one element: Rrf({ ranks: [textRank, vectorRank] })","For a single rank, skip RRF and use the expression directly, or still wrap it: Rrf({ ranks: [onlyRank] })","Guard dynamic lists before fusing: if (ranks.length === 0) return fallbackRank;"],"exampleFix":"// before\nconst fused = Rrf({ ranks: retrievers.filter(enabled) }); // may be []\n\n// after\nconst active = retrievers.filter(enabled);\nconst fused = active.length > 1\n  ? Rrf({ ranks: active })\n  : active[0]; // single or zero retriever: no fusion needed","handlingStrategy":"validation","validationCode":"if (!Array.isArray(ranks) || ranks.length === 0) {\n  throw new Error('RRF requires at least one retriever');\n}\nconst fused = Rrf({ ranks });","typeGuard":"const isNonEmptyRankArray = (v: unknown): v is RankInput[] =>\n  Array.isArray(v) && v.length > 0;","tryCatchPattern":"try {\n  const fused = Rrf({ ranks });\n} catch (e) {\n  if (e instanceof TypeError && e.message.includes('at least one rank')) {\n    return singleRank; // degrade to the single retriever\n  }\n  throw e;\n}","preventionTips":["Wrap single expressions in an array: Rrf({ ranks: [only] })","Gate fusion on list length: fuse only when activeRetrievers.length > 1","Validate k first if both are dynamic — k errors mask this one"],"tags":["rrf","ranks","empty-array","input-validation"],"backgroundTag":"empty-array-argument","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}