@@ -379,37 +379,90 @@ When the device is offline, these writes are queued and synced automatically whe
379379
380380---
381381
382- ### Semantic search
382+ ### Semantic, lexical, and hybrid search
383383
384- Find records by meaning, not just field equality. Two paths: let Koolbase
385- embed text for you on the server (recommended — no client-side model
386- needed) , or pass a precomputed vector .
384+ Find records by meaning, exact terms, or both. Koolbase ships three
385+ retrieval modes from a single API — pick the one that matches your
386+ query characteristics , or use ` 'hybrid' ` as a strong production default .
387387
388388Declare a vector field on the collection from the dashboard or CLI first
389389(picking a dimension; v1 supports 384, 768, 1024, and 1536).
390390
391- ** Server-side embedding (recommended).** Configure an AI provider on the
392- project once (Gemini's free tier works; OpenAI also supported), tag the
393- vector field with the provider/model/source_field, and Koolbase
394- auto-embeds records as they're inserted or updated:
391+ #### The three search modes
395392
396393``` typescript
397- // One-time setup via dashboard. Then just write records normally:
398- await Koolbase .db .insert ({
394+ // Semantic (default) — pure vector search via HNSW + cosine. Best for
395+ // fuzzy or conceptual queries where exact words don't have to match.
396+ const result = await Koolbase .db .searchSemantic ({
399397 collection: ' articles' ,
400- data: {
401- title: ' How to ship faster' ,
402- content: ' Cut scope ruthlessly. Ship the smallest useful slice...' ,
403- },
398+ field: ' content_embedding' ,
399+ queryText: ' how do I move quicker?' ,
400+ limit: 10 ,
404401});
405402
406- // Query by text — server embeds inline using the configured provider:
403+ // Lexical — pure BM25 over the field's source text (Postgres
404+ // ts_rank_cd). Best for exact terms, product codes, names, acronyms.
405+ const result = await Koolbase .db .searchSemantic ({
406+ collection: ' articles' ,
407+ field: ' content_embedding' ,
408+ queryText: ' CVE-2024-1234' ,
409+ mode: ' lexical' ,
410+ limit: 10 ,
411+ });
412+
413+ // Hybrid — vector + lexical fused with reciprocal rank fusion (k=60).
414+ // Generally the strongest default; both rankers vote and the fused
415+ // score promotes records that score well on either signal.
416+ const result = await Koolbase .db .searchSemantic ({
417+ collection: ' articles' ,
418+ field: ' content_embedding' ,
419+ queryText: ' production deploy pipeline' ,
420+ mode: ' hybrid' ,
421+ limit: 10 ,
422+ });
423+ ```
424+
425+ #### Filtering weak matches
426+
427+ For ` 'semantic' ` and ` 'hybrid' ` modes, pass ` minSimilarity ` (0..100) to
428+ drop results below a similarity threshold server-side — saves bandwidth
429+ on weak matches:
430+
431+ ``` typescript
407432const result = await Koolbase .db .searchSemantic ({
408433 collection: ' articles' ,
409434 field: ' content_embedding' ,
410435 queryText: ' how do I move quicker?' ,
436+ mode: ' hybrid' ,
437+ minSimilarity: 70 , // only matches at least 70% similar
411438 limit: 10 ,
412439});
440+ ```
441+
442+ ` minSimilarity ` is rejected by the server when used with ` 'lexical' ` —
443+ BM25 rank scores aren't comparable to cosine similarity, and silently
444+ ignoring the parameter would produce confusing behavior.
445+
446+ #### Server-side embedding (recommended)
447+
448+ Configure an AI provider on the project once (Gemini's free tier works;
449+ OpenAI also supported), tag the vector field with the
450+ provider/model/source_field, and Koolbase auto-embeds records as
451+ they're inserted or updated. Lexical indexing happens automatically on
452+ the same write, so all three search modes work without extra setup:
453+
454+ ``` typescript
455+ // One-time setup via dashboard. Then just write records normally —
456+ // vectors AND lexical rows land within ~1s.
457+ await Koolbase .db .insert ({
458+ collection: ' articles' ,
459+ data: {
460+ title: ' How to ship faster' ,
461+ content: ' Cut scope ruthlessly. Ship the smallest useful slice...' ,
462+ },
463+ });
464+
465+ // Iterate over hits the same way regardless of mode:
413466for (const hit of result .hits ) {
414467 console .log (` ${hit .record .data .title } ${hit .distance .toFixed (3 )} ` );
415468}
@@ -430,8 +483,11 @@ await Koolbase.db.embedText({
430483});
431484```
432485
433- ** Client-side embedding (advanced).** If you'd rather control the
434- embedding model yourself, pass a vector instead of text:
486+ #### Client-side embedding (advanced)
487+
488+ If you'd rather control the embedding model yourself, pass a vector
489+ instead of text. Note that lexical and hybrid modes require text, since
490+ BM25 has no notion of "vector queries":
435491
436492``` typescript
437493// Set a vector you've encoded yourself
@@ -445,7 +501,7 @@ await Koolbase.db.setVector(
445501const v = await Koolbase .db .getVector (articleId , ' embedding' );
446502console .log (` ${v .vector .length }-dim, updated ${v .updatedAt } ` );
447503
448- // Search with a precomputed vector
504+ // Search with a precomputed vector — semantic mode only.
449505const result = await Koolbase .db .searchSemantic ({
450506 collection: ' articles' ,
451507 field: ' embedding' ,
@@ -458,25 +514,32 @@ const result = await Koolbase.db.searchSemantic({
458514await Koolbase .db .deleteVector (articleId , ' embedding' );
459515```
460516
461- A few behaviors worth knowing:
517+ #### Behaviors worth knowing
462518
463519- ** Pass exactly one of ` queryVector ` or ` queryText ` .** Supplying both
464520 or neither throws an ` Error ` .
521+ - ** ` queryVector ` is for semantic mode only.** Lexical and hybrid need
522+ raw text — the server uses it for BM25 ranking (and embeds it inline
523+ for the vector half of hybrid).
465524- ** Vector length must match the declared dimension.** Mismatches throw
466525 ` KoolbaseVectorDimensionMismatchError ` .
526+ - ** ` minSimilarity ` must be 0..100.** Values outside that range throw
527+ an ` Error ` client-side before the request is sent.
467528- ** Online-only.** Vector operations are not cached locally or queued
468- offline — HNSW similarity search has no useful offline semantics.
529+ offline — HNSW similarity and BM25 ranking have no useful offline
530+ semantics.
469531- ** Read rule applies post-search.** ` owner ` /` scoped ` /` conditional `
470- records are filtered to the caller after the HNSW lookup , so strict
471- rules may return fewer than ` limit ` results.
532+ records are filtered to the caller after retrieval , so strict rules
533+ may return fewer than ` limit ` results.
472534- ** ` embedText ` is async.** Returns when the job is queued (~ 100ms).
473535 The vector lands within 1 second once the worker picks it up.
474536- ** Higher dimensions coming.** ` text-embedding-3-large ` (3072 dim)
475- supported once pgvector is upgraded. Use ` dimensions=1536 ` Matryoshka
476- truncation in the meantime.
537+ supported once pgvector is upgraded. Use ` dimensions=1536 `
538+ Matryoshka truncation in the meantime.
477539
478540See [ Semantic search docs] ( https://docs.koolbase.com/database/vectors )
479- for setup, provider configuration, and embedding model recommendations.
541+ for setup, provider configuration, embedding model recommendations,
542+ and when to pick each mode.
480543
481544---
482545
0 commit comments