RAG: полный обзор
Как устроена генерация с поиском: восемь шагов, RAG в сравнении с дообучением и длинным промптом, плюсы и минусы, три шага в коде, ошибки и правила надёжной системы.
Коротко
RAG (retrieval-augmented generation, генерация с поиском) — способ заставить языковую модель отвечать по вашим материалам, а не по своей общей памяти. Документы режутся на куски, каждый кусок получает вектор — числовой отпечаток смысла — и ложится в базу. Когда приходит вопрос, ближайшие по смыслу куски находятся и передаются модели вместе с вопросом, и модель отвечает только по ним, ссылаясь на источники. Так ИИ-ассистенты отвечают о ценах, условиях и товарах, ничего не выдумывая. Если ничего подходящего не нашлось, хорошая RAG-система так и говорит и передаёт разговор человеку.
RAG коротко
Главные факты одной таблицей: что такое RAG, из чего он состоит и где применяется.
- Что это
- Поиск по вашим материалам плюс ответ модели по найденному
- Термин
- Введён в статье Facebook AI Research 2020 года
- Части
- Нарезка, модель эмбеддингов, векторный индекс, языковая модель
- Где живут векторы
- PostgreSQL с pgvector, SQLite с расширением или отдельная векторная база
- Модели
- Claude, GPT или локальные модели — под задачу и под данные
- Обновление
- Изменённый документ переиндексируется — без переобучения модели
- Главный риск
- Плохой поиск: модель уверенно отвечает по не тем кускам
Как устроен RAG: 8 шагов
Первые четыре шага делаются один раз и повторяются при изменении документов; последние четыре — на каждый вопрос.
| Шаг | Что происходит | От чего зависит качество |
|---|---|---|
| 1. Собрать | документы, каталог, условия, ответы на частые вопросы | актуальные и непротиворечивые источники |
| 2. Нарезать | куски по заголовкам и абзацам | одна мысль на кусок, вместе с заголовком |
| 3. Векторизовать | каждый кусок получает вектор смысла | модель эмбеддингов, которая знает язык |
| 4. Сохранить | куски и векторы ложатся в базу | источник и дата рядом с каждым куском |
| 5. Найти | ближайшие куски по смыслу и по словам | порог сходства, фильтры по разделам |
| 6. Переранжировать | вторая модель пересортировывает кандидатов | по желанию, но повышает точность |
| 7. Ответить | модель пишет только по пронумерованным кускам | строгая инструкция и ссылки |
| 8. Записать | вопрос, найденные куски, ответ, оценка | разбор журнала и закрытие пробелов |
RAG, дообучение или длинный промпт
Три способа передать модели ваши знания. Они решают разные задачи и часто сочетаются.
| Критерий | RAG | Дообучение | Всё в промпте |
|---|---|---|---|
| Хорош для | фактов, которые меняются | стиля и формата ответов | небольшого неизменного набора текстов |
| Обновление знаний | переиндексировать документ | обучать заново | поправить промпт |
| Источники в ответе | да | нет | частично |
| Объём знаний | практически без предела | ограничен данными обучения | ограничен окном контекста |
| Цена вопроса | низкая: только найденные куски | низкая | высокая: весь текст каждый раз |
| Права доступа | фильтры при поиске | невозможны | отдельный промпт на роль |
| Старт | дни | недели и набор данных | часы |
Плюсы и минусы RAG
В RAG ответ хорош ровно настолько, насколько хорош поиск. И сильные, и слабые стороны идут отсюда.
Плюсы · 5
-
Ответы по вашим данным
Цены, условия и характеристики берутся из ваших документов, а не из памяти модели.
-
Источники можно проверить
У каждого утверждения есть ссылка на кусок, из которого оно взято.
-
Свежесть без переобучения
Изменённый прайс переиндексируется за секунды.
-
Права при поиске
Клиент и менеджер получают ответы по разным наборам документов.
-
Любая модель
Модель можно заменить, не пересобирая базу знаний.
Минусы · 4
-
Поиск решает всё
Если нужный кусок не нашёлся, даже лучшая модель ответит плохо.
-
Мусор в базе
Устаревшие и противоречивые документы превращаются в уверенные неверные ответы.
-
Вопросы по всей базе
«Сколько было заказов в марте» — это запрос к базе данных, а не поиск по текстам.
-
Требует ухода
Журнал нужно разбирать, а пробелы в знаниях — закрывать.
Как RAG выглядит в коде: 3 шага
Нарезка, индексация и ответ на TypeScript с PostgreSQL и pgvector. Цепочка прогнана целиком на тестовой базе знаний мебельного магазина.
Нарезка на куски
Куски идут по заголовкам и абзацам: в одном куске одна мысль, и он знает, откуда взят.
// Разрезать документ на куски до ~800 символов по границам абзацев.
// Каждый кусок помнит источник и заголовок — ответ будет на них ссылаться.
export type Chunk = { source: string; heading: string; text: string };
export function chunk(source: string, markdown: string, maxChars = 800): Chunk[] {
const chunks: Chunk[] = [];
let heading = '';
let buffer: string[] = [];
const flush = () => {
const text = buffer.join('\n\n').trim();
if (text) chunks.push({ source, heading, text });
buffer = [];
};
for (const block of markdown.split(/\n{2,}/)) {
if (block.startsWith('#')) {
flush(); // новый заголовок всегда начинает новый кусок
heading = block.replace(/^#+\s*/, '');
continue;
}
if (buffer.join('\n\n').length + block.length > maxChars) flush();
buffer.push(block);
}
flush();
return chunks;
}
Индексация
Векторы — от любого OpenAI-совместимого API; повторная индексация заменяет куски документа одним запросом, без дублей.
// Индексация: каждый кусок получает вектор и ложится в PostgreSQL с pgvector.
// CREATE TABLE chunks (id bigserial PRIMARY KEY, source text, heading text,
// text text, embedding vector(384));
import pg from 'pg';
import { chunk } from './chunk.ts';
const db = new pg.Pool({ connectionString: process.env.DATABASE_URL });
// Любой OpenAI-совместимый API эмбеддингов — облачный провайдер или локальная модель
export async function embed(texts: string[]): Promise<number[][]> {
const res = await fetch(`${process.env.EMBEDDINGS_URL}/v1/embeddings`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.EMBEDDINGS_KEY}`,
},
body: JSON.stringify({ model: process.env.EMBEDDINGS_MODEL, input: texts }),
});
if (!res.ok) throw new Error(`embeddings: HTTP ${res.status}`);
const { data } = (await res.json()) as { data: { embedding: number[] }[] };
return data.map((d) => d.embedding);
}
export async function indexDocument(source: string, markdown: string): Promise<number> {
const pieces = chunk(source, markdown);
const vectors = await embed(pieces.map((p) => `${p.heading}\n${p.text}`));
// Один запрос: старые куски документа заменяются атомарно
await db.query(
`WITH old AS (DELETE FROM chunks WHERE source = $1)
INSERT INTO chunks (source, heading, text, embedding)
SELECT $1, h, t, e::vector FROM unnest($2::text[], $3::text[], $4::text[]) AS u(h, t, e)`,
[source, pieces.map((p) => p.heading), pieces.map((p) => p.text), vectors.map((v) => JSON.stringify(v))],
);
return pieces.length;
}
Ответ с источниками
В проверке вопросы о доставке, возврате и гарантии нашли нужный кусок со сходством 0,52–0,60, а вопрос о погоде набрал 0,14 и до модели не дошёл.
// Ответ: найти ближайшие куски, отдать их модели с номерами, потребовать ссылки —
// и вовсе не вызывать модель, если ничего не нашлось
import pg from 'pg';
import { embed } from './index.ts';
const db = new pg.Pool({ connectionString: process.env.DATABASE_URL });
const API = process.env.ANTHROPIC_BASE_URL ?? 'https://api.anthropic.com';
export async function ask(question: string) {
const [q] = await embed([question]);
const { rows } = await db.query(
`SELECT source, heading, text, 1 - (embedding <=> $1) AS score
FROM chunks ORDER BY embedding <=> $1 LIMIT 4`,
[JSON.stringify(q)],
);
// Порог подбирается на ваших вопросах; ниже него — не ответ
const found = rows.filter((r) => r.score > 0.5);
if (found.length === 0) return { answer: null, sources: [] }; // передать человеку
const context = found.map((r, i) => `[${i + 1}] ${r.heading}\n${r.text}`).join('\n\n');
const res = await fetch(`${API}/v1/messages`, {
method: 'POST',
headers: {
'content-type': 'application/json',
'x-api-key': process.env.ANTHROPIC_API_KEY ?? '',
'anthropic-version': '2023-06-01',
},
body: JSON.stringify({
model: 'claude-sonnet-5',
max_tokens: 600,
system:
'Отвечай только по источникам ниже и ссылайся на них как [1], [2]. ' +
'Если в источниках нет ответа, так и скажи.\n\n' + context,
messages: [{ role: 'user', content: question }],
}),
});
if (!res.ok) throw new Error(`model: HTTP ${res.status}`);
const data = (await res.json()) as { content: { type: string; text: string }[] };
return { answer: data.content[0].text, sources: found.map((r) => r.source) };
}
Частые ошибки в RAG-проектах
-
Куски фиксированной длины
Нарезка каждые 500 символов рвёт правило пополам, и ни одна половина не отвечает на вопрос.
-
Нет порога
Ближайший кусок находится всегда, даже на вопрос о погоде, — и модель строит ответ на нём.
-
Только векторный поиск
Артикулы, названия моделей и коды лучше ищутся по словам — два поиска сочетают.
-
Модель эмбеддингов только для английского
Вопросы на русском ничего не находят; модель должна знать языки ваших клиентов.
-
Проиндексировать и забыть
Прайс поменялся, а база нет — ассистент называет старые цены.
-
Нет набора проверочных вопросов
Без 30–50 настоящих вопросов с ожидаемыми ответами любая правка — гадание.
7 правил надёжного RAG
-
01
Сначала вычистить источники
Убрать устаревшие версии и противоречия до индексации.
-
02
Куски по смыслу
Заголовки и абзацы как границы, заголовок — внутри каждого куска.
-
03
Два поиска вместе
По смыслу и по словам; результаты объединяются.
-
04
Порог и честное «не знаю»
Ниже порога модель не вызывается — вопрос уходит человеку.
-
05
Ссылки обязательны
Ответ без источника не показывается.
-
06
Права — в запросе
Фильтры по ролям применяются при поиске, а не в инструкции модели.
-
07
Мерить на настоящих вопросах
Постоянный набор вопросов прогоняется после каждого изменения базы или настроек.
Вопросы о RAG
Что такое RAG простыми словами?
Сначала найти ответ в ваших материалах, потом дать модели его сформулировать — со ссылкой на источник.
Убирает ли RAG выдумки модели?
Резко сокращает, если есть порог, строгая инструкция и ссылки; без них — нет.
Нужна ли векторная база?
Отдельная обычно нет: pgvector в PostgreSQL держит миллионы кусков рядом с остальными данными.
Используются ли мои данные для обучения модели?
Нет при деловом доступе к Claude или GPT через API; для самых строгих случаев есть локальные модели.
Какие документы подходят?
Тексты, таблицы, PDF, страницы сайта, каталог; сканы сначала распознаются в текст.
Как измерить качество?
На наборе настоящих вопросов: нашёлся ли нужный кусок, верен ли ответ и подтверждён ли источником.
Как RAG связан с MCP?
RAG ищет по текстам, MCP даёт модели инструменты — заказы, остатки, CRM. Ассистенты часто используют оба.
Форма
Ассистент
по вашим данным
Делаю ИИ-ассистентов, которые отвечают по вашим документам, каталогу и правилам — со ссылками на источники и передачей человеку, когда ответа нет. Расскажите о задаче — отвечу в течение рабочего дня.