Common.AI.ImageGen — Генерация изображений
Автор: Art Yg Версия: 1.0
ImageGen — универсальный плагин для асинхронной генерации изображений через внешний Webhook Processor, с сохранением результата в lead (URL и/или base64) и поддержкой сценарного выхода (success/error/timeout).
Плагин спроектирован так, чтобы работать в двухфазном режиме, как LLMQuery:
- PHASE 1 — отправить запрос (async) и выйти из скрипта
- PHASE 2 — обработать callback-ответ от процессора и продолжить сценарий
Зачем он существует
В Metabot-сценариях нельзя считать генерацию изображения “быстрой синхронной функцией”:
- внешние провайдеры отвечают с задержкой
- ответ может потеряться
- пользователь может продолжать писать, пока мы ждём
- результат может прийти не в том формате (URL vs b64)
- иногда важен жёсткий контроль ожидания (timeout), иначе сценарий “повиснет” навсегда
ImageGen стандартизирует этот поток:
- отправляет запрос через Webhook Processor (Remote transport)
- ждёт callback и распознаёт “это callback или пользовательский ввод”
- сохраняет результат в атрибуты lead
- может уйти в
successScript(опционально) - может уйти в
error.script/timeout(если настроены)
Что использует внутри
ImageGen — это обёртка, которая опирается на инфраструктурные компоненты:
-
Common.Remote.RemoteApiCall — транспорт, отправляет запрос в Webhook Processor и включает
asyncResponse - Webhook Processor — внешний слой, который делает реальный HTTP-запрос к провайдеру и возвращает callback
-
Common.Platform.AsyncFallback — таймаут/фоллбек-оркестрация (используется внутри
ImageGenчерезtimeout)
system/user, включая ссылки вида $alias
локальная таблица провайдеров внутри ImageGen — mapping baseUrl/endpoint/method
Важно: RemoteApiCall как транспорт в принципе поддерживает любых провайдеров, но текущая версия ImageGen по умолчанию заточена под OpenAI Images API.
Поддерживаемые провайдеры
На текущий момент поддержан:
-
openai
Если вам нужен другой провайдер (например, Replicate, Stability, Midjourney proxy, внутренний сервис) — свяжитесь с командой, и мы расширим плагин.
Примечание
дляпоархитектуры:архитектуре: сейчас таблица провайдеров (PROVIDERS) находится внутри плагина. При необходимости её можно вынести наружу (в конфиг бота/таблицу/отдельный реестр), чтобы вы могли подключать свои провайдеры без изменения кода плагина.
Что сохраняет
В зависимости от настроек:
-
save.urlAttr— URL изображения (если провайдер вернул url) -
save.b64Attr— base64 JSON (b64_json), если пришёл именно он -
save.rawJsonAttr— сырой payload ответа (для диагностики)
Двухфазный протокол выполнения
PHASE 1 — отправка запроса
Когда isFirstImmediateCall = true:
-
Инициализирует timeout-policy через
Common.Platform.AsyncFallback(если заданtimeout)
Берёт токен из атрибута бота (по auth.tokenKey)
Собирает итоговый prompt:
приоритетесли задан
— собираетimage.promptAttrpromptssystem[] + user[]
элементы массива могут быть:
$alias (берётся из promptTable для agentName)
если в image.promptprompts(строкой)используются $alias, то обязательны agentName и promptTable
Формирует request body под /images/generations
Отправляет запрос через RemoteApiCall.send(..., asyncResponse: true)
(опционально) показывает messages.wait
Возвращает false — сценарий “выходит” и ждёт callback
PHASE 2 — обработка callback
Когда isFirstImmediateCall = false:
-
Проверяет, что это действительно callback от процессора (
payload.is_async_response) -
Если это не callback (пользователь что-то написал):
- показывает
messages.processing - возвращает
false
- показывает
-
Если callback:
- снимает таймаут-job через
AsyncFallback.unschedule() - парсит
payload.content - извлекает
urlилиb64_json - сохраняет в lead
- если включён
requireUrlи url нет → ошибка -
Если задан
successScript— делаетbot.run(successScript), иначе возвращаетсяtrue
Конфигурация
Ниже описаны ключевые блоки ImageGen.run().
Provider + Auth
provider: "openai",
auth: { tokenKey: "OPENAI_API_KEY" }
-
provider— ключ провайдера (сейчас актуальноopenai) -
auth.tokenKey— имя атрибута бота, где лежит API ключ
Prompts (таблица + массивы)
ImageGen поддерживает интерфейс промптов “на вырост” — как в LLMQuery: массивы промптов и табличные ссылки.
agentName: "orion",
promptTable: "gpt_prompts",
prompts: {
system: ["$avatar_brief_generator"],
user: ["...основной запрос..."]
}
Принципы:
prompts.system и prompts.user — массивы строк.
элементы массива могут быть:
$alias — тогда текст берётся из promptTable для указанного agentName
если вы используете $alias, то:
agentName обязателен
promptTable обязателен
иначе это конфигурационная ошибка (плагин бросает throw, чтобы не было “тихих” генераций без системного слоя)
Примечание: OpenAI Images API принимает один
prompt(строкой), поэтомуImageGenсклеивает массивы в итоговый текст (обычноsystem+ пустая строка +user). Это сделано для унификации с LLMQuery и поддержки других провайдеров/форматов в будущем.
Image параметры
image: {
model: "dall-e-3",
prompt: "...",
promptAttr: null,
n: 1,
size: "1024x1792",
quality: "standard",
style: "natural",
background: null,
response_format: "url"
}
Практика по форматам результата:
-
dall-e-2 / dall-e-3: можно запросить
response_format: "url"и получить временный URL -
gpt-image- модели*: часто отдают
b64_json(URL может не прийти)
requireUrl
requireUrl: true
Если true, то отсутствие URL считается ошибкой (даже если пришёл b64).
Это удобно для MVP-потока “дай URL, а скачивание/сохранение сделаю потом”.
Таймаут (fallback)
timeout: {
seconds: 120,
script: "Orion_Image_Timeout"
}
Если callback не пришёл за указанное время — планировщик Metabot запускает timeout.script.
Таймаут реализован через Common.Platform.AsyncFallback внутри ImageGen.
Namespace для fallback
fallback: {
namespace: "orion_image_reflection"
}
Нужен, чтобы несколько асинхронных операций не конфликтовали.
Если не задан, будет auto:
imagegen_${code.toLowerCase()}
UX сообщения
messages: {
wait: "🜁 Генерируем…",
processing: "⏳ Подожди, ещё формируется…",
error: "⚠️ Не удалось получить изображение"
}
-
wait— показываем при отправке (PHASE 1) -
processing— показываем если пользователь пишет во время ожидания (PHASE 2, но это не callback) -
error— сообщение по умолчанию при ошибке (если нетerror.)script)script
Ошибки
error: {
script: "Orion_Image_Error",
flagAttr: "orion_image_error",
reasonAttr: "orion_image_error_reason"
}
-
flagAttr/reasonAttr— сохранить состояние ошибки в lead -
script— куда перейти при ошибке (опционально)
Успешный выход
successScript: "Orion_Image_Ready"
Если не указан — ImageGen.run() возвращается в ту же точку (return true), без перехода.
РекомендуемыйТаймаут: паттернвстроенный сvs таймаутомвнешний
СамВ большинстве сценариев таймаут проще и чище задавать внутри ImageGen можетчерез бытьtimeout, использованпотому вместечто сплагин сам использует Common.Platform.AsyncFallback.
ПочемуВнешнее этоуправление важно:таймаутом безимеет timeoutсмысл, у тебя будет “вечное ожидание”, если callback потеряется.
Типовой паттерн:если:
PHASEвы1:хотитеодин общий timeout на несколько async-операцийAsyncFallback.configure(...).schedule()PHASEвы2централизованно(callback):оркестрируетенесколькоfb.unschedule()
fb.fail(reason, details)Примеры использования
Пример 1 — таймауткак используется в Orion: system prompt из таблицы + timeout + error + successScriptURL (всё внутри вызова ImageGen)MVP)
/**
* orion_profiling_reflection_image
*
* Назначение:
* - асинхронно сгенерировать вертикальный "Operator Reflection" образ
* - сохранить URL в лид (скачивание/сохранение — отдельным шагом)
* - приtimeout успехе+ уйтиerror ввнутри successScriptImageGen *(как -у при ошибке или таймауте уйти в сценарии обработкиLLMQuery)
*/
const ImageGen = require("Common.AI.ImageGen");
return ImageGen.run({
lead,
isFirstImmediateCall,
code: "OrionActorImage",
// Provider/Auth
provider: "openai",
auth: { tokenKey: "OPENAI_API_KEY" },
// ТаймаутPrompts (fallback)из timeout:таблицы + inline
agentName: "orion",
promptTable: "gpt_prompts",
prompts: {
seconds:// 120,системный script:слой (табличный)
system: ["Orion_Image_Timeout"
}$avatar_brief_generator"],
// Ошибкиосновной запрос (унифицированоinline)
какuser: в[`
LLMQuery)
error: {
script: "Orion_Image_Error",
flagAttr: "orion_image_error",
reasonAttr: "orion_image_error_reason"
},
// Режим MVP: хотим именно URL (а не b64)
requireUrl: true,
image: {
model: "dall-e-3",
size: "1024x1792",
quality: "standard",
style: "natural",
response_format: "url",
// PROMPT — компактнее, без потери смысла (и без “портрета человека”)
prompt: `Create a vertical codex-grade mythotech Operator icon representing an abstract Operator entity from the NetWorld universe.in Aurum Void aesthetic:aesthetic.
Aurum Void: cold matte gold schematic lines (axes, nodes, ritual UI glyphs) over deep graphite void.
No glossy sci-fi, no neon, no superhero vibe, no humor.
TheFaceless figure must be faceless (hood/shadow/mask/void), calm andcalm, stable, centered on a strong vertical axis.
Heavy materials: carbon composite armor/robe, matte metal, dense fabric, worn realistic textures.
Subtle fog/particles for depth.
This is NOT a human portrait. It is an operational manifestation of a system entrepreneur / product leader at the scaling stage.
Show structural responsibility and architect-like gravity.
Output: a visual artifact, not an illustration.
`.trim()]
},
// Таймаут (fallback)
timeout: {
seconds: 120,
script: "Orion_Image_Timeout"
},
// Ошибки (как в LLMQuery)
error: {
script: "Orion_Image_Error",
flagAttr: "orion_image_error",
reasonAttr: "orion_image_error_reason"
},
// Namespace чтобы не конфликтовать
fallback: {
namespace: "orion_image_reflection"
},
// MVP: хотим именно URL
requireUrl: true,
image: {
model: "dall-e-3",
size: "1024x1792",
quality: "standard",
style: "natural",
response_format: "url"
},
messages: {
wait: "🜁 ORION формирует визуальное отражение…",
processing: "⏳ Подожди, образ ещё куется…",
error: "⚠️ Не удалось получить изображение"
},
save: {
urlAttr: "orion_actor_image_url",
b64Attr: "orion_actor_image_b64",
rawJsonAttr: "orion_actor_image_payload"
},
// Опциональный выход: если не задан — вернёмся в ту же точку successScript: "Orion_Image_Ready" // опционально
});
Пример 2 — “простаяминимальный вставка”:сценарий с таблицей промптов, без successScript, но с таймаутом и error-обработчикомsuccessScript
Подходит, когда вы хотите вернуться “в ту же точку” и решать дальше в тоттекущем же сценарий и уже там решить, что делать дальше (например, проверить orion_actor_image_url и показать кнопку “скачать”).шаге.
const ImageGen = require("Common.AI.ImageGen");
return ImageGen.run({
lead,
isFirstImmediateCall,
code: "OrionActorImage"OperatorIcon",
provider: "openai",
auth: { tokenKey: "OPENAI_API_KEY" },
agentName: "orion",
promptTable: "gpt_prompts",
prompts: {
system: ["$avatar_brief_generator"],
user: ["Create a vertical mythotech Operator icon in Aurum Void aesthetic..."]
},
timeout: { seconds: 120,90, script: "Orion_Image_Timeout"Image_Timeout" },
error: { script: "Orion_Image_Error",
flagAttr: "orion_image_error",
reasonAttr: "orion_image_error_reason"Image_Error" },
requireUrl: true,
image: {
model: "dall-e-3",
size: "1024x1792",
response_format: "url",
prompt: "Create a vertical codex-grade mythotech Operator icon in Aurum Void aesthetic..."
},
save: {
urlAttr: "orion_actor_image_url"operator_icon_url",
rawJsonAttr: "orion_actor_image_payload"operator_icon_payload"
}
});
Пример 3 — когдавнешний таймаутконтроль управляетсяtimeout снаружичерез AsyncFallback (черезпродвинутый AsyncFallback)режим)
Этот способ полезен, еслиесли:
const ImageGen = require("Common.AI.ImageGen");
const AsyncFallback = require("Common.Platform.AsyncFallback");
// PHASE 1: конфиг + планирование таймаута
if (isFirstImmediateCall) {
AsyncFallback.configure({
lead,
namespace: "orion_image_reflection",
timeout: { seconds: 120, script: "Orion_Image_Timeout" },
error: { flagAttr: "orion_image_error", reasonAttr: "orion_image_error_reason" }
}).schedule();
}
// Сам вызов генерации (без встроенного timeout)
const res = ImageGen.run({
lead,
isFirstImmediateCall,
code: "OrionActorImage",
provider: "openai",
auth: { tokenKey: "OPENAI_API_KEY" },
agentName: "orion",
promptTable: "gpt_prompts",
prompts: {
system: ["$avatar_brief_generator"],
user: ["Create a vertical mythotech Operator icon in Aurum Void aesthetic..."]
},
// timeout внутри не задаём, потому что контролируем снаружи
error: {
script: "Orion_Image_Error",
flagAttr: "orion_image_error",
reasonAttr: "orion_image_error_reason"
},
requireUrl: true,
image: {
model: "dall-e-3",
size: "1024x1792",
response_format: "url",
prompt: "Create a vertical codex-grade mythotech Operator icon in Aurum Void aesthetic..."
},
save: {
urlAttr: "orion_actor_image_url",
rawJsonAttr: "orion_actor_image_payload"
},
error: {
script: "Orion_Image_Error",
flagAttr: "orion_image_error",
reasonAttr: "orion_image_error_reason"
},
successScript: "Orion_Image_Ready"
});
// PHASE 2: если callback пришёл и операция завершилась — снимаем таймаут
if (!isFirstImmediateCall && res === true) {
AsyncFallback.configure({
lead,
namespace: "orion_image_reflection"
}).unschedule();
}
return res;
Частые сценарии отказов и как реагировать
-
send_failed — процессор не принял запрос / RemoteApiCall не отработал →
error.scriptили retry -
provider_error — провайдер вернул ошибку (например, HTTP != 200) → смотреть
save.rawJsonAttr, логировать, предлагать retry
url_missing при requireUrl=true
→ либо переключиться на b64_json, либо поменять модель/response_format
no_result — ответ пришёл, но данных нет
→ сохранить rawJsonAttr и смотреть, что вернул провайдер/процессор
timeout (через AsyncFallback)timeout.script)
→ отдельный timeout-script может предложить повторить генерацию или вернуться в меню
Итог
Common.AI.ImageGen — это стандартизированный способ подключить генерацию изображений в сценарии Metabot:
- двухфазный async flow
- сохранение результата в lead
- UX на случай “пользователь пишет во время ожидания”
- встроенный timeout через
Common.Platform.AsyncFallback
$alias через Common.AI.Prompts
расширяемая система провайдеров (сейчас OpenAI)
Common.Platform.AsyncFallback