// rag guide · 7 min

Embeddings & vector search

Retrieval decides which passages the model gets to see, which means it decides the answer. The model can only be as right as the paragraphs handed to it. So this is the half worth understanding properly, and it turns out to run on something much stranger and much dumber than most people assume.

An embedding is meaning as numbers

You've searched your own notes for something you knew you'd written and come up empty. You typed "server fell over" and the file said "outage at 3am", and those share not one word, so the search shrugged. That's every search you've ever used: it matches letters. An embedding model does something stranger. It reads a piece of text and hands back a few hundred numbers, and the trick is that text meaning the same thing lands on nearby numbers even when the words are completely different. "How do I get a refund" and "what's your returns policy" end up as neighbours.

So an embedding is a coordinate. Not a summary, not a set of tags, a position. Every chunk of your text gets placed somewhere in a space with hundreds of dimensions (text-embedding-3-small uses 1536 of them), and the model that does the placing was trained so that things people would call similar end up close together. Once your text is a coordinate, "find the most relevant passage" collapses into "find the nearest coordinate", which is arithmetic a computer can do a million times a second.

Nothing here understands your question. A model turned it into a list of numbers, and the database ranked your chunks by the angle between two lists of numbers. It has no idea it's doing search. The sense that it "got" what you meant is entirely yours.

That matters practically, not just philosophically. Because it is geometry, it degrades in geometric ways. Two chunks that argue the opposite of each other about the same subject sit very close together, so a store containing both your old policy and your new one will happily hand the model the old one. Meaning-space has no notion of correct.

Closeness is measured with cosine similarity, the angle between the two vectors, ignoring their length. A score near 1 means the vectors point the same way, near 0 means unrelated. Those scores come back on every result, so you can see how confident the match actually was.

Why documents get split into chunks

You do not embed a whole document as one vector. A 40-page contract crushed into a single coordinate is a blur of forty topics, and when it matches a query you still have forty pages to hand the model. So before embedding, each file is split into chunks of roughly a few hundred words, and every chunk gets its own coordinate.

Chunk size is a genuine trade and it is where most bad retrieval comes from. Chunks that are too small are precise but stranded: a paragraph reading "this does not apply to enterprise accounts" is meaningless once separated from the clause above it, and it will still be retrieved on its own and still be fed to the model. Chunks that are too big drift back towards the blur, matching everything weakly and nothing strongly. Overlapping the chunks slightly is the standard hedge, so a sentence sitting on a boundary appears in both neighbours and cannot fall down the crack between them.

Ringside handles the splitting during ingest, which is why results come back as passages carrying a chunk index and a page rather than as whole documents. If an answer is confidently wrong and you can see the retrieved chunk was a fragment torn from its context, you have found your bug and it is not in the model.

What actually happens when you ask a question

The query text goes to the same embedding model the store was built with, and comes back as a vector. That vector is compared against the chunk vectors, the top matches by similarity are kept, and their text is what gets put in front of the model. Retrieval ends there. Everything after it is the model writing prose from what it was handed.

Comparing against every chunk one at a time is fine for a thousand chunks and hopeless for ten million, so the vectors live in an approximate-nearest-neighbour index that finds the near ones without scanning all of them. Approximate is doing real work in that name. The index trades a small chance of missing a borderline match for an enormous speedup, which is nearly always the right trade, and it is why two runs of the same query can occasionally rank results slightly differently.

How many chunks come back is the other lever worth knowing about. Ask for too few and the answer sits in the passage you did not retrieve. Ask for too many and you are back to diluting the prompt with noise, and paying for it.

What comes back, and why you want the scores

Each result carries the chunk text, the file it came from, its chunk index and page, and a similarity score. The model uses the text. You use the rest. Showing "answered from these 3 sources" in your UI is the obvious use, but the scores earn their keep during debugging, because a wrong answer with a top score of 0.31 is a retrieval failure and a wrong answer with a top score of 0.89 is the model ignoring good context. Those two have completely different fixes.

Ringside logs every query with its question text, top-K scores, returned file ids, latency and embedding tokens. So when a customer says search was bad on Tuesday, you can open Tuesday and look at what was actually retrieved rather than reasoning about it from first principles.

Choosing and changing the embedding model

You pick the embedding model at store creation with embedding_model. A smaller model is cheaper, faster and produces shorter vectors that cost less to store. A larger one draws finer distinctions between things that are nearly the same, which matters more for dense technical corpora than for an FAQ. text-embedding-3-small is a sensible default and most stores never move off it.

Everywhere else, changing your mind about that is expensive. Every chunk has to be embedded again, and since most systems threw the parsed text away, that means re-uploading and re-paying to parse every file you have. On Ringside the parse output is cached, so a model swap re-embeds in the background from the cached text, builds the new index alongside the live one, and cuts over atomically when it is complete. Queries keep working the whole way through, you pay embedding tokens and not parse tokens, and the previous index is retained for seven days in case the new model turns out to be worse for your data.

One hard rule underneath all of this: a query has to be embedded with the same model as the chunks, or the coordinates are not comparable and the scores are meaningless. That is why a swap re-embeds the entire store rather than mixing models inside one index.

Where this approach runs out

Vector search finds the chunk most similar to the question and stops. When the answer lives in one passage, that is exactly right and nothing beats it for the money. It breaks on a specific and very common shape of question: the answer exists in your documents but no single chunk contains it. One file says a product depends on a part. Another says who supplies that part. A third says where the supplier is. Ask where the supplier is and every one of those chunks scores as only loosely related, because none of them is about the question you asked.

The geometry cannot help you here. Nearness is a one-shot measurement and the answer needs three hops. That gap is what the next page is about.