{"record":{"id":"6e2b32e960e1b4cc","repo":"chroma-core/chroma","slug":"number-of-weights-len-self-weights-must-match","errorCode":null,"errorMessage":"Number of weights ({len(self.weights)}) must match number of ranks ({len(self.ranks)})","messagePattern":"Number of weights \\((.+?)\\) must match number of ranks \\((.+?)\\)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"chromadb/execution/expression/operator.py","lineNumber":1211,"sourceCode":"    weights: Optional[List[float]] = None\n    normalize: bool = False\n\n    def to_dict(self) -> Dict[str, Any]:\n        \"\"\"Convert RRF to a composition of existing expression operators.\n\n        Builds: -sum(weight_i / (k + rank_i)) for each rank\n        Using Python's overloaded operators for cleaner code.\n        \"\"\"\n        # Validate RRF parameters\n        if not self.ranks:\n            raise ValueError(\"RRF requires at least one rank\")\n        if self.k <= 0:\n            raise ValueError(f\"k must be positive, got {self.k}\")\n\n        # Validate weights if provided\n        if self.weights is not None:\n            if len(self.weights) != len(self.ranks):\n                raise ValueError(\n                    f\"Number of weights ({len(self.weights)}) must match number of ranks ({len(self.ranks)})\"\n                )\n            if any(w < 0.0 for w in self.weights):\n                raise ValueError(\"All weights must be non-negative\")\n\n        # Populate weights with 1.0 if not provided\n        weights = self.weights if self.weights else [1.0] * len(self.ranks)\n\n        # Normalize weights if requested\n        if self.normalize:\n            weight_sum = sum(weights)\n            if weight_sum == 0:\n                raise ValueError(\"Sum of weights must be positive when normalize=True\")\n            weights = [w / weight_sum for w in weights]\n\n        # Zip weights with ranks and build terms: weight / (k + rank)\n        terms = [w / (self.k + rank) for w, rank in zip(weights, self.ranks)]\n","sourceCodeStart":1193,"sourceCodeEnd":1229,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/chromadb/execution/expression/operator.py#L1193-L1229","documentation":"When weights are supplied to Chroma's Rrf, every ranking strategy must have exactly one weight so that zip(weights, ranks) pairs them for the terms weight_i / (k + rank_i). Rrf.to_dict() raises this ValueError at serialization time when len(weights) != len(ranks) — including weights=[] against a non-empty ranks list, since validation runs before defaults are applied.","triggerScenarios":"Rrf(ranks=[knn1, knn2], weights=[1.0]) (one weight for two ranks), or weights=[] with non-empty ranks; typically ranks are built from a dynamic strategy list while weights come from static config, and the two drift out of sync.","commonSituations":"Adding a new retrieval strategy to hybrid search without extending the weights config; weights loaded from JSON/YAML that was written for an older strategy set; per-environment config divergence between staging and production.","solutions":["Make weights match ranks one-to-one, e.g. Rrf(ranks=[a, b], weights=[1.0, 1.0])","Prefer omitting weights entirely for equal weighting — Chroma then uses 1.0 per rank","Build weights from the same source as ranks: weights = [cfg[s.name] for s in strategies]","Assert len(weights) == len(ranks) before constructing/serializing Rrf"],"exampleFix":"# before\nstrategies = [dense, sparse, full_text]  # grew to 3\nrrf = Rrf(ranks=[Knn(query=s.query, return_rank=True) for s in strategies],\n          weights=[1.0, 1.0])  # still 2\n\n# after\nranks = [Knn(query=s.query, return_rank=True) for s in strategies]\nrrf = Rrf(ranks=ranks, weights=[1.0] * len(ranks))","handlingStrategy":"validation","validationCode":"ranks = [Knn(query=s.query, key=s.key, return_rank=True) for s in strategies]\nif weights is not None and len(weights) != len(ranks):\n    raise ValueError(f\"{len(weights)} weights for {len(ranks)} ranks; must match\")\nrrf = Rrf(ranks=ranks, weights=weights, k=60)","typeGuard":"def weights_match_ranks(weights, ranks) -> bool:\n    return weights is None or (isinstance(weights, (list, tuple)) and len(weights) == len(ranks))","tryCatchPattern":"try:\n    plan = rrf.to_dict()\nexcept ValueError as e:\n    raise ValueError(\n        f\"invalid RRF configuration: {len(rrf.weights or [])} weights vs \"\n        f\"{len(rrf.ranks)} ranks\"\n    ) from e","preventionTips":["Derive weights from the same strategy list that builds ranks, or omit weights for equal weighting","When adding a retrieval strategy, update the weights config in the same change","Assert length equality at config-load time so failures happen at startup, not at query time"],"tags":["chromadb","rrf","rank-expression","validation","configuration","hybrid-search"],"backgroundTag":"rrf-invalid-parameters","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}