惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threatpost
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Engineering at Meta
Engineering at Meta
T
The Blog of Author Tim Ferriss
Recent Announcements
Recent Announcements
G
Google Developers Blog
Google DeepMind News
Google DeepMind News
The Register - Security
The Register - Security
MongoDB | Blog
MongoDB | Blog
U
Unit 42
B
Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
L
LangChain Blog
Stack Overflow Blog
Stack Overflow Blog
P
Privacy International News Feed
L
LINUX DO - 最新话题
博客园_首页
博客园 - Franky
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tor Project blog
V
Visual Studio Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
P
Privacy & Cybersecurity Law Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
K
Kaspersky official blog
C
Cisco Blogs
博客园 - 【当耐特】
阮一峰的网络日志
阮一峰的网络日志
I
Intezer
罗磊的独立博客
MyScale Blog
MyScale Blog
Last Week in AI
Last Week in AI
A
About on SuperTechFans
G
GRAHAM CLULEY
Y
Y Combinator Blog
Microsoft Security Blog
Microsoft Security Blog
GbyAI
GbyAI
T
Threat Research - Cisco Blogs
P
Proofpoint News Feed
D
DataBreaches.Net
The Hacker News
The Hacker News
Spread Privacy
Spread Privacy
AWS News Blog
AWS News Blog
I
InfoQ
T
The Exploit Database - CXSecurity.com
Simon Willison's Weblog
Simon Willison's Weblog
博客园 - 叶小钗
Project Zero
Project Zero

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Запустил AI-репетитор английского месяц назад: технические грабли соло-дева
anatoliyshir · 2026-05-12 · via Все публикации подряд на Хабре

Уровень сложностиПростой

Время на прочтение8 мин

Охват и читатели367

Мнение

Я соло-делаю Speakwithai — AI-репетитор английского для русскоязычной аудитории. Месяц назад выкатил публично, за этот месяц получил 50 регистраций, 3 платящих и набор технических граблей, которые честнее разобрать, пока они свежие, а не через год по сглаженной памяти.

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

Контекст

Что построил: web + Android-приложение, в котором пользователь голосом общается с AI-репетитором («Эмма»). Под капотом — real-time голосовая AI-модель для диалога и отдельная multimodal-модель для оценки произношения. Стек: NestJS на бэке, React на фронте, TypeORM + Postgres.

Цифры на сегодня (1 месяц в проде):

  • Зарегано: 50

  • Платящих: 3

  • MRR: ~3 тыс руб (тарифы 899 и 1799)

  • Дистрибуция: Pikabu (1185/4/0), TG-канал @speakwithai (175 подписчиков), TG mini-app демо

То есть классическая стадия, когда продукт работает, а маркетинг — нет. Дальше — про техническую часть, она интереснее.

Что построил: web + Android-приложение, в котором пользователь голосом общается с AI-репетитором («Эмма»). Под капотом — real-time голосовая AI-модель для диалога и отдельная multimodal-модель для оценки произношения. Стек: NestJS на бэке, React на фронте, TypeORM + Postgres.

Цифры на сегодня (1 месяц в проде):

  • Зарегано: 50

  • Платящих: 3

  • MRR: ~3 тыс руб (тарифы 899 и 1799)

  • Дистрибуция: Pikabu (1185/4/0), TG-канал @speakwithai (175 подписчиков), TG mini-app демо

То есть классическая стадия, когда продукт работает, а маркетинг — нет. Дальше — про техническую часть, она интереснее.

Архитектура голоса: один пайплайн не подошёл

Real-time voice-модель стримит PCM-аудио по WebSocket в обе стороны: ты гонишь микрофон, тебе обратно приходит ответ модели. На десктопе это решается стандартно — Web Audio API, AudioWorklet, MediaStream. Я это собрал за пару дней, всё работало.

Потом открыл сайт на iPhone и обнаружил, что половина Web Audio там либо отсутствует, либо ведёт себя по-другому. iOS Safari не даёт AudioWorklet нормально работать в фоне, требует user gesture для unlock, AudioContext часто залипает в suspended.

Поразмыслив, я не стал переписывать рабочий PCM-путь под iOS, а сделал параллельный пайплайн через Media Source Extensions:

  • На iPhone сервер ре-инкейпсулирует PCM-стрим в fragmented MP4 на лету (ffmpeg)

  • Фронт скармливает фрагменты в ManagedMediaSource (iOS-вариант MSE)

  • Атрибут disableRemotePlayback обязателен, иначе iOS пытается прокинуть стрим на AirPlay

  • Web Speech API на iOS — no-op, поэтому транскрипт получаю серверный (модель отдаёт outputAudioTranscript параллельно с аудио)

