chroma-core/chroma · error · TypeError

Rrf requires at least one rank expression

Error message

Rrf requires at least one rank expression

What it means

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.

Source

Thrown at clients/new-js/packages/chromadb/src/execution/expression/rank.ts:465

export interface RrfOptions {
  ranks: RankInput[];
  k?: number;
  weights?: number[];
  normalize?: boolean;
}

export const Rrf = ({
  ranks,
  k = 60,
  weights,
  normalize = false,
}: RrfOptions): RankExpression => {
  if (!Number.isInteger(k) || k <= 0) {
    throw new TypeError("Rrf k must be a positive integer");
  }
  if (!Array.isArray(ranks) || ranks.length === 0) {
    throw new TypeError("Rrf requires at least one rank expression");
  }

  const expressions = ranks.map((rank, index) =>
    requireRank(rank, `ranks[${index}]`),
  );

  let weightValues = weights
    ? weights.slice()
    : new Array(expressions.length).fill(1);
  if (weightValues.length !== expressions.length) {
    throw new Error("Number of weights must match number of ranks");
  }
  if (weightValues.some((value) => typeof value !== "number" || value < 0)) {
    throw new TypeError("Weights must be non-negative numbers");
  }

  if (normalize) {
    const total = weightValues.reduce((sum, value) => sum + value, 0);

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Ensure ranks is an array with at least one element: Rrf({ ranks: [textRank, vectorRank] })
  2. For a single rank, skip RRF and use the expression directly, or still wrap it: Rrf({ ranks: [onlyRank] })
  3. Guard dynamic lists before fusing: if (ranks.length === 0) return fallbackRank;

Example fix

// before
const fused = Rrf({ ranks: retrievers.filter(enabled) }); // may be []

// after
const active = retrievers.filter(enabled);
const fused = active.length > 1
  ? Rrf({ ranks: active })
  : active[0]; // single or zero retriever: no fusion needed
Defensive patterns

Strategy: validation

Validate before calling

if (!Array.isArray(ranks) || ranks.length === 0) {
  throw new Error('RRF requires at least one retriever');
}
const fused = Rrf({ ranks });

Type guard

const isNonEmptyRankArray = (v: unknown): v is RankInput[] =>
  Array.isArray(v) && v.length > 0;

Try / catch

try {
  const fused = Rrf({ ranks });
} catch (e) {
  if (e instanceof TypeError && e.message.includes('at least one rank')) {
    return singleRank; // degrade to the single retriever
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of chroma-core/chroma@aecdd12c8a (2026-08-16). Data as JSON: /api/errors/a753c3cfd5e3c674. Report an issue: GitHub.