VITON13School

Generative AI for BeginnersLesson 16 of 21

RAG and vector databases

A model knows nothing about your lecture notes, and pasting all of them into every prompt is slow, costly and unreliable. Retrieval-augmented generation finds the few passages that matter and hands only those to the model. Here Study Buddy learns to answer from your notes and show where each answer came from.

Course by , Founder of VITON13 SCHOOL
  • Part 3: Make it trustworthy
  • 50 min

You will be able to

  • Explain how retrieval-augmented generation reduces made-up answers
  • Split documents into chunks with overlap and useful metadata
  • Retrieve the most relevant chunks with embeddings and cosine similarity
  • Write a grounding prompt that forces citations and an honest not-found answer
  • Choose between an in-memory index, a local vector database and a managed service

Why retrieval beats a bigger prompt

A language model answers from patterns learned during training, which ended at some cutoff and never included your private files. Ask it about your biology notes and it will produce something plausible, which is worse than producing nothing.

You could paste every note into the prompt, but that runs into three walls. Context windows have limits, every extra token costs money and time, and models can overlook a detail buried in a very long context. Retrieval-augmented generation, or RAG, solves this by searching first and generating second.

The pattern has two halves. Offline, you index your documents once. Online, for each question, you find the most relevant passages, put them into the prompt with clear rules, and let the model write an answer based on them. Updating knowledge then means updating the index, not retraining anything.

RAG is usually the first tool to reach for when the problem is missing knowledge. Fine-tuning, covered in lesson 19, changes how a model behaves, but it is a poor way to teach facts that change every week.

Indexing: chunk, embed, store

Chunking splits long documents into pieces small enough to retrieve precisely and large enough to make sense alone. Splitting by headings or paragraphs keeps ideas together better than cutting every fixed number of characters. A small overlap between neighbouring chunks stops a sentence that crosses a boundary from being lost.

Clean the text before you split it. Text pulled out of PDFs and web pages often carries page headers, footers, menus and broken line breaks, and that noise ends up inside chunks and their vectors.

Keep metadata with each chunk: the file name, the heading, a page number or a date. Metadata lets you filter results, for example to one course, and it is what you show the learner as the source.

Each chunk then goes through an embedding model, which turns text into a vector of numbers where similar meanings sit close together. Use the same embedding model for documents and questions, because vectors from different models are not comparable. If you switch models later, you must re-embed everything.

  • Too small: chunks lose context, and the model gets fragments that cannot answer anything.
  • Too large: one chunk mixes several topics, so its vector matches nothing strongly.
  • A sensible start: a few hundred words per chunk with a short overlap, then tune on your own questions.

Retrieval: finding the right passages

At question time you embed the question and compare it with every stored vector. Cosine similarity, the angle between two vectors, is the usual score; dot product and Euclidean distance are common alternatives. You keep the top few chunks, often three to five.

Vector search understands paraphrases, so a question about how plants make food finds a note titled photosynthesis. It is weaker with exact strings such as error codes, names or formula labels, where classic keyword search shines. Hybrid search runs both and merges the results, and most serious RAG systems end up there.

Two upgrades help when results look noisy. A re-ranker takes the top twenty candidates and scores each one against the question more carefully, keeping the best few. A minimum similarity threshold stops the app from passing weak matches to the model when nothing relevant exists.

In a chat, follow-up questions are often too short to search with. A message like "and why does it need light?" means nothing on its own, so ask the model to rewrite it into a standalone question using the conversation history, then embed the rewritten version.

Grounding and citations

Retrieval only helps if the prompt tells the model how to use it. Number the passages, tell the model to answer only from them, ask it to cite the numbers it used, and give it a clear way out when the passages do not contain the answer.

Show the cited sources in the interface, with the file name and the passage text. Learners can then check an answer in seconds, and you can spot a wrong citation while testing.

Evaluate the two halves separately. Retrieval quality asks whether the right chunk appeared in the top results at all. Answer quality asks whether the reply is faithful to the passages and actually answers the question. When an answer is wrong, the log of retrieved chunks tells you which half failed.

Retrieved text is data, never instructions. A shared document can contain a line telling the model to ignore its rules, which is the indirect prompt injection from lesson 14, so keep the passages clearly separated from your system prompt and never let them trigger tools on their own.

Choosing a vector store

For a few thousand chunks, a Python list and a loop, as in the example, is fast enough and has nothing to install. Save the vectors to a file so you do not pay to embed the same notes on every start.

When the collection grows or you need filters and persistence, move to a real store. The choice depends on size, hosting, metadata filtering, hybrid search support and how backups work, not on which name is most popular.

  • Libraries in your process: FAISS or plain NumPy arrays for fast similarity search without a server.
  • Embedded databases: Chroma, LanceDB or SQLite with a vector extension, stored as local files.
  • Your existing database: PostgreSQL with the pgvector extension keeps vectors next to normal tables.
  • Search engines: Elasticsearch and OpenSearch combine keyword and vector search.
  • Dedicated servers or managed services: Qdrant, Weaviate, Milvus or Pinecone for large collections.

