Search Module
Public Summary
Dual-mode AI-powered search: hybrid vector+keyword search for products and smart recipe-based search with geolocation and budget calculation.
Internal Details
Files
| File | Role |
|---|---|
search.controller.js | Hybrid search HTTP handler |
search.service.js | Hybrid search logic (vector + keyword + RRF) |
search.routes.js | Search route definitions |
smart-search.controller.js | Smart/recipe search HTTP handler |
smart-search.service.js | Recipe decomposition + geolocation logic |
smart-search.routes.js | Smart search route definitions |
embedding.service.js | Gemini embedding generation |
product-embedding-sync.service.js | Incremental embedding sync |
intent-parser.service.js | Gemini intent/query parsing |
product-embedding.model.js | Embedding vector storage |
product-embedding.repository.js | Embedding data access |
Hybrid Search (SearchService)
Endpoint: GET /search?q=...&marketId=...
- Intent parsing via Gemini (with a lighter fallback model) — detects search terms, price preference, language.
- Parallel execution of vector search (cosine similarity on embeddings) and keyword search (MongoDB text/regex).
- RRF merge with k=60 to combine ranked results.
- Market scoping optimization when
marketIdis provided.
Smart Search (SmartSearchService)
Endpoints:
POST /smart-search— Recipe search with geolocation, body:{ q, lat, lon, budgetOnly }GET /smart-search/budget— Weekly budget calculation
Embedding Service
- Model: Gemini text embeddings (768 dimensions); exact model id in
embedding.service.js(MODELconstant). - Task-type-aware prompts: the raw text is wrapped differently depending on whether it's being embedded as a document (
title: ... | text: ...) or a query (task: search result | query: ...), matching Gemini's retrieval-tuning convention for asymmetric search. - Batch processing: rate-limited at 350ms between batches.
- Retry: exponential backoff on rate-limit and transient network errors (timeouts, DNS failures, connection resets).
Embedding Sync
- Uses a Mongoose cursor to stream products instead of bulk-loading all documents into memory.
- Loads only
productandtextHashfields from existing embeddings (skips 768-dim vectors) to build the diff map. - Only products with changed or missing text hashes are sent for embedding generation.
Intent Parser Service
- Model: Gemini, with a lighter fallback model if the primary is unavailable; exact model ids in
intent-parser.service.js(MODELSconstant). - Bilingual: Cyrillic + Latin Macedonian
- Detects: recipe vs. product search, price preference (asc/desc), ingredient decomposition
- Cache: 5-minute local cache, 500 entry max
Feature Flag Gates
| Flag | Controls |
|---|---|
ai-search | Enables hybrid vector+keyword search |
smart-search | Enables recipe decomposition search |
When flags are disabled, search falls back to basic keyword-only behavior.
Source Anchors
| Path | Relevance |
|---|---|
apps/server/src/modules/search/ | All search services, controllers, routes, models |
Failure Modes
| Failure | Behavior |
|---|---|
| Gemini API unavailable | Graceful degradation to keyword-only search |
| Embedding generation fails | Products searchable via keyword only |
| Intent parsing fails | Raw query used as-is |
| Rate limit on embedding API | Exponential backoff retry |