Search by meaning
Ask a collection for the rows nearest to some words, and they come back closest first. With an embeddings model, "excursion" finds the museum trip. Without one, it still works on the words. The vectors are kept in the same SQLite file as the rows, and there is no index to build.
const { items } = await sluurp.collection("notes").list({ near: "museum trips" });GET /api/collections/notes/records?near=museum%20tripsEach row comes with a _score: 1 is the same, and lower is further away. Everything else about a list still applies. The caller sees only the rows the rules let them list, and filter narrows them before they are ranked:
await sluurp.collection("notes").list({
near: "a pupil who is struggling",
filter: `class = "4B"`,
perPage: 10,
});Which fields are compared
By default, the collection’s searchable fields are compared: those with "searchable": true in the schema, the same fields search= looks in. A collection with none compares its text, email and URL fields. on names others:
await sluurp.collection("posts").list({ near: "school trip", on: "title,body" });Meaning, or words
What the vectors capture depends on the AI settings in the admin (Platform → AI).
- With an embeddings model, near means near in meaning. It can be any server that speaks the OpenAI embeddings API: Ollama on your own machine, OpenAI, or others. On Ollama, run
ollama pull nomic-embed-text, set the base URL tohttp://localhost:11434/v1and the embeddings model tonomic-embed-text. Rows then leave the server only for that machine. - Without one, the vectors are made from the words and their three-letter pieces. “museum trips” still finds “Museum trip — 4B”, and a typo is forgiven, but “excursion” does not find it. This needs nothing and sends nothing anywhere.
Changing the model is safe. Vectors are kept per model, so the new one makes its own as they are needed.
near and search
search= | near= | |
|---|---|---|
| Finds | rows containing these words | rows about the same thing |
| Order | as sort says | closest first |
| Behind it | SQLite’s full-text index | vectors, compared one by one |
| Good for | names, codes, exact phrases | questions, topics, “anything like this” |
Use search when the reader knows the words, and near when they know what they mean.
How it works
- A vector is made the first time a row is searched, and kept with a hash of the words it was made from. When those words change, it is made again. Nothing is done when rows are written, so writes cost what they always did.
- Vectors are rows in an ordinary table in the project’s database (
_vectors_<dimensions>), and are backed up with everything else. They are compared by sqlite-vector (Apache-2.0), which is compiled into the binary. - The comparison is exact. Every row the caller may list, up to 5,000 after
filter, is compared, so there is no approximate index to tune and no row missed by one. For a larger collection, usefilterto narrow it first: a class, a term, the last year. - A page has at most 500 rows, as any list.
The social feed
Posts are found the same way, from the feeds the reader may read:
const { items } = await sluurp.social.feed({ near: "lost property" });GET /api/social/feed?near=lost%20propertyMikroSchool uses this in its search palette (Ctrl-K), where posts are found by what they are about as well as by their words.