{"record":{"id":"ff24a9ce659af70c","repo":"chroma-core/chroma","slug":"rrf-requires-at-least-one-rank","errorCode":null,"errorMessage":"RRF requires at least one rank","messagePattern":"RRF requires at least one rank","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"chromadb/execution/expression/operator.py","lineNumber":1204,"sourceCode":"            normalize=True,\n            k=100\n        )\n    \"\"\"\n\n    ranks: List[Rank]\n    k: int = 60\n    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)","sourceCodeStart":1186,"sourceCodeEnd":1222,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/chromadb/execution/expression/operator.py#L1186-L1222","documentation":"Rrf (Reciprocal Rank Fusion) fuses several ranking strategies by building -sum(weight_i / (k + rank_i)), and its validation lives in to_dict() — so an Rrf(ranks=[]) object constructs fine but raises this ValueError the moment the query is serialized or executed. At least one rank expression is required because the fused sum indexes terms[0].","triggerScenarios":"Rrf(ranks=[], k=60) followed by .to_dict(), or passing such an Rrf into a query — typically because the ranks list is built from a dynamic set of searches that came back empty (e.g. all optional retrieval strategies disabled or filtered out).","commonSituations":"Configurable hybrid search where every retrieval strategy was toggled off; ranks assembled from per-tenant or per-request config that yields an empty list; refactoring that moves list construction after Rrf creation.","solutions":["Pass at least one rank expression, e.g. Rrf(ranks=[Knn(query=..., return_rank=True)])","Guard at construction time: if not ranks: fall back to a plain (non-RRF) query instead of building Rrf","Validate before serializing: raise early with your own message if len(ranks) == 0","Check config/logic that populates ranks so it cannot silently produce an empty list"],"exampleFix":"# before\nrrf = Rrf(ranks=[], k=60)\nquery_plan = rrf.to_dict()  # ValueError here\n\n# after\nif not strategies:\n    raise ValueError(\"enable at least one retrieval strategy\")\nrrf = Rrf(\n    ranks=[Knn(query=s.query, key=s.key, return_rank=True) for s in strategies],\n    k=60,\n)","handlingStrategy":"validation","validationCode":"if not ranks:\n    raise ValueError(\"RRF needs at least one rank; enable a retrieval strategy\")\nrrf = Rrf(ranks=[Knn(query=r.query, key=r.key, return_rank=True) for r in ranks], k=60)","typeGuard":"def has_rrf_ranks(ranks) -> bool:\n    return isinstance(ranks, (list, tuple)) and len(ranks) >= 1","tryCatchPattern":"try:\n    plan = rrf.to_dict()\nexcept ValueError as e:\n    raise ValueError(f\"invalid RRF configuration (ranks={len(rrf.ranks)}, k={rrf.k}): {e}\") from e","preventionTips":["Remember RRF validates at to_dict()/query time, not at construction — check config before you build the query","Ensure at least one retrieval strategy is enabled when config is dynamic","Set return_rank=True on Knn ranks fed to RRF so they produce rank outputs"],"tags":["chromadb","rrf","rank-expression","validation","hybrid-search"],"backgroundTag":"rrf-invalid-parameters","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}