bytedance/deer-flow · error · ValueError

retrieval category filter must be a string

Error message

retrieval category filter must be a string

What it means

Raised by the FTS5 retrieval path in deermem when a caller passes a filters dict whose 'category' value is present but not a string. The engine's SQL search binds category as a text parameter, so non-string values (int, list, dict, bool) are rejected before querying. This is an input-contract error on the public search/retrieve API surface.

Source

Thrown at backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/retrieval.py:626

    def search(
        self,
        query: str,
        *,
        scopes: list[dict[str, str | None]],
        top_k: int,
        mode: str,
        filters: dict[str, Any] | None,
    ) -> list[dict[str, Any]]:
        if not query.strip() or top_k <= 0:
            return []
        if mode not in {"hybrid", "fts5", "lexical"}:
            raise ValueError(f"unsupported FTS5 retrieval mode: {mode}")

        filters = filters or {}
        category = filters.get("category")
        if category is not None and not isinstance(category, str):
            raise ValueError("retrieval category filter must be a string")

        results: list[dict[str, Any]] = []
        per_scope_limit = top_k * 4
        for scope in scopes:
            scope_user, scope_agent = _scope_key(scope)
            for candidate in self._engine.search(
                query,
                scope_user=scope_user,
                scope_agent=scope_agent,
                category=category,
                top_k=per_scope_limit,
            ):
                fact = dict(candidate)
                score = float(fact.pop("score", 0.0))
                bm25_score = float(fact.pop("bm25_score", 0.0))
                if any(fact.get(key) != value for key, value in filters.items()):
                    continue
                results.append(

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Coerce or reject the category before calling search: use a single string, e.g. filters={'category': str(value)} only after confirming it is scalar.
  2. If the caller has a list of categories, issue one search per category and merge/limit results, since the backend accepts exactly one.
  3. Validate the whole filters dict against the documented schema ({category?: str}) at the API boundary and return a 4xx to the client instead of letting the backend raise.

Example fix

// before
results = memory.search(query="user preferences", top_k=5, mode="hybrid", filters={"category": ["preference", "context"]})
# after (python)
results = []
for category in ["preference", "context"]:
    results.extend(memory.search(query="user preferences", top_k=5, mode="hybrid", filters={"category": category}))
Defensive patterns

Strategy: validation

Validate before calling

category = (filters or {}).get("category")
if category is not None and not isinstance(category, str):
    if isinstance(category, (list, tuple)):
        raise TypeError("run one search per category; backend accepts a single string")
    category = str(category)
filters = {"category": category} if category is not None else {}

Type guard

from typing import Any

def is_valid_category_filter(filters: dict[str, Any] | None) -> bool:
    category = (filters or {}).get("category")
    return category is None or isinstance(category, str)

Try / catch

try:
    results = backend.search(query, top_k=5, mode="hybrid", filters=filters)
except ValueError as exc:
    if "category filter" in str(exc):
        raise  # fix the filter shape upstream; surface 4xx to the client
    raise

Prevention

When it happens

Trigger: Calling memory search/retrieve with filters={'category': 5}, filters={'category': ['preference']}, or filters={'category': True}. Any code that forwards untyped user or LLM tool output straight into the filters argument of the retrieval backend hits this immediately.

Common situations: LLM tool-calls that emit JSON numbers or arrays where the schema expects a single string; frontends forwarding a multi-select category picker as a list; refactors that changed the filter value type without updating callers.

Related errors


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/dad76dc2a409488c. Report an issue: GitHub.