Example

Ask your notes, with citations

import math, os, pathlibfrom openai import OpenAI client = OpenAI(base_url=os.environ["LLM_BASE_URL"], api_key=os.environ["LLM_API_KEY"])EMBED_MODEL = os.environ["LLM_EMBED_MODEL"]  # an embedding model from the same provider def chunks(text, size=120, overlap=30):    words = text.split()    return [" ".join(words[i:i + size]) for i in range(0, max(len(words) - overlap, 1), size - overlap)] def embed(texts):    return [d.embedding for d in client.embeddings.create(model=EMBED_MODEL, input=texts).data] def cosine(a, b):    dot = sum(x * y for x, y in zip(a, b))    return dot / (math.sqrt(sum(x * x for x in a)) * math.sqrt(sum(y * y for y in b))) pieces = [(p.name, c) for p in sorted(pathlib.Path("notes").glob("*.md"))          for c in chunks(p.read_text(encoding="utf-8")) if c]vectors = embed([text for _, text in pieces])question = input("Question: ")q = embed([question])[0]top = sorted(zip(pieces, vectors), key=lambda pv: cosine(q, pv[1]), reverse=True)[:3]sources = "\n\n".join(f"[{i}] ({name}) {text}" for i, ((name, text), _) in enumerate(top, 1))rules = "Answer only from the numbered sources and cite them like [1]. If they do not answer the question, say it is not in the notes."reply = client.chat.completions.create(model=os.environ["LLM_MODEL"], messages=[    {"role": "system", "content": rules},    {"role": "user", "content": f"Sources:\n{sources}\n\nQuestion: {question}"}])print(reply.choices[0].message.content)

Put a few Markdown files into a notes folder and set LLM_EMBED_MODEL to an embedding model your provider offers, since chat models cannot create embeddings. The script embeds all notes on each run, which is fine for practice; save the vectors once your folder grows.

Practice

Teach Study Buddy to answer from your notes

  1. Create a notes folder in Study Buddy with three to five Markdown files from a subject you are studying, each a few paragraphs long.
  2. Save the example as ask_notes.py, set LLM_EMBED_MODEL, and run it with a question your notes clearly answer.
  3. Check that the cited number matches the file that really contains the answer, and print the three retrieved chunks if it does not.
  4. Ask a question your notes do not cover and confirm that Study Buddy says so instead of inventing an answer.
  5. Try a chunk size of 40 and then 400 words on the same three questions and keep the size that cites the right file most often.

Done when: For three questions from your notes the answer cites the correct file, and for one outside question Study Buddy says the notes do not contain the answer.

Common mistakes

  • Embedding the question with a different model than the documents, which makes every similarity score meaningless.
  • Throwing away metadata during chunking, so the app can retrieve a passage but cannot tell the learner where it came from.
  • Blaming the model for a bad answer without checking whether retrieval returned the right chunk in the first place.
  • Starting with a hosted vector service and its setup for a collection that fits comfortably in memory.

Check yourself

Pick an answer to see whether it is right and why.

What is the main job of the retrieval step in RAG?

Right.RAG keeps the model unchanged and improves the input, giving it the passages it needs to ground the answer.

Not quite.The right answer is “To find the few passages most relevant to the question and add them to the prompt”. RAG keeps the model unchanged and improves the input, giving it the passages it needs to ground the answer.

A learner searches for an exact error code and vector search returns unrelated chunks. What helps most?

Right.Exact strings are a known weak spot of embeddings, and keyword search catches them, so combining both covers each other's gaps.

Not quite.The right answer is “Hybrid search that adds keyword matching to vector similarity”. Exact strings are a known weak spot of embeddings, and keyword search catches them, so combining both covers each other's gaps.

Why should a grounding prompt tell the model what to say when the sources lack the answer?

Right.Models are trained to be helpful, so an explicit not-found instruction makes the honest answer the expected behaviour.

Not quite.The right answer is “Without an allowed way out, the model tends to fill the gap with a plausible guess”. Models are trained to be helpful, so an explicit not-found instruction makes the honest answer the expected behaviour.

From the founder

Use your real notes for this task, not sample text, because only then will you see where retrieval fails. Keep the questions you test here and add them to the eval set from lesson 15.
Vitalii Tarasov, Founder of VITON13 SCHOOL

Course by Vitalii Tarasov, VITON13 SCHOOL. Draws in part on Microsoft's Generative AI for Beginners, MIT licence, Copyright (c) Microsoft Corporation. microsoft/generative-ai-for-beginners