Выбор метода Retrieval-Augmented Generation — не академическая задача. Это вопрос денег, времени и нервов. Ты можешь потратить неделю на настройку векторной базы, а потом понять, что дешёвый grep решал бы проблему быстрее. Или, наоборот, пытаться вытащить смысл из саркастичного комментария через регулярки — и получать кашу.
Я перелопатил с десяток production-систем. И вот три реальных кейса, где выбор метода RAG решал — жить сервису или умереть под нагрузкой. Без воды, с кодом и ценами на ошибку.
Кейс 1: Шаблонные документы — Regex рулит, забудьте про эмбеддинги
Проблема: У вас сотни тысяч контрактов, накладных, счетов-фактур. Они заполнены по строгим шаблонам: «Отгрузка товара по накладной №XXX от ДД.ММ.ГГГГ». Пользователь спрашивает: «Покажи накладную №12345». Вы кидаете это в RAG с cosine similarity и ловите ложные срабатывания. Модель возвращает похожие по смыслу документы, но не те. Беда.
В чём корень: шаблонные документы — это структурированные данные, замаскированные под текст. Эмбеддинги хороши для семантики, но для точного поиска по номеру или дате они — как отвёртка для забивания гвоздей.
Решение: Regex + Rule-based retrieval. Выделяем паттерны (номер накладной, дата, контрагент) регулярными выражениями, индексируем их в отдельной таблице (SQLite или Redis). Для поиска — точное совпадение или LIKE. LLM используем только для формулировки ответа.
import re
from typing import Dict, List
# Паттерны для накладных
PATTERNS = {
'invoice_number': r'накладн[а-я]+\.?\s*№\s*(\d{5,10})',
'date': r'от\s+(\d{2}\.\d{2}\.\d{4})',
'contract_amount': r'сумма[а-я]*\s*[::]?\s*(\d+[\s\.,]?\d*)\s*руб'
}
def extract_fields(text: str) -> Dict[str, str]:
result = {}
for field, pattern in PATTERNS.items():
match = re.search(pattern, text, re.IGNORECASE)
if match:
result[field] = match.group(1)
return result
# Пример использования
doc = "Накладная №98765 от 01.06.2026 на сумму 45000 руб."
fields = extract_fields(doc)
print(fields) # {'invoice_number': '98765', 'date': '01.06.2026', 'contract_amount': '45000'}
Ошибка №1: Пытаться распарсить PDF напрямую через PyMuPDF без предобработки. Шрифты, кривые кодировки — regex сломается. Сначала конвертировать в текст через pdfplumber или Unstructured.
Когда этот метод НЕ работает. Если шаблон не жёсткий, а вариативный — например, «отгрузка произведена 15-го мая», «отгрузили 15 мая 2026», «дата отгрузки 15.05.2026». Тут уже нужен fuzzy-матчинг (Levenshtein) или даже ML. Но для строгих корпоративных документов — regex вне конкуренции. Никакой LLM не нужен, latency — миллисекунды.
Кейс 2: Сарказм и неструктурированный текст — гибридный поиск + реранкинг
Проблема: У вас отзывы клиентов, чаты техподдержки, посты в соцсетях. Пользователь пишет: «Отличный сервис, чуть не умер со смеху, ждал доставку три дня». Смысл — негативный, но слова — позитивные. Эмбеддинговая модель вернёт этот отзыв на запрос «положительный опыт», хотя на деле — жалоба. Модель с сарказмом не дружит.
Сарказм — это контекст и интонация. Чистые эмбеддинги теряют до 40% точности на таких запросах. Нужен второй этап — реранкинг с Cross-Encoder, который оценивает релевантность пары (запрос, документ) целиком.
Решение: Гибридный поиск (BM25 + эмбеддинги) + Cross-Encoder реранкер. BM25 ловит ключевые слова («три дня», «не умер»), эмбеддинги хватают общий смысл, а Cross-Encoder из пары «запрос — документ» выдаёт скоринговую оценку. Отсекаем всё ниже порога.
from rank_bm25 import BM25Okapi
from sentence_transformers import SentenceTransformer, CrossEncoder
import numpy as np
# Данные
documents = [
"Отличный сервис, чуть не умер со смеху, ждал доставку три дня",
"Быстрая доставка, всё отлично",
"Ужасно долго, три дня — это никуда не годится"
]
query = "негативный отзыв о задержке доставки"
# BM25
tokenized_docs = [doc.lower().split() for doc in documents]
bm25 = BM25Okapi(tokenized_docs)
bm25_scores = bm25.get_scores(query.lower().split())
# Эмбеддинги
bi_encoder = SentenceTransformer('all-MiniLM-L6-v2')
query_emb = bi_encoder.encode(query)
doc_embs = bi_encoder.encode(documents)
cosine_scores = np.dot(doc_embs, query_emb) / (np.linalg.norm(doc_embs, axis=1) * np.linalg.norm(query_emb))
# Гибридная комбинация (weight=0.5)
hybrid = 0.5 * (bm25_scores / max(bm25_scores)) + 0.5 * cosine_scores
top_indices = np.argsort(hybrid)[-2:][::-1] # топ-2
# Реранкинг с Cross-Encoder
cross_encoder = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
pairs = [(query, documents[idx]) for idx in top_indices]
scores = cross_encoder.predict(pairs)
best_idx = top_indices[np.argmax(scores)]
print(f"Лучший документ: {documents[best_idx]}, confidence: {max(scores):.3f}")
Как это работает. BM25 дал высокий вес документу с «три дня» (совпадение терминов), эмбеддинги тоже схватили близость к «негативный отзыв». Cross-Encoder подтвердил: связка «негативный отзыв о задержке» с текстом про «чуть не умер» — это сарказм, релевантно. Второй этап — обязателен. Без него первым бы вылетел документ про быструю доставку.
Ошибка №2: Использовать только BM25 для саркастичных текстов — он игнорирует смысловой контекст. Использовать только эмбеддинги — они сглаживают интонацию. Только гибрид + реранкер даёт приемлемую точность (на production — 85-90%).
Подробнее про reranking я писал в статье Cross-Encoders и Reranking: тихий геноцид посредственного поиска в RAG. Там же — бенчмарки latency и cost.
Кейс 3: Изображения и сканы — Vision модели как primary retriever
Проблема: У вас PDF-отчёты с графиками, сканы договоров, фото товаров. Пользователь спрашивает: «Сколько продаж было в мае?» График не парсится в текст, OCR выдаёт «Май-2026», а числа — криво. Классический RAG с текстовым пайплайном тут бесполезен.
Решение на 2026 год — Vision-модели последнего поколения (GPT-4o, Gemini 2.5 Pro, Qwen2.5-VL-72B). Они читают изображения, графики, таблицы на уровне эксперта. Но вопрос: как их внедрить в RAG без перегрузки API и потери контекста?
Решение: Двухэтапка. Сначала мультимодальные эмбеддинги (CLIP, SigLIP) для визуально-семантического поиска среди изображений. Находим топ-5 кандидатов. Затем отправляем эти изображения (или их фрагменты) в Vision-модель с промптом, который требует извлечь ответ.
from PIL import Image
from sentence_transformers import SentenceTransformer, util
import torch
# Используем мультимодальную модель для эмбеддингов (например, CLIP через SentenceTransformers)
model = SentenceTransformer('clip-ViT-B-32')
# Закодируем изображения (предварительно загружены как тензоры или Pillow)
image_emb = model.encode([Image.open('chart_may.png'), Image.open('chart_june.png')])
query_emb = model.encode('продажи в мае 2026')
cosine_scores = util.cos_sim(query_emb, image_emb)[0]
top_image_idx = torch.argmax(cosine_scores).item()
print(f"Лучшее изображение: chart_may.png, score: {cosine_scores[top_image_idx]:.3f}")
# Затем отправляем его в Vision-модель
# Пример через OpenAI API (GPT-4o, версия 2026)
# from openai import OpenAI
# client = OpenAI()
# response = client.chat.completions.create(
# model="gpt-4o-2026-06-07",
# messages=[{
# "role": "user",
# "content": [
# {"type": "text", "text": "Проанализируй график и скажи, сколько продаж было в мае?"},
# {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}
# ]
# }]
# )
Важный нюанс: Не кидайте в Vision-модель всю страницу A4. Она съест контекстное окно и начнёт галлюцинировать. Лучше предварительно нарезать PDF на логические блоки (через unstructured.partition_pdf или PyMuPDF с детекцией изображений), а потом для каждого блока — свой ретривер. Блоки с текстом — текстовый RAG, блоки с изображениями — визуальный.
Ошибка №3: Использовать OCR Tesseract на сканах плохого качества. В 2026 году бессмысленно — GOT-OCR2 (General OCR Theory 2.0) даёт 99.5% точности даже на рукописных текстах. Ставьте его как препроцессор.
Более подробно про мультимодальные эмбеддинги и кросс-модальный поиск — в статье Мультимодальные эмбеддинги и реранкеры: кросс-модальный поиск в RAG, который работает.
Сравнительная таблица: когда что брать
| Кейс | Рекомендуемый метод | Точность (F1) | Стоимость / 1000 запросов | Latency (p95) |
|---|---|---|---|---|
| Шаблонные документы (контракты, накладные) | Regex + SQL-like точный поиск | 99%+ | $0.01 (только compute) | 5 ms |
| Сарказм, неструктурированные отзывы | Гибрид (BM25 + эмбеддинги) + Cross-Encoder | ~87% | $0.50 (2× инференс эмбеддинги + реранкер) | 300 ms |
| Изображения, сканы, графики | Мультимодальные эмбеддинги + Vision LLM | ~90% | $2.50 (токены Vision модели) | 1.5 s |
Цифры приблизительные, взяты из продакшен-систем на базе RAG 2026: От гибридного поиска до production — roadmap, который работает.
Через два года Regex будет мёртв? Нет. Vision модели станут дефолтом — да. Но пока grep работает за доли цента, а GPT-4o на скане счёт-фактуры — за доллар — не выкидывайте старые инструменты. DevOps — это про выбор правильного молотка под гвоздь, а не про модный нейросетевой швейцарский нож.