Four search modes. One smart router.
Skryx picks the right backend per query — keyword for SKUs, semantic for questions, hybrid for the messy middle — and merges results with Reciprocal Rank Fusion so you never pay for AI you don't need.
A heuristic that knows when not to use AI.
Every search through Skryx hits SearchModeRouter. It runs ten
cheap checks (in order) and picks the cheapest mode that can answer the
query. Most importantly: it knows when keyword is enough, so you don't pay
for an embedding call on "iPhone 15 Pro 256GB".
// SearchModeRouter::decide() — actual order of checks 1. plan quota exhausted → keyword 2. caller passed search_mode=… → respect it 3. q is empty or "*" → keyword 4. q is "quoted" → keyword (cost saver) 5. q matches SKU regex → keyword /^[A-Za-z]{2,5}[\-_]?\d{3,9}…/ 6. q is one word → keyword 7. q is brand+model (2–3 words, last token has 2+ digits) → keyword 8. q is two words → hybrid 9. q ends with "?" or contains how/why/cum/care nu/best … → semantic 10. q is 6+ words → semantic default (3–5 words) → hybrid // AFTER keyword leg of a hybrid query: // if keyword returned 5–30 hits, skip the vector call → keyword only
Each one is overridable per query.
The classic engine path, tuned for precision.
Pure full-text search through the Skryx engine. Typo tolerance is on
(per-index num_typos, default 2 with a length floor),
prefix matching is on by default, stop-words are stripped per index,
and ranking rules + custom-ranking tiebreakers all apply.
- Cheapest mode — no embeddings, no LLM calls
- Wins on SKUs, brand names, exact titles
- Default for free / Starter tenants and any tenant without a semantic quota
Vector-only search for natural-language queries.
Skryx embeds the query (cached 3 ways), runs an HNSW nearest-neighbour lookup against per-document vectors, and returns the closest matches by cosine distance. Results below the per-index confidence threshold are dropped (default 0.65 for main hits, 0.50 for related).
- Used for questions, problem queries, and any 6+ word phrase
- Document text = title × 3 + brand + category + description (capped)
- Embedding field is always stripped from API responses (~14 KB / hit saved)
{
"q": "phone won't charge",
"search_mode": "semantic",
// engine returns
"hits": [
{ "vector_distance": 0.42,
"document": "USB-C charging cable" },
{ "vector_distance": 0.48,
"document": "Wireless charger" }
]
}
Keyword + vector, merged via Reciprocal Rank Fusion.
Runs both legs (keyword first, then vector if needed), then merges the two
rank lists with the standard RRF formula
score = 1/(60 + rankk) + 1/(60 + rankv).
Documents that appear in both lists are boosted (sum of contributions);
pure-semantic matches that pass the confidence threshold ride along.
- RRF constant
k = 60— equal weight, no per-leg tuning required - Confidence-tier split: main hits + a separate
related_hitsarray (similarity 0.50–0.65) - Smart skip: if the keyword leg already returns 5–30 hits, the vector call is skipped (saves an embedding)
{
"hits": [
{ "match_type": "hybrid", // in both lists
"document": { "id": "sku-001" } },
{ "match_type": "keyword",
"document": { "id": "sku-002" } },
{ "match_type": "semantic",
"document": { "id": "sku-003" } }
],
"related_hits": [ /* similarity 0.50–0.65 */ ]
}
The router picks for you, per query.
The default mode for every index. You can override per query by passing
search_mode, or change the index default in Search Settings.
Tenants who don't think about it get the right behaviour automatically;
tenants who want control have full control.
- Set per-index in
search_mode_default - Override per query via
?search_mode=or POST body - Override always wins (rule #2 in the router)
An optional rewrite step for vague queries.
Before the engine runs, queries that look uncertain get rewritten by Skryx AI into a tighter, in-language form. The rewrite is conservative, cached, and gated by your monthly AI quota — so it doesn't fire on queries that don't need it.
4+ words, a question marker, or a buying-intent phrase.
Skips empty, SKU, single- and two-word queries (unless they contain
?, how, why, best,
recommend, care, pentru,
cum). Triple cache: per-request memo → DB cache table → fresh
call. Triple gate: heuristic check → cache check → monthly quota slot.
- Language detected by quick heuristic (RO markers:
ă â ț ș î) - Rewrite returned in user's language (≤ 6 words)
- Optional
user_messagefor "Showing results for X" banners
{
"intent": "buy_part",
"rewritten_query": "usb-c charger 65w",
"alternative_queries": [
"fast charger usb c",
"laptop charger 65w"
],
"detected_problem": null,
"use_case": "laptop charging",
"confidence": "high",
"user_message": "Showing 65W USB-C chargers"
}
Three caches, one quota, live progress.
Pick your model
At enable time. Four Skryx embedding models are available, ranging from 512-dim (fast, cheap) to 1024-dim (highest recall). Cost per million tokens is shown in the dashboard before you commit.
Index the catalog
GenerateEmbeddingsJob walks every document, builds the
text-rep (title × 3 + brand + category + description capped), batches
embedding calls, persists vectors to the engine. Per-batch retry with
backoff; live progress updates after every batch.
Stay in sync
Every document POST/PATCH queues an EmbedDocumentsJob for
just that doc. Title or description edits trigger a re-embed; price /
stock edits don't. The engine field stays consistent without full
re-indexes.
The embedding model is multi-lingual by design.
A French query against an English catalog still works — the underlying Skryx embedding model maps both into the same vector space. Query understanding detects Romanian vs English and keeps the rewritten query in the original language. Per-language synonyms and per-language Romanian morphology layer on top via the Synonyms & Typo module.