Skip to main content
This guide demonstrates a minimal, practical retrieval-augmented generation (RAG) pipeline using Ollama for embeddings and generation and Chroma for vector storage and search. We move from an in-memory demo to ingesting files from disk, showing a simple end-to-end flow:
  • Read .md/.txt files from a data/ folder.
  • Chunk documents into paragraph-style pieces.
  • Embed chunks with Ollama and persist embeddings in Chroma.
  • At query time embed the user question, retrieve top-k similar chunks from Chroma, build a prompt that forces the model to answer only from the returned context, and include citations.
This example is intentionally simple (no batching, no BM25 or fancy optimizations) to keep it easy to extend. Below we walk through the important pieces of app_v2.py. The full script is included in the sections below.

Prepare example data

Create a data directory with a couple of small documents:
Now data/ contains two short documents we will ingest.

app_v2.py — settings and imports

Below are the imports and the demo’s simple hard-coded settings. These values are easy to change for your environment.
Callout with configuration summary: You can change LLM_MODEL, EMBED_MODEL, TOP_K, and the Chroma path as needed.

Embedding & generation helpers (Ollama)

These helpers handle embeddings and text generation. The embedding helper supports both prompt= and input= parameter styles used by different Ollama client versions.
The _embed helper attempts both parameter styles to maintain compatibility across Ollama client versions. If you control the client, pick one style and simplify the helper.

Chroma collection helper

Create or get a persistent Chroma collection. This example uses the duckdb+parquet implementation and a local persist directory.

Simple paragraph-based chunking

The chunking strategy below splits documents into paragraphs and packs them greedily into chunks with optional overlap. Paragraph-based chunks are small, document-like, and work well for many short-doc corpora.
Tune max_chars and overlap for your documents. Paragraph packing keeps chunks coherent and readable by the model.

Iterating files to ingest

Only .txt and .md files are considered. This helper walks a directory tree and yields matching files.

Quick environment check (init)

A small command verifies embeddings, text generation, and Chroma connectivity. Run python app_v2.py init to validate your environment.

Semantic search helper

Embed the user question, query Chroma for the top-k results, and return hits with document text, metadata, and distance scores.

Build the prompt with citations

Format the retrieved chunks into a context block and construct a deterministic prompt that instructs the LLM to answer ONLY from that context and to cite the sources.
Returning the citation strings separately makes CLI printing and logging easier.

Ingest command

This command ingests all .md/.txt files under a directory:
  • Read files from disk
  • Chunk documents
  • Create deterministic chunk IDs based on file path + chunk content
  • Embed chunks and add to Chroma, skipping duplicates
Deterministic chunk IDs allow safe re-ingestion: the script skips chunks already present in Chroma. If your Chroma client supports efficient upserts, you can swap the existence check for an upsert workflow.

Ask command

At query time we run semantic search, build the prompt with the returned chunks, call the LLM, and print the model’s answer along with a list of cited sources.

Stats and reset

Utilities to inspect and reset the local Chroma index:

CLI wiring (argparse)

The CLI wires the commands described above. This is the entrypoint for the script.

Quick demo (example commands and expected outputs)

Use these example commands to verify the workflow.
  1. Initialize (environment check):
  1. Ingest local files:
  1. Check stats:
  1. Ask a question using top-k retrieval:
  1. Ask another question:
  1. Reset the index, then ask (shows the behavior when there is no index):
Re-run ingest to rebuild the index and queries will work again.

Command reference


What we accomplished

  • Replaced the tiny in-memory demo with a simple file-based ingestion pipeline.
  • Implemented deterministic chunk IDs so re-ingests skip duplicates.
  • Used top-k retrieval from Chroma to build a deterministic context for the LLM.
  • Instructed the LLM to answer only from the provided context and to include citations.
  • Kept the code intentionally minimal so you can extend it for batching, semantic chunking, richer metadata, or different embeddings/LLM providers.
This demo is for local testing and small datasets. For production, consider secure deployment of Ollama/Chroma, robust error handling, rate limits, batching, and privacy/PII considerations for ingested content.

Next steps

  • Improve chunking (semantic splits, sentence boundaries, or models like BERT-based splitters).
  • Add update/delete semantics for documents (incremental ingestion).
  • Add richer metadata (file paths, timestamps, tags) to improve retrieval filtering.
  • Add batching and parallelization for embeddings and adds.
  • Expose a small web UI to demo RAG in a browser.

This completes the minimal file-based RAG demo using Ollama + Chroma.

Watch Video