How to Power RAG Search With a Vector Database

RAG retrieval has two steps: embed every document once at ingest time, then embed each query and find the closest matches by vector similarity. Here's how to wire that loop with ChromaDB and a local embeddings model.

AI Engineerragvectorsopensearch

The two-step retrieval loop

RAG (Retrieval-Augmented Generation) retrieval works the same way regardless of which vector store you use:

  1. Index - call an embeddings model for each document and store the resulting vector alongside the document text.
  2. Query - embed the incoming query with the same model, then ask the vector store for the top-k documents whose vectors are closest (by cosine similarity) to the query vector.

The LLM that generates the answer is downstream of this retrieval step - retrieval quality is what makes or breaks the feature.

Indexing documents into ChromaDB

import json, urllib.request, chromadb

EMBED_URL = "http://ollama:11434/api/embeddings"
EMBED_MODEL = "nomic-embed-text"

_client = chromadb.EphemeralClient()
_collection = _client.get_or_create_collection("docs")

def _embed(text: str) -> list[float]:
    req = urllib.request.Request(
        EMBED_URL,
        data=json.dumps({"model": EMBED_MODEL, "prompt": text}).encode(),
        headers={"Content-Type": "application/json"},
    )
    with urllib.request.urlopen(req, timeout=60) as r:
        return json.loads(r.read())["embedding"]

def index_docs(docs: list[str]) -> None:
    for i, doc in enumerate(docs):
        vec = _embed(doc)
        _collection.add(ids=[str(i)], embeddings=[vec], documents=[doc])

chromadb.EphemeralClient() keeps the index in memory - swap it for chromadb.PersistentClient(path="/data/chroma") when you need the index to survive a restart. For production scale, the same pattern works with OpenSearch, pgvector, or Pinecone - only the client and add/query calls change.

Querying for the top-k results

def search(query: str, k: int = 3) -> list[str]:
    qv = _embed(query)
    res = _collection.query(query_embeddings=[qv], n_results=k)
    return res["documents"][0]   # list of doc strings, ranked by similarity

collection.query returns results ranked by cosine distance - the closest vectors first. res["documents"][0] is the ranked list for the first (and only) query vector.

Putting it together

DOCS = [
    "Kubernetes orchestrates containers across a cluster of nodes.",
    "PostgreSQL is a powerful open-source relational database engine.",
    "Redis is an in-memory key-value store often used for caching.",
]

index_docs(DOCS)

for q in ("container orchestration", "in-memory caching"):
    print(f"Q: {q}")
    for doc in search(q):
        print(f"  -> {doc}")

The query "in-memory caching" surfaces the Redis doc first, even though the word "caching" doesn't appear in every candidate - that's semantic similarity at work.

What to tune in production

Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.

Try it in a workspace →

What you'll practice

FAQ

How does RAG vector database search work?

You embed every document with an embeddings model and store the vectors in a vector database. At query time you embed the question with the same model and retrieve the top-k documents whose vectors are nearest by cosine similarity. Those documents are injected into the LLM prompt as context.

How do I use ChromaDB for RAG retrieval in Python?

Create an EphemeralClient collection, call collection.add(ids=[...], embeddings=[...], documents=[...]) at ingest time, then call collection.query(query_embeddings=[qv], n_results=k) to retrieve the top-k results. Use the same embeddings model for both indexing and querying.

Which embeddings model should I use for RAG?

nomic-embed-text is a strong open-source choice for English text - it runs locally via Ollama and produces 768-dimensional vectors. For production you can also use OpenAI text-embedding-3-small or text-embedding-3-large. The key rule: always use the same model for indexing and querying or the similarity scores become meaningless.

Keep learning

Restore a Broken LLM API IntegrationAI/ML projectHarden an LLM Pipeline Against API FailuresAI/ML projectParse JSON From an LLM (Strip Markdown Fences)AI/ML projectAI/ML roadmapStep by step to hiredAI/ML interview questionsSTAR answersAll AI/ML projectsProjects hub

Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.

Open the workspace →