Главный урок: для iOS-quirks делай параллельную ветку, а не пытайся унифицировать. У меня PCM-путь работает с десктопного дня один, и я к нему не возвращаюсь. iOS-ветка — отдельная история со своими граблями, но они не лезут в общий код.

Деплой получился даже интересный: основной бэк живёт на Railway (с Postgres), а параллельный сервис только для iPhone-ffmpeg-пути работает на Render — там удобнее с системным ffmpeg. Один и тот же Dockerfile из корня репозитория, разные команды старта.

Pronunciation pipeline: 503 на пустом месте

Кроме голоса в реальном времени есть отдельная фича — оценка произношения. Пользователь записывает фразу → отправляет на /api/pronunciation/assess → бэк зовёт multimodal-модель с аудио + текст эталона, парсит JSON-ответ с оценкой и подсветкой проблемных слогов.

Sentry начал регулярно показывать HeadersTimeoutError и TypeError: fetch failed из этого endpoint. Я добавил fallback-цепочку из трёх моделей одного провайдера (от более тяжёлой к более лёгкой), думая, что 503 в первой → fallback во вторую. На деле — нет.

Две причины:

  1. SDK провайдера под капотом использует undici fetch, у которого headersTimeout по умолчанию 5 минут. Если модель ушла в туман и не отвечает, один запрос блокирует поток на 5 минут до того, как мы вообще попробуем fallback.

  2. Catch-блок проверял регулярку только на UNAVAILABLE|503|overload|RESOURCE_EXHAUSTED|429HeadersTimeoutError и fetch failed сюда не попадали — exception разворачивался в 503 для юзера, fallback не срабатывал.

Починка — watchdog поверх каждого вызова:

const TIMEOUT_MS = 45_000;
const request = this.ai.generateContent({...});
const response = await Promise.race([
  request,
  new Promise<never>((_, reject) =>
    setTimeout(() => reject(new Error(`AI ${model} timeout`)), TIMEOUT_MS),
  ),
]);

И расширил регулярку транзиентности:

const transient =
  /UNAVAILABLE|503|overload|RESOURCE_EXHAUSTED|429|timeout|fetch failed|ECONN|socket hang up/i.test(msg) ||
  /UND_ERR|ETIMEDOUT|ECONN|EAI_AGAIN/i.test(code);

Worst case теперь — 135 секунд (3 модели × 45 сек) вместо «висит 5 минут и юзер видит 503». Урок: дефолтный undici headersTimeout — это 5 минут, и его нужно перебивать самому, потому что в production-fallback-цепочках это не работает.

Cost-телеметрия: счёт прилетел, источника не видно

Параллельно Sentry показал странные ивенты «модель отдала text-part вместо audio» в голосовом сервисе. С облака AI-провайдера в этот же период списали неприятную сумму. Подозрение было: модель иногда отдаёт текстовый парт вместо аудио, мы это игнорируем, но в токены провайдер это всё ещё считает. Без логирования usage metadata подтвердить было нельзя.

Прикрутил аккумуляторы:

interface UsageMetadata {
  promptTokenCount?: number;
  responseTokenCount?: number;
  thoughtsTokenCount?: number;
  promptTokensDetails?: { modality: string; tokenCount: number }[];
  responseTokensDetails?: { modality: string; tokenCount: number }[];
}

Накапливаю по сессии: usagePrompt, usageResponse, usageThoughts, разбивку по модальностям, а также text-part count и chars total. На close пишу одной строкой в лог: ID юзера, длительность сессии, токены по модальностям, был ли text-вместо-audio.

Через 2 недели данных можно будет сказать, есть ли реальная корреляция «text-part anomaly» с overcharge, или причина в другом. Урок: для платных AI-API всегда логируй usage metadata с первого дня, иначе будешь дебажить стоимость вслепую.

RuStore: отказ → миграция с TWA на Capacitor

Изначально мобильное Android-приложение было обычным TWA (Trusted Web Activity) — фактически нативный wrapper над PWA. Это самый простой способ выкатить web-продукт в Play Store.

В RuStore такой подход не прошёл модерацию. Их требование: приложение должно быть полноценным нативным, с собственными permissions, а не просто браузерным wrapper'ом. Не буду спорить, разумна ли их позиция — факт в том, что TWA пришлось убрать.

