Видеоклип, который собрал ИИ-агент на GLM-5.3-Flash в pi

Суть: ИИ-агент на GLM-5.3-Flash в pi собрал музыкальный клип про скилл knowledge-extraction. Он написал музыку кодом в strudel, сочинил текст и собрал видеоряд в Remotion. Я загрузил музыку и текст в Suno и забрал готовую песню. Затем агент получил через Whisper временные метки фраз и подстроил под них видеоряд. Показываю, как прошёл эксперимент. Главный вывод — на результат сильнее влияет окружение вокруг модели, чем сама модель.

Повод — клипы «Opus 5.5»

Последний месяц YouTube завален клипами, собранными «Opus 5.5». Качество разное, от офигительного до «так себе».

Тёмная кинозала: на экране поют неодушевлённые терминалы и вкладки браузера

Я кинул агенту три подобных ролика агенту и сказал, что реклама TasK в таком виде была бы прикольна. Агент заглянул в один ролик, предложил музыку попробовать сделать в Suno. Я ответил намеренно жёстко — «у меня денег нет. есть альтернативы?» Этот приём «денег нет» закрывает путь агенту к готовым решениям в виде внешних интеграций и заставляет его придумывать своё решение.

Агент придумал. В течении часа фоновую музыку он собрал скриптом на Python, голос сделал синтезатор речи piper, видеоряд собрал в Remotion, смонтировал всё в ffmpeg. Если с видеорядом у него получилось неплохо (я ожидал худшего), то фоновая музыка звучала ужасно.

Результат мне не зашёл, и я отложил эксперимент.

Music as Code — strudel и агентский цикл

Через пару дней мне попался на глаза ролик Switch Angel «Coding Music for Cold Machines». Там музыка пишется человеком с помощью кода. Кинул агенту ссылку на ролик и вопрос: «а ты смог бы мне написать демо композицию на этом языке».

Он написал мне код и говорит закинь его в https://strudel.cc. Я вставил, запустил, код выдал ошибку. Дальше я спросил агента «А локально нельзя это делать чтобы ты написал и смог проверить синтаксис и потом только я смог послушать. Замути какой то агентский цикл как с кодом обычным».

Агент развернул Strudel локально. На чистом Node.js завести движок у него не получилось, поэтому он собрал решение через headless Chrome. Оно на выходе готовый аудиофайл. Теперь цикл замкнулся как с обычным кодом: агент пишет музыку кодом, проверяет синтаксис, собирает аудиофайл и проверяет, не пустой ли он. Первую ошибку цикл поймал сам. Пауза в этом языке пишется тильдой вместо точки. Агент исправил её и пересобрал трек за секунды.

Результат мне понравился, было ощущение, что агент стащил у кого-то исходники музыки :). Я спросил: «Это ты сочинил?» Затем попросил: «Давай ещё, жги». Агент написал новую композицию.

Во время проверок агент нашёл ошибку, которую я на слух не заметил. Трек уже приятно звучал. Чтобы проверить темп, агент отдельно собрал дорожку из одних барабанов. На выходе получился пустой файл. Синтезаторные звуки при этом работали. Агент стал разбираться и выяснил, что сервер с барабанными сэмплами недоступен с моей машины. Все «готовые» сборки играли без барабанов, одни электронные подложки. Бас и переливы маскировали их отсутствие. Агент подключил библиотеку Dirt-Samples напрямую с GitHub и перегнал трек заново.

Выстроился процесс: теперь агент сам проверял результат перед тем, как показать его мне. Я уже получаю рабочий результат более автономно.

Когда новый трек был готов, я сказал: «Молодец. Сохрани то, чему мы научились». Агент записал в README проекта, как запускать рендер, проверять результат и какие ошибки мы уже нашли. Эти заметки остались для следующих сессий.

Космогония — план песни и предел самопроверки

Следующим запросом была уже «Космогония». Я попросил: «Сочини что нибудь эпичное про зарождение вселенной, человека, а затем ИИ». Агент выдал план песни на три с половиной минуты. Десять сцен, от пустоты с пульсарами до союза человека и машины. Лучшее в ней — тема человека в сцене ИИ возвращается той же мелодией, но холодным машинным звуком и с дополнительным верхним голосом. «Не замена, продолжение» — это его слова.

Вот весь код, который агент написал для «Космогонии»:

