Надежный RAG с проверкой источников: Gemma-4, BGE-M3, pgvector | AiManual
AiManual Logo Ai / Manual.
08 Июн 2026 Гайд

Создание надежного RAG с проверкой источников: Gemma-4, BGE-M3, pgvector и гард от галлюцинаций

Пошаговый гайд по созданию RAG-пайплайна с Gemma-4, BGE-M3 и pgvector. Программная проверка источников, реранкинг и отказ при отсутствии данных — защита от галл

Реклама
vec_recv1

Когда AI врет убедительно: почему обычный RAG - это лотерея

Вы когда-нибудь спрашивали у RAG-системы "Какая столица Франции?" и получали ответ "Лион, потому что в документах упоминался Лион 10 раз"? Я - да. И это не баг, а фича обычного RAG без гарда. LLM великолепно выдумывает, если контекст пуст или нерелевантен. Проблема в том, что модель не умеет молчать - она обязана дать ответ, даже если информации нет. В результате - уверенные галлюцинации, которые пользователь принимает за чистую монету.

В 2026 году, когда RAG-системы уже стали мейнстримом (читайте наш roadmap), галлюцинации остаются главной головной болью. Но решение есть: программная проверка источников на каждом этапе. Мы не полагаемся на "честность" LLM, а строим конвейер, который либо подтверждает наличие фактов, либо отказывается отвечать.

Стек, который не подведет: Gemma-4, BGE-M3 и pgvector в одном флаконе

Набор инструментов на июнь 2026 года выглядит так:

  • Ollama - локальный рантайм для LLM, последняя версия 0.9.x. Идеально для тестов и production с GPU.
  • Gemma-4 (8B и 26B) - модель от Google, доступная через Ollama. Превосходное качество в задачах RAG, хотя есть странности с кодом (подробности в нашем тесте).
  • BGE-M3 (BAAI) - мультиязычная эмбеддинг-модель, поддерживает dense и sparse векторы. Актуальная версия - v1.2. Сравнение с конкурентами показало, что M3 выигрывает на русскоязычных данных.
  • pgvector с HNSW-индексом - векторное расширение PostgreSQL. Версия 0.9.0+.
  • bge-reranker (BAAI) - реранкер для второго прохода. Тяжелый, но точный.
  • Программный гард - модуль проверки: пороги, цитирование, отказ.

💡 Зачем здесь реранкер? Первичный поиск по BGE-M3 дает 10-30 чанков. Реранкер пересчитывает релевантность с учетом запроса и каждого чанка - качество скачет с 70% до 95%. Экономия: не тащим весь контекст в LLM, а берем только топ-3-5.

Шаг за шагом: от документов к ответу с верификацией

1 Поднимаем инфраструктуру

Устанавливаем Ollama, пуллим модели и настраиваем PostgreSQL с pgvector.

# ОLLAMA
curl -fsSL https://ollama.com/install.sh | sh
ollama pull gemma4:8b          # или gemma4:26b если есть 24+ GB VRAM
ollama pull bge-m3
ollama pull bge-reranker

# PostgreSQL + pgvector
docker run -d --name pgvector -e POSTGRES_PASSWORD=pass -p 5432:5432 pgvector/pgvector:0.9.0-pg16

2 Индексация документов

Разбиваем документы на чанки по 512 токенов с пересечением 64 токена. Каждый чанк превращаем в эмбеддинг через BGE-M3 и сохраняем в таблицу.

import ollama
import psycopg2
from sentence_transformers import SentenceTransformer

# Используем BGE-M3 через Ollama (или локально через sentence-transformers)
# Советую локально - быстрее.
model = SentenceTransformer('BAAI/bge-m3', device='cuda')

conn = psycopg2.connect(...)
cur = conn.cursor()

# Таблица
cur.execute("""
CREATE TABLE IF NOT EXISTS docs (
    id SERIAL PRIMARY KEY,
    chunk TEXT,
    source TEXT,
    embedding vector(1024)
);
CREATE INDEX ON docs USING hnsw (embedding vector_cosine_ops);
""")

chunks = split_document(text)  # ваша функция
for chunk in chunks:
    emb = model.encode(chunk)
    cur.execute("INSERT INTO docs (chunk, source, embedding) VALUES (%s, %s, %s)",
                (chunk, filename, emb.tolist()))
conn.commit()

3 Поиск + реранкинг

Пользователь задает вопрос. Получаем его эмбеддинг, делаем векторный поиск в pgvector, затем реранкинг через bge-reranker.

