Устав от проблем с доступом к зарубежным сервисам, я решил создать собственный аналог NotebookLM — доступный инструмент для работы с исследовательскими материалами, который подойдёт не только жителям России, но и всем, кто ищет альтернативу импортным решениям.
Что такое NotebookLM и зачем его заменять?
NotebookLM — это сервис, позволяющий загружать научные статьи, PDF, видео и другие материалы, а затем задавать по ним вопросы на естественном языке. Система анализирует контент, строит базу знаний, генерирует выжимки и даже помогает готовиться к экзаменам через флешкарточки.
На первый взгляд всё просто: загрузил документ — получил ответ. Но внутри — сложный пайплайн: шесть этапов обработки, четыре стратегии поиска, система управления контекстом и тонкая работа с векторами. В этой статье — как это устроено, почему выбран именно такой стек и на какие ошибки я наступил.
Зачем делать своё, если есть ChatGPT или RAG?
Да, ChatGPT умеет работать с PDF. Но только пока речь о паре файлов. Когда нужно проанализировать 15 статей для курсовой, модель начинает путать авторов, приписывать одни выводы другим. Не потому что тупая — просто это чат. Закрыл вкладку — и всё забыто.
Можно собрать RAG самостоятельно. Есть фреймворки: LlamaIndex, Haystack. Они режут текст на чанки, векторизуют, ищут. На чистом PDF — красиво. В реальности: скан учебника превращается в мусор, YouTube-лекции не парсятся, а поиск возвращает одни и те же фрагменты из 40 источников.
Я решил сделать систему, где источники загружаются один раз и остаются в блокноте навсегда. Система сама решает, сколько контекста взять из каждого документа. Плюс — инсайты, заметки, флешкарточки. Всего этого нет в стандартных RAG-решениях.
Как это выглядит для пользователя
Работа строится вокруг блокнотов — проектов вроде «Курсовая по нейробиологии» или «Подготовка к ЕГЭ». Внутри — источники, чат, заметки и карточки.
Загрузил материалы один раз — они остаются. Через месяц добавил новую статью к 20 старым — задал вопрос. JuliaLM видит всё сразу. Не нужно начинать с нуля.
Источники — это PDF, ссылки, YouTube-видео, сканы, текст из буфера. JuliaLM сама извлекает текст, делает саммари и готовит фрагменты для поиска.
Чат — задаёшь вопрос, получаешь ответ с привязкой к источникам. Без галлюцинаций. Только по тому, что загружено.
Архитектура
Фронтенд — Nuxt.js, бэкенд — FastAPI, основная база — SurrealDB. Всё в Docker Compose.
SurrealDB — неочевидный выбор. Это мультимодальная база: реляционные таблицы, графовые связи и встроенный векторный поиск — всё в одном. Можно спорить о зрелости, но для моего случая это заменило Postgres + Redis + Pinecone. Один сервис вместо трёх — проще деплоить и дешевле поддерживать.
Что происходит при загрузке источника
Пользователь добавляет источник: ссылку, файл или текст. Запускается многоступенчатый пайплайн.
Обработка очереди
Обработку нельзя делать синхронно. PDF на 200 страниц может парситься 30–40 секунд. Держать HTTP-соединение — плохая идея: таймауты, обрывы, спиннер без прогресса.
Решение: два режима. Для лёгких источников — синхронная обработка (5–10 сек). Для тяжёлых — асинхронная: API сразу возвращает command_id, статус «Обработка…», фронтенд опрашивает прогресс.
Очередь задач — своя реализация на SurrealDB. Можно было взять Celery или RQ, но это ещё один Redis. Наш вариант хранит задачи в базе: с ретраями (до 5 попыток), экспоненциальным бэкоффом и отслеживанием прогресса. Не для масштаба, но для нас — работает.
Извлечение текста: каскад фолбэков
Тип источника определяет путь извлечения. Для каждого — не один метод, а цепочка с резервными вариантами.
PDF и документы: основной парсер — Docling (через обёртку content_core). Он сохраняет структуру: заголовки, таблицы, списки — в чистый Markdown. Для PPTX или XLSX — Gotenberg (headless LibreOffice), который конвертирует почти всё.
YouTube: три уровня:
- youtube-transcript-api — официальные субтитры. Приоритет: русские → английские → испанские/португальские (для академического контента).
- pytubefix с флагом ANDROID — притворяется мобильным приложением, реже блокируется.
- Firecrawl или Jina — крайние случаи, когда всё сломалось.
Каждые пару месяцев YouTube что-то ломает. Каскад спасает: пользователь не замечает сбоев.
Веб-страницы: основной — Playwright (headless Chromium) с полным рендерингом JS. Рандомизируем User-Agent, viewport, navigator — иначе капча или пустая страница. Текст извлекаем через readability (алгоритм из Firefox). Если не сработало — Jina Reader, потом простой HTTP. Опять каскад.
Изображения: сканы и фото отправляются в vision-модель как base64. Медленно, но хорошо работает с рукописным текстом и плохими сканами.
Векторизация: нарезка, эмбеддинги и грабли
После извлечения текста — подготовка к семантическому поиску. Это фоновая задача vectorize_source.
Нарезка на чанки
Фрагменты по ~500 токенов с перекрытием 15%. Сплиттер использует приоритеты: сначала двойные переносы (абзацы), потом одинарные, точки, запятые, пробелы — чтобы не рвать предложения.
Почему 500 токенов? Маленькие чанки (100–200) — точный поиск, но теряется контекст. Большие (1000+) — наоборот. 500 — эмпирическая золотая середина для научных статей и конспектов.
Четыре стратегии поиска
При вызове search_sources запускаются четыре стратегии параллельно:
- Vector search — семантический поиск по эмбеддингам. Находит по смыслу: «проблемы со сном» → «нарушения циркадных ритмов».
- Text search — классический BM25. Точные совпадения ключевых слов.
- Title search — поиск по заголовкам. Быстро, если знаешь, откуда информация.
- Insight search — поиск по саммари и инсайтам. Полезно, когда ключевые тезисы уже выделены.
Результаты дедуплицируются: если три стратегии нашли один источник — это сильный сигнал. Скор увеличивается на +0.05 за каждое дополнительное попадание. Возвращаются топ-5.
Почему четыре? Ни одна стратегия не работает везде. Комбинация покрывает больше случаев.
Бюджет контекста: как не взорвать окно модели
Проблема: модель нашла 5 релевантных источников, но каждый — по 100 страниц. Отдать всё? Не влезет. По одному чанку? Потеряем контекст.
Решение — система бюджетов. Общий лимит на запрос — 300 000 символов (~75K токенов). На каждый источник — минимум 5 000, максимум 40 000 символов.
Три сценария:
- Короткий источник — отдаём целиком.
- Длинный, запрос известен — инсайты + релевантные чанки. Запускаем vector search внутри документа (порог ≥ 0.15).
- Длинный, запрос неизвестен — инсайты + начало текста. Лучше, чем ничего.
Так можно работать с книгами на сотни страниц, не перегружая контекст.
Флешкарточки и интервальное повторение
Отдельная фишка: генерация флешкарточек с интервальным повторением.
Пользователь выбирает источники, указывает количество карточек — модель генерирует пары «вопрос-ответ». Один факт — одна карточка. Формат — строгий JSON, с валидацией.
После генерации — FSRS (Free Spaced Repetition Scheduler). Тот же алгоритм, что в Anki, но современная реализация. Каждая карточка хранит стабильность, сложность и состояние (новая, изучается, повторяется, переучивается). При оценке 1–4 алгоритм рассчитывает следующий показ.
Зачем не интегрировались с Anki? Чтобы убрать шаги. Читаешь в JuliaLM — генерируешь карточки — учишь. Без экспорта, импорта, переключений.
Поиск по базе знаний
Кроме RAG в чате — отдельный поиск: BM25 (полнотекстовый) и vector search (семантический).
Есть и третий режим — «Спросить базу знаний». Это LangGraph-граф:
- Модель анализирует вопрос и генерирует стратегию: до 5 поисковых запросов с инструкциями.
- Запросы выполняются параллельно, каждый возвращает до 10 результатов.
- Для каждого — промежуточный ответ.
- Финальная модель синтезирует всё в один связный ответ.
Звучит как overkill, но для сложных вопросов («в чём авторы расходятся по теме X?») работает заметно лучше, чем одиночный поиск.
Планы и квоты
Тарификация — сложный инженерный вызов. Три плана:
- Free — 0₽, 20 промптов за всю жизнь.
- Pro — 799₽/мес, 800 промптов в 5-часовом скользящем окне.
- Business — 1199₽/мес, 2000 промптов.
Скользящее 5-часовое окно вместо дневного или месячного — компромисс для студентов. Типичный паттерн: 100 запросов за вечер, потом неделя без активности. Месячный лимит — щедрый, дневной — жёсткий. Пятичасовое окно — баланс: можно бустить, но нельзя утилизировать всё за час.
Перед каждым вызовом LLM проверяется квота. При истечении подписки — автоматический даунгрейд на Free. Без сюрпризов, без скрытых списаний.
Полный путь: от PDF до ответа
От загрузки PDF до ответа с цитатами — 15–30 секунд на обработку + 5–10 секунд на генерацию.
Итог
JuliaLM — не обёртка над ChatGPT. Это система с собственным пайплайном, четырёхстратегийным RAG, бюджетированием контекста и интервальным повторением.
Не всё идеально: YouTube периодически ломает транскрипцию, SurrealDB пока не дотягивает до Postgres по зрелости. Но для задачи «загрузил 30 статей, задал вопрос, получил ответ с цитатами» — работает.
Есть бесплатный план на 20 запросов. Сервис в бета-версии — возможны баги.