Полный код «Космогонии» на Strudel
// «КОСМОГОНИЯ: Пустота · Жизнь · Машина» — симфоническая поэма для live-code
// Пустота → Большой взрыв → Звёзды → Океан → Человек → Кремний → ИИ → Союз → Пустота
// 120 BPM (setcpm 30), ~3:28. Рендер: strudel-render cosmogony.js -o out.wav

samples('github:tidalcycles/dirt-samples') // сэмплы с raw.githubusercontent
setcpm(30) // 30 тактов/мин × 4 четверти = 120 BPM, такт = 2 сек

// ================= ПУСТОТА =================
// Бесконечный дрон A + редкие квазаровы пинги

const drone = stack(
  note("a1").sound("sawtooth").slow(8).clip(1).lpf(140).gain(.22).room(.6),
  note("a1").sound("sawtooth").slow(8).clip(1).lpf(140).gain(.12)
    .superimpose(x => x.add(.08)).room(.6), // расстроенная копия — биения
)

const pings = note("<e5 ~ ~ ~ ~ ~ ~ ~ a5 ~ ~ ~ ~ ~ ~ ~ g5 ~ ~ ~ ~ ~ ~ ~ e6 ~ ~ ~ ~ ~ ~ ~>")
  .slow(2)
  .sound("sine").clip(.2).gain(.2).room(.9).delay(.4).delaytime(.75).delayfeedback(.5)

// ================= БОЛЬШОЙ ВЗРЫВ (4 такта) =================
// Нарастание энергии → удар → вселенная расцветает

const boom     = stack(s("bd").slow(4).gain(1), s("cy").slow(4).gain(.5).room(.5))
const swell    = s("noise").lpf("<200 800 3000 9000>").gain("<.02 .05 .1 .18>").room(.2)
const riseTone = note("<e1 f1 g1 a1>").sound("sine").clip(1)
  .gain("<.12 .16 .2 .26>").lpf(300).room(.4)

// ================= ЗВЁЗДЫ (16 тактов) =================
// Первый ритм = пульсары. Прогрессия Am–G–F–E

const starKick = s("bd*4").gain(.5)
const starHat  = s("hh*8").hpf(9000).gain(.18)

const starsBass = note("<a1*8 g1*8 f1*8 e1*8>")
  .sound("sawtooth").clip(.4).lpf(450).gain(.6)

const starsArp = note(`<
  [a2 c3 e3 a3]*4
  [g2 b2 d3 g3]*4
  [f2 a2 c3 f3]*4
  [e2 g#2 b2 e3]*4
>`)
  .sound("sawtooth").clip(.3)
  .lpf(sine.range(500, 4000).slow(16))
  .gain(.32).delay(.2).delaytime(.1875).delayfeedback(.3)

const droneSoft = drone.gain(.4)

// ================= ОКЕАН (16 тактов) =================
// Жизнь: относительный мажор, треугольная волна, сердце = бас

const heart = s("[bd ~ bd ~ ~ ~ ~ ~]").gain(.5)

const lifeBass = note("<f1*8 g1*8 a1*8 a1*8>")
  .sound("triangle").clip(.5).lpf(400).gain(.6)

const lifeArp = note(`<
  [f3 a3 c4 a3]*2
  [g3 b3 d4 b3]*2
  [a3 c3 e4 c4]*2
  [a3 c3 e4 c4]*2
>`)
  .sound("triangle").clip(.4).lpf(2500).gain(.3).room(.2)

const lifePad = note("<[f3,a3,c4] [g3,b3,d4] [a3,c3,e4] [a3,c3,e4]>")
  .sound("triangle").clip(1).lpf(800).gain(.22).room(.7)

const whale = note("<c3 g3 a3 a3>").sound("sine").clip(1)
  .gain("<.1 .12 .14 .12>").lpf(500).room(.9).delay(.3).delaytime(.5)

// ================= ЧЕЛОВЕК (16 тактов) =================
// Тема человека возвращается в сцене ИИ

const theme = n(`<
  [0 ~ ~ 2]
  [3 ~ 4 ~]
  [5 ~ 4 ~]
  [3 ~ 2 ~]
  [0 ~ ~ 2]
  [3 ~ 4 ~]
  [7 ~ 5 4]
  [4 ~ 3 2]
>`)

const themeHuman = theme
  .scale("A:minor")
  .sound("sawtooth").clip(.8).lpf(1800)
  .gain(.3).room(.3)
  .delay(.25).delaytime(.375).delayfeedback(.35)

