Technical branch for developers Assemble manually

RAG: How to Give a Model Company Knowledge

Why doesn’t the LLM know about your CRM? How to connect internal documents without fine-tuning? We’ll walk through building a production-ready RAG pipeline—with code, pitfalls, and evals.

What is RAG and why is it needed?

RAG (Retrieval-Augmented Generation) — architecture where the LLM receives context not from training, but from an external source at the moment of generating a response. This enables:

  • Use current, private data (knowledge bases, documents, CRM exports).
  • Avoid overfitting and reduced model speed.
  • Control the source of the response (important for compliance and auditing).

Without RAG, the LLM “hallucinates”—it doesn’t know what’s in your database until you explicitly provide that context.

RAG Pipeline: 4 Stages

flowchart TD  
    A[Data Source: PDF, DB, Notion] --> B[Preprocessing]  
    B --> C[Vectorization (Embedding)]  
    C --> D[Vector DB (Chroma, Qdrant)]  
    D --> E[Relevance Search]  
    E --> F[Context + Query → LLM]  
    F --> G[Answer with Sources]

Key idea: Divide and conquer — data processing separate from generation.

Minimal RAG Pipeline (Python + LangChain)

from langchain_community.document_loaders import TextLoader  
from langchain_text_splitters import RecursiveCharacterTextSplitter  
from langchain_chroma import Chroma  
from langchain_openai import OpenAIEmbeddings, ChatOpenAI  
from langchain_core.prompts import ChatPromptTemplate  

# 1. Load and split  
loader = TextLoader("docs/internal_knowledge.txt")  
docs = loader.load()  
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)  
chunks = splitter.split_documents(docs)  

# 2. Vectorize and store  
vectorstore = Chroma.from_documents(  
    chunks,  
    embedding=OpenAIEmbeddings(),  
    persist_directory="./chroma_db"  
)  

# 3. Retrieval + generation  
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})  
prompt = ChatPromptTemplate.from_template("""  
Answer the question using only the provided context.  
Question: {question}  
Context: {context}  
""")  
llm = ChatOpenAI(model="gpt-4o-mini")  

# Pipeline  
def rag_chain(question):  
    docs = retriever.invoke(question)  
    context = "\n\n".join([d.page_content for d in docs])  
    return llm.invoke(prompt.format(question=question, context=context))  

print(rag_chain("How to apply for childcare leave?").content)

In production, replace `TextLoader` with real sources (PDF, API, databases), add post-processing and evals.

Common mistakes (and how to avoid them)

  • Chunks are too large → the model loses context. Solution: Chunks of 300–600 tokens, with 10–20% overlap.
  • Bad embeddings → Search returns “similar, but not quite.” Solution: Use specialized models (e.g., `bge-small-en-v1.5` for Russian—`cointegrated/rubert-tiny2`).
  • No noise filtering → the model generates outputs based on outdated/false data. Solution: Add post-filtering by relevance (BM25, cosine similarity + metrics).
  • Ignoring metadata → impossible to trace the source. Solution: Store `source`, `page`, and `updated_at`, and pass them to the prompt.

How to Check RAG Quality: Simple Evaluation

Create a test set of 10–20 questions with model answers and sources. For each question:

  1. Get a response from RAG.
  2. Check if the source is in the top-3 most relevant chunks (precision@3).
  3. Rate the answer on a 0–1 scale (yes/no based on accuracy and completeness).
# Metric example
def eval_rag(question, expected_answer, expected_sources):
    response = rag_chain(question)
    retrieved = retriever.invoke(question)
    sources_found = any(src in [d.metadata.get('source') for d in retrieved] 
                        for src in expected_sources)
    # Simple check: whether there are keywords from the standard
    has_keywords = all(kw.lower() in response.content.lower() 
                       for kw in ["vacation", "child", "law"])
    return int(sources_found and has_keywords)

# Result: 0.8 - 80% correct answers

Goal: >0.7 precision@3 and >0.6 factual accuracy.

Pre-Launch Production Checklist

  • [ ] Data normalized (encoding, HTML cleanup, structuring).
  • [ ] Chunks with overlap and metadata (source, date, type).
  • [ ] Embeddings tested for relevance (visualization via UMAP).
  • [ ] Vector DB with index (HNSW/IVF) and search timeout.
  • [ ] Request logging + retries on timeouts.
  • [ ] Prompt with instruction: “If there is no answer in the context, say so.”
  • [ ] Metrics: latency (p95 < 1.5s), precision@3, user satisfaction (A/B).

Next step

After basic RAG—move on to:

Don’t forget: RAG isn’t “set and forget.” It’s a cycle: monitor → audit → improve chunks and embeddings.