chroma-core/chroma · error · TypeError

Rrf k must be a positive integer

Error message

Rrf k must be a positive integer

What it means

Thrown by the Rrf (reciprocal rank fusion) factory in the Chroma JS client (rank.ts:462) when its `k` smoothing constant — which defaults to 60 — is not an integer greater than zero. k is used as rank.add(k) in the fusion formula 1/(rank+k), so 0 or fractional values are rejected at expression-build time, client-side, before any request. Note k is validated before `ranks`, so a bad k masks an empty-ranks problem.

Source

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

export const Knn = (options: KnnOptions): RankExpression =>
  new KnnRankExpression(normalizeKnnOptions(options));

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");
  }

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Use an integer k >= 1, or omit k to keep the default of 60
  2. If k comes from config, convert and validate: Number.isInteger(k) && k > 0, otherwise fall back to 60
  3. Fix the upstream formula so it yields an integer (Math.round/Math.trunc)

Example fix

// before
const fused = Rrf({ ranks, k: Number(cfg.rrfK) }); // NaN or 0.5 throws

// after
const rawK = Number(cfg.rrfK);
const fused = Rrf({ ranks, k: Number.isInteger(rawK) && rawK > 0 ? rawK : undefined });
Defensive patterns

Strategy: validation

Validate before calling

const safeK = (v: unknown): number | undefined =>
  typeof v === 'number' && Number.isInteger(v) && v > 0 ? v : undefined;
const fused = Rrf({ ranks, k: safeK(cfg.rrfK) }); // undefined -> default 60

Type guard

const isPositiveInteger = (v: unknown): v is number =>
  typeof v === 'number' && Number.isInteger(v) && v > 0;

Try / catch

try {
  const fused = Rrf({ ranks, k });
} catch (e) {
  if (e instanceof TypeError && e.message.includes('Rrf k')) {
    return Rrf({ ranks }); // default k=60
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling Rrf({ ranks, k: 0 }), k: -1, k: 0.5, or k: NaN. Typical sources: k read from config/env without parsing (string '60' is not a number and fails Number.isInteger), or k computed as a float (e.g. ranks.length / 2 with an odd count).

Common situations: Tuning RRF smoothing from external config where the value arrives as a string; formulas that produce fractional constants; copying literature values like k=1.2 (fractional) which this client rejects; setting k=0 intending 'no smoothing', which would cause division by rank 0.

Related errors


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