
В настоящее время Retrieval-Augmented Generation (RAG) стал одним из самых популярных подходов к построению интеллектуальных помощников, корпоративных чат-ботов и систем поиска по внутренней документации. Вместо дорогостоящего дообучения языковой модели, RAG позволяет использовать уже существующие документы, находя в них релевантную информацию и передавая её модели в качестве контекста.
На первый взгляд задача кажется простой: загрузить документы, сгенерировать эмбеддинги и подключить LLM — и система готова. Однако на практике возникает целый ряд вопросов: как оптимально делить документ на чанки; как сгенерировать эмбеддинги и где их хранить; как организовать поиск; чем отличаются модели, применяемые для эмбеддингов и для формирования ответа?
Что такое эмбеддинг и чанки
Эмбеддинг (embedding — вложение, внедрение) — это способ представления реальных объектов (слов, картинок, звуков) в виде векторов, то есть упорядоченных списков чисел фиксированной длины. Главная фишка эмбеддингов — они кодируют скрытый смысл объектов: если два объекта похожи по сути, то и их числовые векторы в многомерном пространстве будут находиться близко друг к другу.
Чанк — минимальная структурированная единица текста, полученная путём разбиения документа на части заданного максимального размера (по токенам или словам). Каждый чанк содержит фрагмент текста и метаданные и служит единицей обработки на этапах индексации и запроса.
Большинство примеров в интернете предлагают готовый стек из OpenAI API, LangChain и облачных векторных баз данных. Такой подход действительно позволяет быстро собрать прототип, но для разработчика он работает как «черный ящик». Фреймворки вроде LangChain или LlamaIndex вызывают под капотом сложные абстракции, скрывая от нас всю внутреннюю механику: вы не видите, как именно текст режется на чанки, как вызываются математические функции сравнения векторов и как формируется финальный промпт.
В реальных проектах такой подход часто упирается в жесткие ограничения:
Конфиденциальность: корпоративные документы нельзя отправлять в зарубежные облачные API.
Автономность: система должна работать в закрытом контуре без доступа в интернет.
Контроль: готовые библиотеки усложняют кастомизацию поиска, когда стандартных алгоритмов становится недостаточно.
Чтобы обойти эти ограничения и по-настоящему разобраться в архитектуре RAG, мы откажемся от тяжелых фреймворков и соберем систему на базе понятного, легковесного и полностью локального стека: Go + PostgreSQL + Ollama. Почему этот стек:
Go — хорошо подходит для написания ядра системы. Он компилируется в один легковесный бинарник, не требует тяжелых runtime-зависимостей (в отличие от Python) и из коробки предоставляет мощные инструменты конкурентности для быстрой параллельной обработки сотен документов.
PostgreSQL с расширением
pgvector— избавляет от необходимости разворачивать и поддерживать отдельную векторную БД. Мы берем проверенную временем реляционную базу, которая уже есть в инфраструктуре 90% проектов, и храним числовые векторы (эмбеддинги) прямо рядом с текстом чанков, связывая их стандартными SQL-запросами.Ollama — реализует сложную работу по запуску и оркестрации локальных моделей. Она предоставляет простой локальный REST API: мы отправляем ей чанк текста — она возвращает вектор эмбеддинга; отправляем промпт — она генерирует ответ, используя мощности вашего CPU или GPU, без внешних запросов.
Разумеется, в рамках одной статьи невозможно охватить все тонкости создания RAG. Поэтому мы ограничимся учебным прототипом: он не предназначен для промышленной эксплуатации, но наглядно показывает ключевые принципы и типовые подходы к построению RAG-систем. Язык реализации — Go, но для понимания RAG это не принципиально: тот же пайплайн можно собрать на Python, JavaScript или любом другом знакомом вам языке.
В результате получится полностью автономная система, которую можно запустить на обычной машине без облачных сервисов и платных API. Конечно, такой проект нельзя назвать полноценной production-системой, однако он охватывает все ключевые этапы классического RAG: индексацию документов, хранение эмбеддингов, семантический поиск и генерацию ответа на основе найденного контекста.
По ходу статьи мы не только реализуем этот пайплайн, но и разберём, почему были выбраны именно такие архитектурные решения, какие у них есть ограничения и в каких случаях их стоит заменить более специализированными инструментами. Сделали репозиторий на GitHub, который можно скопировать себе и воспроизвести примеры на практике.
Чтобы было нагляднее, представим, что мы разрабатываем RAG-систему для компании «Ёлки-метёлки Ltd». У компании есть своя история, миссия и набор нормативных документов. В репозитории они лежат в папке examples в формате Markdown — они нам ещё пригодятся. А пока — в путь!
Для начала: что вообще такое RAG и зачем он нужен
RAG — это архитектурный подход, который позволяет объединить большую языковую модель (LLM) с внешней базой знаний. Идея довольно проста: вместо того чтобы заставлять модель вспоминать информацию, мы сначала просим её найти подходящие документы, а уже потом сформулировать ответ на их основе.
Такой подход появился не случайно. Если использовать LLM как есть, без доступа к внешним данным, довольно быстро проявляются её ограничения.
Знания модели ограничены моментом её обучения. Она ничего не знает о документах, созданных позже, а тем более о внутренних регламентах, корпоративной документации или пользовательских данных.
Языковая модель умеет убедительно генерировать текст. Если нужной информации нет, она может не признаться в этом, а построить правдоподобный, но неверный ответ — то есть галлюцинировать.
Ответ модели практически невозможно проверить. Даже если он выглядит убедительно, пользователь не может понять, на основании каких источников он был получен.
RAG устраняет эти проблемы, разделяя процесс обработки запроса на два независимых этапа. Сначала система ищет в базе знаний наиболее подходящие фрагменты документов, а затем передаёт их языковой модели в качестве дополнительного контекста. Общий процесс выглядит следующим образом:

