# RAG: Retrieval-Augmented Generation как инженерная система

Когда говорят, что LLM "знает всё", это удобная, но опасная метафора. На самом деле модель ничего не знает в момент ответа – она лишь продолжает последовательность токенов, опираясь на статистические зависимости, выученные при обучении. Именно поэтому в реальных продуктах почти всегда возникает вопрос: *как заставить модель отвечать на основе конкретных, актуальных, контролируемых данных?*

Ответом на этот вопрос стала архитектура RAG – Retrieval-Augmented Generation. Это не фича и не трюк с промптом, а полноценная инженерная система, в которой LLM – лишь последний этап пайплайна.

В этой главе мы разберём RAG как систему из трёх связанных фаз:

```
поиск → формирование контекста → генерация,
```

и посмотрим, где здесь математика, где инфраструктура, а где иллюзии.

### Зачем вообще нужен RAG

Представим простую задачу: у вас есть база документов – инструкции, договоры, статьи, тикеты поддержки. Пользователь задаёт вопрос, и вы хотите получить:

* ответ, основанный *строго* на этих документах
* с указанием терминологии именно из базы
* без галлюцинаций
* с возможностью обновлять данные без переобучения модели

Чистая LLM здесь бесполезна. Она может выглядеть убедительно, но:

* она не знает ваших документов
* она не знает, что обновилось вчера"
* она не отличает "нет информации" от "попробую догадаться"

RAG решает это, разделяя ответственность:

* поиск отвечает за факты
* LLM отвечает за формулировку.

***

### Общая схема RAG

В самом общем виде RAG выглядит так:

\[IMAGE: DIAGRAM\_RAG\_PIPELINE]

Промпт для картинки:

“Clean technical diagram of Retrieval-Augmented Generation pipeline: User Query → Embedding → Vector Search → Top-K Documents → Context Assembly → LLM Generation → Final Answer. Minimalistic style, white background, thin arrows, readable labels.”

Формально это можно записать так:

Пусть:

* $$q$$ – пользовательский запрос
* $$D = {d₁, d₂, …, dₙ}$$ – корпус документов
* $$E(·)$$ – функция эмбеддингов
* $$sim(a, b)$$ – мера близости (обычно cosine similarity).<br>

Тогда RAG – это композиция функций:

1. Retrieval:\
   $$r(q) = \arg\max\_{k} ; \mathrm{sim}\bigl(E(q), E(d\_i)\bigr)$$
2. Context construction:\
   $$C = \mathrm{concat}\bigl(d\_{i\_1}, d\_{i\_2}, \dots, d\_{i\_k}\bigr)$$
3. Generation:\
   $$\text{answer} = \mathrm{LLM}(q, C)$$

Важно: LLM не видит весь корпус, она видит только *контекст* $$C$$, который мы ей дали.

### Retrieval: поиск как векторная задача

#### Почему не keyword-поиск

