Create and use a RAG system
===========================
This tutorial walks through the full lifecycle of a Klea RAG system:
preparing documents, building a vector store, configuring the RAG
pipeline, and querying it.
Overview
--------
By the end you will have:
* A Chroma vector store populated with chunks from your own documents
* A running Klea RAG server backed by that store
* Hands-on experience querying the system via the CLI and web UI
New to RAG? Read :doc:`../concepts/rag` for an overview of how
retrieval-augmented generation works and why you might use it.
Prerequisites
-------------
* Python 3.12 or later
* Packages installed (see :doc:`install guide <../install>`) with Chroma and ingestion
extras:
.. code-block:: bash
pip install klea_rag[chroma,ollama] klea_utils[ingest]
.. note::
``klea_rag[chroma]`` provides Chroma vector store support.
``klea_rag[ollama]`` provides the Ollama inference provider.
``klea_utils[ingest]`` pulls in `Docling `_
and its PyTorch dependency. The download is several hundred MB.
On systems with a CUDA-capable GPU, PyTorch will use the GPU
automatically for faster document processing.
* A running `Ollama `_ instance with the required
models:
.. code-block:: bash
ollama pull qwen3:0.6b
ollama pull llama-guard3:1b
ollama pull bge-m3:latest
This tutorial uses Ollama for all inference (chat, guard, and
embeddings). Klea supports other providers too -- see
:doc:`../install` for HuggingFace, OpenAI, Anthropic, and other
LangChain-compatible options.
Step 1: Prepare source documents
---------------------------------
Place the files you want to index in a single directory. Docling
handles a wide range of formats: PDF, HTML, Markdown, DOCX, PPTX, XLSX,
images, and more (see `Docling supported formats
`_
for the full list).
For this tutorial we will refer to this directory as
````.
Step 2: Create a vector store
------------------------------
.. code-block:: bash
klea-stores-create build \\
--collection my-docs \\
--store chroma:/path/to/my-store \\
--bm25-store /path/to/my-bm25-corpus.pkl
The ``build`` command runs the full pipeline:
1. **Convert** -- every supported file is parsed with Docling into a
structured document.
2. **Chunk** -- documents are split into token-aware chunks (450 tokens
by default) using Docling's ``HybridChunker``. Each chunk retains
its heading hierarchy as metadata.
3. **Embed** -- chunks are embedded using ``bge-m3`` (or whichever
embedding model you configure).
4. **Store** -- embeddings and text are written to a Chroma vector store
at the path you specify.
Flags explained:
* ``--collection`` / ``-n`` -- the collection name inside the store
(e.g. ``my-docs``). This must match the ``name`` of the store's
``vector_stores`` / ``bm25_stores`` entry in the RAG config file
(e.g. ``klea.json``) -- retrieval looks stores up by name, so a
mismatch silently returns no results.
* ``--store`` / ``-s`` -- the vector store URI (e.g. ``chroma:/path``).
For Chroma, point at the store *folder*; the database file inside it
is always named ``chroma.sqlite3`` (the filename is not
configurable). A folder that does not exist yet is created. Because
one Chroma store file can hold several collections, the
``--collection`` name selects which collection within that file is
used.
* ``--bm25-store`` -- path to write the combined chunked documents to a
single pickle file that can be used as a BM25 keyword store. Defaults
to ``.pkl`` in the current directory (always written); you
can move the file afterwards and point the config at its new location.
* ``--model`` / ``-m`` -- embedding model (default ``ollama:bge-m3:latest``).
* ``--max-tokens`` -- maximum tokens per chunk (default 450).
* ``--ocr`` / ``--no-ocr`` -- whether to perform optical character
recognition (OCR, see `Wikipedia
`_)
during PDF conversion (default: on). Keep it on for scanned/
image-based PDFs; pass ``--no-ocr`` for text-based PDFs to speed up
conversion considerably.
* ``--force`` / ``-f`` -- re-process all files even if previously cached.
Docling's inference accelerator is configured through environment
variables rather than CLI flags. By default it auto-detects the best
available device, but GPUs whose CUDA capability is below 7.0 (e.g. a
Quadro P1000) fail the Triton compiler used for the layout model. Force
Docling to the CPU in that case:
.. code-block:: bash
DOCLING_DEVICE=cpu DOCLING_NUM_THREADS=16 klea-stores-create build \\
--collection my-docs --store chroma:/path/to/my-store
``DOCLING_NUM_THREADS`` (default 4) sets the CPU threads used for model
inference; ``OMP_NUM_THREADS`` is honoured as an alternative.
Re-running ``klea-stores-create build`` on the same directory is safe --
it skips files whose content has not changed and skips chunks whose
hashes already exist in the store (idempotent). Adding new files to
the source directory and re-running adds only the new content
(incremental ingestion).
The source directory will contain a ``.klea-cache/`` folder after the
first run. This caches converted chunks so subsequent runs skip the
expensive Docling conversion.
The vector store folder will also have been created, with the
``chroma.sqlite3`` database inside it. Later runs point ``--store`` at
the same folder; the file is always named ``chroma.sqlite3``.
Step 3: Configure the RAG system
---------------------------------
Create an environment file (e.g. ``my-rag.env``):
.. code-block:: ini
KLEA_RAG_CHAT_MODEL=ollama:qwen3:0.6b
KLEA_RAG_GUARD_MODEL=ollama:llama-guard3:1b
KLEA_RAG_EMBEDDING_MODEL=ollama:bge-m3:latest
KLEA_RAG_APP_CONFIG_FILE=my-config.json
Create the JSON configuration file (``my-config.json``) that wires the
vector store to a domain:
.. code-block:: json
{
"general": {
"default_k": 5,
"k_max": 10,
"non_domain_chat": true,
"fallback_to_training_data": true
},
"domains": {
"MyDomain": {
"description": "Documents related to my project",
"vector_stores": [
{
"name": "my-docs",
"path": "chroma:/path/to/my-store"
}
],
"bm25_stores": [
{
"name": "my-docs-bm25",
"path": "/path/to/my-bm25-corpus.pkl"
}
]
}
}
}
The ``general`` section controls retrieval behaviour:
* ``default_k`` -- number of documents to retrieve per query. This is the
graph-wide default; individual vector stores can override it (see below).
* ``k_max`` -- maximum ``k`` when the evaluator requests more context.
* ``k_inc`` -- how much ``k`` is increased by each time the evaluator
requests more information.
* ``non_domain_chat`` -- whether to fall back to the LLM's training data
for questions that do not match any domain.
* ``fallback_to_training_data`` -- whether to let the LLM answer from its
own knowledge when retrieval returns nothing useful.
Each entry under ``domains`` defines a knowledge area with one or more
vector stores. The ``description`` helps the classifier route queries
to the right domain.
A store's ``name`` must exactly match the ``--collection`` name passed
to ``klea-stores-create``, and its ``path`` must match what was passed
to ``--store`` (for a vector store) or the location of the written BM25
corpus pickle. Retrieval looks stores up by name, so a mismatch means
the store is never queried.
A domain can also list ``bm25_stores``. Each ``bm25_stores`` entry's
``path`` points to a combined corpus pickle written by
``klea-stores-create --bm25-store`` (or ``klea-stores-create store
--bm25-store``). If you did not pass ``--bm25-store``, the corpus was
written to ``.pkl`` in the directory you ran the command
from; it can be moved anywhere before it is referenced here. When both
are configured, retrieval queries the vector stores and the BM25 stores
and combines the results with Reciprocal Rank Fusion -- exact
name/symbol matches from BM25 complement the semantic matches from the
vector stores.
Vector stores can override the retrieval settings independently. Stores
that set their own ``default_k``, ``k_max``, and ``k_inc`` use those
values instead of the ``general`` fallbacks, which is useful when stores
cover corpora of very different sizes:
.. code-block:: json
{
"general": {
"default_k": 5,
"k_max": 10,
"k_inc": 1
},
"domains": {
"MyDomain": {
"description": "Documents related to my project",
"vector_stores": [
{
"name": "large-corpus",
"path": "chroma:/path/to/large-store",
"default_k": 10,
"k_max": 25,
"k_inc": 5
},
{
"name": "small-corpus",
"path": "chroma:/path/to/small-store"
}
]
}
}
}
Here ``large-corpus`` retrieves up to 10 documents and can grow to 25 in
steps of 5 when the evaluator asks for more context, while ``small-corpus``
inherits the ``general`` settings (5, capped at 10, stepping by 1). The
dynamic ``k_inc``/``k_max`` adjustments only apply to stores that are
already loaded, so a store only grows once it has been queried once.
.. seealso::
:doc:`../install` for details on HuggingFace, OpenAI, and other
provider model naming conventions.
Step 4: Start the RAG server
-----------------------------
For local single-user use this step is optional: the client commands in
Step 5 start a server on the local machine automatically when none is
already running. Run ``klea-rag-serve serve`` instead when you want a
persistent backend, for example to share one server between several
clients or to run it in a separate terminal:
.. code-block:: bash
KLEA_RAG_ENV_FILE=my-rag.env klea-rag-serve serve
The server loads the configuration, initialises the embedding model,
and compiles the LangGraph pipeline. Once ready, check it is alive:
.. code-block:: bash
curl http://127.0.0.1:8005/health/ready
A ``200 OK`` response means the system is ready to accept queries.
Step 5: Query the RAG
---------------------
The client commands below use ``http://127.0.0.1:8005`` by default.
If no server is running there, they start one on the local machine for
the session and stop it when they exit; if a server is already running
(for example from Step 4) they reuse it. Pointing ``--server`` at a
remote host connects without starting anything.
Single-query mode is the quickest way to test:
.. code-block:: bash
klea-rag cli --single-query "What does my collection of documents cover?"
For an interactive session:
.. code-block:: bash
klea-rag cli
Type your questions at the prompt. Use ``quit`` to exit.
For a graphical interface, launch the NiceGUI web UI:
.. code-block:: bash
klea-rag web
The web UI uses NiceGUI and requires the ``[nicegui]`` extra, while the
CLI mode has no extra dependencies.
Both methods use the server at ``http://127.0.0.1:8005`` by default.
Use ``--server`` to point at a different address.
Going further
--------------
Once the basic pipeline works, here are natural next steps:
**Metadata enrichment**
Add source URLs or other metadata to retrieved chunks. First run
``klea-stores-create chunk`` to generate a ``metadata-map.template.json``.
Each file's ``DEFAULT`` entry is pre-filled automatically with
bibliographic metadata (title, authors, keywords, DOI, URL) where it
could be extracted -- see :doc:`../concepts/rag` for the extraction
cascade. Review and correct the values (check the
``_metadata_complete`` flag), then ``klea-stores-create store
--metadata-map ``. See ``klea-stores-create --help`` for
examples. The metadata-map file may live inside the source directory:
it and the generated template are excluded from ingestion.
**Different embedding models**
Swap ``ollama:bge-m3:latest`` for a HuggingFace embedding model
(see :doc:`../install` for model naming conventions).
**Multiple domains**
Add more ``domains`` entries in the JSON config, each with its own
vector store and description. The classifier will route queries
automatically.
**MCP tools**
Add ``mcp_servers`` to a domain config to give the LLM access to
external tools (e.g. a NeuroML validation server). See the example
in ``rag_pkg/example-configs/klea_rag.json``.
**Separate chunk-and-store workflow**
Use ``klea-stores-create chunk`` to convert and cache without writing
to a store, then ``klea-stores-create store`` later. This lets you
inspect the chunks and edit the metadata map before embedding.
**Hybrid keyword retrieval**
Add a BM25 store alongside a vector store: run
``klea-stores-create store --bm25-store /path/to/corpus.pkl`` and add a
``bm25_stores`` entry to the domain config. Retrieval then fuses
semantic and lexical matches with Reciprocal Rank Fusion, which helps
with exact names, symbols, and terminology. See :doc:`../concepts/rag`
for details.
Troubleshooting
---------------
**Ollama is not running**
Start it with ``ollama serve`` or run Ollama as a system service.
**Model not found**
Ensure you have pulled all three models (chat, guard, embedding).
Run ``ollama list`` to see what is available.
**Server fails to start**
Check that ``KLEA_RAG_ENV_FILE`` points to a valid env file and that
the JSON config file path inside it is correct. Look for JSON syntax
errors (trailing commas, missing quotes).
**Queries return empty or irrelevant results**
Increase ``default_k`` in the JSON config. Verify the vector store
path and collection name match. Check that your source files are in
a format Docling supports.
.. seealso::
* :doc:`../cli/klea-stores-create` -- full CLI reference for vector store
creation
* :doc:`../cli/klea-rag-serve` -- server CLI reference
* :doc:`../cli/klea-rag` -- client CLI reference
* :class:`~klea_utils.stores.ingestion.StoresBuilder` -- Python API for
ingestion
* :class:`~klea_utils.stores.retrieval.vs.VSRetriever` -- Python API for
retrieval