Skip to content

Hybrid product search: RRF fuse BM25(title) + CLIP vector #2

Description

@hev

Goal

Combine BM25 lexical search over product titles with the existing CLIP vector search; fuse with Reciprocal Rank Fusion (RRF). Improves recall for queries that name a product literally ("bose qc45") while keeping the vibes-based vector recall.

Turbopuffer schema change

Online schema patch — no namespace recreate, no row backfill (`title` is already a written attribute, see `indexer/app/records.py:194`):

```http
POST /v2/namespaces/amazon-products
{
"schema": {
"title": {"type": "string", "full_text_search": true}
}
}
```

Tpuff builds the BM25 index in the background after the schema flip.

Hybrid query (native, one round-trip)

```http
POST /v2/namespaces/amazon-products/query
{
"queries": [
{"rank_by": ["vector", "ANN", [...768 floats...]], "top_k": 40, "include_attributes": ["asin","title","image_url"]},
{"rank_by": ["title", "BM25", ""], "top_k": 40, "include_attributes": ["asin","title","image_url"]}
]
}
```

`layer-gateway` is a pass-through for full Tpuff functionality, so no gateway-side work.

RRF (client-side in indexer)

Standard formula, k=60:

```
score(doc) = Σ_i 1 / (60 + rank_i(doc))
```

Per-side fetch `top_k * 2` so RRF has overlap to work with. Dedupe by `asin`.

Files

  • `indexer/app/layer_client.py` — new `hybrid_query_namespace(namespace, vector, text, top_k)` using the `queries` array. Existing `query_namespace` stays for review search (vector-only).
  • `indexer/app/main.py` — `/search` gains a `mode=vector|hybrid` query param (default `hybrid` once verified). Vector-only stays available for A/B and rollback.
  • `indexer/tests/` — RRF unit test (small synthetic ranking, verify fusion math + dedup).
  • One-off: POST the schema patch to amazon-products (script or kubectl exec).

Acceptance

  • `curl -s -X POST -d '{"query":"bose qc45","top_k":5,"mode":"hybrid"}' https://api.hev-shop.com/search\` returns the literal product, where `mode=vector` did not.
  • Vibes queries ("cozy reading corner") still return reasonable vector-driven results under `mode=hybrid`.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions