Урок 4. Логика без модели

Три предыдущих урока крутились вокруг модели. Этот — нет: в графе не будет ни одной LLM-ноды. Сообщение придёт на вход, нода проверит условие, две ветки соберут разный ответ, и обе сойдутся в один выход.

Главный аргумент урока — цена. Запуск воркфлоу стоит один кредит, а всё остальное в счёте — это вызовы моделей и платные внешние операции. Здесь их нет, поэтому каждый прогон стоит ровно один кредит из ста месячных, сколько бы раз вы его ни повторяли, пока отлаживаете условие. Для сравнения: один ответ бота из первого урока обходится примерно в два кредита. Разбор формулы — в «Кредитах».

Заодно это два навыка, на которых спотыкаются чаще всего: ветвление и шаблоны.

Вход
Если / Иначе
Срочное
Обычное
Выход
  • Исполнение + данные
Условие выбирает одну ногу. Обе ветки ведут в один выход — так можно, потому что они исключают друг друга.

Что будет делать граф

Сценарий бытовой: посетитель пишет в чат, и мы хотим отвечать по-разному на срочное обращение и на обычный вопрос — без модели, по слову в тексте. Логика простая нарочно: цель урока — механика, а не сообразительность бота.

Сборка

  1. В разделе Воркфлоу нажмите Новый воркфлоу, задайте имя. На пустом холсте поставьте Вход и Выход — они в разделе палитры Input / Output, группа Boundaries.

  2. Перетащите ноду Если / Иначе — раздел Logic & Flow, группа Branching. Соедините выход Входа с её входом.

  3. Откройте настройки ноды (выделите её и нажмите карандаш) и впишите в поле «Условие» одно выражение:

    {{ 'срочно' in inputs.input | lower }}

    inputs.input — то, что пришло на её вход, то есть текст сообщения. Фильтр | lower приводит его к нижнему регистру, чтобы «Срочно» и «СРОЧНО» тоже засчитались.

  4. Поставьте две ноды Шаблон — раздел Data, группа Values & State. Одну соедините с выходом True ноды Если / Иначе, вторую — с выходом False.

  5. В шаблоне первой напишите ответ для срочного обращения, во второй — для обычного. Внутрь можно подставить само сообщение:

    Приняли как срочное: {{ inputs.input }}. Ответим в течение часа.
  6. Соедините выходы обеих нод Шаблон с входом Выхода.

  7. Нажмите Проверить, затем Сохранить.

Условие: пишите одно выражение

Поле «Условие» — шаблонное, и от того, как вы его напишете, зависит результат.

app.iterna.ai

Настройка ноды

Если / Иначе

Параметры

Условие*
{{ 'срочно' in inputs.input | lower }}

Вся строка — одно выражение: результат приходит как логическое значение.

Условие целиком помещается в одно выражение — так оно возвращает настоящее «да/нет».

Правило одно и оно общее для всех шаблонных полей: если вся строка состоит из одного {{ ... }}, наружу приходит настоящее значение — число, список, да/нет. Если выражение смешано с текстом, результат всегда строка. Подробно — в «Шаблонах».

Для условия это важно вот почему. Смешанную запись вида {{ inputs.input }} > 100 платформа тоже понимает: она подставляет значение, а потом пытается прочитать получившееся как сравнение двух чисел. Пока по обе стороны оказываются числа, всё честно. Но если слева окажется текст — например, пришло «Здравствуйте» — сравнивать нечего, и строка целиком считается непустой, то есть истинной. Ветка True будет срабатывать всегда, а выглядеть это будет как «условие сломалось».

Полезные формы для этой ноды: {{ 'слово' in inputs.input | lower }} — вхождение подстроки; {{ inputs.input | length > 200 }} — длина; {{ variables.plan == 'paid' }} — сравнение с переменной.

Два способа сослаться на значение

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

