Техническая ветка для разработчиков Собрать руками

RAG: как давать модели знания компании

Почему LLM не знает про вашу CRM? Как подключить внутренние документы без переобучения? Рассказываем, как собрать RAG-пайплайн, который работает в продакшене — с кодом, ловушками и evals.

Что такое RAG и зачем он нужен

RAG (Retrieval-Augmented Generation) — архитектура, при которой LLM получает контекст не из обучения, а из внешнего источника в момент генерации ответа. Это позволяет:

  • Использовать актуальные, приватные данные (базы знаний, документы, CRM-выгрузки).
  • Избежать переобучения и снижения скорости работы модели.
  • Контролировать источник ответа (важно для compliance и аудита).

Без RAG LLM «обобщает» — он не знает, что у вас в базе, пока вы не дадите ему этот контекст явно.

Пайплайн RAG: 4 этапа

flowchart TD
    A[Источник данных: PDF, DB, Notion] --> B[Preprocessing]
    B --> C[Векторизация (embedding)]
    C --> D[Векторная БД (Chroma, Qdrant)]
    D --> E[Поиск по релевантности]
    E --> F[Контекст + запрос → LLM]
    F --> G[Ответ с источниками]

Ключевая идея: разделяй и властвуй — обработка данных отдельно от генерации.

Минимальный RAG-пайплайн (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. Загрузка и разбиение
loader = TextLoader("docs/internal_knowledge.txt")
docs = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_documents(docs)

# 2. Векторизация и хранение
vectorstore = Chroma.from_documents(
    chunks, 
    embedding=OpenAIEmbeddings(),
    persist_directory="./chroma_db"
)

# 3. Поиск + генерация
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
prompt = ChatPromptTemplate.from_template("""
Ответь на вопрос, используя только контекст.
Вопрос: {question}
Контекст: {context}
""")
llm = ChatOpenAI(model="gpt-4o-mini")

# Пайплайн
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("Как оформлять отпуск по уходу за ребёнком?").content)

В продакшене замените `TextLoader` на реальные источники (PDF, API, базы), добавьте пост-обработку и evals.

Типичные ошибки (и как их избежать)

  • Слишком крупные чанки → модель теряет контекст. Решение: чанки 300–600 токенов, с перекрытием 10–20%.
  • Плохие эмбеддинги → поиск возвращает «похожее, но не то». Решение: использовать специализированные модели (e.g., `bge-small-en-v1.5` для русского — `cointegrated/rubert-tiny2`).
  • Нет фильтрации шума → модель генерирует на основе устаревших/ложных данных. Решение: добавить пост-фильтрацию по релевантности (BM25, косинусная близость + метрики).
  • Игнорирование метаданных → невозможно отследить источник. Решение: хранить `source`, `page`, `updated_at` и передавать в промпт.

Как проверить качество RAG: простой eval

Создайте тестовый набор из 10–20 вопросов с эталонными ответами и источниками. Для каждого вопроса:

  1. Получите ответ от RAG.
  2. Проверьте, есть ли источник в топ-3 релевантных чанках (precision@3).
  3. Оцените ответ по шкале: 0–1 («да/нет» по точности и полноте).
# Пример метрики
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)
    # Простая проверка: есть ли ключевые слова из эталона
    has_keywords = all(kw.lower() in response.content.lower() 
                       for kw in ["отпуск", "ребёнок", "закон"])
    return int(sources_found and has_keywords)

# Результат: 0.8 — 80% правильных ответов

Цель: >0.7 по precision@3 и >0.6 по factual accuracy.

Чеклист перед запуском в продакшен

  • [ ] Данные нормализованы (кодировка, HTML-очистка, структурирование).
  • [ ] Чанки с перекрытием и метаданными (источник, дата, тип).
  • [ ] Эмбеддинги протестированы на релевантность (визуализация через UMAP).
  • [ ] Векторная БД с индексом (HNSW/IVF) и таймаутом поиска.
  • [ ] Логирование запросов + ретраи при таймаутах.
  • [ ] Промпт с инструкцией «если в контексте нет ответа — скажи об этом».
  • [ ] Метрики: latency (p95 < 1.5s), precision@3, user satisfaction (A/B).

Следующий шаг

После базового RAG — переходите к:

Не забудьте: RAG — это не «включил и забыл». Это цикл: мониторинг → аудит → улучшение чанков и эмбеддингов.