newsmode
search
Меню
arrow_back Назад

Behind the Scenes: Building AI-Generated Opportunity Solution Trees

auto_awesomeКраткое саммари

Тереза Торрес рассказывает, как она — продакт-менеджер без инженерного бэкграунда — с помощью AI создала сервисы для генерации деревьев возможностей и решений (opportunity solution trees) для платформы Vistaly. После запуска альфа-версии (более 100 заявок, 8 дизайн-партнёров) команды захотели загружать больше интервью, и обновление готового дерева оказалось куда сложнее генерации с нуля. Главная проблема: древовидные диффы неоднозначны — операции вроде split и merge становятся невидимыми в обычном сравнении, поэтому пришлось заставить модель «показывать свою работу» в виде наборов изменений (change sets). Ошибки достигали 50% запусков, пока Тереза не пришла к ключевому инсайту: дать LLM самому исправлять свои ошибки через агентский цикл с валидацией-инструментом, который повторяет попытки до прохождения проверки. Решение заработало (исправление за 1–2 хода), и обновлённый сервис был отгружен Vistaly в понедельник в полдень. В выводах она признаёт, что после Opus 4.6 дала Claude слишком много свободы, но теперь снова держит его «на коротком поводке», и подчёркивает, что за 15 месяцев освоила новую профессию AI-инженера.

Я только что завершила полноценный инженерный спринт. Мне самой это кажется странным.

Я пишу код время от времени уже несколько лет. Но я бы не назвала себя инженером. Я продакт-менеджер. А раньше была дизайнером. Прошло много времени с тех пор, как я писала код профессионально.

Но AI меняет то, что стало возможным прямо сейчас. И не только для продуктов, которые мы создаём. Но и для нас самих. Мы можем сделать больше, чем когда-либо могли себе представить. Именно таким и был мой опыт.

Сегодня я хочу поделиться с вами инженерной историей. Будут технические моменты. Что-то может показаться вам непонятным. Но я хочу, чтобы вы знали: год назад я понятия не имела, как всё это делается. Я научилась этому с помощью AI. Делясь своими инженерными историями, я хочу показать вам, что стало возможным прямо сейчас.

Создание деревьев возможностей и решений, сгенерированных с помощью AI

В середине февраля я объявила, что создаю два сервиса для Vistaly — сгенерированные с помощью AI снимки интервью и сгенерированные с помощью AI деревья возможностей и решений. Мы оба объявили набор альфа-партнёров. До закрытия раунда мы получили более 100 заявок. Мы выбрали восемь команд в качестве наших первых дизайн-партнёров.

Каждая команда загрузила по три интервью с клиентами. Мы выделили из каждого ключевые моменты и возможности, а затем сгенерировали дерево возможностей и решений на основе этих трёх снимков. AI-сервисы предоставляю я. Vistaly строит вокруг них эффектный UI и рабочие процессы.

Наша первая обратная связь была сильной. Команды начали просить нас разрешить загружать больше интервью. Но нам обоим предстояло проделать работу, чтобы это поддержать.

Интеграция новых интервью в сгенерированное с помощью AI дерево возможностей и решений

Обновление дерева возможностей и решений новым содержанием интервью — задача гораздо более сложная, чем генерация нового дерева с нуля. Поначалу я недооценила эту сложность.

У Vistaly и у меня есть общая цель. Мы не хотим выдавать сгенерированные с помощью AI деревья возможностей и решений и чтобы вы принимали их как факт. Мы хотим, чтобы вы взаимодействовали с ними, исправляли их, сотрудничали с AI. Мы хотим выстроить опору для кросс-интервьюного синтеза, а не делать его за вас.

Чтобы это поддержать, нам нужен был способ показать, как изменится ваше дерево после загрузки новых интервью. Мы вдохновились git diff и решили, что нам нужен его эквивалент для деревьев возможностей и решений. Если вы не знакомы с git diff: это то, как git — самая распространённая система контроля версий для кода — показывает вам, что изменилось в файле. Он проводит вас через каждое изменение шаг за шагом.

