Гималаи Открыть приложение

Как устроены Гималаи

Веб-приложение и Telegram Mini App для подготовки к ЕГЭ. Лента подбирает следующую задачу по уровню ученика, после ошибки ведёт по подсказкам, а помощник Химо объясняет именно его попытку. Учитель видит, кто занимался и где ошибается.

Информатика, русский язык, профильная математика, обществознание · MVP, октябрь 2026

Система

Все запросы проходят через nginx. API — единственное место с логикой и данными: бот не ходит в базу, а обращается к API по внутреннему HTTP с общим секретом. Telegram Bot API с российских серверов недоступен, поэтому бот ходит к нему через туннель, который пропускает только сети Telegram. Модель, почта и всё остальное идут напрямую.

Схема системы Пользователи и Telegram Mini App обращаются к nginx на сервере в Москве. nginx отдаёт веб-клиент и проксирует API. API работает с PostgreSQL, вызывает LLM-шлюз Sber500 и почтовый сервис. Бот обращается к API и к Telegram Bot API через туннель. Логи идут в journald, пульс — в healthchecks.io. VPS · Москва · Ubuntu 24.04 2 vCPU · 3.8 ГБ · systemd Ученик Учитель Команда Mini Appвнутри Telegram nginxTLS, HTTP/2 APIFastify 5 · Node 24 PostgreSQL 18только localhost Веб-клиентReact 19 SPA БотgrammY journaldJSON-логи туннель /api статика /api/internal LLM-шлюз Sber500Cloud.ru: DeepSeek V4 Pro,Kimi K2.6 для фото Unisender Goкоды входа healthchecks.ioпульс API и бота Telegram Bot APIlong polling HTTPS, стрим SSE только сети Telegram
Стрелка — направление запроса. Фиолетовая — вызов языковой модели.

Четыре решения, на которых всё держится

  1. Модель не ставит оценок. Проверка ответа и рейтинг ученика — детерминированный код. Модель только объясняет, опираясь на проверенное решение.
  2. Подсказки готовы заранее. Решения и четыре ступени подсказок написаны и проверены до показа. Подсказка открывается мгновенно и не может оказаться выдумкой.
  3. Один процесс API — сознательно. На текущей нагрузке один сервер проще и дешевле кластера. Запас измерен: около 40 RPS при контрольных 10.
  4. Данные в России. База, API и модели рантайма размещены в России. Через туннель идёт только трафик Telegram, обращений к модели в нём нет.

Компоненты

Монорепозиторий pnpm на TypeScript 6 и Node 24. Доменная логика вынесена в пакет core без ввода-вывода: им пользуются и сервер, и клиент, а тестируется он без базы и сети.

Приложения

api
Fastify 5, Drizzle, postgres-jsСессии, практика, Химо, кабинет учителя, домашние задания, метрики. Фоновые задачи: рассылки, снимки прогресса, очистка удалённых аккаунтов.
web
React 19, Vite 8, react-router 8Сайт, приложение ученика и учителя, панель метрик. Та же сборка открывается как Telegram Mini App, с темой и кнопками Telegram.
bot
grammYВход через Telegram, привязка аккаунта, приглашения учителя, кнопка Mini App, доставка уведомлений. В базу не ходит.

Библиотеки

core
Рейтинг Elo, подбор задачи, проверка ответа, определения событий и метрик.
contracts
Zod-схемы API, общие для веба, бота и сервера.
db
Схема Drizzle, 22 миграции, аналитические запросы панели, CLI для операций.
llm
Провайдер Cloud.ru с OpenAI-совместимым API, таймауты, повтор, промпты, учёт токенов и рублей.
log
JSON-логи pino, алерты в Telegram без данных пользователей, пульс, обработка падений.
mail
Отправка кодов входа через Unisender Go.

Что происходит, когда ученик нажимает «Дальше»

Решение задачи

  1. клиент → APIPOST /api/next
  2. API → базарейтинг по номерам, история показов
  3. coreподбор задачи с вероятностью решения около 0.7
  4. клиент → APIGET /api/serves/:id, условие и файлы
  5. клиент → APIPOST /api/serves/:id/attempts, ответ
  6. coreпроверка по спецификации номера, новый рейтинг
  7. API → базапопытка, освоение, событие, опыт
  8. API → клиентверно или нет, лестница подсказок

