Дисклеймер: данная лекция подготовлена в первую очередь для непрофильных студентов магистратуры и аспирантуры, которые используют ИИ в учебной, исследовательской и профессиональной деятельности. Материал носит вводный характер и содержит намеренные упрощения. В то же время лекция может быть полезна и более широкой аудитории пользователей ИИ. Буду признателен за замечания и предложения по улучшению.

Серия «Базовый минимум» (4 части): 

Базовый минимум. Часть 1:  большие языковые модели;

Базовый минимум. Часть 2:  промпт-инжиниринг;

Базовый минимум. Часть 3:  RAG-системы (вы здесь); 

Базовый минимум. Часть 4:  ИИ-агенты. 

«Знание бывает двух видов. Мы либо знаем предмет сами, либо знаем, где можно найти о нем сведения» — Самюэль Джонсон

Содержание

Проблемы обычных языковых моделей

Языковые модели функционируют в пределах своей обучающей выборки. После завершения обучения их знания становятся статичными: они не обновляются автоматически и отражают «состояние мира» на момент формирования набора данных. В быстро меняющихся или высокоспециализированных областях это приводит к ограничениям: модель может не обладать релевантными сведениями или выдавать устаревшую информацию.

Второе ключевое ограничение связано с надежностью ответов. Когда данных недостаточно, модель иногда компенсирует пробелы, генерируя правдоподобные, но фактически вымышленные сведения. Такое поведение называют галлюцинациями: появляются несуществующие факты, числа и интерпретации без ссылок на источники или со ссылками, которые не подтверждают сказанное.

7.drawio (1).png

Дополнительный технический барьер состоит в ограниченном размере  контекстного окна. Модель не всегда способна одновременно «удерживать» большие документы или длинные цепочки рассуждений. При превышении лимита часть входной информации отбрасывается.

Таким образом, без внешних источников данных и механизмов контроля качества ответы языковой модели могут быть нестабильными, непроверяемыми и нередко устаревшими. Эти ограничения и привели к подходам, которые подключают внешние базы знаний и дают доступ к актуальной информации.

Что такое RAG и зачем это нужно

В документации OpenAI Retrieval-Augmented Generation (RAG) определяется как техника, которая улучшает ответы модели за счёт добавления внешнего контекста в запрос во время генерации.

8.drawio.png

Вместо того чтобы полагаться только на знания, полученные на этапе обучения модели, система извлекает релевантную информацию из подключённых источников данных и использует её для генерации.

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

Что такое чанкование (chanking)

Чанкование (chunking) — это разбиение документов на небольшие фрагменты (чанки, chunks), которые затем становятся базовыми единицами индексации и поиска в RAG-системе. Дело в том, что LLM получает документы не целиком, а лишь в виде ограниченного набора извлеченных фрагментов. Поэтому качество чанкования во многом определяет, какие факты окажутся доступными на этапе генерации ответа.

10.drawio.drawio.png

Необходимость чанкования связана с ограниченным контекстным окном и компромиссом между связностью и компактностью: слишком крупные чанки перегружают контекст «шумом», повышают стоимость генерации и могут снижать точность поиска; слишком мелкие разрывают смысловые связи и ухудшают retrieval (например, правило и исключение оказываются в разных фрагментах).

На практике используют разбиение по фиксированной длине или по смысловым границам (заголовки, абзацы), иногда добавляя перекрытие (overlap), чтобы не терять смысл на стыках. Оптимальные параметры подбираются эмпирически под конкретный корпус и тип запросов.

Как работает RAG


Источник: Gao Y., Xiong Y., Gao X., Jia K., Pan J., Bi Y., Dai Y., Sun J., Wang M., Wang H. Retrieval-Augmented Generation for Large Language Models: A Survey // arXiv. 2023. URL: https://arxiv.org/abs/2312.10997 (дата обращения: 05.11.2025).
Источник: Gao Y., Xiong Y., Gao X., Jia K., Pan J., Bi Y., Dai Y., Sun J., Wang M., Wang H. Retrieval-Augmented Generation for Large Language Models: A Survey // arXiv. 2023. URL: https://arxiv.org/abs/2312.10997 (дата обращения: 05.11.2025).

На схеме показан иллюстративный пример: пользователь запрашивает свежие новости. Языковая модель, не имеющая доступа к внешним данным, может ответить, что не располагает актуальной информацией, поскольку её знания ограничены данными обучения.

В подходе RAG (синие стрелки) документы заранее индексируются: разбиваются на чанки, каждый из которых переводится в векторное представление и сохраняется во векторной базе. 

Когда поступает запрос, система сначала выполняет retrieval — преобразует запрос в представление, удобное для поиска, и извлекает наиболее релевантные фрагменты. 

