Search by meaning

Semantic search: query a collection with some text and get rows ranked by similarity. With an embeddings model, "excursion" finds the museum trip; without one, it falls back to fuzzy word matching. Vectors live in the same SQLite file as the rows, with no index to build.

const { items } = await sluurp.collection("notes").list({ near: "museum trips" });
GET /api/collections/notes/records?near=museum%20trips

Each row gets a _score (1 = identical, lower = less similar). Everything else about list still applies: rules limit which rows are considered, and filter narrows them before ranking:

await sluurp.collection("notes").list({
  near: "a pupil who is struggling",
  filter: `class = "4B"`,
  perPage: 10,
});

Fields

By default, fields marked "searchable": true are used (the same ones search= uses). If there are none, text, email and URL fields are used. Override with on:

await sluurp.collection("posts").list({ near: "school trip", on: "title,body" });

Embeddings

How vectors are computed depends on the AI settings in the admin UI (Platform → AI).

  • Local model file: Sluurp computes embeddings in-process, nothing else to run. Point it at a .gguf embeddings model such as nomic-embed-text-v1.5 (84 MB); Find models on this machine lists ones already downloaded by LM Studio or Hugging Face. Supports BERT-style models (Nomic, MiniLM, BGE, E5) in F32, F16, Q8_0, Q4_K, Q5_K or Q6_K. Tens of milliseconds per text on a laptop.
  • Embeddings API: any OpenAI-compatible embeddings endpoint (local Ollama, OpenAI, …). With Ollama: ollama pull nomic-embed-text, base URL http://localhost:11434/v1, model nomic-embed-text. Text is sent only to that server.
  • Neither: vectors are built from words and character trigrams. “museum trips” still finds “Museum trip — 4B” and typos are tolerated, but “excursion” won’t match. Nothing is sent anywhere.

Switching models is safe: vectors are stored per model and recomputed lazily.

search=near=
Matchesrows containing the wordsrows about the same thing
Orderby sortmost similar first
EngineSQLite full-text indexexact vector comparison
Good fornames, codes, exact phrasesquestions, topics, “more like this”

Use search when users know the exact words, near when they know the meaning.

How it works

  • Vectors are computed lazily the first time a row is searched, and stored with a hash of the source text; they’re recomputed when the text changes. Writes cost nothing extra.
  • Vectors are ordinary rows in the project database (_vectors_<dimensions>), backed up with everything else, and compared with sqlite-vector (Apache-2.0), compiled into the binary.
  • Search is exact, not approximate. Every visible row (up to 5,000 after filter) is compared, so there’s no index to tune and no missed rows. For bigger collections, narrow first with filter (a class, a term, the last year).
  • Max 500 rows per page, as with any list.

The social feed

Posts can be searched the same way, across feeds the user can read:

const { items } = await sluurp.social.feed({ near: "lost property" });
GET /api/social/feed?near=lost%20property

A search palette can show both: semantic matches next to keyword matches.