bytedance/deer-flow · error · ValueError

retrieval fact.content must be a non-empty string

Error message

retrieval fact.content must be a non-empty string

What it means

ValueError from FTS5RetrievalAdapter._document: a fact's 'content' is missing, not a string, empty, or whitespace-only. Content is what FTS5 indexes, so a contentless fact is unindexable and rejected rather than stored as a dead row.

Source

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

    document id prevents equal fact ids in two user/agent scopes from
    overwriting each other.
    """

    def __init__(self, db_path: str | Path = ":memory:") -> None:
        self._engine = FTS5Retrieval(db_path)

    @staticmethod
    def _document_id(fact_id: str, scope: dict[str, str | None]) -> str:
        scope_user, scope_agent = _scope_key(scope)
        return json.dumps([scope_user, scope_agent, fact_id], ensure_ascii=False, separators=(",", ":"))

    def _document(self, fact: dict[str, Any], scope: dict[str, str | None]) -> dict[str, Any]:
        fact_id = fact.get("id")
        content = fact.get("content")
        if not isinstance(fact_id, str) or not fact_id:
            raise ValueError("retrieval fact.id must be a non-empty string")
        if not isinstance(content, str) or not content.strip():
            raise ValueError("retrieval fact.content must be a non-empty string")
        scope_user, scope_agent = _scope_key(scope)
        payload = dict(fact)
        payload["scope"] = {"userId": scope.get("userId"), "agentName": scope.get("agentName")}
        source = payload.get("source")
        return {
            "fact_id": self._document_id(fact_id, scope),
            "content": content,
            "category": str(payload.get("category") or "context"),
            "confidence": float(payload.get("confidence") or 0.5),
            "created_at": payload.get("createdAt") if isinstance(payload.get("createdAt"), str) else None,
            "scope_user": scope_user,
            "scope_agent": scope_agent,
            "source": source if isinstance(source, str) else json.dumps(source, ensure_ascii=False, default=str),
            "fact_data": payload,
        }

    def upsert(self, fact: dict[str, Any], *, scope: dict[str, str | None], path: str) -> None:
        del path  # Canonical location belongs to storage; the index is rebuildable.

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Skip facts with blank content before enqueueing for indexing (they carry nothing searchable).
  2. If content is structured, serialize it to a meaningful text representation first.
  3. Fix the extractor so it never emits empty-content facts (guard at the source).

Example fix

# before
for fact in facts:
    adapter.upsert(fact, scope)  # raises on blank content

# after
for fact in facts:
    if isinstance(fact.get("content"), str) and fact["content"].strip():
        adapter.upsert(fact, scope)
Defensive patterns

Strategy: validation

Validate before calling

def has_searchable_content(fact) -> bool:
    c = fact.get('content') if isinstance(fact, dict) else None
    return isinstance(c, str) and bool(c.strip())

Type guard

def has_searchable_content(fact: dict) -> bool:
    c = fact.get('content')
    return isinstance(c, str) and c.strip() != ''

Try / catch

try:
    adapter.upsert(fact, scope)
except ValueError as e:
    if 'fact.content' in str(e):
        logger.warning('skipping contentless fact id=%s', fact.get('id'))
        continue
    raise

Prevention

When it happens

Trigger: Indexing a fact dict whose content is None, '', ' ', or a non-str value (e.g. a dict/list payload put under 'content').

Common situations: Fact extraction emitting empty content for structured events; content field holding structured data instead of text; upstream summarization produced an empty string; whitespace-only content from a template render that substituted nothing.

Related errors


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