
Сегодня пост для тех, кто не наигрался в пошаговые стратегии: о Yandex Cloud Serverless Integration Workflows. Нетрудно догадаться, что это представитель обширнейшего поля Workflow Automation Tools, eg OSS: Apache Airflow/Hop, n8n to name a few. Но YC Wokflows не Open Source, конечно же. Окей, ближайший аналог, скажем, AWS Step Functions.
Одна из его характерных особенностей — использование JQ как одного из краеугольных камней. Прямо скажем, не Yandex's vibe 🚲 ⛔. Не могу сказать, что было легко с JQ, нахлынули какие-то воспоминания об XSLT (не кликайте, не надо!). В целом, конечно, работает, но у любой абстракции существует критическая точка взаимодействия с реальным миром: по отдельности $global, Foreach и сложные шаги, например, работают замечательно, но их комбинация пока является крайним случаем, где всё не совсем очевидно.
Рассмотрим пример простого вызова языковой модели:
yawl: '0.1'
start: step-no-op706
steps:
step-no-op706:
noOp:
output: '\({"sys_prompt": "соль", "usr_prompt":"земли"})'
next: step-foundationModelsCall770
step-foundationModelsCall770:
foundationModelsCall:
generate:
temperature: 0
maxTokens: 100
messages:
messages:
- role: system
text: \(.sys_prompt)
- role: user
text: \(.usr_prompt)
modelUrl: gpt://yrownfldrid/yandexgpt-lite/latest
output: '\({"step-foundationModelsCall770": .})'
description: ''
Ничего необычного: задаём system&user prompts для inference. Работает.
Отступления (не писать же про них отдельно):
Находка: Шаг NoOp! Поддержка посоветовала — большое им спасибо. Этот шаг позволяет отдельно от других шагов вызвать JQ-шаблонизатор. Это может оказаться полезнее, чем кажется. Здесь, например, это позволяет задать входные данные для последующих шагов процесса. В противном случае, при отладочных запусках пришлось бы постоянно копировать и вставлять входной JSON, а так можно запускать процесс в пустым вводом!
А запускать его во время отладки может потребоваться много-много раз.Другой случай полезного NoOp, это PutObject. Объект положили, удобно узнать, куда именно — какой ключ созданного объекта. Но PutObject не возвращает ничего! После него можно поставить NoOp, в котором положить в вывод этот ключ. Впрочем, такая полезность NoOp компенсируется его полным отсутствием на диаграмме шкалы времени, там только его вывод в глобальный контекст. Не спрашивайте.
Совсем далёкая подача от п.1. в соседний сервис облачных функций. Если workflow после исправления можно перезапустить, скопировав параметры предыдущего запуска, то окошко параметров тестирования функции всегда очищается, и дроп-дауна история не имеет. Печаль. Оказалось очень удобно отлогировать параметры вызова
print(json.dumps(event))и потом копировать их из логов или сохранить какsample_invocation.jsonотдельным файлом в редакторе. Файлов-то может быть много!
Итак, простейший inference работает. Теперь, допустим, у нас список user prompts, и нам надо их всех перебрать (batch inference, конечно, подойдёт, просто пример на список из одного элемента).
yawl: '0.1'
start: step-no-op706
steps:
step-no-op706:
noOp:
output: '\({"sys_prompt": "соль", "usr_prompt":["земли"]})' # массив на вводе
next: step-foreach780
step-foreach780:
foreach:
input: \(.usr_prompt | map( {key:.})) # for нужен массив объектов а не строк, превращем строки в объекты
output: '\({"step-foreach780": .})'
do:
start: step-foundationModelsCall296
steps:
step-foundationModelsCall296:
foundationModelsCall:
generate:
temperature: 0
maxTokens: 100
messages:
messages:
- role: system
text: \(.sys_prompt) # а вот внешний конекст тут не доступен - null
- role: user
text: \(.key) # переменная цикла - работает
modelUrl: gpt://yrownfldrid/yandexgpt-lite/latest
output: '\({"inference": .alternatives[0].message.text})'
description: ''
Модель отвечает недоумённо:
{
"step-foreach780": [
{
"inference": "Уточните, пожалуйста, что нужно сделать с этим словом?"
}
]
}
Внимательные читатели уже догадались, что Одно-то слово user message модель получает, а вот system message — нет. Происходит это потому, что на ввод в шаги цикла подаётся только переменная цикла! Как и написано, и, наверное, переменная $global поможет нам? Заменим system message на text: \($global.sys_prompt)
$global, кстати, нет в JQ playground! Где его только нет. Хотя в JQ playground много чего нет.
Получаем ошибку, что, по-моему, лучше, чем пустое (null) значение в прошлый раз!
failed to evaluate JQ expression in messages[0] value, inner error: failed to compile JQ expression ("\($global.sys_prompt)"): variable not defined: $global
Это и есть проблема сложного шага со многими JQ-полями. $global есть в выражении на вкладке Ввод (yaml input:), а в других выражениях — нет.
Найдено два решения:
моё: явно передадим
$global, подвинув переменную цикла. Теперь у насiкак в циклах нормальных языков! А$globalявно передадим какg.Теперь.i,.gдоступны во вложенных шаблонах
foundationModelsCall:
generate:
temperature: 0
maxTokens: 100
messages:
messages:
- role: system
text: "повтори все сообщения пользователя"
- role: user
text: \(.g.sys_prompt)
- role: user
text: \(.i.key)
modelUrl: gpt://yrownfldrid/yandexgpt-litelatest
input: \({i:., g:$global}) # <-- тут вся соль!!!!
output: '\({"inference": .alternatives[0].message.text})'
Теперь .i,.g доступны во вложенных шаблонах. Концепция примера немного поменялась по ходу статьи, но это только подтверждает...
второе
неправильное.Спасибо специалистам поддержки за подсказку: явно подлить$globalили нужное свойство вForeach.inputпри создании списка объектов на итерацию.
step-foreach780:
foreach:
input: \(.usr_prompt | map( {key:., sys_prompt_assigned:$global.sys_prompt}))
output: '\({"step-foreach780": .})'
do:
start: step-foundationModelsCall296
steps:
step-foundationModelsCall296:
foundationModelsCall:
generate:
temperature: 0
maxTokens: 100
messages:
messages:
- role: system
text: "повтори все сообщения пользователя"
- role: user
text: \(.sys_prompt_assigned) # явно переданное
- role: user
text: \(.key)
modelUrl: gpt://yrownfldrid/yandexgpt-lite/latest
output: '\({"inference": .alternatives[0].message.text})'
Модель подтверждает:
{
"step-foreach780": [
{
"inference": "соль\nземли"
}
]
}
Подозреваю, что подобный трюк может понадобиться и в других случаях.
Пока на этом всё. Желаю вам продуктивного рабочего потока!
