{"record":{"id":"97d386e825cc39a3","repo":"chroma-core/chroma","slug":"expected-dict-for-rank-got-type-data-name","errorCode":null,"errorMessage":"Expected dict for Rank, got {type(data).__name__}","messagePattern":"Expected dict for Rank, got (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"chromadb/execution/expression/operator.py","lineNumber":671,"sourceCode":"    @staticmethod\n    def from_dict(data: Dict[str, Any]) -> \"Rank\":\n        \"\"\"Create Rank expression from dictionary.\n\n        Supports operators:\n        - {\"$val\": number} -> Val(number)\n        - {\"$knn\": {...}} -> Knn(...)\n        - {\"$sum\": [ranks]} -> rank1 + rank2 + ...\n        - {\"$sub\": {\"left\": ..., \"right\": ...}} -> left - right\n        - {\"$mul\": [ranks]} -> rank1 * rank2 * ...\n        - {\"$div\": {\"left\": ..., \"right\": ...}} -> left / right\n        - {\"$abs\": rank} -> abs(rank)\n        - {\"$exp\": rank} -> rank.exp()\n        - {\"$log\": rank} -> rank.log()\n        - {\"$max\": [ranks]} -> rank1.max(rank2).max(rank3)...\n        - {\"$min\": [ranks]} -> rank1.min(rank2).min(rank3)...\n        \"\"\"\n        if not isinstance(data, dict):\n            raise TypeError(f\"Expected dict for Rank, got {type(data).__name__}\")\n\n        if not data:\n            raise ValueError(\"Rank dict cannot be empty\")\n\n        if len(data) != 1:\n            raise ValueError(\n                f\"Rank dict must contain exactly one operator, got {len(data)}\"\n            )\n\n        op = next(iter(data.keys()))\n\n        if op == \"$val\":\n            value = data[\"$val\"]\n            if not isinstance(value, (int, float)):\n                raise TypeError(f\"$val requires a number, got {type(value).__name__}\")\n            return Val(value)\n\n        elif op == \"$knn\":","sourceCodeStart":653,"sourceCodeEnd":689,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/chromadb/execution/expression/operator.py#L653-L689","documentation":"Rank.from_dict is the recursive parser for rank expressions ($val, $knn, $sum, ...) and requires every node, including nested operands, to be a dict with exactly one operator key. This TypeError fires when a non-dict reaches the parser - most often a bare number inside a list operand, because from_dict recurses via [Rank.from_dict(r) for r in ranks_data] (operator.py:762). Note Search(rank=0.5) is rejected earlier by plan.py:114 with a different message; primitives must be wrapped as {'$val': ...}.","triggerScenarios":"Rank.from_dict(0.5) or Rank.from_dict('$val') directly; {'$sum': [0.5, 0.3]} where operands recurse into Rank.from_dict(0.5); {'$abs': -0.5}; serialized rank trees where a node decoded to a scalar.","commonSituations":"Hand-writing rank expressions with bare numeric operands; converting formula code (a + b) into dicts without wrapping constants; machine-generated trees that collapse single-operator dicts to their value.","solutions":["Wrap every constant operand: {'$val': 0.5} instead of 0.5.","Use the class API where composition is natural: Search(rank=Val(0.5) + Knn(query=[...]))) - arithmetic operators are overloaded.","If trees arrive serialized, validate each node is a dict before parsing."],"exampleFix":"# before\nSearch(rank={'$sum': [0.5, 0.3]})        # -> TypeError: got float\n\n# after\nSearch(rank={'$sum': [{'$val': 0.5}, {'$val': 0.3}]})\n# or use the classes\nfrom chromadb.execution.expression.operator import Knn, Val\nSearch(rank=Val(0.5) + Val(0.3))","handlingStrategy":"type-guard","validationCode":"def wrap_constants(node):\n    '''Recursively wrap bare numbers as {'$val': n} so Rank.from_dict accepts them.'''\n    if isinstance(node, (int, float)) and not isinstance(node, bool):\n        return {'$val': node}\n    if isinstance(node, list):\n        return [wrap_constants(n) for n in node]\n    if isinstance(node, dict):\n        return {k: wrap_constants(v) for k, v in node.items()}\n    return node\n\nSearch(rank=wrap_constants(rank_cfg))","typeGuard":"def is_rank_node(node) -> bool:\n    return (\n        isinstance(node, dict)\n        and len(node) == 1\n        and next(iter(node), '').startswith('$')\n    )","tryCatchPattern":"try:\n    Search(rank=rank_cfg)\nexcept (TypeError, ValueError) as e:\n    raise ValueError(f'invalid rank expression: {e}') from e","preventionTips":["Prefer Knn/Val objects with overloaded +, -, *, / over hand-built dicts.","Every rank operand is itself a rank dict - constants need {'$val': ...}.","Add a fixture test for each rank tree you ship; the recursion makes mistakes easy."],"tags":["validation","typeerror","rank","expression","chromadb"],"backgroundTag":"type-validation-failed","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}