Хотя это было абсолютно верное решение, я и понятия не имела, сколько работы оно мне создаст. Моему сервису было недостаточно просто сгенерировать обновлённое дерево возможностей и решений с содержанием новых интервью. Он также должен был предоставить пошаговый разбор того, что изменилось.

Прототип — это всего 20% работы

Одна из тем, которую я часто слышу в своём подкасте Just Now Possible, заключается в том, что с AI довольно легко добраться до впечатляющего прототипа, но гораздо труднее дойти до настоящего продукта продакшен-качества. Именно таким и был мой опыт.

Мой сервис деревьев возможностей и решений состоит из двух подсервисов: 1) сгенерировать новое дерево с нуля и 2) обновить существующее дерево на основе новых интервью.

Первый сервис был частью нашей первоначальной альфы и работал хорошо. Второй сервис нужно было построить с нуля, прежде чем кто-либо из наших дизайн-партнёров смог бы добавить четвёртое интервью.

На первый взгляд они не кажутся такими уж разными, но это не так. Когда вы обновляете дерево, вы хотите сохранить как можно больше существующей структуры — если только новое содержание действительно не оправдывает структурное изменение. Приходится учитывать составные операции, такие как слияния, разделения и удаления. И нужно убедиться, что вы не потеряете никаких данных. Что касается данных, у каждого узла дерева есть как исходные возможности — возможности из интервью, которые служат подтверждающим доказательством для возможности в дереве, — так и дочерние элементы, то есть подвозможности дерева.

Но, как и со многими AI-продуктами, я была приятно удивлена, когда всего за несколько дней у меня заработало нечто вполне сносное. Я выкатила это, и наши дизайн-партнёры начали с этим играть.

Один дизайн-партнёр быстро упёрся в наши лимиты беты и спросил, можно ли перейти на платную подписку, чтобы получить полный доступ. Мы были в восторге. Они проявили готовность платить, перешли на платный тариф и тут же начали загружать больше интервью.

Однако, когда они дошли до 14-й, 15-й и 16-й загрузки, в моём первоначальном дизайне сервиса начали проявляться трещины. Мы стали замечать странное поведение.

Команда Vistaly заметила, что наборы изменений (change sets) — пошаговые инструкции, выдаваемые моим сервисом, — не всегда генерировали итоговое дерево, которое выдавал мой сервис. Напомню, мы хотели иметь возможность провести клиента шаг за шагом через то, что изменилось. Поэтому мой сервис возвращал как пошаговые инструкции (наборы изменений), так и итоговое дерево.

Замысел был в том, что Vistaly могла бы взять текущую версию дерева команды, применить набор изменений в том порядке, который я предоставила, и получить обновлённое дерево, которое выдал мой сервис. Но иногда дерево, которое генерировал набор изменений, отличалось от дерева, которое выдавал сервис.

Vistaly пометила это как баг в тот день, когда я летела в Новый Орлеан на Jazz Fest. Оглядываясь назад, я рада, что понятия не имела, что меня ждёт по возвращении домой. Мне оставалось проделать ещё около 80% работы, чтобы сделать обновление дерева возможностей и решений по-настоящему надёжным продуктом. К счастью, сначала я успела насладиться Jazz Fest.

Раскрытие полного масштаба проблемы

Когда я вернулась домой, я начала диагностировать проблему и быстро почувствовала себя погребённой под ней. Мой сервис представлял собой конвейер, сочетавший шаги с LLM и детерминированный код.

На высоком уровне он работал так: я разбила обновление возможности на серию шагов, которые могла выполнить LLM. После завершения этих шагов детерминированный код сравнивал выходное дерево с входным и генерировал наборы изменений.

Когда я начала копаться в багах, я поняла, что это не лучший подход. Диффы деревьев, в отличие от диффов линейных документов, неоднозначны.

