{"record":{"id":"498be6690360890f","repo":"chroma-core/chroma","slug":"val-requires-a-number-got-type-value-name","errorCode":null,"errorMessage":"$val requires a number, got {type(value).__name__}","messagePattern":"\\$val requires a number, got (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"chromadb/execution/expression/operator.py","lineNumber":686,"sourceCode":"        - {\"$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\":\n            knn_data = data[\"$knn\"]\n            if not isinstance(knn_data, dict):\n                raise TypeError(f\"$knn requires a dict, got {type(knn_data).__name__}\")\n\n            if \"query\" not in knn_data:\n                raise ValueError(\"$knn requires 'query' field\")\n\n            query = knn_data[\"query\"]\n\n            if isinstance(query, dict):\n                # SparseVector case - deserialize from transport format\n                if query.get(TYPE_KEY) == SPARSE_VECTOR_TYPE_VALUE:\n                    query = SparseVector.from_dict(query)\n                else:\n                    # Old format or invalid - try to construct directly","sourceCodeStart":668,"sourceCodeEnd":704,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/chromadb/execution/expression/operator.py#L668-L704","documentation":"The $val operator holds a constant rank score and its value must be an int or float (bools technically pass since bool subclasses int). Strings like '0.5', None, lists, or nested dicts raise TypeError because there is no coercion in the parser.","triggerScenarios":"Search(rank={'$val': '0.5'}); {'$val': None}; {'$val': [0.5]}; configs storing weights as strings ('weight': '1.5').","commonSituations":"YAML/env-var configs where numbers stay strings; JSON with quoted numerics; optional weights defaulting to None instead of being omitted from the expression.","solutions":["Use a bare number: {'$val': 0.5}.","Cast known-clean strings at the boundary: {'$val': float(w)}.","If the weight is optional, omit the $val term entirely rather than sending None."],"exampleFix":"# before\nSearch(rank={'$val': boost_str})   # boost_str == '1.5' -> TypeError\n\n# after\nSearch(rank={'$val': float(boost_str)})","handlingStrategy":"validation","validationCode":"weight = request.get('weight')\nrank = {'$val': float(weight)} if weight is not None else None\nSearch(rank=rank)","typeGuard":"def is_numeric_val(node) -> bool:\n    return (\n        isinstance(node, dict)\n        and set(node) == {'$val'}\n        and isinstance(node['$val'], (int, float))\n        and not isinstance(node['$val'], bool)\n    )","tryCatchPattern":null,"preventionTips":["Type config weights as float in your schema layer.","Skip optional weights rather than passing None.","Beware that True/False pass the library's check as 1/0 - never send flags as $val."],"tags":["validation","typeerror","rank","val","chromadb"],"backgroundTag":"type-validation-failed","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}