Затем следует augmentation: найденные фрагменты ранжируются, очищаются от дубликатов и «шума», а при необходимости сжимаются.

После этого начинается generation: к исходному запросу добавляется сформированный контекст из релевантных фрагментов, и LLM генерирует ответ. 

Варианты реализации

Источник: Gao Y., Xiong Y., Gao X., Jia K., Pan J., Bi Y., Dai Y., Sun J., Wang M., Wang H. Retrieval-Augmented Generation for Large Language Models: A Survey // arXiv. 2023. URL: https://arxiv.org/abs/2312.10997 (дата обращения: 05.11.2025).
Источник: Gao Y., Xiong Y., Gao X., Jia K., Pan J., Bi Y., Dai Y., Sun J., Wang M., Wang H. Retrieval-Augmented Generation for Large Language Models: A Survey // arXiv. 2023. URL: https://arxiv.org/abs/2312.10997 (дата обращения: 05.11.2025).

На схеме показаны основные варианты реализации RAG: от базовых к более продвинутым. Слева — Naive RAG: есть индекс документов, строятся эмбеддинги, по запросу извлекаются ближайшие фрагменты и без дополнительной обработки передаются модели. 

В центре — Advanced RAG: добавляется последовательная обработка вокруг поиска. Запрос можно предварительно переформулировать (rewrite), переранжировать (rerank) результаты, очистить от дубликатов и «шума» и т. д. Это заметно повышает качество и во многих практических системах становится вариантом по умолчанию.

Справа — Modular RAG: вместо линейного конвейера используется набор независимых модулей: маршрутизация запросов, память, различные методы поиска, методы объединения результатов (fusion). 

Сравнение RAG и дообучения

Screenshot 2025-11-04 at 22.18.12.png
Источник: Gao Y., Xiong Y., Gao X., Jia K., Pan J., Bi Y., Dai Y., Sun J., Wang M., Wang H. Retrieval-Augmented Generation for Large Language Models: A Survey // arXiv. 2023. URL: https://arxiv.org/abs/2312.10997 (дата обращения: 05.11.2025).

Сравнение способов адаптации языковых моделей [1]

RAG — это один из подходов к адаптации языковых моделей наряду с промпт-инжинирингом и дообучением, однако он движется в другом направлении: вместо изменения состояния модели расширяется её доступ к внешним знаниям. 

Промпт-инжиниринг предполагает оптимизацию запроса с целью получить более точный и устойчивый ответ.

Дообучение, напротив, изменяет саму модель — генератор, ретривер (реранкер) или оба компонента. Такой подход обеспечивает глубокую кастомизацию, но требует данных, обучения и регулярных обновлений.

Практический пример: минимальный RAG-конвейер

Чтобы увидеть описанный выше механизм в действии, реализуем версию Naive RAG на Python для импровизированной задачи. В качестве базы знаний будем использовать фрагмент русской Википедии из 500 статей, а также небольшую языковую модель Qwen2.5-3B-Instruct. Целиком пример можно воспроизвести в Google Colab по ссылке, а ниже разберем ключевые моменты.

Шаг 1. Загрузка корпуса и чанкование

import torch
from datasets import load_dataset
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.documents import Document

device = "cuda" if torch.cuda.is_available() else "cpu"

dataset = load_dataset(
    "wikimedia/wikipedia",
    "20231101.ru",
    split="train[:500]"
)

documents = [
    Document(
        page_content=row["text"],
        metadata={"title": row["title"]}
    )
    for row in dataset
]

splitter = RecursiveCharacterTextSplitter(
    chunk_size=800,
    chunk_overlap=100
)

chunks = splitter.split_documents(documents)

print("Чанков:", len(chunks))

Результат выполнения:

Чанков: 17892

Каждая статья сохраняется как объект Document, текст помещается в page_content, а название в metadata. Далее при помощи готового метода документы разбиваются на чанки длиной 800 символов с перекрытием 100 символов. Напомню, что пока что мы имеет дело с символами, а не с токенами.

Шаг 2. Построение векторного индекса

from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings

embedding_model = HuggingFaceEmbeddings(
    model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
    model_kwargs={"device": device}
)

vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embedding_model
)

В качестве модели эмбеддингов для примера используется мультиязычная sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2. Для хранения векторов применяется Chroma. Эта векторная база данных с высоким уровнем абстрации, которая позволяет одной строчкой создать индекс.

Шаг 3. Retrieval

import textwrap

def search(query, k=3, width=80):
    results = vectorstore.similarity_search(query, k=k)

    for i, doc in enumerate(results, 1):
        print(f"\n--- Результат {i} ---")
        print("Заголовок:", doc.metadata["title"])
        print(textwrap.fill(doc.page_content[:1000], width))

