Skip to main content

Build a RAG bot

A RAG bot answers questions from a folder of documents. The agent indexes the folder once, then on each question retrieves the relevant chunks and uses them as context for the reply. Citations make it easy to verify what the agent claims.

Building on digitorn.ai (managed)? This how-to is for self-host.

On the hosted platform you don't wire the rag module by hand: open the Knowledge manager in Studio, add files or a website, attach it to your agent, and it searches automatically via the built-in knowledge.search tool — no Qdrant to run, no YAML. This how-to builds the self-hosted version, where you run your own daemon and vector backend.

This how-to puts together three things from the tutorials: memory isn't needed (RAG keeps state in its own knowledge base), tools are the rag.* actions, and the agent loop is unchanged.

What you'll build​

  • A folder of three small markdown files about a fictional SaaS product (pricing, onboarding, refund policy).
  • A YAML app rag-bot that uses the rag module.
  • A two-question session where the agent retrieves the right file and quotes from it.

Prerequisites​

  • A running daemon (with the gateway component up; embeddings go through it). digitornd -config config.yaml run covers both.
  • An authenticated user in the daemon.
  • A deepseek_main credential (or whatever provider you prefer) in the per-user vault. Adjust the brain block to taste.

Sample data​

Three markdown files in a single directory.

bash
mkdir -p ./rag-data

rag-data/pricing.md:

markdown
# Pricing notes

Our SaaS plans:
- Starter: $29/month, 1 user, basic features
- Team: $99/month, 5 users, all features
- Enterprise: custom pricing, contact sales

Annual plans get 20% off. We do NOT offer free trials.

rag-data/onboarding.md:

markdown
# Onboarding flow

New users go through:
1. Signup form (email + password, no SSO yet)
2. Email verification (link expires in 24h)
3. Workspace creation (one workspace per account)
4. Invite teammates (Team plan and above)

Average completion time: 8 minutes. Drop-off is highest at step 2.

rag-data/refunds.md:

markdown
# Refund policy

Full refunds are issued within 30 days of purchase, no questions
asked. After 30 days, refunds are pro-rated for the unused portion
of the billing period. Annual plans cancelled mid-year are refunded
the remaining months minus a 10% admin fee.

The YAML​

app.yaml
app:
app_id: rag-bot
name: RAG Bot
version: "1.0"

runtime:
mode: conversation
workdir_mode: auto
max_turns: 8
timeout: 180

agents:
- id: main
role: assistant
brain:
provider: deepseek
model: deepseek-chat
backend: openai_compat
credential:
ref: deepseek_main
scope: per_user
provider: deepseek
config:
api_key: "{{env.DEEPSEEK_API_KEY}}"
base_url: https://api.deepseek.com/v1
temperature: 0
max_tokens: 512
system_prompt: |
You answer questions about a knowledge base named "docs".

On the FIRST turn the knowledge base may not exist yet. If
the user asks you to index something, do it in this order:
1. rag.create_knowledge_base(name="docs")
2. rag.ingest_directory(knowledge_base="docs", path=<the path>,
extensions=[".md"])
Then confirm in one short sentence.

On follow-up turns, call rag.query(knowledge_base="docs",
query=<the user question>) and answer based on the retrieved
chunks. Cite the file name in your reply, e.g. (from pricing.md).

tools:
modules:
rag: {}
capabilities:
default_policy: auto

ui:
greeting: "Ask me to index a folder, then ask questions about its content."

Deploy and chat​

bash
digitorn install rag-bot.yaml
digitorn chat rag-bot --workdir ./rag-data

The CLI doesn't prompt for a working directory - it silently defaults to whatever directory it's run from. Pass -w/--workdir explicitly (or cd ./rag-data first) so the session's workdir is the folder you want indexed.

Live transcript​

Three real turns against a running daemon. The agent created a fresh knowledge base, ingested the three markdown files, then answered two follow-up questions with citations.

Turn 1 - index the folder​

text
> Index everything under ./rag-data into
a knowledge base called 'docs'.

Done! I've indexed everything under
`./rag-data` into the **docs** knowledge
base. It found 3 documents (markdown files) and created 3 chunks.
You can now ask me questions about the content and I'll retrieve
answers from the knowledge base.

The agent fired three tool calls during this turn (tool_calls_count: 3): one rag.create_knowledge_base, one rag.ingest_directory, and one stats call to confirm the load.

Turn 2 - retrieve from refunds.md​

text
> What's the refund policy after 30 days?

Here's what the refund policy says for after 30 days (from
**refunds.md**):

- **Within 30 days**: Full refund, no questions asked.
- **After 30 days**: Refunds are **pro-rated** for the unused
portion of the billing period.
- **Annual plans** cancelled mid-year: You get a refund for the
remaining months **minus a 10% admin fee**.

Five tool calls this turn (tool_calls_count: 5): the agent called rag.query once for the main question, then a few follow-up retrievals to enrich the answer with the within-30-day case and the annual-plan case.

Turn 3 - retrieve from pricing.md​

text
> How much is the Team plan?

The **Team plan** is **$99/month** for up to **5 users** with all
features included. If you go annual, you get **20% off**
(from **pricing.md**).

One tool call (tool_calls_count: 1) - a single rag.query was enough to find the right chunk in pricing.md.

What's actually happening​

The default rag module pipeline does hybrid retrieval: it runs both BM25 (keyword) and semantic (vector) search in parallel, fuses the results with reciprocal-rank fusion, and returns the top-k chunks. With only three small files the fusion barely matters, but the same pipeline scales to thousands of documents without configuration changes.

The chunking strategy is recursive by default - the splitter prefers paragraph boundaries, falls back to sentence boundaries, then to fixed-size as a last resort. Override with chunking: { strategy: paragraph, size: 1000 } under the rag module config if your documents are unusual.

What the system prompt is doing​

rag.ingest_directory (and every ingest tool) creates the knowledge base on the fly if it doesn't exist yet - create_knowledge_base first isn't strictly required. The prompt still spells out the two-step order because it reads clearly and keeps the two actions conceptually separate, not because skipping the first step would error.

The "cite the file name" rule turns RAG from a black box into something verifiable. The user can cat pricing.md and check the quote line by line. Without the citation rule the agent often paraphrases without attribution.

Hardening for production​

The demo YAML is fine locally. For production:

  • Persist the vector backend (Qdrant path or remote URL) under tools.modules.rag.config.backend.
  • Use the real tool names: rag.create_knowledge_base, rag.ingest_directory, rag.query, etc. (see rag for the full list).

Cross-references​