{}const=>[]async()letfn</>var
ИИРазработка

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

Строим понятную схему RAG для личных конспектов: очистка текста, чанки, embeddings, векторный поиск, контекст для модели, ссылки на фрагменты и тестовый набор.

К

Кодик

Автор

7 мин чтения

RAG по своим конспектам строится из пяти частей: очистка файлов, разбиение на смысловые чанки, embeddings, поиск похожих фрагментов и ответ модели только на найденном контексте. Для доверия сохраняйте имя файла и заголовок каждого чанка, а при недостатке данных просите модель честно отказать.

RAG не обучает модель заново и не гарантирует истину. Он подбирает фрагменты перед генерацией ответа. Если поиск вернул не тот конспект, сильная модель всё равно получит плохую основу. Поэтому сначала проверяют retrieval отдельно: для набора вопросов смотрят, попал ли правильный фрагмент в top-k. Только затем оценивают формулировку ответа и ссылки.

1Chunk

Фрагмент заметки с метаданными и законченным смыслом.

2Embedding

Числовой вектор для сравнения близости текста.

3Top-k

Несколько наиболее похожих фрагментов для контекста.

От заметки к чанку, вектору и ответу по найденным фрагментам
Путь вопроса через RAG: заметки чистятся, режутся на чанки по абзацам, превращаются в векторы, поиск отдаёт top-k, и модель отвечает только по этим фрагментам.

RAG по своим конспектам: начните с качественных чанков

Не режьте текст строго каждые 500 символов посреди предложения. Для заметок удобно сначала делить по заголовкам и абзацам, затем собирать блоки до лимита. Пример ниже использует только стандартный Python и сохраняет целые абзацы. В production дополнительно учитывают токены, таблицы, код и перекрытие там, где мысль продолжается между блоками.

def split_notes(text: str, max_chars: int = 700) -> list[str]:
    paragraphs = [part.strip() for part in text.split("\n\n") if part.strip()]
    chunks: list[str] = []
    current: list[str] = []

    for paragraph in paragraphs:
        candidate = "\n\n".join([*current, paragraph])
        if current and len(candidate) > max_chars:
            chunks.append("\n\n".join(current))
            current = [paragraph]
        else:
            current.append(paragraph)

    if current:
        chunks.append("\n\n".join(current))

    return chunks
Ожидаемый результат
  • Абзацы остаются целыми, пока блок не превышает целевой размер
  • Каждый chunk можно связать с именем файла и заголовком

После chunking передайте список текстов в embedding-модель. Ollama рекомендует embeddinggemma, qwen3-embedding и all-minilm. Векторы документа и вопроса должны создаваться одной моделью. Храните рядом source, heading, дату и порядковый номер. При смене embedding-модели переиндексируйте всю коллекцию, потому что пространства векторов несовместимы.

🔥 100 000+ учеников уже с нами

Устал читать теорию?
Пора кодить!

Кодик — приложение, где ты учишься программировать через практику. AI-наставник, интерактивные уроки, реальные проекты.

🤖 AI 24/7
🎓 Сертификаты
💰 Бесплатно
🚀 Начать учиться
Присоединились сегодня

Что проверять в RAG отдельно

ЭлементЧто означаетЧто делать
ОчисткаНет меню, дублей и мусорных страницПросмотреть случайные 20 фрагментов
ChunkingМысль не обрываетсяВопрос понятен по одному chunk
RetrievalПравильный источник в top-kНабор из 30 контрольных вопросов
GenerationОтвет опирается на контекстЦитаты ведут к найденным фрагментам
ОтказНет ответа без данныхВопрос вне коллекции

Большой top-k не всегда лучше. Лишние фрагменты размывают контекст и могут противоречить друг другу. Начните с 3-5 результатов и показывайте их пользователю. Если точный термин не находится семантически, добавьте гибридный поиск по ключевым словам. Для сложных вопросов полезен reranker, который повторно сортирует кандидатов.

Разбор плохого ответа: сначала retrieval, потом генерация
Диагностика плохого ответа: сначала смотрите, какие chunks вернул поиск и есть ли среди них нужный конспект, и только потом правьте формулировку ответа.

Практика: RAG на десяти конспектах с проверкой попадания в top-3