Если я редактирую документ и добавляю предложение, дифф прост. Он показывает, что добавлено предложение. Если я удаляю абзац и переписываю его, дифф показывает удаление старого абзаца и добавление нового.

Но если я разделяю возможность A на две возможности, A и B, а затем позже сливаю B с C, то первое действие оказывается скрытым от диффа.

Позвольте объяснить, почему.

Если модель решает разделить возможность на две разные возможности, ей нужно сообразить, как распределить исходные возможности оригинала (подтверждающие доказательства) и его дочерние элементы (подвозможности) между двумя получившимися возможностями.

Например, если у нас есть возможность A и мы хотим разделить её на A и B, нам нужно понять, какие исходные возможности и дочерние элементы относятся к A, а какие — к B.

Предположим, у A есть исходные возможности 1, 2, 3 и дочерние элементы x, y, z. После разделения мы можем получить A с исходными возможностями 1 и 2 и дочерним элементом x, и B с исходной возможностью 3 и дочерними элементами y и z. Когда модель решает разделить возможность, она должна соответствующим образом распределить исходные возможности и дочерние элементы.

Теперь предположим, что модель решает слить B с C. Когда две возможности сливаются, нам нужно объединить все исходные возможности и дочерние элементы. Напомню, у B есть исходная возможность 3 и дочерние элементы y и z. Если у C есть исходные возможности 4 и 5 и дочерние элементы t, u и v, то после слияния у C будут исходные возможности 3, 4 и 5 и дочерние элементы t, u, v, y и z.

В конце, когда мы делаем дифф между нашим входным деревом и нашим выходным деревом, мы не можем увидеть и разделение, и слияние одновременно. В нашем входном дереве было A с исходными возможностями 1, 2, 3 и дочерними элементами x, y и z, и C с возможностями 4 и 5 и дочерними элементами t, u и v.

В нашем выходном дереве есть A с исходными возможностями 1 и 2 и дочерним элементом x, и C с исходными возможностями 3, 4 и 5 и дочерними элементами t, u, v, y и z. Для нашего диффа это выглядит так, будто часть источников и дочерних элементов A переместилась из A в C. Но мы не знаем почему. Разделение и слияние концептуально объясняют, что произошло, но эти перемещения невидимы для диффа.

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

Это значило, что мне нужно было заставить модель показывать свою работу. И это открыло целый новый ящик Пандоры.

Просим модель показать свою работу

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

Мои промпты уже сообщали модели, какие действия возможны: добавить, удалить, переформулировать, слить, разделить и т. д. Но это были лишь рекомендации. Модель могла делать всё, что угодно, чтобы получить итоговое дерево.

Но теперь мне нужно было, чтобы она показывала свою работу. И мне нужно было, чтобы она показывала её на языке наших наборов изменений — конкретных перемещений, которые мы заранее определили и которые имеют смысл для нашего конечного пользователя. Другими словами, «разделить A на A и B, а затем слить B в C» несёт гораздо больше семантического смысла, чем просто «переместить часть источников и дочерних элементов A в C произвольным образом».

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

После множества тестов и анализа ошибок я выявила две категории ошибок:

Модель пыталась сделать перемещение, которое было недопустимым.У модели было расхождение между набором изменений и рекомендацией. (Другими словами, набор изменений не генерировал рекомендацию.)

Категория 1 была интересной. Это было похоже на то, будто я проектирую игру, а модель — очень изобретательный игрок. Она постоянно находила способы сломать мою игру. Для каждой из этих ошибок мне приходилось решать, следует ли разрешать такое перемещение, и если да, то как модель должна представить это перемещение с помощью наших действий. Если перемещение не следовало разрешать, мне нужно было сообщить об этом модели.

Например, модель пыталась слить родителя с дочерним элементом. Это была проблема. Предположим, у возможности A есть дочерние элементы B, C и D. Если модель пытается слить A с B, что должно произойти?

Слияние направленное. Модель должна определить, какую из двух возможностей сохранить, а какую удалить.

