headroomlabs-ai/headroom · error · ValueError

query_text provided but HNSWVectorIndex does not embed text.

Error message

query_text provided but HNSWVectorIndex does not embed text. Provide query_vector directly or use an Embedder first.

What it means

Raised by HNSWVectorIndex.search when VectorFilter.query_vector is None but query_text is set. HNSWVectorIndex is a pure vector index with no built-in text embedding, so it cannot convert text to a vector; the caller must embed the text first or pass a query_vector.

Source

Thrown at headroom/memory/adapters/hnsw.py:591

        return removed_count

    async def search(self, filter: VectorFilter) -> list[VectorSearchResult]:
        """Search for similar memories using vector similarity.

        Args:
            filter: Vector search filter with query and constraints.

        Returns:
            List of search results sorted by similarity (descending).

        Raises:
            ValueError: If neither query_vector nor query_text is provided,
                       or if query_text is provided (embedding must be done externally).
        """
        if filter.query_vector is None:
            if filter.query_text is not None:
                raise ValueError(
                    "query_text provided but HNSWVectorIndex does not embed text. "
                    "Provide query_vector directly or use an Embedder first."
                )
            raise ValueError("Either query_vector or query_text must be provided")

        query_vector = np.asarray(filter.query_vector, dtype=np.float32)
        if query_vector.shape[0] != self._dimension:
            raise ValueError(
                f"Query vector dimension {query_vector.shape[0]} does not match "
                f"index dimension {self._dimension}"
            )

        with self._lock:
            # NOTE: Use len() directly, not self.size - Lock is not reentrant!
            current_size = len(self._memory_to_hnsw)
            if current_size == 0:
                return []

View on GitHub (pinned to 322425c43b)

Solutions

  1. Embed the text first: vec = await embedder.embed(filter.query_text), then search with VectorFilter(query_vector=vec).
  2. Use a facade/service that pairs an Embedder with the index and accepts text queries.
  3. If you never need text search, ensure only query_vector is ever set on the filter.

Example fix

// before
results = await index.search(VectorFilter(query_text="hello"))

// after
vec = await embedder.embed("hello")
results = await index.search(VectorFilter(query_vector=vec))
Defensive patterns

Strategy: validation

Validate before calling

if filter.query_vector is None and filter.query_text is not None:
    filter.query_vector = await embedder.embed(filter.query_text)
    filter.query_text = None
results = await index.search(filter)

Prevention

When it happens

Trigger: Calling search(VectorFilter(query_text="...")) directly on HNSWVectorIndex instead of going through a higher-level component that owns an Embedder; porting code from an index that did support text queries.

Common situations: Assuming all VectorIndex implementations embed text; wiring a raw index into a search path without an embedding step; misreading the filter schema.

Related errors


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/0b3d0ed0e9df3ea3. Report an issue: GitHub.