search("Кто такой Пушкин?")

И результат:

--- Результат 1 ---
Заголовок: Пушкин, Александр Сергеевич
Изучение Пушкина

--- Результат 2 ---
Заголовок: Пушкин, Александр Сергеевич
Изучение Пушкина

--- Результат 3 ---
Заголовок: Пушкин, Александр Сергеевич
Биография  Происхождение   Происхождение Александра Сергеевича Пушкина идёт от
разветвлённого нетитулованного дворянского рода Пушкиных, восходившего по
генеалогической легенде к «мужу честну» Ратше. Пушкин неоднократно писал о своей
родословной в стихах и прозе; он видел в своих предках образец истинной
«аристократии», древнего рода, честно служившего отечеству, но не снискавшего
благосклонности правителей и «гонимого». Не раз он обращался (в том числе в
художественной форме) и к образу своего прадеда по матери — африканца Абрама
Петровича Ганнибала, ставшего слугой и воспитанником Петра I, а потом военным
инженером и генералом.

Представлена простая функция извлечения трех наиболее релевантных фрагментов из базы. Chroma позволяет выполнить такой поиск одной строкой.

Этот код не является обязательным для конечного результата, поскольку в полном RAG-конвейере retrieval вызывается внутри функции генерации (будет ниже). Однако отдельная демонстрация поиска наглядно показывает, какие фрагменты попадают в контекст модели.

В выводе можно увидеть дубликаты (первые два результата). Скорее всего, это связано с разбиением текста на чанки с перекрытием. В реальных системах такие особенности необходимо отслеживать и устранять, однако в демонстрационном примере это можно не исправлять.

Шаг 4. Generation

from transformers import AutoTokenizer, AutoModelForCausalLM
import torch
import textwrap


model_name = "Qwen/Qwen2.5-3B-Instruct"

tokenizer = AutoTokenizer.from_pretrained(model_name)

llm = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype=torch.float16,
    device_map="auto"
)

def generate_answer(query, k=3, width=50):
    # 1. Retrieval
    results = vectorstore.similarity_search(query, k=k)
    context = "\n\n".join([doc.page_content for doc in results])

    # 2. Prompt
    prompt = f"""Ответь на вопрос, используя контекст.
    
            Контекст: {context}

            Вопрос: {query}
            
            Ответ:
            """

    # 3. Generation
    inputs = tokenizer(prompt, return_tensors="pt").to(llm.device)

    outputs = llm.generate(
        **inputs,
        max_new_tokens=200,
        temperature=0.7,
        do_sample=True
    )

    text = tokenizer.decode(outputs[0], skip_special_tokens=True)
    answer = text[len(prompt):].strip()

    print(f"\nВопрос:\n{textwrap.fill(query, width)}\n")
    print("Ответ:\n")
    print(textwrap.fill(answer, width))

# Пример
generate_answer("Кто такой Пушкин?")

И результат:

Вопрос:
Кто такой Пушкин?

Ответ:

Александр Сергеевич Пушкин был русским поэтом,
драматургом и прозаиком, значительное место в
мировой литературе занимающий. Он родился в
дворянской семье и воспитывался в духе
аристократического восприятия жизни. Его биография
связана с неоднократными упоминаниями о своей
родословной, где он рассматривал предков как
образцы истинной «аристократии», честно служившей
отечеству. Кроме того, Пушкин часто обращался к
образу своего прадеда по матери — африканца Абрама
Петровича Ганнибала, который был слугой и
воспитанником Петра I и затем военным инженером и
генералом. Его творчество включ

Видно, что генерация опирается на найденный контекст (вывод с шага 3). Упоминаются происхождение Пушкина и образ его прадеда Абрама Ганнибала. При этом модель не воспроизводит текст дословно, а перерабатывает его в связный и адаптированный ответ.

Заключение

Итого, RAG решает проблему устаревших знаний и галлюцинаций, объединяя LLM с внешними источниками данных. Чем сложнее стратегия, тем больше автономности и точности система получает без изменения самой модели. На каком-то этапе система уже умеет самостоятельно уточнять запросы, повторно обращаться к хранилищам и проверять результаты.

Это логически подводит к следующему шагу — ИИ-агентам, которые не только генерируют ответы, но и целенаправленно действуют: планируют шаги, вызывают инструменты и работают с внешними данными самостоятельно.

Далее — Базовый минимум: ИИ-агенты (в разработке).

Список используемых источников 

1. Gao Y., Xiong Y., Gao X., Jia K., Pan J., Bi Y., Dai Y., Sun J., Wang M., Wang H. Retrieval-Augmented Generation for Large Language Models: A Survey // arXiv. 2023. URL: https://arxiv.org/abs/2312.10997 (дата обращения: 05.11.2025).