const tribal = stack(
  s("lt ~ mt ~ lt lt ~ mt").gain(.4),
  s("~ ~ cp ~").gain(.45),
)

const humanBass = note("<a1*8 f1*8 c2*8 e1*8>")
  .sound("sawtooth").clip(.4).lpf(400).gain(.62)

const humanPad = note("<[a2,c3,e3] [f2,a2,c3] [c3,e3,g3] [e2,g#2,b2]>")
  .sound("sawtooth").clip(1).lpf(900).gain(.25).room(.7)

// ================= КРЕМНИЙ: БИЛД (6 тактов) =================

const mechKick = s("bd*4").gain(.7)
const mechHat  = s("hh*16").hpf(8000).gain(.25)
const roll     = s("<cp*2 cp*4 cp*8 cp*16 cp*16 cp*32>").gain("<.3 .35 .4 .45 .5 .6>")
const riser    = s("noise").lpf("<300 600 1500 3500 7000 12000>").gain("<.05 .07 .1 .14 .19 .25>").room(.1)

// ================= ИИ (16 тактов) =================
// Тема человека возвращается квадратной волной с верхним голосом

const crash = s("cy").slow(16).gain(.45).room(.4)

const aiKick = s("bd(5,16)").gain(.85) // евклидов ритм 5 из 16
const aiHat  = s("hh*16").hpf(9000).gain(.28)
const aiCp   = s("~ cp ~ cp").gain(.5)
const aiPerc = s("perc(7,16)").gain(.2)

const aiBass = note("<a1*8 g1*8 f1*8 e1*8>")
  .sound("sawtooth").clip(.35).lpf(600).gain(.68)

const aiArp = note(`<
  [a3 c4 e4 a4]*4
  [g3 b3 d4 g4]*4
  [f3 a3 c4 f4]*4
  [e3 g#3 b3 e4]*4
>`)
  .sound("square").clip(.2).hpf(300)
  .gain(.2).delay(.25).delaytime(.1875).delayfeedback(.45)

const aiTheme = theme
  .scale("A:minor")
  .sound("square").clip(.55).lpf(3200)
  .gain(.28).room(.2)
  .delay(.3).delaytime(.375).delayfeedback(.4)
  .superimpose(x => x.add(7).gain(.5)) // верхний голос

const aiPad = note("<[a2,c3,e3] [g3,b3,d4] [f3,a3,c4] [e2,g#2,b2]>")
  .sound("sawtooth").clip(1).lpf(1100).gain(.24).room(.6)

// ================= СОЮЗ (16 тактов) =================
// Человек и ИИ играют одну тему вместе

const unionHuman = themeHuman.gain(.26).lpf(1500)
const unionAI    = aiTheme.gain(.26)

// ================= ФИНАЛ: ВОЗВРАТ К ПУСТОТЕ =================

const endChord = note("[a1,a2,e3,a3]").sound("sawtooth").clip(1).lpf(600).room(.9).gain(.3)

// ================= ПАРТИТУРА =================

const void1   = stack(drone, pings)
const bang    = stack(boom, swell, riseTone, drone)
const stars   = stack(starKick, starHat, starsBass, starsArp, droneSoft)
const ocean   = stack(heart, lifeBass, lifeArp, lifePad, whale)
const human   = stack(heart, tribal, humanBass, humanPad, themeHuman)
const build   = stack(mechKick, mechHat, roll, riser)
const ai      = stack(aiKick, aiHat, aiCp, aiPerc, aiBass, aiArp, aiPad, aiTheme, crash)
const union   = stack(aiKick, aiCp, aiHat, aiBass, aiPad, unionHuman, unionAI, aiArp.gain(.16), crash)
const void2   = stack(drone.gain(.7), pings)

$: arrange(
  [8,  void1],   // 0:00 пустота
  [4,  bang],    // 0:16 большой взрыв
  [16, stars],   // 0:24 звёзды и первый ритм
  [16, ocean],   // 0:56 океан, сердце, жизнь
  [16, human],   // 1:28 человек
  [6,  build],   // 2:00 кремний
  [16, ai],      // 2:12 ИИ
  [16, union],   // 2:44 союз
  [4,  void2],   // 3:16 возвращение в пустоту
  [2,  endChord],// 3:24 последний аккорд A
)