Подсказка и вопрос Химо

  1. клиент → APIPOST /api/serves/:id/hints
  2. API → базаследующая из четырёх ступеней, готовый текст
  3. клиент → APIвопрос Химо, Accept: text/event-stream
  4. API → базадневной лимит, условие, попытки, открытые ступени
  5. API → LLMпока ответ не раскрыт, ключа в контексте нет
  6. API → клиенттокены модели стримом SSE
  7. APIответ модели проверяется на утечку ключа
  8. API → базареплика и строка llm_calls: токены, задержка, рубли

Вход: три пути к одной учётной записи

Почта
Шестизначный код живёт 10 минут, даёт 5 попыток. В базе хранится только HMAC кода.
Mini App
Подпись initData проверяется по HMAC-SHA256 и должна быть не старше часа.
Telegram с сайта
Сайт открывает бота с одноразовым кодом, ученик жмёт «Да, войти», сайт узнаёт об этом опросом.
Сессия
На вебе httpOnly-cookie на 30 дней с продлением и CSRF-заголовком. В Mini App Bearer-токен, только в памяти.

Задача, которую ученик решит с вероятностью 70%

У ученика свой рейтинг по каждому номеру экзамена, по схеме Elo. Вероятность решить задачу зависит от разницы между рейтингом и сложностью. Лента выбирает задачу, где эта вероятность ближе всего к 0.7: достаточно трудно, чтобы учиться, и достаточно посильно, чтобы не бросить.

Вероятность решить задачу
Кривая ожидаемой вероятности Логистическая кривая: при равных рейтинге и сложности вероятность 0.5, при рейтинге выше сложности на 147 пунктов — 0.7. Полоса от 0.5 до 0.9 подсвечена. полоса 0.5–0.9: только здесь начисляется опыт 0 0.5 1 −400 0 +400 рейтинг ученика − сложность задачи цель 0.7

p = 1 / (1 + 10(сложность − рейтинг) / 400)
рейтинг += K · (результат − p), K снижается с 40 до 16 за первые 30 попыток

Как выбирается следующая задача

  1. Пул. Задачи, которых ученик не видел, и нерешённые, показанные больше 14 дней назад.
  2. Тренировка. Если ученик выбрал один номер, берём из него.
  3. Возврат. После ошибки лента возвращается к этому номеру.
  4. Старт. Пока попыток меньше пяти — по задаче каждого номера.
  5. Номер. Слабость, давность, вес в экзамене; один номер не идёт трижды подряд.
  6. Задача. p ближе всего к 0.7, случайно из трёх ближайших.

Это одна чистая функция, случайность передаётся ей снаружи, поэтому в тестах подбор детерминирован. Номер считается освоенным после трёх попыток при p ≥ 0.7.

Проверка ответа

У каждого номера своя типизированная спецификация ответа: целое число, цифры, кортеж, строки таблицы, слово, набор цифр, выбор варианта, десятичная дробь. Ответ не того формата отклоняется с пояснением и не засчитывается как ошибка.

Химо объясняет, но не оценивает

Модели и ограничения

Шлюз
Sber500, каталог Cloud.ru, OpenAI-совместимый API
Чат
deepseek-v4-pro, без рассуждений
Фото задач
kimi-k2.6
Закрепление
с чужим адресом или моделью API не стартует
Ответ
до 800 токенов в чате
Таймаут
60 с на попытку и между чанками стрима
Повтор
один, и только до первого байта ответа
Лимит
20 вопросов и 40 фото на ученика в день

Что модель видит

До раскрытияПосле
Условие задачидада
Попытки ученикадада
Открытые ступенидада
Ключ ответанетда
Проверенное решениенетда

Текст ученика отделён случайной границей против инъекций, ответ модели проверяется на утечку ключа.

LLM-cost / DAU

Каждый вызов модели пишет строку в llm_calls: назначение, токены на входе, выходе и из кеша, задержку, время до первого токена, исход и стоимость в рублях. Панель метрик считает стоимость на DAU, на вызов и на действие и шлёт алерт, если порог превышен.