Если она определяет слияние как «сохранить A и удалить B», то слияние допустимо.

Но если она определяет слияние как «сохранить B и удалить A», то что произойдёт с B, C и D? У них больше нет родителя, и теперь они осиротели.

Эти ошибки было приятно разрешать. Это было похоже на решение маленьких головоломок.

Категория 2 была гораздо сложнее. Мне пришлось пройти через несколько итераций дизайна промптов, чтобы подтолкнуть модель к лучшему результату.

Мне удалось снизить эти ошибки примерно до 1 случая из 40. Но в каждом запуске сервиса 10–20 вызовов LLM, поэтому это означало, что я сталкивалась с этой ошибкой примерно в 50% запусков сервиса. Очевидно, недостаточно хорошо для продакшен-продукта.

В какой-то момент я упёрлась в стену и не могла снизить частоту этой ошибки дальше. Я почувствовала, что застряла. И встревожилась. У нас был платящий клиент, ожидавший моего исправления. И ещё несколько дизайн-партнёров, которым не терпелось всё попробовать.

Попытка исправить ошибки модели в коде

Сначала я попыталась исправлять ошибки модели детерминированным кодом. Я обязалась перед Vistaly, что мои наборы изменений будут генерировать выходное дерево, поэтому мне нужен был код, который бы это проверял.

После того как модель генерировала набор изменений, я прогоняла по нему ряд проверок качества. Были ли в наборе изменений какие-либо конфликты? Конфликтом могло быть, например, то, что модель удалила узел дерева на одном шаге, а затем на последующем шаге пыталась использовать его в слиянии. Была ли какая-либо потенциальная потеря данных — учтены ли все исходные возможности и дочерние элементы? Не осиротила ли модель какие-либо узлы дерева? И так далее.

Я думала: если я могу обнаружить эти проблемы, может быть, я смогу их исправить. Но чтобы это сделать, мне приходилось гадать, что именно имела в виду модель.

Если у модели в наборе изменений была такая последовательность — «удалить A, затем слить A с B» — что это значило? Должна ли я удалить A или слить его исходные возможности и дочерние элементы с B?

Таких случаев были десятки, и простых ответов не было.

К этому моменту я уже 11 дней работала над этим полный рабочий день — с момента пробуждения до момента отхода ко сну (включая выходные). Я говорю это не как скромное хвастовство. Я ненавижу культуру хастла, и я не так выстраивала свою жизнь. Я делюсь этим, потому что хочу, чтобы вы понимали контекст. Я застряла и была измотана. А потом меня осенило.

Пусть модель сама исправляет свои ошибки

Я гуляла с собакой вместе с мужем. Он тоже инженер и все эти несколько дней слышал о моих взлётах и падениях. Когда я объясняла ему свой упрямый сценарий сбоя, меня осенило. Я поняла, что могу заставить LLM саму исправлять свои ошибки.

Мой контракт данных с Vistaly требует, чтобы мой набор изменений генерировал выходное дерево. Это и был ключевой баг. Это происходило не в 100% случаев. За предыдущую неделю я написала много кода для валидации наборов изменений.

Теперь я точно знала, когда набор изменений генерировал неправильное дерево. Но сколько бы prompt engineering я ни делала, я не могла снизить частоту ошибок.

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

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

Я начала с прототипирования решения. Я написала простой Node.js-скрипт, который делал один вызов LLM, получал результат, прогонял его через мой верификатор, а затем отправлял обратную связь LLM для исправлений.

Поначалу это вообще не работало. Модель просто продолжала вносить всё новые ошибки. Цикл так и не сходился к допустимому решению. Он лишь наматывал вычислительные ресурсы.

Но я продолжала пробовать. Я экспериментировала с тем, как сообщать LLM об ошибках. Я экспериментировала с тем, сколько контекста о более крупной проблеме ей давать. И в конце концов я заставила это работать. Модель исправляла свои собственные ошибки и возвращала допустимый набор изменений после одной-двух правок.