all(x => x.gain(.8))

Открыть «Космогонию» в Strudel и запустить

Агент сверил результат с планом. Длительность сошлась до секунды, звук в пиках не перегружался, дуга громкости расписана по сценам. И тут же признал предел — «числа подтверждают структуру, но „эпично ли“ — решает ухо». Инструмент проверяет структуру. Слушателя он не заменяет.

Я послушал. Тема прослеживается, ассоциации возникают. Но MIDI-звук меня отталкивал. Где-то попался совет загрузить трек в Suno как основу, чтобы сервис его обработал. Я попробовал, трек после обработки Suno стал звучать заметно лучше. Так в пайплайне появился ручной шаг — Suno.

Можно послушать обе версии.

Исходник Strudel, 3:33:

Версия Suno, 3:29:

Клип про скилл — пайплайн с человеком внутри

Затем я вернулся к идее сделать клип про knowledge-extraction. Спросил агента: «А ты помнишь, я просил тебя сделать видео про этот скилл?» Он помнил.

Я попросил его изучить историю сессий агента и рассказать о скилле knowledge-extraction от своего лица, от лица ИИ-агента который пользуется скиллом: что он умеет и где возникают трудности. Ещё нужно было сочинить музыку на Strudel, подготовить текст для Suno, придумать синхронизацию с песней и собрать клип. Отдельно оговорил: «мы экспериментируем, не рекламируем».

Пайплайн собрался такой.

flowchart LR A["strudel — музыка кодом"] --> B["Remotion — видеоряд"] B --> C["Suno — вокал, руками человека"] C --> D["Whisper — тайминги фраз"] D --> E["склейка и мастеринг"]

Где в этом всё-таки человек? В самой сборке мой ручной шаг один. Агент отдаёт слова и фоновую музыку, я загружаю в Suno и забираю песню. Suno на free подписке генерирует варианты на движке v6 mini. При первой загрузке Suno выдала предупреждение — «matched this upload to a known recording». Я показал его агенту. Он предположил, что сервис распознал сэмплы ударных, и заменил их синтезированными звуками. Новую версию Suno приняла.

Дальше пошла синхронизация, и тут я особенно ясно увидел преимущество связки с локальными инструментами. На первой сборке подписи в клипе не совпадали с фразами песни. Я ткнул агента на использование Whisper, таймштампы фраз взять оттуда.

Агент зашёл на мой локальный сервер, нашёл whisper.cpp с RTX 5090 и моделью large-v3, прогнал варианты. Вскрылось то, что ушами я не сформулировал бы. Suno перестроила структуру песни. Вокал начинается раньше плана. Второго припева в первом варианте вообще нет, сразу после первого припева идёт бридж. Агент написал выравниватель. Он стал брать тайминги из реальных фраз в песне.

Итерации — где пайплайн сырой

Дальше началась доводка, и моё настроение поехало вниз. Если начальный результат удивил, думал сейчас закину пару замечаний и за часик всё будет готово. Но мелочи отняли много времени. Азарт и воодушевление сменились усталостью от долгих итераций.

Я торопил агента и хотел поскорее получить готовый ролик. Во время итераций из клипа пропадали целые сцены, а агент этого не видел. Он проверял код и собирал новые версии, но готовое видео не просматривал. Я писал: «ты много <цензура> после того как я тебя попросил по висперу свести фразы с клипом». Потом добавил: «<цензура>! ты <цензура> много из того что есть в skill-video.mp4 — там в начале больше было динамики».

В какой-то момент я попросил его проверить сам ролик: «А ты можешь теперь сам по кадрово весь ролик проверить, может посекундно, покадрово жестко». Агент извлёк по кадру на каждую секунду, собрал 19 контактных листов и просмотрел их в vision-режиме GLM-5.3-Flash. Так он увидел пустой припев и застывшие сцены. Дальше он исправлял конкретные кадры по результатам просмотра.

Контактный лист для посекундной проверки клипа: девять кадров на одном экране

Один из 19 контактных листов. Агент просматривал такими наборами весь ролик.

Приходилось и осаживать фантазии. Я уже согласился с окончательным вариантом, а агент после этого добавил в начало видео эффект матрицы. «Какого <цензура>... не было же». Вокруг сцены решения потёк «дождь из данных». «Поясни нафига ты сделал дождь из данных? Просто ответь. Я же не просил, я просил закрепить решение».