2. Lewis P., Perez E., Piktus A., Petroni F., Karpukhin V., Goyal N., Küttler H., Lewis M., Yih W.-t., Rocktäschel T., Riedel S., Kiela D. Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks // Advances in Neural Information Processing Systems. — 2020. — Vol. 33.

3. Martineau K. What is retrieval-augmented generation (RAG)? [Электронный ресурс] // IBM Research : Blog. — 22 Aug 2023. — URL: https://research.ibm.com/blog/retrieval-augmented-generation-RAG (дата обращения: 03.01.2026).

4. RAG: учим искусственный интеллект работать с новыми данными [Электронный ресурс] // Yandex Cloud : блог. — 20 мая 2025 г. — URL: https://yandex.cloud/ru/blog/posts/2025/05/retrieval-augmented-generation-basics (дата обращения: 03.01.2026). 

5. Что такое RAG: как работает генерация с дополненной выборкой в нейросетях [Электронный ресурс] // Developers.Sber.ru : база знаний. — 27 октября 2025. — URL: https://developers.sber.ru/help/business-development/what-is-rag (дата обращения: 03.01.2026).

Комментарии (12)


  1. denis_iii
    24.02.2026 07:16

    Про подмес в контекст знаний, через запрос в RAG достаточно понятно. Интересно будет узнать про агентов и их работу через создание MCP инструментов (tools) в аналогичном минималистичном примере.


    1. Sherstpasha Автор
      24.02.2026 07:16

      Да. В следующей части такое планируется) Тоже в Google Colab, чтобы воспроизвести было просто.


  1. Yurez777
    24.02.2026 07:16

    Самое сложное это если книгу отсканировал ( нельзя выделить текст)

    То OCR плохо всю информацию переводит в нормальный текст, и тогда RAG не работает или криво работает , может есть статья как с этим бороться?


    1. Sherstpasha Автор
      24.02.2026 07:16

      Зависит от OCR-движка. Лучше всего будет сначала перевести страницы в текст, если качества недостаточно, то взять модели лучше или вообще мультимодальную модель или API-сервис. Если текст печатный, обычные OCR обычно справляются без проблем. Есть пример страниц?


      1. Yurez777
        24.02.2026 07:16

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


        1. Sherstpasha Автор
          24.02.2026 07:16

          1. Sherstpasha Автор
            24.02.2026 07:16

            Я думал, что вполне можно распознать документы, а потом применить раг. Модель небольшая, но если она не влезет локально, то можно с помощью LM Studio запустить что нибудь квантованное


  1. Sherstpasha Автор
    24.02.2026 07:16

    https://colab.research.google.com/drive/1o3q4Px5YWGY3vFfWDeVu2fbZtC4XTmAb?usp=sharing
    Кажется, что вполне получилось распознать с правильным макетом


  1. Yurez777
    24.02.2026 07:16

    Я хотел локально развернуть , максимально для ответов выбирал модели 7b.

    Для OCR пробовал

    RapidOCR ,PaddleOCR, docTR, Surya

    Моя цель была Локальный RAG собрать.

    Спасибо.)

    ,


  1. sergei_ai
    24.02.2026 07:16

    Хорошая систематизация. Из практики добавлю пару вещей, которые всплывают на продакшене:

    Re-ranking — на больших базах top-K из векторного поиска часто притаскивает шум. Cross-encoder reranker сильно помогает с релевантностью.

    По чанкингу — размер очень зависит от домена. Для юридических документов 800 символов мало (теряется контекст), для FAQ и 300 хватает. Тут только эксперименты.


    1. Sherstpasha Автор
      24.02.2026 07:16

      Спасибо! Отличное дополнение) Реранкер может значительно повысить релевантность поиска, хотя и вместе с этим значительно увеличить время на извлечение (не так важно, потому что по сравнению инференсом ллм это копейки). Причем, можно извлечь больше, а среди них реранкером отобрать топ N. Есть очень простой пример "поглубже", чем в статье https://github.com/sherstpasha/practicum_yandex_retrieval


    1. gavexe
      24.02.2026 07:16

      Что за модели вы оба используете? Проблемы шума давно нет. Добавление дополнительных (непротиворечивых, конечно) данных не снижает качество модели. Единственный минус - количество токенов. А так хоть забивай весь мегабитный контекст чанками топ-100 - качество не упадет. А то, что размер чанков зависит от предметной области - это точно подмечено. Еще завист от функционала системы: если это чат-бот - одно. Если агент, пишущий код - другое. Лучшие практики - parental retrievement. Размер большого чанка - абзац/таблица. Маленького - размер среднего входного текста. Если чат бот - предложение. Пользователи обычно не пишут больше одного предложения чат боту.