Перейти к содержимому

Разбор Aginx: LLM-пайплайн, который собирает ролик за 5 минут и 250 рублей

Константин Потапов
9 мин

Разбор экспериментального контура «бриф → сценарий → медиа → монтаж»: Pydantic вместо магии, fallback провайдеров, 250 ₽ на ролик и грабли от бана ElevenLabs до субтитров без таймстемпов.

Разбор Aginx: LLM-пайплайн, который собирает ролик за 5 минут и 250 рублей

Aginx это мой CLI-контур: из текстового брифа собирает вертикальный ролик до 60 секунд. Один проход без ручных действий: бриф, сценарий, видео, голос, музыка, монтаж с субтитрами. На выходе mp4 для Reels, Shorts или TikTok.

Цифры, с которых я смотрю на этот ящик:

  • около 5 минут от брифа до файла, без учёта итераций по сценарию;
  • около 250 ₽ себестоимость 60-секундного ролика через API;
  • около 0 ₽ за видео в экспериментальной ветке, про неё ниже.

Ролик собран этим пайплайном целиком. Сценарий, видеосцены, озвучка, музыка, субтитры, монтаж. Руками не трогал ничего:

Это не SaaS под SLA. Десятки прогонов, не тысячи задач в проде. Собирал, чтобы проверить, можно ли пройти путь от брифа до файла, не утонув в каше LLM-ответов и чужих API. Продакшен-проблемы в нём настоящие: валидация нестабильного вывода, падения провайдеров, региональные баны, повторный запуск без пересоздания уже оплаченных кусков.

Четыре стадии, один контур

Стек: Python, asyncio, Typer, Pydantic, httpx, ffmpeg. Никакого фреймворка для «AI-агентов». Обычный оркестрируемый пайплайн.

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

Три пайплайна, аудио, визуал, сборка, друг о друге не знают. Общаются через файлы на диске.

MediaProvider сначала бьёт в fal.ai, при ошибке в Replicate. Модели задаются конфигом: Flux для картинок, Kling v3 для видео, Stable Audio для музыки.

Три формата ролика, решение принимает бриф или сценарист: broll это сгенерированные видеосцены, slideshow картинки с движением камеры, talking_head аватар, который анимируется под озвучку через LivePortrait.

Модель предлагает, код решает

Самая переносимая часть не «агенты». Схема в промпте, json.loads, Pydantic.

class Scene(BaseModel):
    index: int
    duration: float = Field(ge=1.0, le=30.0)
    visual_prompt: str   # английский, cinematic
    narration: str       # текст озвучки
    mood: str
    camera: str = "static"
 
class Script(BaseModel):
    title: str
    content_type: ContentType   # broll | talking_head | slideshow
    total_duration: float
    scenes: list[Scene]
    music_prompt: str
    voice_id: str
    voice_style: str
    hook: str

Модель получает схему и инструкцию «Return ONLY valid JSON». После парсинга код перезаписывает всё, чему модели доверять нельзя.

content_type из брифа затирает выбор модели: если заказчик сказал broll, сценарист не имеет права решить иначе. voice_id берётся из конфига. total_duration пересчитывается как сумма длительностей сцен. Арифметике LLM я не доверяю.

data = json.loads(raw)
 
if brief.content_type is not None:
    data["content_type"] = brief.content_type.value
 
scenes = [Scene(**s) for s in data["scenes"]]
data["total_duration"] = round(sum(s.duration for s in scenes), 1)
 
return Script(**data)

Если Pydantic не принял ответ, генерация падает на сценарии, когда ещё не потрачено ни рубля на медиа. Это дешёвое место для ошибки. Пайплайн нарочно роняет именно здесь.

Оркестратор дёргает Claude не через SDK, а субпроцессом: claude -p "<промпт>" --model claude-sonnet-4-6. Самый короткий путь до работающего сценариста. Расплата: таймаут 120 секунд, нет стриминга, процесс на каждый вызов. Для одного вызова на ролик терпимо. Для сервиса с очередью я бы ушёл на API со structured output.