Классический поиск по словам (LIKE, [TF-IDF](https://apphp.gitbook.io/ai-for-php-developers/vvedenie/glossarii#tf-idf), BM25) ищет совпадения токенов. RAG же опирается на семантическую близость.

Фразы:

* "расторгнуть договор"
* "прекращение действия контракта"

почти не имеют общих слов, но смысл у них один. Эмбеддинги позволяют это уловить.

#### Эмбеддинги и пространство

Эмбеддинг – это отображение текста в вектор:

$$
E \colon \text{text} \rightarrow \mathbb{R}^d
$$

Обычно $$d$$ = 384, 768, 1024 или 1536.

{% hint style="info" %}
Размерность эмбеддинга определяется архитектурой трансформера: она кратна числу attention heads и отражает компромисс между выразительностью и вычислительной стоимостью.

В трансформере:

$$
d = h \times d\_{\text{head}}
$$

Где:

* $$h$$ – число attention heads
* $$d\_{\text{head}}$$​ – размерность одного head

Типичные конфигурации:

* 12 × 64 = 768
* 16 × 64 = 1024
* 24 × 64 = 1536

Это нужно для:

* эффективного параллелизма
* равномерного разделения признаков
* оптимизации под GPU/CPU SIMD
  {% endhint %}

Интуитивно:

* близкие по смыслу тексты → близкие векторы
* дальние по смыслу → дальние векторы.

Чаще всего используется косинусная близость:

$$
\mathrm{sim}(a, b) = \frac{a \cdot b}{\lVert a \rVert , \lVert b \rVert}
$$

Значение в диапазоне \[-1, 1], но на практике – от 0 до 1.

\[IMAGE: VECTOR\_SPACE]

Промпт для картинки:

“2D visualization of vector space with clustered text embeddings. Query vector highlighted, nearest document vectors connected with dashed lines. Simple, educational style.”

### Chunking: незаметная, но критическая часть

Документы редко кладут в векторную базу целиком. Их режут на чанки.

Почему?

* эмбеддинг длинного текста усредняет смыслы
* retrieval начинает возвращать "всё понемногу"
* контекст становится шумным.

Типичные стратегии:

* 300–500 токенов
* overlap 10–20%
* логические границы (абзацы, заголовки).

Формально мы ищем не документы, а чанки:

$$
D = {c\_1, c\_2, \dots, c\_m}
$$

Именно они участвуют в argmax (argument of the maximum).

### Context building: инженерия промпта

Получив Top-K чанков, мы должны превратить их в контекст.

Это не просто склейка текста. Здесь решаются вопросы:

* порядок (по релевантности или по источнику)
* формат (plain text, bullets, JSON)
* инструкции для модели.

Пример шаблона:

"Ты отвечаешь строго на основе контекста ниже. Если ответа нет – скажи, что информации недостаточно".

### Контекст:

### {{chunk\_1}}

### {{chunk\_2}}

Вопрос: {{query}}”

Здесь RAG превращается в контролируемую систему, а не в “умного болтуна”.

### Generation: что на самом деле делает LLM

LLM в RAG:

* не ищет информацию
* не проверяет факты
* не знает, что было вне контекста

Она решает задачу:

$$
P(\text{token}*t \mid \text{tokens}*{1:t-1}, \text{context})
$$

То есть переформулирует найденное.

Если retrieval плохой – генерация не спасёт.

Если контекст шумный – ответ будет мутным.

Отсюда важный инженерный принцип:

> В RAG качество ответа определяется retrieval-ом, а не моделью.

### Минимальный RAG на PHP (упрощённо)

Рассмотрим упрощённый пример без внешней векторной БД.

#### Хранение эмбеддингов

```php
$documents = [
    ['id' => 1, 'text' => 'Расторжение договора возможно по соглашению сторон'],
    ['id' => 2, 'text' => 'Договор может быть прекращён в одностороннем порядке'],
];

$embeddings = [
    1 => [0.12, 0.88, 0.44],
    2 => [0.10, 0.90, 0.40],
];
```

#### Косинусная близость

```php
function cosineSimilarity(array $a, array $b): float {
    $dot = $normA = $normB = 0.0;

    foreach ($a as $i => $v) {
        $dot += $v * $b[$i];
        $normA += $v * $v;
        $normB += $b[$i] * $b[$i];
    }

    return $dot / (sqrt($normA) * sqrt($normB));
}
```

#### Поиск Top-K

```php
$queryEmbedding = [0.11, 0.89, 0.42];

$scores = [];
foreach ($embeddings as $id => $vector) {
    $scores[$id] = cosineSimilarity($queryEmbedding, $vector);
}

arsort($scores);
$topIds = array_slice(array_keys($scores), 0, 1);
```

#### Контекст

```php
$context = '';
foreach ($topIds as $id) {
    $context .= $documents[$id - 1]['text'] . "\n";
}
```

Дальше этот контекст отправляется в LLM API.

### Типовые ошибки RAG

Ниже приведены типичные ошибки для RAG:

* слишком большие чанки
* слишком маленький Top-K
* отсутствие инструкции "не выдумывай"
* надежда, что "более умная модель всё исправит".

RAG – это не магия, а баланс сигнал/шум.

### RAG как архитектурный паттерн

Важно понимать: RAG – это не только QA.

Это базовый паттерн для:

* корпоративных ассистентов
* аналитических систем
* поиска по событиям и таймлайнам
* explainable AI (ответ + источники)

LLM здесь – интерфейс к данным, а не их носитель.

### Ключевая мысль главы

RAG – это способ вернуть контроль.

Контроль над источниками.

Контроль над актуальностью.

Контроль над тем, где модель должна сказать: "я не знаю".

И именно поэтому RAG – не про модели, а про инженерию.
