chroma-core/chroma · error · TypeError

Rank input must be a RankExpression, number, or plain object

Error message

Rank input must be a RankExpression, number, or plain object

What it means

RankExpression.from() accepts a RankExpression instance, a number, a plain object (raw literal), or null/undefined. Anything else — notably strings (including numeric strings like "5"), arrays, and non-plain class instances — raises this TypeError. Because requireRank() in rank.ts routes operands of add/subtract/multiply/divide/max/min through this same function, arithmetic on rank expressions fails identically.

Source

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

    return MinRankExpression.create(expressions);
  }
}

export abstract class RankExpression extends RankExpressionBase {
  public static from(input: RankInput): RankExpression | undefined {
    if (input instanceof RankExpression) {
      return input;
    }
    if (input === null || input === undefined) {
      return undefined;
    }
    if (typeof input === "number") {
      return new ValueRankExpression(input);
    }
    if (isPlainObject(input)) {
      return new RawRankExpression(input);
    }
    throw new TypeError(
      "Rank input must be a RankExpression, number, or plain object",
    );
  }
}

class RawRankExpression extends RankExpression {
  constructor(private readonly raw: RankLiteral) {
    super();
  }

  public toJSON(): RankLiteral {
    return deepClone(this.raw);
  }
}

class ValueRankExpression extends RankExpression {
  constructor(private readonly value: number) {
    super();

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Use numbers or Val() for constants: score.multiply(0.5) or score.multiply(Val(weight))
  2. Parse serialized expressions before passing: RankExpression.from(JSON.parse(raw))
  3. Convert numeric strings upstream: Number(weight) at the config boundary

Example fix

// before
const combined = knn.multiply(cfg.weight); // cfg.weight = "0.5" string -> TypeError

// after
const weight = Number(cfg.weight);
const combined = knn.multiply(Number.isFinite(weight) ? weight : 1);
Defensive patterns

Strategy: type-guard

Validate before calling

const toRankOperand = (v: unknown): number | RankExpression => {
  if (typeof v === "string") {
    const n = Number(v);
    if (Number.isFinite(n)) return n;
  }
  if (typeof v === "number" || v instanceof RankExpression || (typeof v === "object" && v !== null)) {
    return v as number | RankExpression;
  }
  throw new Error("Rank operands must be numbers, rank expressions, or plain objects");
};
const combined = knn.multiply(toRankOperand(cfg.weight));

Type guard

function isRankInput(v: unknown): v is number | RankExpression | Record<string, unknown> {
  return (
    typeof v === "number" ||
    v instanceof RankExpression ||
    (typeof v === "object" && v !== null && !Array.isArray(v) && v.constructor === Object)
  );
}

Try / catch

try {
  const expr = Rrf({ ranks: [knn, bm25, cfg.extra] });
} catch (e) {
  if (e instanceof TypeError && /Rank input|must be a rank expression/.test(e.message)) {
    // drop or coerce the offending operand (Number("0.5")) and rebuild
  } else throw e;
}

Prevention

When it happens

Trigger: score.add("weight") or rankExpr.multiply([1, 2]); Rrf({ ranks: ["bm25"] }). Passing a JSON string that was never parsed, or a Map/typed instance instead of a plain object.

Common situations: Mixing string weights from config or LLM output into rank arithmetic instead of numbers or Val(...). Forgetting JSON.parse on serialized rank expressions. Assuming numeric strings coerce (they do not — typeof "5" is "string").

Related errors


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