Из чего складываются 250 ₽

Себестоимость 60-секундного broll через API около 250 ₽.

КомпонентСервисДоля стоимости
ВидеосценыKling v3 через fal.aiпочти 100%
ОзвучкаYandex SpeechKit~1 ₽ за минуту
СценарийClaudeкопейки
МузыкаStable Audioкопейки

Оптимизировать стоимость значит оптимизировать видео. Остальное можно не считать. Отсюда режим slideshow: картинки Flux вместо сцен Kling, на порядки дешевле. Для цитат, анонсов и списков этого хватает.

Порядок стадий выбран по деньгам. Аудио первым: если TTS упал, платная видеогенерация не стартует. В коде отдельный комментарий: «If TTS fails, do not start paid video generation».

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

if out.exists() and out.stat().st_size > 1000:
    progress_cb(f"Сцена {scene.index + 1} уже есть, пропускаю...")
    return out

Экспериментальная ветка гоняет видео через бесплатный Hyperframe, которым управляют агенты. Это отдельный от основного кода контур, стоимость видео сбивает к нулю. Пока не прод. Базовый путь через Kling остаётся эталоном по качеству.

Грабли

Лучший голос для русского была ElevenLabs. Сервис из России перестал работать. Реэкспортные шлюзы добавляют хрупкости, которую в контуре терпеть не хочу.

В коде осталась цепочка: ElevenLabs, если ключ жив, затем MiniMax speech-02 через fal.ai, затем Replicate. В рабочем контуре оставил Yandex SpeechKit: рубль за минуту, никаких банов, по живости хуже. Стабильность контура важнее последних процентов озвучки. Осадок остался.

Каждый внешний провайдер это точка отказа. Сегодня rate-limit, завтра бан по региону. Абстракция с fallback нужна с первого дня.

У каждой медиамодели свои рамки, и сценарист о них не знает. Kling принимает длительность строкой от «5» до «15» секунд. Сценарист может написать сцену на 3 секунды, модель всё равно сгенерирует 5. Pydantic разрешает 1–30, реальность провайдера 5–15:

kling_duration = str(min(max(int(duration), 5), 15))

Правильнее зашить ограничения медиамоделей в схему сценариста, а не клампить молча внизу. У меня пока второй вариант. Это техдолг.

«Return ONLY valid JSON» не работает. Сколько ни пиши «без markdown-фенсов», модель периодически возвращает ```json. Промптом это не лечится. Лечится тремя строками, которые срезают фенсы перед парсингом.

TTS отдаёт mp3 без пословных таймингов. Точную синхронизацию субтитров из этого не получить. Реальная длительность аудио берётся через ffprobe, текст раскладывается пропорционально длине сцен, дальше режется чанками по 7 слов. Не идеально, зато стабильно и без лишних вызовов.

SRT плюс force_style в ffmpeg это боль с экранированием. ASS стилизуется нативно, поэтому субтитры сразу в ASS и прожигаются одним фильтром.

Replicate на бесплатном тарифе держит burst = 1. Между сценами стоит asyncio.sleep(2). Некрасиво. Это цена нулевого бюджета на резервного провайдера.

Чего нет до прода

Очереди и воркера вместо локального CLI нет. Централизованных логов, лимита на стоимость прогона, нормальных retry по типам ошибок, хранения артефактов вне локального диска и тестов на регрессии сценария тоже нет. Approval-gate перед дорогой видеогенерацией нет.

Поэтому Aginx для меня рабочий прототип. Контур живой: бриф уходит в CLI, через примерно 5 минут возвращается ролик за примерно 250 ₽. Узкие места понятны: русский TTS после ухода ElevenLabs и цена видео. На цену направлен эксперимент с бесплатной генерацией через агентов.

Как только появляются платные API и риск сжечь деньги на невалидный JSON, задача перестаёт быть про промпты. Где валидировать, что можно перезапускать, что нельзя пересоздавать, сколько стоит ошибка. Карточка проекта: /ru/projects/aginx.