Каждый этап отвечает за свою задачу:
Запрос: пользователь отправляет свой вопрос через API.
Построение эмбеддинга: текст вопроса преобразуется в векторное представление (эмбеддинг).
Поиск: эмбеддинг запроса сравнивается с базой эмбеддингов, полученных при индексации документов.
Извлечение: система отбирает топ наиболее релевантных текстовых фрагментов.
Генерация: отобранный контекст вместе с исходным вопросом передается в LLM.
Ответ: языковая модель генерирует итоговый релевантный ответ.
Важно понимать: в архитектуре RAG языковая модель перестает быть источником знаний. Её задача — не хранить информацию, а формулировать ответ, используя документы, найденные системой поиска. Именно поэтому качество RAG во многом зависит не только от выбранной LLM, но и от того, насколько хорошо реализованы индексация документов, разбиение на чанки и семантический поиск.
Архитектура системы
Теперь посмотрим на архитектуру нашего проекта. Несмотря на небольшой размер, система состоит из нескольких независимых компонентов, каждый из которых отвечает только за одну задачу. Такое разделение позволяет упростить разработку, тестирование и дальнейшее развитие проекта.

На схеме сразу бросается в глаза одна особенность: индексация документов и обработка пользовательских запросов полностью разделены.
Это сделано намеренно.
Индексация — длительная офлайн-операция. Она включает чтение файлов, разбиение текста на чанки, вычисление эмбеддингов и сохранение результатов в базе данных. Пользователь не взаимодействует с этим процессом напрямую, поэтому скорость его выполнения не критична.
REST API, напротив, обслуживает пользовательские запросы в режиме реального времени. Здесь важны минимальная задержка ответа и предсказуемое время выполнения каждого запроса.
Поэтому проект состоит из двух отдельных приложений:
Компонент | Назначение |
|---|---|
ingest CLI | Индексация документов: чтение файлов, чанкинг, построение эмбеддингов и сохранение в PostgreSQL |
REST API | Приём запросов, поиск релевантных чанков и генерация ответа |
PostgreSQL + pgvector | Хранение документов, эмбеддингов и выполнение семантического поиска |
Ollama | Построение эмбеддингов и генерация ответа языковой моделью |
Оба приложения используют общие пакеты из internal/, но имеют разные точки входа (cmd/ingest и cmd/api). Такое разделение позволяет независимо развивать оба компонента. Например, в будущем CLI можно заменить фоновой очередью обработки документов, не меняя REST API.
Почему именно такая архитектура
При проектировании системы хотелось добиться двух вещей: сохранить архитектуру максимально простой, но не жертвовать расширяемостью. Поэтому каждый компонент отвечает только за одну задачу. CLI ничего не знает о пользовательских запросах; REST API ничего не знает о процессе индексации; PostgreSQL отвечает исключительно за хранение данных; Ollama занимается только построением эмбеддингов и генерацией текста. Благодаря этому заменить любой из компонентов можно практически независимо. Например: вместо PostgreSQL использовать Qdrant; вместо Ollama подключить OpenAI или vLLM; вместо REST API реализовать gRPC-сервис. При этом остальные компоненты системы практически не изменятся.
Где хранятся данные
Во время индексации система создаёт две сущности. Документ — информация о файле (название, путь, дата загрузки). Каждый документ может содержать десятки или сотни чанков, поэтому используется классическая схема один ко многим. При удалении документа автоматически удаляются и все связанные чанки благодаря внешнему ключу с ON DELETE CASCADE. Такое разделение позволяет независимо работать как со списком документов, так и с их содержимым.
Почему Ollama
Для построения RAG необходимы две разные модели LLM: первая преобразует текст в эмбеддинги, вторая отвечает за генерацию текста. В данном проекте оркестрацию обеих моделей берет на себя Ollama. Она предоставляет единую локальную среду для их запуска и управления, что даёт несколько важных преимуществ:
все вычисления происходят локально;
документы не покидают компьютер;
не нужно платить за запросы;
модели можно заменить буквально одной переменной окружения.
Используются две модели.
Модель | Назначение |
|---|---|
nomic-embed-text | Построение эмбеддингов размерностью 768 |
llama3.2 | Генерация ответа по найденному контексту |
Важно понимать, что модель эмбеддингов (nomic-embed-text) используется как при индексации документов, так и при обработке пользовательских запросов. Если проиндексировать документы одной моделью, а затем вопросы преобразовывать другой, эмбеддинги окажутся в разных векторных пространствах, и качество поиска резко ухудшится. Это одна из самых распространенных ошибок при знакомстве с RAG.
Важно понимать, что модель эмбеддингов (nomic-embed-text) используется как при индексации документов, так и при обработке пользовательских запросов. Если проиндексировать документы одной моделью, а затем вопросы преобразовывать другой, эмбеддинги окажутся в разных векторных пространствах, и качество поиска резко ухудшится. Это одна из самых распространенных ошибок при знакомстве с RAG.
После генерации эмбеддингов встает вопрос: как быстро найти среди тысяч или миллионов векторов наиболее похожие на поисковый запрос. Самый очевидный путь — точный поиск (Exact Search) через полный перебор таблицы: ORDER BY vector <=> query_vector. Он отлично работает на малых объемах, но с ростом базы линейная сложность (O(N)) парализует систему: при каждом запросе PostgreSQL придется сканировать все строки и сравнивать вектор запроса со всеми эмбеддингами в таблице.
Для оптимизации используют приближенный поиск (ANN — Approximate Nearest Neighbor) и алгоритм HNSW (Hierarchical Navigable Small World). Вместо полного перебора он строит над векторами многослойный граф близости, реализуя концепцию «шести рукопожатий»:
Верхние слои содержат редкие векторы-ориентиры. Поиск начинается здесь: алгоритм делает огромные прыжки, чтобы быстро локализовать нужную область многомерного пространства.
Нижние слои содержат плотную сеть векторов. Спускаясь туда, алгоритм делает мелкие шаги, ювелирно докручивая точечную точность.
В итоге сложность поиска падает до логарифмической — (O(log N)). База находит релевантный контекст за приемлемое время даже на миллионах строк, жертвуя лишь микроскопической долей точности. В PostgreSQL вся эта математика графов разворачивается одной миграцией:
CREATE INDEX idx_chunks_embedding ON chunks USING hnsw (embedding vector_cosine_ops);
В нашем проекте поиск выполняется по косинусному расстоянию (для этого мы и указали vector_cosine_ops в индексе).
Оператор<=>, предоставляемый расширением pgvector, возвращает математическое расстояние между двумя векторами: чем оно меньше, тем ближе тексты по смыслу. Однако отдавать «сырое» расстояние клиенту неудобно. Чтобы инвертировать логику и сделать метрику интуитивно понятной, мы преобразуем расстояние в привычный score (коэффициент сходства) по простой формуле:
score = 1 — distance
Теперь большее значение score (ближе к 1.0) соответствует максимальной релевантности найденного фрагмента текста.
Схема базы данных
Несмотря на то что RAG ассоциируется прежде всего с векторным поиском, основу нашей системы составляет вполне обычная реляционная база данных.
Для хранения информации используются две таблицы: documents — сведения о загруженных документах; chunks — текстовые фрагменты и соответствующие им эмбеддинги.
Схема выглядит следующим образом.
CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE documents ( id BIGSERIAL PRIMARY KEY, title TEXT NOT NULL, source_path TEXT NOT NULL DEFAULT '', created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE chunks ( id BIGSERIAL PRIMARY KEY, document_id BIGINT NOT NULL REFERENCES documents(id) ON DELETE CASCADE, content TEXT NOT NULL, chunk_index INT NOT NULL, embedding vector(768), created_at TIMESTAMPTZ NOT NULL DEFAULT now() );
Может возникнуть вопрос: почему не хранить всё в одной таблице?
Документ и его текстовые фрагменты — это сущности разного уровня. Документ содержит только метаданные: название; путь к файлу; дату загрузки.
Основной объём данных находится именно в чанках. Каждый документ может быть разбит на десятки или даже сотни фрагментов, и для каждого из них хранится собственный эмбеддинг.
Такое разделение даёт несколько преимуществ. API может быстро получить список документов, не загружая содержимое всех чанков. Удаление документа автоматически удаляет связанные с ним фрагменты благодаря ON DELETE CASCADE. При необходимости к документам легко добавить новые метаданные (автор, категория, теги), не затрагивая таблицу с эмбеддингами.
Замечание. Размерность поля vector(768) напрямую зависит от используемой модели эмбеддингов. В нашем случае используется nomic-embed-text, которая возвращает векторы длиной 768 элементов. Если заменить модель, схему базы данных и индекс придётся пересоздать, а документы — переиндексировать.
Пайплайн индексации (ingest)
Теперь посмотрим, что происходит после того, как пользователь запускает CLI для загрузки документов.
На высоком уровне процесс выглядит достаточно просто.

Шаг 1. Чтение файла
CLI поддерживает несколько распространенных текстовых форматов: .txt, .md, .go, .json, .yaml, .yml. Для демонстрационного проекта этого более чем достаточно. Файл полностью считывается в память и передаётся следующему этапу обработки. Разумеется, в production-системе такой подход имеет ограничения. При работе с большими файлами обычно используют потоковую обработку, а список поддерживаемых форматов расширяют за счёт PDF, DOCX, HTML и других типов документов. Однако в нашем случае важно показать сам принцип работы RAG, не отвлекаясь на особенности парсеров.
Шаг 2. Разбиение на чанки
Это один из самых важных этапов всей системы. Интуитивно может показаться, что проще построить один эмбеддинг для всего документа. На практике такой подход почти никогда не работает.
Языковые модели имеют ограничение на размер входного текста.
Один вектор начинает описывать сразу несколько независимых тем. Чем длиннее документ, тем сильнее "размывается" его семантика, а значит, снижается качество поиска.
Поэтому документ сначала разбивается на небольшие перекрывающиеся фрагменты — чанки. По умолчанию используются следующие параметры: размер чанка — 500 символов; перекрытие — 50 символов.
Схематично это выглядит так:

Перекрытие играет важную роль. Представим, что предложение оказалось ровно на границе двух чанков. Если разделить текст без перекрытия, часть смысла попадёт в один фрагмент, а часть — в другой. В результате оба эмбеддинга могут оказаться менее информативными. Небольшое пересечение позволяет сохранить контекст и заметно повышает качество поиска. Ещё одна деталь, на которую стоит обратить внимание, — в нашем случае, разбиение выполняется не по байтам, а по рунам ([]rune). Это позволяет корректно работать с UTF-8 и гарантирует, что кириллица, эмодзи и любые другие многобайтовые символы не будут разрезаны посередине.
Шаг 3. Построение эмбеддингов
После разбиения документа каждый чанк независимо отправляется в Ollama. Запрос выполняется через HTTP API:
POST /api/embeddings
В ответ Ollama возвращает массив из 768 чисел с плавающей точкой. Именно этот массив и называется эмбеддингом. Важно понимать, что эмбеддинг хранит не текст, а его семантическое представление.
Шаг 4. Запись в БД
После получения эмбеддингов остаётся сохранить результаты в базе данных. Для каждого документа выполняются две операции.
Создаётся запись в таблице documents.
Для каждого чанка добавляется запись в таблицу chunks, содержащая:
— идентификатор документа;
— текст чанка;
— его порядковый номер;
— вычисленный эмбеддинг.
Перед записью массив чисел сериализуется в строковый формат, который понимает расширение pgvector. Например:
[0.12,-0.34,0.56,...]
После этого документ считается полностью проиндексированным и сразу становится доступен для поиска.
Запуск индексации
После сборки CLI загрузить документы можно одной командой.
Один файл:
./bin/ingest -file ./examples/01-mission.md
Вся директория:
./bin/ingest -dir ./examples
Изменение параметров:
./bin/ingest -file doc.txt \ -chunk-size 800 \ -overlap 100
Эти параметры позволяют быстро поэкспериментировать с размером чанков и величиной перекрытия, чтобы увидеть, как они влияют на качество семантического поиска.
Пайплайн запроса (RAG Query)
После того как документы проиндексированы, система готова отвечать на вопросы пользователей.
Разберём, что происходит после получения запроса (во введении мы писали, что для интересна мы пишем систему для передовой компании Ёлки-метёлки):
Ёлки-метёлки Ltd производитель новогодних украшений
На первый взгляд кажется, что вопрос сразу отправляется языковой модели. На самом деле LLM начинает работать только на последнем этапе. До этого система должна найти информацию, на основе которой будет строиться ответ.
Весь процесс выглядит следующим образом.

1. Векторизация вопроса
Первое, что делает система, — преобразует текст вопроса в эмбеддинг. Для этого используется та же модель nomic-embed-text, которой ранее индексировались документы. Это важный момент. Полученный вектор становится поисковым ключом для следующего этапа.
2. Семантический поиск
Получив эмбеддинг вопроса, система обращается к PostgreSQL. Поиск выполняется обычным SQL-запросом.
SELECT c.id, c.document_id, c.content, c.chunk_index, 1 - (c.embedding <=> $1::vector) AS score FROM chunks c WHERE c.embedding IS NOT NULL ORDER BY c.embedding <=> $1::vector LIMIT 5;
На первый взгляд запрос выглядит необычно, однако здесь используется всего одна специальная возможность pgvector. Оператор <=> вычисляет косинусное расстояние между двумя векторами. Чем меньше полученное значение, тем ближе документы по смыслу к пользовательскому запросу. Для удобства отображения расстояние преобразуется в привычную оценку релевантности:
score = 1 − distance
Теперь большее значение соответствует более релевантному документу.
Почему именно Top-K = 5? После сортировки система выбирает только несколько наиболее подходящих фрагментов. В проекте используется значение: Top-K = 5
Это компромисс между полнотой контекста и количеством информации, которую получает языковая модель. Если выбрать слишком маленькое значение, существует риск, что важная информация просто не попадёт в промпт. Если же передать модели десятки чанков, возникнет другая проблема — большая часть контекста окажется нерелевантной, а качество ответа начнёт ухудшаться. Универсального значения здесь не существует. Для небольших проектов Top-K = 5 обычно оказывается хорошей отправной точкой, однако в production-системах этот параметр почти всегда подбирается экспериментально.
3. Формирование контекста
После завершения поиска система получает несколько наиболее релевантных чанков.
Например:
[1] **Ёлки-метёлки Ltd** — ведущий российский производитель новогодних украшений, ёлок, гирлянд и метёлок премиум-класса. --- [2] ## Миссия Делать каждый праздник ярче, безопаснее и экологичнее, производя украшения, которыми гордятся семьи и бизнес. --- [3] | 2018 | Основание в гараже, 3 сотрудника, первая гирлянда «Сосновая радость» |
Теперь эти фрагменты объединяются в единый контекст и добавляются в промпт.
Именно здесь происходит буква A в аббревиатуре RAG — Augmented.
Языковая модель получает уже не только вопрос пользователя, но и найденную информацию. Кроме контекста, в промпт добавляется небольшая инструкция.
Например:
Ответь на вопрос, используя только предоставленный контекст. Если информации недостаточно, честно сообщи об этом.
На первый взгляд подобная инструкция выглядит незначительной.
На практике именно такие ограничения позволяют заметно снизить количество галлюцинаций модели. Разумеется, полностью исключить их невозможно, однако правильно составленный промпт существенно повышает качество ответов.
4. Генерация ответа
На последнем этапе сформированный промпт отправляется в Ollama. Запрос выполняется через стандартный HTTP API:
POST /api/generate
После этого управление полностью переходит языковой модели. Она анализирует вопрос, использует переданный контекст и формирует итоговый ответ. API возвращает не только текст ответа, но и список источников, использованных при поиске. Это позволяет пользователю проверить, на основании каких документов был получен результат. Теперь весь пайплайн можно увидеть в работе.
curl -X POST http://localhost:8080/api/query \ -H "Content-Type: application/json" \ -d '{ "question": "Ёлки-метёлки Ltd производитель новогодних украшений" }'
Ответ:
{ "answer": "Ёлки-метёлки Ltd -- ведущий российский производитель новогодних украшений, ёлок, гирлянд и метёлок премиум-класса. Компания основана в 2018 году в г. Сосновка.", "sources": [ { "id": 1, "document_id": 1, "content": "**Ёлки-метёлки Ltd** -- ведущий российский производитель новогодних украшений, ёлок, гирлянд и метёлок премиум-класса.", "chunk_index": 0, "score": 0.91 } ] }
Обратите внимание, что вместе с ответом возвращаются и найденные чанки. Это одно из ключевых преимуществ RAG по сравнению с обычным использованием LLM: пользователь может не только получить ответ, но и проверить, на каких документах он основан.
Структура Go-проекта
К этому моменту у нас уже есть понимание архитектуры RAG и пайплайна обработки данных. Осталось посмотреть, как всё это организовано в коде. Проект специально построен максимально близко к рекомендациям сообщества Go. Здесь нет сложных фреймворков или магии — только стандартная структура каталогов и небольшие независимые пакеты.

Принципы организации кода
cmd/ — только wiring. Точки входа подключают зависимости, запускают сервер или CLI. Бизнес-логики здесь нет.
internal/ — вся логика. Пакет недоступен для импорта извне модуля (ограничение Go).
Разделение embed и llm. Хотя оба клиента ходят в Ollama, они инкапсулируют разные API-эндпоинты. Это упрощает замену: можно подключить другой провайдер эмбеддингов, не трогая LLM.
handler отделён от rag. HTTP-слой не знает деталей RAG-пайплайна. rag.Service можно переиспользовать из gRPC, CLI или тестов без HTTP.
REST API
Метод | Путь | Описание |
|---|---|---|
|
| Проверка доступности API и PostgreSQL |
|
| Список загруженных документов |
|
| RAG-запрос: |
Docker-инфраструктура
Сервисы
Сервис | Образ | Порт | Назначение |
|---|---|---|---|
|
| 5432 | БД + векторный поиск |
| собственный Dockerfile | 8080 | REST API |
|
| 11435 | LLM (опционально) |
|
| — | Загрузка моделей (опционально) |
Два режима запуска
make up — PostgreSQL + API. Ollama работает на хосте (порт 11434). API обращается к ней через host.docker.internal. Это рекомендуемый режим: не нужно дублировать Ollama в контейнере, нет конфликтов портов.
make up-all — всё в Docker, включая Ollama на порту 11435 (чтобы не конфликтовать с хостовой). Профиль bundled-ollama в docker-compose включает сервисы ollama и ollama-init.
Быстрый старт
1. Скачать модели (на хосте)
ollama pull nomic-embed-text ollama pull llama3.2
2. Запустить инфраструктуру
make up
3. Собрать CLI
go build -o bin/ingest ./cmd/ingest
4. Загрузить тестовые документы
./bin/ingest -dir ./examples
5. Задать вопрос
make query Q="Что такое RAG?"
Директория examples/ содержит тестовые файлы описания нашей вымышленной компании, их можно заменить своими и поэкспериментировать с тем как будет работать RAG.
Заключение
За последнее время концепция RAG стала стандартом в области генеративного ИИ. Из-за обилия готовых библиотек часто возникает иллюзия, что для построения такой системы необходимы сложные enterprise-фреймворки и облачные API. В этой статье мы показали обратное, шаг за шагом собрав полностью автономную RAG-систему, реализующую все базовые этапы: сегментацию текста, векторизацию, семантический поиск и генерацию ответа на основе контекста.
Предложенная реализация не является коммерческим решением — в ней осознанно опущены механизмы гибридного поиска, реранкинга документов и потокового инференса. Однако лаконичность архитектуры позволяет полностью освоить её за один вечер. При масштабировании проекта вам не придётся менять фундамент; развитие системы пойдет по пути модульной модернизации — интеграции более точных моделей, оптимизации стратегий чанкинга или выноса индексации в отдельный сервис. При этом ключевой конвейер останется неизменным:
Документы → Чанки → Эмбеддинги → Семантический поиск → Контекст → LLM → Ответ
Понимание базовой механики RAG важнее владения конкретным инструментарием. Фреймворки и библиотеки неизбежно устаревают, в то время как базовые принципы Retrieval-Augmented Generation остаются стандартом индустрии.
Автор статьи — Александр Донцов
НЛО прилетело и оставило здесь промокод для читателей нашего блога:
-15% на заказ нового VDS — HABRFIRSTVDS.
