{"record":{"id":"c0a56c56e0ac5dba","repo":"RyanCodrai/turbovec","slug":"filter-must-be-a-dict-of-metadata-key-value-pairs","errorCode":null,"errorMessage":"filter must be a dict of metadata key/value pairs or a callable taking a Document, got {type(filter).__name__}","messagePattern":"filter must be a dict of metadata key/value pairs or a callable taking a Document, got (.+?)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"turbovec-python/python/turbovec/langchain.py","lineNumber":681,"sourceCode":"        if isinstance(filter, dict):\n            items = list(filter.items())\n            # Key presence is required (#381). `dict.get` returns None both\n            # for \"absent\" and for \"present and None\", so the old\n            # `doc.metadata.get(k) == v` form let a document with no `k` at\n            # all satisfy `filter={\"k\": None}`. The reference\n            # InMemoryVectorStore accepts *only* callables, so nothing\n            # upstream fixes the dict form's meaning — but the dict form is\n            # sugar for the callable a user would otherwise write, and\n            # nobody writes `lambda d: d.metadata.get(\"k\") is None` meaning\n            # \"documents without k\". Matching an absent key also can't be\n            # asked for any other way, whereas \"has k, and it's None\" can't\n            # be expressed at all under the loose form. This is the same\n            # leak Agno's `_meta_matches` fixed in #144; the two dict\n            # filters now agree.\n            return lambda doc: all(\n                k in doc.metadata and doc.metadata[k] == v for k, v in items\n            )\n        raise TypeError(\n            \"filter must be a dict of metadata key/value pairs or a callable \"\n            f\"taking a Document, got {type(filter).__name__}\"\n        )\n\n    # ---- Max marginal relevance ---------------------------------------\n    #\n    # MMR requires the full-precision vector of every candidate to compute\n    # pairwise diversity scores. turbovec discards full vectors after\n    # quantization (that's the point), so we can't faithfully implement\n    # MMR. Raise loudly with a useful message rather than silently fall\n    # back to the base class's bare NotImplementedError.\n\n    _MMR_MSG = (\n        \"TurboQuantVectorStore does not support max-marginal-relevance \"\n        \"search because the underlying quantized index discards \"\n        \"full-precision vectors after compression. MMR requires the \"\n        \"original embedding for every candidate to compute pairwise \"\n        \"diversity. Use `similarity_search` / `similarity_search_with_score` \"","sourceCodeStart":663,"sourceCodeEnd":699,"githubUrl":"https://github.com/RyanCodrai/turbovec/blob/ccab9f325e6ce2a270a87daf01ae4e443bcf2d49/turbovec-python/python/turbovec/langchain.py#L663-L699","documentation":"TypeError raised in _compile_filter (used by _search_vector) when the filter is neither a dict of metadata key/value pairs nor a callable taking a Document — e.g. a string or list. Only these two forms are compiled into the predicate applied to each candidate document.","triggerScenarios":"Thrown at turbovec-python/python/turbovec/langchain.py:681 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Pass a dict like {'source': 'web'} or a predicate lambda doc: doc.metadata['k'] == v.","Wrap richer filter expressions into a callable taking the Document.","Catch the TypeError in search code to reject malformed filters with a clear message."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"ccab9f325e6ce2a270a87daf01ae4e443bcf2d49","analyzedAt":"2026-09-06T08:39:18.516Z","contentChangedAt":"2026-09-06T08:39:18.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}