Запись Что читает
{{ inputs.input }} Значение, которое доставил непосредственный предшественник на этот порт. Имя относительно каждой ноды: у второй ноды Шаблон это то, что пришло из Если / Иначе, а не из Входа.
{{ nodes.if_1.output }} Значение любой ноды графа по её идентификатору. if_1 — тот самый id, который виден в её настройках рядом с названием.

Рядом с output лежат остальные поля результата. У Если / Иначе это {{ nodes.if_1.branch }} — строка true или false: удобно, когда в ответ хочется вписать, какой веткой он собран.

Кстати, о значении: Если / Иначе ничего не меняет, а пропускает вход дальше как есть. Поэтому {{ inputs.input }} в обеих нодах Шаблон — это исходное сообщение пользователя.

Ссылка в шаблоне не запускает ноду

Это ключевая мысль, и она объясняет половину пустых результатов. {{ nodes.X.output }} — это чтение из памяти запуска: там лежат выходы тех нод, которые уже отработали. Ссылка никого не будит и очередь исполнения не меняет — ноду запускает только входящее ребро (разбор — «Как исполняется граф»).

Значит, если сослаться на ноду из невыбранной ветки, читать будет нечего. Ошибки не будет: неизвестное имя рендерится пустой строкой, и нода спокойно доработает с дырой в тексте. Найти дыру можно: платформа записывает все пути, которые не разрешились, в отладку ноды под ключом __undefined_refs__ — это единственный след опечатки в id. Где его смотреть, написано в «Шаблонах».

Почему две ветки в один вход — это законно

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

Предупреждение wire.andjoin_partial_payload, которое вы видели при проверке, ровно об этом и говорит: значения второй ветки у выхода не будет. Обычно это и нужно, поэтому подсказка, а не ошибка.

Разница ровно в вопросе «могут ли эти две ветки сработать в одном запуске». Взаимоисключающие — предупреждение, одновременные — ошибка.

Проверка

Откройте вкладку Тест и отправьте два сообщения: одно со словом «срочно», другое без. Ответы должны отличаться.

На холсте вкладки видно, какая нода отработала, а какая осталась серой. Кликните по пропущенной ноде Шаблон — в её карточке будет причина пропуска: условие выше выбрало другую ветку. Это штатная работа графа, а не сбой: запуск при этом считается завершённым, а не частичным. Полный список причин — в «Статусах».

Каждый такой прогон стоит один кредит и ни одного токена — можно спокойно перебрать формулировки условия.

Если не получилось

Что видноПочемуЧто делать
Всегда срабатывает ветка TrueУсловие записано смешанной формой и после подстановки стало непустым текстом, а не сравнениемЗанесите всё сравнение внутрь одних скобок: {{ inputs.input | length > 100 }}
Сработали обе веткиВетки идут не от одного условного узла, а параллельно — тогда они не исключают друг другаПроверьте, что оба ребра выходят из одной ноды «Если / Иначе», с портов True и False
Валидатор требует барьер (wire.fanin_needs_barrier)Ветви могут сработать в одном запуске — гонка за входной портСведите их нодой «Дождаться всех» перед общим входом
Нода серая, помечена пропущеннойУсловие выбрало другую ветку либо выше по графу оборвался путьОткройте карточку ноды в запуске и прочитайте причину пропуска
В ответе дыра: часть текста пустаяИмя в шаблоне не разрешилось — опечатка в id или ссылка на ноду из невыбранной веткиСмотрите __undefined_refs__ в отладке ноды; сравните id с тем, что написано в настройках
Фигурные скобки видны в ответе как естьПоле не является шаблонным — подстановку делают только помеченные поляПеренесите выражение в поле «Шаблон» ноды «Шаблон»
Предупреждение andjoin_partial_payloadОжидаемо для этого графа: взаимоисключающие ветви сходятся в один входНичего не делать — оно не блокирует ни сохранение, ни публикацию

Что дальше