ВеличинаОценкаКак получена
Один ответ Химо≈ 1 ₽сверху: 3000 входных токенов × 0.183 ₽ и 800 выходных × 0.732 ₽ за тысячу
Потолок на ученика в день≈ 20 ₽дневной лимит, 20 вопросов
На DAU≈ 1 ₽если 30% активных спрашивают Химо по 3 раза в день
1000 DAU в месяц≈ 30 000 ₽при той же доле

Цены DeepSeek V4 Pro на октябрь 2026. Фактическое значение — на вкладках «ИИ» и «Техника» панели метрик.

Данные

PostgreSQL 18, схема в Drizzle, 22 миграции.

Люди и вход
users identities sessions email_challenges consents
Банк
subjects task_types tasks task_keys task_solutions hint_rungs
Практика
serves attempts mastery recovery_due hint_opens xp_ledger
Химо
himo_threads himo_turns himo_days
Учитель
teacher_groups teacher_links assignments submissions reviews
Метрики
events llm_calls api_load mastery_snapshots metrics_daily

Уведомления через outbox

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

Логи и метрики

Логи и алерты

Логи
JSON-строки в journald, срок хранения не ограничен; access-логи nginx за год
Ошибки
алерт в Telegram: тип, код, статус, число повторов за 10 минут
Браузер
ошибки клиента пишутся в лог API, алерт от 20 за 10 минут
Пульс
healthchecks.io раз в минуту, пока свежи проба базы и планировщик
Падение
запись в лог, алерт, выход; systemd перезапускает процесс

В лог не попадают строки запроса, заголовки, тела, почта, коды и токены, в алерт — никакие данные пользователей.

Панель метрик

Аудитория
DAU, новые и вернувшиеся, целевое действие, воронка онбординга
Активность
взаимодействия на DAU по типам
Удержание
возврат на 1, 3 и 7 день
ИИ
вызовы, медиана и p95 задержки, ошибки, стоимость на DAU
Техника
средний и пиковый RPS, токены в секунду, нагрузочный тест

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

Около 40 RPS на основном цикле

Прогон 13 октября 2026 на боевом сервере: отдельный экземпляр API со своей базой и заглушкой модели, 300 синтетических учеников, k6 на той же машине. Шаг ученика — открыть приложение, взять задачу, ответить; в среднем это 4.35 запроса. Уровень засчитан, если p95 меньше 500 мс, а ошибок меньше 1%.

p95 задержки по уровням нагрузки, мс
p95 задержки по уровням нагрузки 5 RPS — 299 мс, 10 — 472, 20 — 77, 30 — 88, 40 — 58, 60 — 935 мс, выше порога 500 мс. 0 250 750 1000 порог p95: 500 мс 58 935 ✕ 5 10 20 30 40 60 целевая нагрузка, запросов в секунду
ЦельФактp50p95p99Итог
54.823299641засчитан
1010.929472899засчитан
2019.62077514засчитан
3029.92188444засчитан
4039.71958165засчитан
6059.9479351517✕ p95

Запросы в секунду и миллисекунды; ошибок 0% на всех уровнях. Первые два уровня медленнее, пока процесс прогревается. Упирается всё в CPU: API, Postgres и k6 делят два ядра, так что для одного API цифра занижена. Вызовы модели в тест не входят: их задержка и скорость генерации — на вкладке «Техника».

Развёртывание и безопасность

  1. Ветка SBH-N задача в Jira, тест до кода, слияние в main локально
  2. GitHub Actions lint, typecheck и тесты на Postgres 18
  3. Выкладка архив main на сервер, установка, сборка веба, миграции, перезапуск
  4. Наблюдение пульс, алерты, панель метрик

Сервер

ОС
Ubuntu 24.04 в Москве; swap; BBR для медленных мобильных сетей
Сервисы
API и бот под systemd с автоперезапуском; Postgres слушает только localhost
Веб
nginx отдаёт сборку с хешами в именах файлов, кеш на год; TLS от certbot
Секреты
только в файлах окружения на сервере, в репозитории их нет

Безопасность и 152-ФЗ

Данные
база, API и модели рантайма размещены в России
Согласия
фиксируются до регистрации, отдельно для веба и Telegram
Удаление
30 дней на восстановление, затем очистка и обезличивание событий
Загрузки
фото перекодируются, метаданные удаляются
Заголовки
HSTS, nosniff; встраивание страницы разрешено только Telegram
Лимиты
на вход, гостевой режим, события, загрузки и вопросы Химо