Работу я принял через несколько часов после заказа. «Сохрани этот результат. Пойдет». Итоговый ролик вышел длиной 2:43.

Ещё я попросил проверить, сможет ли он сделать достойную 3D-версию. Она получилась осмысленной, но примитивной. Мне не хватило сил и желания тратить время на её доработку. Решил остановиться на 2D-анимации, а 3D-вариант оставил как есть.

Ролик выложил на YouTube и Rutube. Описание попросил подготовить агента по образцу клипа «Not a Normal Technology» с той самой волны клипов Claude Opus 5.5 Music Video.

Теперь про затраченное время. Первые пробные подходы занимали не более часа. Но клип я собирал около шести часов: от первого результата до принятого варианта прошло пять с половиной часов, ещё около часа ушло на 3D версию. Я писал свои замечания, проверял и опять писал замечания.

Выводы

Решает окружение модели. Инструменты, которыми она пользуется. Агент всё это время работал на одной и той же GLM-5.3-Flash. Без инструментов он выдал примитивную музыку, со strudel получился приличный трек, с Remotion и Whisper собрался клип.

Обратная связь должна идти к модели от инструментов. Сцена с пропавшими барабанами показала это лучше всего. Агент сказал бы «готово» и не узнал бы, что трек пустой. Теперь я ставлю задачу так. Сначала цикл «сделал — сам посмотрел — поправил», только затем показывать результат мне, подталкиваю агента создавать инструменты для самопроверки, не важно как и какие он сделает, главное чтобы мог сам себя проверять.

Задача человека — выстроить процесс и дать рабочие инструменты. Прежде чем просить результат, я показал модели, где лежат нужные возможности. Я сказал: «Whisper на nserv, по SSH зайди, он есть». Агент подключился к серверу и сам нашёл подходящий инструмент для точной синхронизации текста с песней. Человеку полезно не выполнять за модель каждый шаг, а открывать ей доступ к рабочим инструментам и ясно ставить цель.

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

Феликс Ризеберг из команды Claude в октябре 2026 г. выпустил текст «How to be more ambitious: A tutorial for humans with models». Он предлагает ставить модели высокие цели за гранью ожиданий и утверждать, что задачу всё-таки можно решить. Ещё один совет — проверять результат внешним способом. Не проси модель судить себя, дай ей инструмент мгновенной проверки. Процесс работы над клипом проявил оба правила. С 3D-версией я не стал настаивать и искать для неё отдельные инструменты. Возможно, задача решаема, но я выбрал 2D-анимацию и не стал дорабатывать вариант с 3D.

Милое несовершенство работает, если компетентность показана. Психолог Эллиот Аронсон в 1966 году провёл такой эксперимент. Участники слушали записи, где два человека отвечают на вопросы викторины. Один отвечал почти всегда правильно, второй часто ошибался. В конце записи кто-то из них разливал кофе и извинялся. Сильный участник после оплошности нравился слушателям больше, чем без неё. Средний участник после той же оплошности становился менее симпатичным.

Мой клип — и есть та самая пролитая чашка кофе. Простоватая анимация и несовершенный звук не брак, а характер. Добиваться идеала в клипе незачем. Совершенства стоит требовать от самого скилла, а пусть клип остаётся живым и чуть небрежным.

В конце я сказал агенту прямо: «Пайплайн сырой. Много моего времени ушло на проверку. И ты еще тупишь». Я до сих пор остаюсь частью процесса. Загружаю трек в Suno руками, проверяю звук ушами, поправляю кадры на глаз. Пайплайн станет автономным, когда агент сможет проверять результат без меня. Первый шаг уже сделан — покадровая проверка работает.

У меня есть ещё одна гипотеза, которую нужно проработать. Информация в форме песни с ритмом, эмоциями и видеорядом укладывается в голове лучше обычного текста. Кажется, реклама именно это и делает. Почему бы не попробовать то же для TasK?

Что дальше

Первым делом закрою свой ручной шаг. Сейчас я загружаю музыку и текст в Suno, а там генерируется вокал. Попробую заменить Suno локальной моделью ACE-Step. Тогда весь пайплайн будет работать без внешних сервисов. Пока это только идея — локально не проверял. Как проверю, напишу.

А вы что дали бы собрать своему агенту? Ролик целиком здесь, «Скормил. Спросил. Готово.»


Источники: