Введение
RAG (Retrieval-Augmented Generation) — это не просто модный термин, а рабочий инструмент для создания точных ответов на основе собственных данных. Однако на практике разработчики сталкиваются с тремя узкими местами: как правильно разбить документы на чанки, как улучшить поиск с помощью гибридных методов и как заставить генератор не выдумывать факты. Я собрал 10 проверенных промтов, которые помогают решать эти задачи. Каждый промт — это готовый шаблон с примером кода и пояснением. Вы можете адаптировать их под свои проекты, будь то чат-бот для техподдержки или аналитическая система.
Подборка промтов
1. Чанкинг с перекрытием для контекстной связности
Проблема: При разбиении текста на фиксированные куски теряется смысл на стыках. Решение — использовать перекрытие (overlap). Этот промт задаёт стратегию чанкования с настраиваемым размером и перекрытием, чтобы модель не теряла контекст.
Пример использования: Вы обрабатываете техническую документацию. Без перекрытия предложения «Отключите питание» и «Затем нажмите кнопку Reset» могут попасть в разные чанки, и модель не поймёт логику.
Код:
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=100,
length_function=len,
separators=["\n\n", "\n", ".", " "]
)
chunks = text_splitter.split_text(document)
2. Эмбеддинги с нормализацией для косинусного сходства
Проблема: Векторные представления (эмбеддинги) имеют разную длину, что искажает косинусное сходство. Промт задаёт нормализацию векторов перед поиском.
Пример: При использовании модели all-MiniLM-L6-v2 без нормализации результаты ранжирования могут быть нестабильными. Нормализация фиксирует это.
Код:
from sentence_transformers import SentenceTransformer
import numpy as np
model = SentenceTransformer('all-MiniLM-L6-v2')
embeddings = model.encode(chunks)
normalized = embeddings / np.linalg.norm(embeddings, axis=1, keepdims=True)
3. Гибридный поиск: BM25 + векторный поиск
Проблема: Векторный поиск плохо находит точные совпадения (например, артикулы товаров), а BM25 — семантически близкие фразы. Гибрид объединяет оба подхода.
Пример: Поиск «iPhone 15»: BM25 найдёт точное совпадение, а векторный — «смартфон Apple 2024 года». Результаты объединяются через взвешенную сумму.
Код:
from rank_bm25 import BM25Okapi
import numpy as np
bm25 = BM25Okapi([chunk.split() for chunk in chunks])
vector_results = vector_search(query, top_k=10)
bm25_results = bm25.get_top_n(query.split(), chunks, n=10)
# Комбинируем с весами 0.5 и 0.5
combined = combine_results(vector_results, bm25_results, alpha=0.5)
4. Переранжирование с помощью кросс-энкодера
Проблема: Векторный поиск возвращает много релевантных, но не точных результатов. Кросс-энкодер (например, cross-encoder/ms-marco-MiniLM-L-6-v2) переранжирует топ-N.
Пример: После первого этапа у вас 20 чанков. Кросс-энкодер оценивает пару «запрос-чанк» и выдаёт скор, что повышает точность с 70% до 90%.
Код:
from sentence_transformers import CrossEncoder
model = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
scores = model.predict([(query, chunk) for chunk in top_chunks])
reranked = [chunk for _, chunk in sorted(zip(scores, top_chunks), reverse=True)]
5. Генерация с проверкой фактов (grounding)
Проблема: LLM может выдумывать факты (галлюцинации). Промт заставляет модель опираться только на переданные чанки и указывать источник.
Пример использования: В чат-боте по продукту модель пишет «Наш сервер поддерживает 1000 запросов в секунду», хотя в документации указано 500. Промт предотвращает это.
Код:
prompt = f"""Ответь на вопрос, используя ТОЛЬКО следующий контекст. Если контекст не содержит ответа, скажи 'Не знаю'. Укажи номер чанка-источника.
Контекст: {context}
Вопрос: {query}
Ответ:"""
response = llm.generate(prompt)
6. Динамический чанкинг по семантическим границам
Проблема: Фиксированный размер чанка не учитывает логику текста (например, начало и конец раздела). Используйте NLTK или spaCy для разбивки по предложениям.
Пример: Документ с параграфами: чанкинг по границам предложений сохраняет целостность абзацев.
Код:
import nltk
nltk.download('punkt')
sentences = nltk.sent_tokenize(document)
chunks = []
current = []
for sent in sentences:
if len(' '.join(current)) + len(sent) > 500:
chunks.append(' '.join(current))
current = [sent]
else:
current.append(sent)
7. Фильтрация нерелевантных чанков по порогу сходства
Проблема: Векторный поиск возвращает чанки с низкой релевантностью. Установите порог (например, 0.7), чтобы отсечь шум.
Пример: При запросе «цветы» система может вернуть чанк про «садовые инструменты» с косинусным сходством 0.4. Порог удаляет его.
Код:
threshold = 0.7
filtered = [(chunk, score) for chunk, score in results if score >= threshold]
8. Аугментация запроса: переформулировка для улучшения поиска
Проблема: Пользовательский запрос короткий («цена»). LLM генерирует несколько вариантов (стоимость, тариф) и ищет по каждому.
Пример: Запрос «отзывы» -> варианты: «рецензии», «мнения пользователей», «оценки». Поиск становится полнее.
Код:
queries = llm.generate(f"Сгенерируй 5 синонимичных вариантов запроса '{query}':")
results = [vector_search(q) for q in queries]
aggregated = merge_results(results)
9. Хранение метаданных в векторной БД для фильтрации
Проблема: Поиск по всем документам без фильтрации по дате или категории замедляется и снижает точность. Добавьте метаданные (дата, тег) в индекс.
Пример: Вопрос «новости за июль 2026» — фильтр по метаданным даты ускоряет поиск в 10 раз.
Код:
# Пример для ChromaDB
collection.add(
documents=chunks,
metadatas=[{"date": "2026-07-01", "category": "tech"}],
ids=["id1", "id2"]
)
results = collection.query(query_texts=[query], where={"date": {"$gte": "2026-07-01"}})
10. Оценка качества RAG через метрики (hit rate, MRR)
Проблема: Невозможно улучшить то, что не измеряешь. Промт вычисляет hit rate (доля запросов, где ответ найден в топ-1) и MRR (средняя обратная позиция).
Пример: На 100 запросах hit rate = 0.75 означает, что 75 ответов — в первом чанке. Это baseline для улучшения.
Код:
from sklearn.metrics import ndcg_score
hit_rate = sum(1 for q in queries if q['relevant_chunk'] in q['top_chunks'][:1]) / len(queries)
mrr = sum(1 / (q['top_chunks'].index(q['relevant_chunk']) + 1) for q in queries if q['relevant_chunk'] in q['top_chunks']) / len(queries)
print(f"Hit Rate: {hit_rate:.2f}, MRR: {mrr:.2f}")
Заключение
Эти 10 промтов — база для построения RAG-системы, которая работает стабильно. Начните с чанкинга и эмбеддингов, затем подключите гибридный поиск и переранжирование. Главное — тестируйте каждое изменение на своих данных. В следующей статье разберём, как автоматизировать оценку качества с помощью этих метрик. Если у вас есть вопросы — пишите в комментариях, обсудим.
Комментарии