Баннер мобильный (3) Пройти тест

Что такое RAG и как заставить нейросеть отвечать по документам, а не выдумывать факты

Простое объяснение с примерами и практикой на Python

Разбор

6 июля 2026

Поделиться

Скопировано
Что такое RAG и как заставить нейросеть отвечать по документам, а не выдумывать факты

Содержание

    ChatGPT, Claude, Gemini и другие языковые модели обучены на огромном количестве текстов из интернета и открытых источников. Из-за этого они хорошо справляются с общими задачами — например, могут объяснить какое-то понятие, помочь набросать структуру текста или пересказать общеизвестные факты. Однако у них нет доступа к вашим внутренним данным: корпоративной базе знаний, актуальным версиям документов, регламентам и прочей информации.

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

    Чтобы нейросеть могла правильно отвечать даже на специфические вопросы, ей можно дать своего рода шпаргалку. В нее модель и будет заглядывать каждый раз, прежде чем ответить на то, чего она не знает. Этот прием называется RAG, и в статье мы разберем его подробнее. Вы узнаете, что это такое, где применяется и как устроено, а в конце соберем простой RAG на Python в Google Colab. Статья рассчитана на новичков, поэтому все будем упрощать и объяснять на примерах.

    Что такое RAG и где его используют

    Аббревиатура RAG расшифровывается как retrieval-augmented generation — «генерация, дополненная поиском». Проще говоря, модель сначала находит подходящие фрагменты в источниках, а уже потом собирает ответ на их основе. Это похоже на студента на экзамене с открытым конспектом: он не пытается вспомнить все наизусть, а быстро находит нужную страницу и отвечает по ней.

    Главное преимущество такого подхода в том, что нейросеть получает доступ к свежим и закрытым данным, которых не было в обучении, и отвечает строго по ним, ничего не добавляя от себя. При этом вам не нужно дорого и долго переобучать модель — достаточно открыть ей доступ к нужным документам.

    Заодно RAG решает проблему ограниченной памяти модели. У любой нейросети есть контекстное окно — максимальный объем текста, который она может удерживать в рамках запроса. Вся база знаний туда обычно не помещается, поэтому RAG обходит это ограничение: он не передает модели все документы, а находит несколько релевантных фрагментов и отправляет их вместе с вопросом.

    Представьте, что у вас есть сотня PDF-файлов, в каждом не меньше тысячи страниц, — и все они описывают регламенты компании. Где-то в одном из них есть пара абзацев про то, за сколько дней нужно подавать заявление на отпуск. Загрузить все эти файлы в чат ChatGPT или любой другой нейросети не выйдет — а RAG справится: сам найдет нужный фрагмент и передаст модели только его.

    Именно из‑за своей специфики RAG используют везде, где важно отвечать строго по проверенным данным. Вот несколько типичных сценариев:

    • чат-боты по базе знаний компании;
    • служба поддержки — отвечает по инструкциям и регламентам;
    • юридические и комплаенс-помощники — ищут ответы в договорах и законах;
    • ассистенты для разработчиков — отвечают по документации и API;
    • аналитика по отчетам и исследованиям — быстро находит нужные цифры и выводы в объемных документах.

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

    Как работает RAG — два этапа от документа до ответа

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

    Работа RAG
    Источник: автор статьи

    Этап 1. Подготовка документов

    Сначала нужно собрать все материалы, по которым нейросеть будет отвечать: инструкции, регламенты, статьи из базы знаний, PDF-файлы и другие документы. Затем их предобрабатывают — извлекают чистый текст, убирают лишнее форматирование, повторяющиеся элементы, артефакты копирования, навигацию и прочий мусор. Без этой подготовки качество поиска и ответов заметно падает.

    После очистки текст делят на небольшие фрагменты — чанки. Например, вместо того чтобы хранить и искать по целой статье на 20 страниц, ее режут на куски по 1–3 абзаца или по небольшим разделам. Так поиск быстрее находит нужное место, а модель получает ровно тот фрагмент, что отвечает на вопрос, и не размазывает ответ по всему документу. Режут обычно по смыслу на абзацы, подразделы или пункты, — чтобы каждый чанк был понятен сам по себе.

    Далее каждый чанк превращают в эмбеддинг — вектор (набор чисел), который отражает смысл фрагмента. Подробнее об этом механизме у нас есть отдельная большая статья, но если коротко — представьте, что модель переводит текст в координаты вроде [0.12, −0.03, 0.77, …]. В этом случае чанки «заявление на отпуск» и «как оформить отпуск» получат близкие векторы, а «рецепт борща» или «как заменить шину» — далекие. Поэтому, когда пользователь спрашивает «за сколько дней подавать заявление на отпуск», система превращает вопрос в эмбеддинг и находит в базе те чанки, чьи векторы ближе всего по смыслу.

    И в завершение подготовительного этапа все эти векторы складывают в специальную векторную базу данных. От обычной базы она отличается тем, что умеет быстро находить ближайшие по смыслу векторы среди миллионов — а это как раз то, что нужно для поиска. Вот несколько примеров таких баз: Pinecone, Weaviate, Qdrant, Chroma и pgvector — расширение для PostgreSQL.

    Этап 2. Ответ на вопрос

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

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

    Давайте еще раз уточним: нейросеть видит только те фрагменты, которые нашлись на этапе поиска. Если документы подготовлены плохо или поиск выдал нерелевантные куски, ответ получится слабым — даже у самой умной модели.

    Собираем простой RAG на Python своими руками

    В этом разделе мы соберем базовый RAG: возьмем несколько коротких документов, нарежем их на чанки, переведем в эмбеддинги, найдем ближайший по смыслу к вопросу фрагмент и передадим его модели. Есть и более продвинутые подходы — гибридный поиск, переранжирование, агентный RAG, GraphRAG. Они точнее работают на больших и сложных базах, но устроены сложнее. Однако принцип у всех один: сначала найти нужное, потом ответить.

    Почти все действия мы выполним в бесплатном онлайн‑сервисе Google Colab — вам нужно создать новый блокнот и повторять шаги. В конце понадобится API‑ключ, чтобы отправить найденный фрагмент языковой модели и получить ответ. Здесь мы будем использовать Claude от Anthropic, но подойдет и любая другая модель. Можно и без ключа: тогда вы дойдете до готовой шпаргалки с вопросом и нужным фрагментом, но финальный ответ модель уже не сгенерирует.

    Шаг 1. Устанавливаем библиотеку и загружаем документы

    Чтобы не обучать нейросеть с нуля, возьмем готовую предобученную модель. Подключить ее можно с помощью библиотеки sentence-transformers — она сама загрузит модель и затем будет превращать все наши тексты в эмбеддинги.

    Скопируйте команду ниже в первую ячейку и запустите ее:

    !pip install -q sentence-transformers

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

    docs = ["Сотрудникам предоставляется 28 календарных дней оплачиваемого отпуска в год.",    "Заявление на отпуск нужно подавать не позднее чем за две недели до его начала.",    "Рабочий день длится с 10:00 до 19:00, обеденный перерыв — один час.",    "Удаленно можно работать до трех дней в неделю по согласованию с руководителем.",    "Раз в год компания компенсирует обучение на сумму до 50 000 рублей.",    "Больничный оплачивается на основании электронного листа нетрудоспособности.",]

    Вот как это должно выглядеть в Google Colab: 

    Шаг 1
    Источник: автор статьи

    Шаг 2. Превращаем чанки в эмбеддинги

    На прошлом шаге мы установили библиотеку — а сейчас загрузим саму модель, которая умеет переводить текст в числа. Возьмем модель paraphrase-multilingual-MiniLM-L12-v2, которая понимает русский язык. Команда model.encode(…) прогонит через нее документы и вернет для каждого эмбеддинг. А параметр normalize_embeddings=True приводит векторы к единому масштабу, чтобы их было удобно сравнивать. Вот код, который выполнит все эти действия:

    from sentence_transformers import SentenceTransformer
    import numpy as np
    
    model = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
    
    doc_embeddings = model.encode(docs, normalize_embeddings=True)
    
    print("Готово: каждый кусок превращен в вектор длиной", doc_embeddings.shape[1])

    После выполнения команды каждый фрагмент становится вектором из 384 чисел:

    Шаг 2
    Источник: автор статьи

    Шаг 3. Ищем нужный фрагмент по вопросу

    Теперь напишем функцию search. Она превращает вопрос в эмбеддинг, сравнивает его со всеми векторами документов и возвращает близкий по смыслу фрагмент. Чтобы проверить, как это работает, прогоним через нее три вопроса:

    def search(question, top_k=1):    
        q = model.encode([question], normalize_embeddings=True)[0]    
        scores = doc_embeddings @ q                 # косинусная близость    
        best = np.argsort(scores)[::-1][:top_k]    
        return [(docs[i], float(scores[i])) for i in best]
    
    questions = [    "Сколько дней отпуска у сотрудника?",    "Можно ли работать из дома?",    "Как оплачивается больничный?",]
    
    for q in questions:    
        chunk, score = search(q)[0]    
        print(f"Вопрос:  {q}")    
        print(f"Найдено: {chunk}")    
        print(f"Близость: {score:.2f}\n")

    После запуска для каждого вопроса модель покажет найденный фрагмент и число «Близость» — показатель того, насколько фрагмент подходит по смыслу.

    Чем ближе к 1, тем сильнее совпадение; чем ближе к 0 — тем дальше по смыслу:

    Вопрос:  Сколько дней отпуска у сотрудника?
    Найдено: Сотрудникам предоставляется 28 календарных дней оплачиваемого отпуска в год.
    Близость: 0.68
    
    Вопрос:  Можно ли работать из дома?
    Найдено: Удаленно можно работать до трех дней в неделю по согласованию с руководителем.
    Близость: 0.35
    
    Вопрос:  Как оплачивается больничный?
    Найдено: Больничный оплачивается на основании электронного листа нетрудоспособности.
    Близость: 0.50

    Шаг 4. Собираем подсказку для модели

    Найденный с помощью функции search фрагмент — это и есть наша шпаргалка. Но просто так передать ее модели нельзя: нужно правильно оформить запрос. Для этого напишем функцию build_prompt: она формирует короткую инструкцию для модели — «ответь только по этому тексту, а если ответа в нем нет, так и скажи» — и подставляет в нее найденный фрагмент и сам вопрос. Пока мы только собираем этот запрос и выводим его на экран, чтобы посмотреть, как он выглядит:

    def build_prompt(question, context):    
        return f"""Ответь на вопрос, опираясь только на контекст ниже.Если ответа в контексте нет, так и скажи.
    
    Контекст:
    {context}
    
    Вопрос: 
    {question}
    Ответ:"""
    
    question = "Сколько дней отпуска положено сотруднику?"
    context = search(question)[0][0]
    print(build_prompt(question, context))

    Получится подсказка с вопросом и найденным фрагментом:

    Ответь на вопрос, опираясь только на контекст ниже.
    Если ответа в контексте нет, так и скажи.
    
    Контекст: Сотрудникам предоставляется 28 календарных дней оплачиваемого отпуска в год.
    Вопрос: Сколько дней отпуска положено сотруднику?
    Ответ:

    На этом основные шаги закончены. Осталось отправить нашу подсказку настоящей языковой модели и получить от нее готовый ответ. Для этого нужно получить API-ключ и оплатить доступ к модели. Подробно останавливаться на получении ключа и оплате мы не будем — если вы никогда этого не делали, посмотрите наш материал про Claude Code, где мы все подробно разобрали.

    Шаг 5. Получаем ответ от нейросети

    Установим библиотеку Anthropic:

    !pip install -q anthropic

    Чтобы модель ответила, ей необходим доступ к API. Перейдем на платформу Anthropic Console, создадим там API-ключ и вернемся в Google Colab. Сам ключ в коде писать нельзя — это небезопасно, поэтому Colab хранит такие данные отдельно. Слева в Colab откройте раздел «Секреты» (значок ключа), нажмите «Добавить секрет», в поле названия впишите ANTHROPIC_API_KEY, в поле значения вставьте свой ключ и включите тумблер «Доступ из блокнотов»:

    Шаг 4
    Источник: автор статьи

    У нас все готово для последнего шага. Код ниже подключается к Claude и отправляет ему подсказку. Обратите внимание на строку userdata.get(«ANTHROPIC_API_KEY») — именно так код безопасно достает ключ из секретов Colab, не показывая его в самом коде. Дальше функция ask_llm берет вопрос, находит к нему фрагмент через search, собирает подсказку через build_prompt и передает все модели, а та возвращает осмысленный ответ:

    import anthropic
    from google.colab import userdata
    
    client = anthropic.Anthropic(api_key=userdata.get("ANTHROPIC_API_KEY"))
    
    def ask_llm(question, context):    
        prompt = build_prompt(question, context)    
            message = client.messages.create(        
            model="claude-sonnet-4-6",        
            max_tokens=300,        
            messages=[{"role": "user", "content": prompt}],    
        )    
        return message.content[0].text
    
    question = "Сколько дней отпуска положено сотруднику?"
    context = search(question)[0][0]
    print("Контекст:", context)
    print("Ответ:", ask_llm(question, context))

    Мы использовали модель claude-sonnet-4-6. Если к моменту чтения она устареет или окажется недоступной, не страшно — просто укажите в параметре model актуальный идентификатор. Список доступных моделей и их точные названия можно посмотреть в Anthropic Console. После запуска модель прочитает подсказку и вернет аккуратный ответ, опираясь только на найденный фрагмент:

    Контекст: Сотрудникам предоставляется 28 календарных дней оплачиваемого отпуска в год.
    Ответ: Сотруднику положено 28 календарных дней оплачиваемого отпуска в год.

    А теперь проверим, не начнет ли модель фантазировать, если ответа в документах нет. Зададим ей вопрос на тему, которой в нашей базе точно не было:

    question = "Можно ли приводить на работу собаку?"
    context = search(question)[0][0]
    print(ask_llm(question, context))

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

    Шаг 5
    Источник: автор статьи

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

    Полезные статьи и ссылки по теме

    Разбор

    Поделиться

    Скопировано
    0 комментариев
    Комментарии