Я была в восторге. Но мне ещё предстояло превратить это во что-то, что я могла бы запускать в продакшене. И тут я вспомнила: я ведь уже построила утилиту агентского цикла для поддержки моего AI-интервьюера.

Вот как выглядит моя финальная продакшен-реализация. У меня довольно стандартный агентский цикл. Он начинается с вызова модели. Модель может выдать запрос на вызов инструмента. Цикл запускает инструмент и отправляет результат инструмента обратно агенту. Он итерирует, пока инструмент (мой код валидации) не вернёт сигнал успеха или пока мы не достигнем максимального числа ходов, заданного мной в коде.

Первоначальный вызов модели — это задача построения дерева. Вызов инструмента запускает мой код валидации. Если набор изменений проходит валидацию, инструмент выдаёт сигнал успеха, и цикл завершается. Если набор изменений не проходит валидацию, инструмент возвращает простые инструкции для модели по исправлению ошибок. Модель пытается исправить ошибки и снова вызывает инструмент валидации. Мой инсайт привёл к рабочему решению.

Ошибки всё ещё теоретически возможны. Цикл исправления может дать сбой и упереться в максимум ходов. Но за всё моё тестирование я этого не видела. Агент обычно исправляет плохой набор изменений за один-два хода.

Этот инсайт меня осенил в прошлый четверг. Я работала все выходные, чтобы прототипировать его, протестировать и убедиться, что он может работать. Затем я построила версию продакшен-качества и интегрировала её в свой конвейер. Я отгрузила полностью переработанный сервис в Vistaly в понедельник в полдень. Они работают над его интеграцией, и вскоре он станет доступен нашим дизайн-партнёрам.

Я почувствовала облегчение. И была готова к выходному дню.

Несколько размышлений обо всём процессе

Последние две недели были напряжёнными. Но я многому научилась и в основном получала удовольствие от процесса. Я хочу поделиться с вами несколькими ключевыми размышлениями.

У меня множество ограничивающих убеждений насчёт того, чтобы быть инженером. За последние две недели я избавилась от многих из них. Чтобы заставить это работать хорошо и надёжно, мне пришлось решить несколько действительно сложных проблем. Это приятное чувство.Эта работа была по-настоящему увлекательной. Мне нравилась головоломная часть — проектировать набор действий, а затем смотреть, как модель раздвигает этот дизайн за пределы его задуманных границ. Модели невероятно изобретательны, и было забавно наблюдать, что она будет делать с фиксированным набором инструментов.Я многое узнала о том, когда я могу, а когда не могу доверять Claude написание кода за меня. С выходом Opus 4.6 я дала Claude гораздо более длинный поводок. После прошедших двух недель Claude снова на коротком поводке. Я нашла множество пробелов в своей реализации в тех местах, где я просто доверилась, что Claude всё сделал правильно, тогда как на самом деле это было не так. Я знаю, что ведущие лаборатории утверждают, что модели пишут большую часть их кода, но я понимаю, что если у вас нет правильной инфраструктуры, это может обернуться катастрофой. В ближайшие пару недель я буду гораздо больше вкладываться в свои инструменты планирования, тестирования и код-ревью. И, конечно, я буду делиться этим здесь по ходу дела.Если бы эта работа была растянута на два месяца, она была бы по-настоящему приятной. Я понимаю, что мне действительно нравится быть AI-инженером. Я была готова к новой главе, и теперь я ещё больше уверена, что это она. Здорово объединять мою прошлую работу — деревья возможностей и решений — с моими новыми интересами — AI-инженерией.

Я очень рада поделиться работой, которую мы с Vistaly делаем вместе, с большим числом людей. Скоро мы будем добавлять новых дизайн-партнёров. Вы узнаете об этом первыми. А пока записывайтесь в лист ожидания.

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

Аудиоверсия

Аудиоверсия доступна только платным подписчикам.