mem0ai/mem0 · error · ValueError
Filter value for {key!r} must be str, int, float, or bool, g
Error message
Filter value for {key!r} must be str, int, float, or bool, got {type(value).__name__} What it means
OpenSearch filter values become term query values, which must be scalars (str, int, float, bool). This error fires when a filter value is a list, dict, None-with-type or other object — the backend has no operator syntax (no $in/$gt), so non-scalars cannot be expressed and might inject query DSL.
Source
Thrown at mem0/vector_stores/opensearch.py:26
except ImportError:
raise ImportError("OpenSearch requires extra dependencies. Install with `pip install opensearch-py`") from None
from pydantic import BaseModel
from mem0.configs.vector_stores.opensearch import OpenSearchConfig
from mem0.vector_stores.base import VectorStoreBase
logger = logging.getLogger(__name__)
_SAFE_FILTER_KEY = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_.]*$")
_IDENTITY_FILTER_KEYS = ("user_id", "agent_id", "run_id")
def _validate_filter(key: str, value) -> None:
if not isinstance(key, str) or not _SAFE_FILTER_KEY.match(key):
raise ValueError(f"Invalid filter key: {key!r}")
if not isinstance(value, (str, int, float, bool)):
raise ValueError(
f"Filter value for {key!r} must be str, int, float, or bool, "
f"got {type(value).__name__}"
)
def _build_filter_clauses(filters):
"""Build term clauses from every filter key, not just the identity keys."""
filter_clauses = []
for key, value in (filters or {}).items():
if value is None:
continue
if value == "*":
# "Any value" wildcard (a documented Platform pattern): match
# documents where the field exists — as opensearch.ts already
# does for every key — instead of a literal, near-always-empty
# term match on the string "*".
_validate_filter(key, value)
filter_clauses.append({"exists": {"field": f"payload.{key}"}})View on GitHub (pinned to 001c235229)
Solutions
- Use scalar equality only; for multi-value, issue one search per value and merge
- For range/wildcard needs, query OpenSearch directly with your own DSL instead of the filters parameter
- Validate filters shape before calling (see guard below)
Example fix
// before
filters = {"user_id": ["a", "b"], "ts": {"$gte": 100}}
// after
results = [r for v in ["a", "b"] for r in store.search(q, vec, k, {"user_id": v})] Defensive patterns
Strategy: type-guard
Validate before calling
def assert_scalar(filters: dict) -> None:
bad = [k for k, v in (filters or {}).items() if not isinstance(v, (str, int, float, bool))]
if bad:
raise TypeError(f"non-scalar filter values for {bad}")
assert_scalar(filters)
store.search(query, vector, top_k, filters=filters) Type guard
def is_scalar_filters(filters: dict) -> bool:
return all(isinstance(v, (str, int, float, bool)) for v in (filters or {}).values()) Try / catch
try:
store.search(q, vec, filters=filters)
except ValueError as e:
if "must be str, int, float, or bool" in str(e):
filters = {k: v for k, v in filters.items() if isinstance(v, (str, int, float, bool))}
store.search(q, vec, filters=filters)
else:
raise Prevention
- Term-equality only on this backend; fan out lists client-side
- Type your public filter contract as scalars
- Use direct OpenSearch DSL for range/wildcard needs
When it happens
Trigger: filters={"user_id": ["a","b"]}, {"range": {"gte": 1}}, or a nested object passed as a filter value to the OpenSearch backend's search.
Common situations: Copying Qdrant/Mongo-style structured filters to OpenSearch; hoping for range/in semantics that this term-only builder does not support; forwarding untyped request bodies.
Related errors
- Filter value for '${key}' must be a string, number, or boole
- Invalid filter key: {key!r}
- Filter list for '${key}' contains an object, which may conta
- Filter value for '${key}' must be a scalar (string, number,
- ${key} filter value must be an array.
AI-assisted analysis of mem0ai/mem0@001c235229 (2026-08-15).
Data as JSON: /api/errors/fd22db32b2201fc2.
Report an issue: GitHub.