Skip to content

Research: Streamlining Search (FTS vs Hybrid Embeddings) #79

Description

@onmax

Context

While evaluating whether we can retire the OpenAI-driven semantic layer, we catalogued the current search stack and identified places where plain PostgreSQL full-text search (FTS) could cover most use cases with less complexity.

Current State

  • Text path already indexes name, street, city, and country on the fly via to_tsvector('english', …).
  • Semantic path calls OpenAI text-embedding-3-small, stores vectors in pgvector, and uses cosine similarity + category fallbacks before re-querying Postgres.
  • Autocomplete relies exclusively on the text path; embeddings are only precomputed in the background.
  • SIMILARITY_THRESHOLD is intentionally low (0.3) to catch looser matches, which increases recall but also noise and cost.

Pain Points

  • External dependency, latency, and cost from embedding generation + caching plumbing.
  • Complex control flow (parallel queries, deduping, keyword fallback) increases maintenance overhead.
  • Hard to reason about relevance changes because we lack deterministic benchmarks (tracked separately in Add offline embedding regression tests #65).

Simplification Opportunities

  • Persist a generated tsvector column on locations with a GIN index instead of rebuilding vectors per request.
  • Swap to_tsquery sanitisation for websearch_to_tsquery to better handle natural language and punctuation.
  • Add weighting + ts_rank_cd so name hits outrank address-only matches.
  • Maintain a lightweight synonym table or expand KEYWORD_FALLBACKS for domain-specific equivalences (espresso ↔ café, btc ↔ crypto, etc.).
  • Localise FTS dictionaries when we need non-English stemming instead of relying on embeddings for language coverage.

UI & UX Ideas

  • Surface direct category pills inside autocomplete when the query matches a known category ID, so users can jump straight to that filter.
  • Optionally show the category icon in text results using the existing icon select field from searchLocationsByCategories.

Recommended Experiments

  1. FTS-only baseline: disable the semantic branch behind a flag and record coverage/latency deltas on a copy of production data.
  2. Hybrid vs FTS A/B: run both paths for a week, log result counts, first-result relevance, and latency; feed logs into the harness from Add offline embedding regression tests #65.
  3. Indexing impact: benchmark response times before/after adding the stored tsvector column + GIN index.
  4. Synonym table trial: curate 20–30 high-value synonyms and compare recall against the embedding path for the same queries.

Next Steps

  • Implement the benchmarking harness from Add offline embedding regression tests #65 to give us hard numbers.
  • Prioritise FTS indexing + ranking tweaks (likely quickest wins).
  • Decide whether to keep embeddings purely as a fallback for non-English/ambiguous intent once we have baseline metrics.

Open Questions

  • Do we have a budget/latency target that would automatically justify dropping embeddings if achieved by FTS tweaks?
  • How many non-English queries are we receiving today, and can FTS dictionaries cover them adequately?

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions