Swelv· Part 1 of 2
A RAG without vectors: the model finds its own books
Every month, five books picked for each reader. The model invents no title: it queries the catalogue with tools, and code checks everything.
6 min readLire en français
Every month, Swelv builds each reader a selection of five books, from what they read, love and give up on. Each book comes with a tagline and a “why” that names their own reads.
A selection that reflects you
Léa's monthly selection October 2026
The Count of Monte Cristo
Alexandre Dumas
Classics · Adventure · Historical
Camille, Léo liked it
Because you loved The Three Musketeers
Why this is for you
You loved The Three Musketeers: the same pace, the same Dumas, and a patient revenge that holds for a thousand pages.
- 1A book that existsFound in the catalogue, never quoted from memory.
- 2An axisOut of five: three safe, one adjacent, one discovery.
- 3A framed taglineA few set phrasings, always in a friendly voice.
- 4A why you can checkIt names a book from the profile, and a real link between the two.
RAG: retrieve, then generate
A language model writes very well, but it does not know our catalogue, and it can confidently quote a book that does not exist. RAG (Retrieval-Augmented Generation) fixes that: before answering, you fetch the right information, and the model writes from it.
The best-known version uses embeddings: texts become vectors, and you look for the ones closest to the question. It is ideal to find a passage in documentation. To recommend a book, much less so: the real constraints are exact (the right language, not a book already read, not the same author twice), and vector proximity guarantees none of them.
So Swelv does the opposite: the model gets tools, and searches the catalogue itself, the way you would query a database. This is function calling: the model does not answer directly, it asks to call a function, the server runs it, and hands back the result.
- + ideal to find free text
- − no guarantee on an exact rule
- + exact filters, verifiable results
- − more round trips, so slower
The challenge. Leave the model what it does well (understanding taste, forming hypotheses, writing) and keep in code everything that must be exact. A model can forget an instruction; an SQL clause, never.
The loop
The model gets the reader's profile, then searches again and again: it forms a hypothesis (“they love big historical adventures”), searches, intersects the results, eliminates. When it proposes its five books, code rereads each one, and sends the model back to work if one does not pass.
- loved books, favourites
- books given up, and why
- recent activity
- languages read
Never the private notes.
It forms hypotheses, searches, intersects lists, eliminates.
- does the book exist?
- already read or proposed?
- right language?
- same author twice?
✗ → the model is sent back
Each search is a small filter the model writes, which the server turns into SQL. The server always adds what the model should not have to handle: the books already read, and the reader's languages.
search({ genre: "adventure", themes: ["revenge", "betrayal"], keep_as: "like_dumas"})
SELECT title, author, id FROM catalogueWHERE genre = $1 AND summary ~* $2 AND id NOT IN (:already_read) AND language IN (:reader_languages)ORDER BY rating DESC LIMIT 20
The model gets → a handful of books, each with an id to copy as is, and the list kept under a name to intersect it later.
Language, learned the hard way
The feature's first incidents all had the same cause: language. Each one moved a rule from the prompt into code.
Three gates
- 01On every searchNarrowed by default to the reader's languages.
- 02Inside the loopAn off-language pick is refused, the model asked again.
- 03At validationLast net: the book is dropped, then replaced.
What taught it
- 1A French-only reader gets books in other languages.→ Language was only a hint in the prompt: it becomes a filter, at three levels.
- 2The tagline in one language, the explanation in another.→ The output language is decided in code, and enforced on the model.
- 3Editions mislabelled by the data source.→ A language quota per selection, and the language cross-checked.
The right model is not the cheapest
Before switching models, we replay real selections on the same profiles and compare: cost, time, number of corrections, and above all the quality of the copy.
Cost per selection, relative
The system prompt is the same for every reader: the provider serves almost all of it from its cache.
What I take from it
- Give tools, not memories. The model only proposes what it found.
- What matters goes into code. A prompt instruction always ends up forgotten.
- Pick the model on what the user reads, not only on the bill.