Возьмите десять своих заметок и соберите на них минимальный поиск с ответом. Нужны три части: функция split_notes для чанков, embeddings одной моделью и выдача трёх фрагментов вместе с source и heading. Успехом считается такое: на контрольный вопрос нужный конспект попадает в top-3, а ответ модели ссылается именно на него.

  1. Чистка десяти заметок. Сложите десять файлов в одну папку, уберите меню, навигацию и дубли, а для PDF откройте извлечённый текст и прочитайте его глазами. Просмотрите 20 случайных фрагментов: обрывков вёрстки и склеенных слов в них быть не должно.
  2. Чанки по абзацам. Прогоните тексты через split_notes с max_chars=700 и распечатайте длины полученных блоков. Абзацы обязаны остаться целыми, а разрыв посреди предложения считайте браком чанкинга.
  3. Метаданные source и heading. Для каждого chunk сохраните source, heading, дату и порядковый номер в списке словарей. Проверьте выборочно: по одному фрагменту должно быть понятно, из какого файла и какого раздела он взят.
  4. Векторы одной моделью. Постройте embeddings всех чанков одной моделью, например all-minilm, и той же моделью векторизуйте вопрос. Имя модели запишите рядом с индексом, иначе через месяц будет непонятно, чем считались векторы.
  5. Top-3 и ответ с источниками. Задайте вопрос, выведите на экран три ближайших фрагмента с их source и heading, и только потом отдайте их модели как контекст. В ответе должны остаться имена файлов, по которым его можно открыть и проверить.

Сломайте условие намеренно: посчитайте вектор вопроса другой embedding-моделью, оставив старые векторы документов. Код не упадёт, ответ будет звучать уверенно, но выдача станет случайной, потому что пространства векторов несовместимы. Отдельно задайте вопрос по теме, которой в ваших десяти заметках нет: модель должна честно отказать, а не сочинить абзац. Верните исходную модель и переиндексируйте коллекцию целиком.

Критерий готовности. Готово, когда вы отделяете ошибку retrieval от ошибки генерации, не гадая. Подтверждение простое: на наборе из 30 вопросов с заранее указанным ожидаемым файлом вы считаете долю попаданий источника в top-3, а не любуетесь красивой формулировкой. Каждый ответ должен вести к конкретному абзацу конспекта через source и heading.

Дальше добавьте гибридный поиск по ключевым словам: точные термины, номера и редкие названия семантика часто теряет. Потом попробуйте reranker, который пересортирует кандидатов перед отправкой в модель, и сравните долю попаданий до и после. Держите top-k в диапазоне 3-5: лишние фрагменты размывают контекст и начинают противоречить друг другу. Перекрытие между чанками включайте точечно, только там, где мысль переходит в следующий абзац.

Частые ошибки и почему они появляются

Частая ошибка состоит в оценке только красивого ответа. Модель может угадать из общих знаний, хотя ваш конспект не найден. Вторая ошибка: индексировать PDF без проверки извлечённого текста. Третья: менять embedding-модель и оставлять старые векторы. Такой индекс выглядит рабочим, но близость теряет смысл.

Один огромный chunk

Поиск получает слишком общий вектор, а модель тратит контекст на лишнее.

Нет источников

Без метаданных пользователь не может открыть исходный абзац и проверить ответ.

Нет тестовых вопросов

Качество нельзя оценить по двум удачным примерам. Соберите вопросы с ожидаемыми источниками.

Самопроверка

Обучает ли RAG модель на моих конспектах?

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

Почему embedding-модель для документов и вопроса должна совпадать?

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

Что проверять первым, если RAG отвечает неправильно?

Первым проверяют retrieval: какие chunks вернул поиск и есть ли среди них правильный источник. Если нужного фрагмента в top-k нет, править промт бесполезно, основа ответа уже плохая. Разбирать формулировку и ссылки имеет смысл только при верной выдаче.

Какого размера делать чанки для заметок?

Чанки для заметок удобнее собирать по заголовкам и абзацам с лимитом порядка 700 символов, а не резать текст каждые 500 символов посреди предложения. Главный критерий: по одному chunk понятно, на какой вопрос он отвечает. Один огромный фрагмент даёт слишком общий вектор и съедает контекст модели.

Сколько фрагментов брать в top-k?

В top-k разумно начинать с 3-5 фрагментов. Большой top-k не улучшает ответ сам по себе: лишние куски размывают контекст и могут спорить друг с другом. Показывайте найденные фрагменты пользователю, чтобы он видел, на чём построен ответ.

Зачем хранить имя файла и заголовок рядом с чанком?

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

Что делать дальше

Возьмите десять заметок и тридцать вопросов, для каждого заранее укажите ожидаемый файл. Сначала измерьте попадание источника в top-3, затем подключайте генерацию. Реализацию загрузчика и тестов можно собрать после курса GenAI. Выбор модели уточните в статье про Ollama и память, а формат запросов в материале про промты.

🎯Хватит откладывать

Понравилась статья?
Пора применять на практике!

В Кодик ты не просто читаешь — ты сразу пишешь код. Теория + практика = реальный скилл.

Мгновенная практика
🧠AI объяснит код
🏆Сертификат

Без регистрации • Без карты