Перенёс на Capacitor. Компромисс: всё ещё React-фронт внутри WebView, но обёрнут так, что выглядит как настоящее нативное приложение, со своим AndroidManifest, permissions, intent-filters. Не самый чистый стек, но позволил сохранить кодовую базу фронта 1-в-1.

Грабли по дороге:

  • Bearer-token auth: на сайте session — httpOnly cookie, в Capacitor WebView таким не пользуешься (домен другой). Пришлось добавить отдельный header-based auth flow, чтобы Capacitor хранил token в native storage.

  • Deep links: пользователь жмёт в email на «сброс пароля» — должен вернуться в приложение, а не в браузер. Это решается App Links: autoVerify intent-filter в AndroidManifest на пути типа /reset-password и /payment-return, плюс .well-known/assetlinks.json на сайте. Capacitor appUrlOpen listener ловит URL и роутит внутрь SPA.

  • YooKassa payment-return: оплата открывается в Custom Tab, после успеха Custom Tab кидает на /payment-return → App Links перехватывает → Browser.close() → юзер уже в приложении и видит активную подписку.

Каждая из этих трёх вещей в TWA «просто работает», в Capacitor — отдельный кусок кода и тест.

Биллинг: календарный месяц — это анти-фича

Сначала я сделал лимиты по календарному месяцу: 40 минут голоса в месяц, сброс первого числа. Это казалось простым и привычным («как у всех»).

Через пару недель один из платящих сделал скриншот: 30/600 минут потрачено, «next reset 1-го числа». То есть он купил подписку 11-го, и через 20 дней ему «дадут полные» минуты, как будто только что заплатил.

Очевидно, что это бред. Надо привязывать к anniversary — заплатил 11 мая, следующий цикл начинается 11 июня, не 1 июня.

Рефакторинг получился чуть сложнее, чем казалось:

// было
const usage = await getOrCreateUsage(userId, startOfMonth(now));

// стало
const usage = await getOrCreateUsage(userId, sub.currentPeriodStart);

Плюс утилита addOneMonth() для апгрейда плана (вместо date-fns'овой endOfMonth, которая делала ровно то, что не нужно).

Урок: «по календарю» — это удобство для разработчика, не для пользователя. Если у тебя подписка с периодом — period start у пользователя должен быть датой его оплаты, не первым числом.

Маркетинг: 1185 показов, 4 клика, 0 регистраций

Технические грабли я могу решать неделями. Маркетинговые — провалил быстрее.

Первая попытка холодного трафика — статья на Pikabu в стиле «3-minute test для самопроверки английского». Сделал без хайпа, с UTM-меткой на блог.

Результат:

  • 1185 показов в ленте

  • 4 клика по UTM

  • 0 регистраций

Распределение работает (показы есть), а вот связка article → click и landing → signup — мёртвая. Параллельно в Telegram-боте есть бесплатное демо Эммы (mini-app с auto-demo-аккаунтами) — за неделю 3 человека его открыли.

Главный урок не в том, что Pikabu плохой канал. А в том, что продукт про голос, а статьи про голос — это разные продукты. Текст не передаёт магию того, как AI отвечает голосом и слышит твой акцент. Возможно, надо переходить на видео-форматы, где модальность канала совпадает с модальностью продукта. Это следующий эксперимент.

Чем закончу

Если ты делаешь что-то с голосовыми AI-моделями — главные вещи, на которые я бы потратил день в начале:

  1. Логирование usage-metadata из первого вызова (потом будет поздно)

  2. Watchdog поверх SDK-вызова провайдера (5 минут undici timeout — не шутка)

  3. Параллельные ветки для iOS вместо «универсального» web-кода

Если делаешь продукт для RU-рынка в 2026 — учитывай, что все западные Merchant-of-Record (Stripe, Paddle, LemonSqueezy и т.д.) блокируют residents РФ. Принимать оплаты от российских пользователей сейчас можно только через российский же шлюз (я использую YooKassa) и российское юрлицо или самозанятость.

Открытый вопрос для комментов: где брать холодный трафик в RU-нише языковых продуктов в 2026 при небольшом бюджете? Pikabu и Дзен дают показы без конверсии. Что реально работает на 1-3к подписчиков в месяц для соло-дева?


Speakwithai — приложение в RuStoreдемо в TG. 7 сессий и 40 минут голоса бесплатно, без карты.