query = "Столица Франции - это Париж?"
q_emb = model.encode(query).tolist()

# Поиск 20 ближайших
cur.execute("""
SELECT chunk, source, 1 - (embedding <=> %s::vector) AS cosine_sim
FROM docs
ORDER BY embedding <=> %s::vector
LIMIT 20
""", (q_emb, q_emb))
candidates = cur.fetchall()

# Реранкинг
import ollama
rerank = ollama.rerank('bge-reranker', query=query, documents=[c[0] for c in candidates])
# Получаем отсортированные чанки с новыми скорами
reranked = sorted(rerank.results, key=lambda x: x.relevance_score, reverse=True)
top = [r.document for r in reranked[:5]]

4 Гард: проверка порога и отказ

Критический момент. Мы не отправляем в LLM пустой контекст. Если лучший релевантность-скор ниже 0.6 - системы отвечает "Извините, я не могу найти подтверждение этой информации в доступных источниках".

THRESHOLD = 0.6
if not top or reranked[0].relevance_score < THRESHOLD:
    return {"answer": "Недостаточно данных для ответа.", "sources": []}
# Иначе формируем контекст
context = '\n---\n'.join(top)

5 Генерация ответа с цитированием

Промпт требует от Gemma-4 отвечать только на основе контекста и указывать номера источников. После генерации проверяем, что в ответе есть цитаты.

prompt = f"""Ты - ассистент, который отвечает только на основе предоставленных документов.
Если документа не хватает для ответа - напиши "Недостаточно информации".
Документы:
{context}
Вопрос: {query}
Ответ (с указанием номеров документов в квадратных скобках):"""

response = ollama.chat(model='gemma4:8b', messages=[{'role': 'user', 'content': prompt}])
answer = response['message']['content']

# Простая проверка: ответ содержит хотя бы одну ссылку вида [1]
import re
if not re.search(r'\[\d+\]', answer):
    # Повторяем запрос с жесткой инструкцией или возвращаем отказ
    return {"answer": "Не удалось сформировать ответ с цитатами.", "sources": []}

return {"answer": answer, "sources": [c[1] for c in top]}

Ловушки, в которые я вляпался (и вы тоже вляпаетесь)

  • Слишком агрессивный порог. 0.6 может отсекать полезные чанки на специфических доменах. Лучше подбирать эмпирически: протестируйте на 100 запросах с известными ответами. Мы используем 0.55 для общих знаний и 0.7 для медицинских данных.
  • Реранкер убивает latency. bge-reranker на CPU может занимать 2-3 секунды на 20 чанков. Совет: используйте GPU или ограничьте количество кандидатов до 5 и откажитесь от реранкера, если скорость критична. Но качество упадет.
  • Gemma-4 игнорирует инструкции. Несмотря на явный промпт, модель иногда выдумывает источники. Методы аблитерации не помогают с игнорированием инструкций - здесь спасает только второй проход с LLM-as-judge. Мы добавляем проверку: отправляем ответ и контекст на другую LLM (например, llama3) и просим верифицировать, что все утверждения соответствуют документам.
  • Модель "молчит" при недостатке данных. Если вы настроили отказ, пользователи увидят много "Не знаю". Готовьте UX: показывайте лучшие найденные фрагменты, даже если ответа нет.

Часто задаваемые вопросы (неочевидные)

Зачем BGE-M3, если есть эмбеддинги от OpenAI?

OpenAI эмбеддинги платные, а качество на русском у BGE-M3 выше - сравнение тут. К тому же вы контролируете данные.

Как быть с обновлением документов?

Добавляйте поле updated_at и при индексации удаляйте старые записи по source. Или используйте партиционирование по дате.

А что, если пользователь попросит "расскажи о себе"?

Хороший вопрос: такие запросы не пройдут проверку контекста - порог не будет достигнут, и система вежливо откажется. Это защита от промпт-инъекций.

Последний совет: не доверяйте LLM даже с гардом. Периодически запускайте автоматические тесты на наборе вопросов с эталонными ответами. Мы используем ту же Gemma-4 как judge, но с отдельным промптом. И да, визуализация эмбеддингов очень помогает понять, куда уходят ваши документы.

Построить надежную RAG-систему реально. Но только если вы перестанете надеяться на "магию" LLM и начнете проверять каждый шаг. Gemma-4, BGE-M3 и pgvector дают твердую основу. Гард от галлюцинаций - ваш последний рубеж. Не пропускайте врага.

Подписаться на канал