Files
HadTavern/docs/VARIABLES.md

697 lines
46 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Переменные и макросы в НадTavern: очень понятный гайд
Этот файл объясняет, как в проекте подставлять нужные кусочки данных прямо в шаблоны, не ломая голову. Представьте «наклейки» — вы клеите их в JSON на нужные места, и система сама подменяет их на значения: из входящего запроса, из предыдущих узлов, из ваших переменных, из памяти выполнения.
Если вы открыли визуальный редактор по адресу http://127.0.0.1:7860/ui/editor.html — все примеры ниже можно копировать прямо туда.
Файлы, где «живет» эта магия:
- Сервер и конечные точки: [agentui/api/server.py](agentui/api/server.py)
- Исполнитель узлов (сердце конвейера): [PipelineExecutor.run()](agentui/pipeline/executor.py:316)
- Ноды: [ProviderCallNode.run()](agentui/pipeline/executor.py:2007), [RawForwardNode.run()](agentui/pipeline/executor.py:2477), [ReturnNode.run()](agentui/pipeline/executor.py:2798), [IfNode.run()](agentui/pipeline/executor.py:2892), SetVars внутри того же файла
- Шаблонизатор (подстановки [[...]] и {{ ... }}): [render_template_simple()](agentui/pipeline/templating.py:196), булевы выражения If: [eval_condition_expr()](agentui/pipeline/templating.py:382)
— — —
1) Что такое «переменные» и «макросы»
- Переменные — это значения, которые вы где-то определили: в узле SetVars, они появились из входящего запроса, их вернул провайдер (OpenAI/Gemini/Claude), или их сохранил движок выполнения в «память».
- Макросы — короткие «заклинания» вида [[...]] или {{ ... }}, которые вы вставляете прямо в текст/JSON. На ходу они заменяются на нужные значения.
Простейший пример:
- Было в шаблоне: "Authorization":"Bearer [[VAR:incoming.headers.authorization]]"
- Стало при выполнении: "Authorization":"Bearer eyJhbGciOi..."
— — —
2) Откуда берутся данные (куда «лезут» макросы)
- incoming.* — всё про входящий HTTPзапрос клиента:
- method, url, path, query
- headers — словарь заголовков
- json — JSON из тела запроса
- Быстро взять: [[VAR:incoming.headers.authorization]], [[VAR:incoming.json.model]]
- Формируется на сервере: [build_macro_context()](agentui/api/server.py:143)
- params.* — «нормализованные» параметры (температура, топ‑п и т.д.):
- Примеры: [[VAR:params.temperature]], [[VAR:params.max_tokens]]
- См. нормализацию: [normalize_to_unified()](agentui/api/server.py:44)
- model, system, vendor_format — общие поля запроса:
- [[VAR:model]], [[VAR:system]], [[VAR:vendor_format]]
- chat.* — удобный доступ к сообщениям:
- [[VAR:chat.last_user]] — текст последнего пользователя
- [[VAR:chat.messages]] — весь список сообщений
- vars.* — ваши переменные из SetVars:
- Можно писать коротко, без VAR и точек: [[MY_VAR]] или {{ MY_VAR }}
- Задаются в узле SetVars (подробно ниже)
- OUT.* — машина времени с результатами узлов:
- [[OUT1]] — «лучший текст» из узла n1 (короткая форма)
- [[OUT:n2.result]] — весь JSON результата узла n2
- [[OUT:n3.result.choices.0.message.content]] — вложенный путь в JSON
- Как извлекается «лучший текст», описано ниже (раздел «[[OUTx]] внутри»)
- STORE.* — постоянное хранилище (между шагами пайплайна):
- [[STORE:MY_VAR]] — значение переменной, сохранённой узлами (например SetVars)
- [[VAR:STORE.snapshot.OUT_TEXT.n1]] — текст, который движок сохранил как «лучший» для узла n1
- Доступ фигурными скобками тоже работает: {{ store.snapshot.OUT_TEXT.n1 }}
— — —
3) Главные «заклинания» (макросы)
- [[VAR:путь]] — взять значение из контекста
- Примеры: [[VAR:incoming.headers.authorization]], [[VAR:params.temperature]]
- [[OUT1]], [[OUT2]], … — быстро взять «лучший» текст из узла n1, n2, …
- Удобно для Return и подсказок к следующему провайдеру
- [[OUT:nX.что‑то]] — взять сырой JSON из узла и провалиться по пути
- [[OUT:n2.result]] — весь ответ провайдера
- [[OUT:n2.result.candidates.0.content.parts.0.text]] — кусочек Geminiответа
- [[PROMPT]] — хитрый фрагмент JSON из Prompt Blocks
- В ноде ProviderCall он разворачивается в правильные поля, в зависимости от провайдера:
- OpenAI → "messages": [...]
- Gemini → "contents": [...], "systemInstruction": {...}
- Claude → "system": "...", "messages": [...]
- Под капотом это делает: [ProviderCallNode.run()](agentui/pipeline/executor.py:1631)
- {{ путь }} — «фигурные скобки» возвращают значение без кавычек
- Подходит для чисел/массивов/объектов. Пример: "temperature": {{ params.temperature|default(0.7) }}
- Есть фильтр по умолчанию: |default(значение) — безопасно подставит запасной вариант
- Разбирает наш шаблонизатор: [render_template_simple()](agentui/pipeline/templating.py:191)
- [[NAME]] — «голая» переменная из SetVars (короткая запись)
- Ищется сперва в ваших vars, потом в общем контексте
- [[STORE:путь]] — достать из постоянного хранилища (store.*)
Подсказка: макросы [[...]] можно писать в строках JSON, а {{ ... }} — там, где нужно вставить число/массив/объект без кавычек.
— — —
4) Куда именно это писать (по узлам)
4.1 ProviderCall — «собрать запрос к провайдеру»
Где живут макросы:
- endpoint — можно вставлять {{ model }} или ключи в URL (например Gemini ?key=…)
- headers — это JSONобъект в текстовом поле: сюда можно писать [[VAR:incoming.headers.authorization]], [[Clod]] и т.д.
- template — главное поле: JSONтело запроса. Обязательно валидный JSON после разворачивания макросов.
Мини‑шпаргалка:
- OpenAI (пример заголовка): {"Authorization":"Bearer [[VAR:incoming.headers.authorization]]"}
- Gemini (ключ в URL): /v1beta/models/{{ model }}:generateContent?key=[[VAR:incoming.api_keys.key]]
- Claude (заголовки): {"x-api-key":"[[VAR:incoming.headers.x-api-key]]","anthropic-version":"2023-06-01"}
Полный пример OpenAIтела:
{
"model": "{{ model }}",
[[PROMPT]],
"temperature": {{ incoming.json.temperature|default(params.temperature|default(0.7)) }},
"top_p": {{ incoming.json.top_p|default(params.top_p|default(1)) }},
"max_tokens": {{ incoming.json.max_tokens|default(params.max_tokens|default(256)) }},
"stop": {{ incoming.json.stop|default(params.stop|default([])) }},
"stream": {{ incoming.json.stream|default(false) }}
}
Важное правило:
- [[PROMPT]] — это «кусок» JSON без запятых по краям. Убедитесь, что вокруг него стоят запятые там, где нужно, но не лишние.
- Если шаблон не превращается в валидный JSON — ProviderCall упадёт с понятной ошибкой.
4.2 RawForward — «пропусти запрос как есть» (reverse proxy)
Куда писать:
- base_url — куда слать (можно с макросами)
- override_path — переопределить путь (опционально)
- extra_headers — JSONобъект (строкой), можно вставлять [[...]] и {{ ... }}
По умолчанию узел пробрасывает заголовки клиента (кроме Host/ContentLength) и тело JSON «как есть». Это удобно, если вы хотите просто дойти до провайдера без сложной сборки, но при этом подмешать один‑два заголовка.
4.3 Return — «оформить финальный ответ в стиле клиента»
- target_format: auto/openai/gemini/claude — во что завернуть ответ
- text_template — обычно "[[OUT1]]", но можно собрать возврат из нескольких кусков:
- "Вот ваш результат: [[OUT2]]"
Return сам завернёт текст в правильную структуру. Пример OpenAIответа возвращается как "choices[0].message.content".
4.4 If — «ветвление по условию»
Пишем одно выражение, которое возвращает true/false. Поддерживаются:
- Логика: &&, ||, ! (not)
- Сравнения: ==, !=, <, <=, >, >=
- Ключевое слово contains — проверка подстроки или вхождения в список
- Макросы [[...]] и {{ ... }} прямо в выражении
Примеры:
- [[OUT1]] contains "Красиво"
- {{ OUT.n6.response_text|default('') }} != ""
- {{ params.temperature|default(0.7) }} >= 0.3 && {{ params.temperature|default(0.7) }} <= 1
- !([[OUT3]] contains "error")
Вычислитель выражений: [eval_condition_expr()](agentui/pipeline/templating.py:336)
4.5 SetVars — «завести свои переменные»
Два режима на каждую переменную:
- mode=string — строка после разворачивания макросов (обычный случай)
- mode=expr — мини‑формула (безопасная), в которой доступны специальные функции.
Доступные безопасные функции в expr:
- from_json(x)
- Парсит JSONстроку в объект (если в строке макросы — они сначала подставятся)
- jp(value, path, join_sep="\n")
- Достаёт данные по «dotJSONPath» (индексы и звёздочка поддерживаются)
- Примеры путей: a.b.0.c, items.*.title
- jp_text(value, path, join_sep="\n")
- То же, что jp, но вернёт строку: если массив — склеит через join_sep
Готовые рецепты:
- Извлечь текст из ответа провайдера в переменную TXT и вернуть:
- name: TXT
- mode: expr
- value: jp_text([[OUT:n2.result]], 'candidates.0.content.parts.0.text')
- Return.text_template: "[[TXT]]"
- Парсинг JSONстроки и взять число:
- name: VAL
- mode: expr
- value: jp(from_json('{"a":{"b":[{"x":1},{"x":2}]}}'), 'a.b.1.x')
- результат: 2
- Собрать несколько полей в строку:
- name: TITLES
- mode: expr
- value: jp_text([[OUT:n4.result]], 'items.*.title', ' | ')
- Return: "[[TITLES]]" → "Title1 | Title2 | Title3"
Подсказки:
- Если путь возвращает несколько элементов — используйте jp_text, чтобы сразу получить склейку.
- Если у вас уже объект/массив (не строка) — можно сразу передать его в jp/jp_text без from_json.
- Внутри expr макросы пишите внутри кавычек (в аргументах функций) — они развернутся перед вычислением.
Ограничения безопасности в expr:
- Разрешены только литералы, базовая арифметика/логика, сравнения и ф‑ции rand(), randint(a,b), choice(list), from_json(), jp(), jp_text().
- Доступ к произвольным атрибутам/импортам Python запрещён.
— — —
5) Где в редакторе быстро подсмотреть макросы
Вверху справа есть кнопка «ПЕРЕМЕННЫЕ». Откроется панель:
- Выберите «snapshot», чтобы видеть всю «картину мира» последнего запуска (incoming, params, OUT_TEXT, алиасы OUT1/OUT2 и т.д.)
- Клик по строке копирует готовый макрос в буфер (можно переключить режим «фигурные» — для {{ ... }})
- Поиск работает и по именам, и по значениям
За поведение панели отвечают страничка и скрипты:
- [static/editor.html](static/editor.html)
- [static/js/serialization.js](static/js/serialization.js)
- [static/js/pm-ui.js](static/js/pm-ui.js)
— — —
6) Что на самом деле делает [[OUTx]] (как выбирается «лучший текст»)
Когда нода (ProviderCall или RawForward) получила JSON от провайдера, движок старается «вынуть» из него удобный текст:
- OpenAI: choices[0].message.content
- Gemini: все parts[].text во всех candidates склеиваются через "\n" (пустые/пробельные части игнорируются)
- Claude: content[].text (склейка)
- Если формат неизвестен — идёт «лучший догадчик» по глубине, чтобы найти текст
- Можно задать пресет (JSONPath) в настройках ноды или глобально в «Запуск» → «Пресеты парсинга OUTx»; для Gemini альтернатива авто: json_path="candidates.*.content.parts.*.text", join_sep="\n"
Логика извлечения и пресетов находится в: [ProviderCallNode.run()](agentui/pipeline/executor.py:1631), [RawForwardNode.run()](agentui/pipeline/executor.py:1939)
Для продвинутых: глобальные мета‑настройки пайплайна (timeouts, стратегия, пресеты) редактируются в «Запуск», а сохраняются в [pipeline.json](pipeline.json).
— — —
7) Безопасность (прочитать обязательно)
- Не храните настоящие APIключи в файлах пайплайна/пресетах. Передавайте их через заголовки клиента:
- OpenAI: Authorization: Bearer XXXXXX
- Anthropic: x-api-key: XXXXXX
- Gemini: ?key=XXXXXX в URL
- В шаблонах используйте значения из входящего запроса: [[VAR:incoming.headers.authorization]], [[VAR:incoming.headers.x-api-key]], [[VAR:incoming.api_keys.key]]
- Логи в редакторе показывают запросы/ответы целиком. Для продакшена отключайте/маскируйте чувствительные части.
- Прокси и TLSпроверка (если нужно сниффить трафик/пробрасывать через Burp) настраиваются здесь: [agentui/config.py](agentui/config.py), HTTPклиент: [build_client()](agentui/providers/http_client.py:21)
— — —
8) Рецепты «скопируй и вставь»
8.1 Самый короткий пайплайн «получил → отправил к OpenAI → вернул»
- Узел ProviderCall:
- provider: openai
- headers: {"Authorization":"Bearer [[VAR:incoming.headers.authorization]]"}
- template — возьмите из примера OpenAI выше (с [[PROMPT]])
- Prompt Blocks:
- system: «Ты — помощник. Отвечай коротко.»
- user: «[[VAR:chat.last_user]] — перепиши аккуратнее»
- Узел Return:
- target_format: auto
- text_template: "[[OUT1]]"
8.2 Gemini через ключ в URL, без заголовков
- ProviderCall:
- provider: gemini
- endpoint: /v1beta/models/{{ model }}:generateContent?key=[[VAR:incoming.api_keys.key]]
- headers: {}
- template — пример Gemini выше (с [[PROMPT]])
8.3 Склейка двух ответов и проверка условием If
- ProviderCall(n2) и ProviderCall(n3) что‑то генерят
- ProviderCall(n6): user prompt: «Объедини [[OUT3]] и [[OUT2]] красиво. Напиши слово "Красиво" в конце.»
- If(n7): expr = [[OUT6]] contains "Красиво"
- Return: text_template = "[[OUT6]]"
— — —
9) Где это в коде (для любопытных)
- Создание приложения/роутов: [create_app()](agentui/api/server.py:270)
- Нормализация payload (OpenAI/Gemini/Claude → единый вид): [normalize_to_unified()](agentui/api/server.py:44)
- Контекст для макросов (incoming/chat/params/…): [build_macro_context()](agentui/api/server.py:143)
- Исполнитель конвейера и «волны»: [PipelineExecutor.run()](agentui/pipeline/executor.py:316)
- Узел вызова провайдера (сборка [[PROMPT]], лог HTTP): [ProviderCallNode.run()](agentui/pipeline/executor.py:2007)
- Прямой форвард запроса: [RawForwardNode.run()](agentui/pipeline/executor.py:2477)
- Финализация ответа под формат клиента: [ReturnNode.run()](agentui/pipeline/executor.py:2798)
- Условия If (contains, &&, ||, ! и макросы): [IfNode.run()](agentui/pipeline/executor.py:2892), [eval_condition_expr()](agentui/pipeline/templating.py:382)
- Шаблонизатор макросов [[...]] и {{ ... }}: [render_template_simple()](agentui/pipeline/templating.py:196)
- Определение провайдера по форме JSON: [detect_vendor()](agentui/common/vendors.py:8)
— — —
Всё. Старайтесь думать так: «Мне нужен кусочек нужного значения в нужное место». Найдите его в панели «ПЕРЕМЕННЫЕ», скопируйте макрос — и вклейте. Если «кусочков» несколько — заведите переменные SetVars и соберите их в строку. Если сомневаетесь — начните с Return и [[OUT1]]: это самый короткий путь увидеть, что реально получается.
— — —
Приложение A. SetVars: функции и операции в mode=expr (простым языком)
Где это запрограммировано
- Логика разбора и разрешённые операции реализованы в [SetVarsNode._safe_eval_expr()](agentui/pipeline/executor.py:1092). Если коротко: там «белый список» безопасных функций и операций, всё остальное запрещено.
- Подстановка макросов внутри строк (для функций from_json/jp/jp_text) делает [render_template_simple()](agentui/pipeline/templating.py:191).
Что можно писать в mode=expr
1) Случайные значения
- rand() — число от 0 до 1 (типа 0.01.0)
Пример: 0.35, 0.92 …
- randint(a, b) — целое в диапазоне [a; b] включительно
Примеры:
- randint(1, 6) → бросок кубика d6
- randint(100, 999) → трёхзначный код
- choice(list_or_tuple) — случайный элемент из списка/кортежа
Примеры:
- choice(['ru','en','de'])
- choice((128, 256, 512))
2) Арифметика (как в обычных формулах)
- Операции: +, -, *, /, //, %
- / — деление с плавающей точкой
- // — целочисленное деление (отбрасывает дробную часть)
- % — остаток от деления
- Скобки и унарные знаки:
- (1 + 2) * 3
- -5, +7
Примеры:
- 9.99 * 1.2 → 11.988
- (rand() * 100) // 1 → псевдо‑процент от 0 до 99
- randint(1, 6) + 10 → 11..16
3) Логика и сравнения
- Логические: and, or
- Сравнения: ==, !=, <, <=, >, >=
- Можно «цепочкой»: 1 &lt; x &lt;= 10
Примеры:
- (rand() &gt; 0.5) and (choice([True, False]) or True)
- 120 &lt;= choice([64,128,256]) and 256 &gt;= 200 → True
4) Литералы (что можно писать напрямую)
- Числа: 42, 3.14
- Строки: 'привет', "hello"
- Списки/кортежи/словарики: [1,2,3], (1,2,3), {'a':1, 'b':2}
- Логические и nullподобные: True, False, None
Отдельный режим: «чистый JSON»
- Если ВЕСЬ expr — корректный JSON (и только он), он принимается как значение.
Примеры:
- {"a":{"b":[{"x":1},{"x":2}]}}
- ["ru","en","de"]
- true / false / null
- Внутри «обычных» выражений используйте Pythonформы True/False/None.
5) Макросы внутри expr — как правильно
Важно: «голые» макросы внутри expr не работают. Подставлять их можно ТОЛЬКО как строки‑аргументы специальных функций from_json/jp/jp_text — они сначала разворачиваются в текст, потом парсятся.
Примеры (готовы к копипасте):
- Взять JSON из предыдущей ноды и достать одно число:
name: SCORE
mode: expr
value: jp(from_json('[[OUT:n2.result]]'), 'meta.score')
- Вытащить текст из OpenAIответа и склеить варианты через двойной перенос:
name: TEXTS
mode: expr
value: jp_text(from_json('[[OUT:n6.result]]'), 'choices.*.message.content', '\n\n')
- Разобрать JSON из входящего запроса:
name: UID
mode: expr
value: jp(from_json('[[VAR:incoming.json]]'), 'user.id')
- Подставить фигурные скобки как строку и распарсить:
name: LIMIT
mode: expr
value: jp(from_json('{{ params|default({"max_tokens":256}) }}'), 'max_tokens')
Доступные функции (подробнее)
- from_json(x)
- Принимает строку JSON (в т.ч. со вставленными макросами), возвращает объект/массив/число/строку/bool/None.
- Удобно, когда источник — текст, а работать хочется с полями.
- jp(value, path, join_sep="\n")
- «Точечный JSONPath»: a.b.0.c, items.*.title
- Возвращает значение или список значений (если в пути есть «*»)
- join_sep здесь игнорируется (имеет смысл в jp_text)
- jp_text(value, path, join_sep="\n")
- То же, но всегда возвращает строку. Если значений несколько — склеит через join_sep.
- rand(), randint(a,b), choice(list_or_tuple)
- Случайности без сидирования (не детерминированы).
- randint — границы включительные. choice — список/кортеж не должен быть пустым.
Чего делать нельзя (и почему)
- Нельзя любые другие функции: len(), sum(), not, print(), и т.п. — «белый список» (смотри [SetVarsNode._safe_eval_expr()](agentui/pipeline/executor.py:1092))
- Нельзя обращаться к переменным/атрибутам по именам (кроме ваших [[NAME]] в шаблонах вне expr)
- Нельзя генераторы/списки‑выражения, **kwargs, *args
- Нельзя «голые» макросы внутри expr: [[...]] или {{ ... }} нужно класть строкой в аргумент from_json/jp/jp_text
Быстрая шпаргалка «на каждый день»
1) Бросок кубика d6
- name: DICE
- mode: expr
- value: randint(1, 6)
2) Случайный стиль ответа
- name: TONE
- mode: expr
- value: choice(['официально','дружелюбно','коротко'])
3) Выбрать лимит токенов
- name: TOKENS
- mode: expr
- value: choice((128, 256, 512))
4) Наценка 20%
- name: PRICE_WITH_VAT
- mode: expr
- value: price * 1.2 ← если price — ваша строковая переменная, используйте вне expr (в шаблоне) {{ PRICE }}; внутри expr внешние имена не видны
5) Вытянуть текст из ответа модели n6
- name: ANSWER
- mode: expr
- value: jp_text(from_json('[[OUT:n6.result]]'), 'choices.0.message.content')
Подсказка по ошибкам
- Function X is not allowed — вызвали неразрешённую функцию
- choice() expects list or tuple — передали не список/кортеж
- choice() on empty sequence — пустой список
- randint invalid arguments — границы не числа или a &gt; b
- Expression not allowed — в выражении есть запрещённые элементы (например, not/len/компрехеншены)
Если видите ошибку — упростите выражение: разбейте задачу на 2 переменные SetVars (сначала jp/from_json, потом математика/выбор), либо перенесите часть логики в шаблон {{ ... }} (но помните: в шаблоне нет rand/randint/choice).
Где посмотреть действующие значения
- В редакторе нажмите «ПЕРЕМЕННЫЕ»: там видно STORE (включая snapshot OUT_TEXT и алиасы OUT1/OUT2). Клик по строке — копирование макроса для вставки.
Схема работы панели описана в [static/editor.html](static/editor.html) и скриптах [static/js/serialization.js](static/js/serialization.js), [static/js/pm-ui.js](static/js/pm-ui.js).
10) Работа с изображениями (ProviderCall + переменные)
Обзор
- Теперь можно:
- хранить изображения в переменных SetVars в виде data URL;
- вставлять изображения в Prompt Blocks через Markdown-нотацию;
- вызывать провайдеров OpenAI / Claude / Gemini с мультимодальными сообщениями.
Как хранить изображения в переменных
- В SetVars (mode=expr) доступны новые безопасные функции:
- file_b64(path) — читает файл и возвращает base64-строку (без префикса).
- data_url(b64, mime) — собирает data URL: data:mime;base64,&lt;b64&gt;.
- file_data_url(path, mime?) — обёртка: читает файл, определяет mime по расширению (если не указан) и возвращает полноценный data URL.
- Примеры SetVars:
- name: IMG1
mode: expr
value: file_data_url('static/samples/cat.png','image/png')
- name: IMG2
mode: expr
value: data_url(file_b64('static/samples/dog.jpg'), 'image/jpeg')
- После этого [[IMG1]] / [[IMG2]] вернут строку-полный data URL, пригодный для мультимодальных LLM.
Как вставлять картинки в Prompt Blocks
- Внутри блока (system/user/assistant) используйте Markdown-нотацию:
- ![alt]([[IMG1]])
- ![alt](https://example.org/pic.png)
- ![alt](data:image/webp;base64,....)
- Во время исполнения ProviderCall блок превращается в список частей:
- текстовые сегменты → {"type":"text","text":"..."}
- изображения → {"type":"image_url","url":"..."}
- Для plainтекста ничего не меняется (обратная совместимость полностью сохранена).
Маппинг для провайдеров
- OpenAI (chat.completions):
- message.content → либо строка, либо массив частей:
- {"type":"text","text":"..."}
- {"type":"image_url","image_url":{"url":"<http(s) или data:...>"}}
- Claude (messages v20230601):
- message.content — массив блоков:
- {"type":"text","text":"..."}
- {"type":"image","source":{"type":"url","url":"..."}} — для http(s)
- {"type":"image","source":{"type":"base64","media_type":"image/png","data":"..."}} — для data URL
- Gemini (generateContent):
- contents[].parts — микс:
- {"text":"..."}
- {"inline_data":{"mime_type":"image/png","data":"&lt;base64&gt;"}} — для data URL
- http/https URL напрямую не инлайнится; либо преобразуйте в data URL, либо реализуйте внешнюю загрузку (не требуется для базовой поддержки).
Мини-рецепт
1) SetVars:
- name: IMG
- mode: expr
- value: file_data_url('static/pictures/cat.png', 'image/png')
2) ProviderCall (OpenAI/Gemini/Claude), Prompt Blocks, user:
- Опиши картинку ниже кратко.
- ![cat]([[IMG]])
Примечания
- Никакого тримминга больших строк и base64 по умолчанию не применяется (по пожеланию). Храните картинки разумного размера.
- JSONтело ProviderCall остаётся валидным JSON после развёртывания [[PROMPT]], т.к. преобразование в «части» выполняется до формирования payload.
- Для OpenAI и Claude можно использовать как http/https URL, так и data URL. Для Gemini предпочтительно data URL (inline_data).
Где реализовано
- Backend преобразование блоков и маппинг под провайдеров — см. файл agentui/pipeline/executor.py.
- Новые функции expr для SetVars — см. тот же файл внутри SetVarsNode._safe_eval_expr().
- Шаблонизатор как и раньше отвечает за развёртку [[...]] / {{ ... }} — см. agentui/pipeline/templating.py.
10.1) Супер‑короткая запись для картинок: img(mime)[[...]]
Задача
- У вас есть base64строка (без префикса data:), например вы её извлекли пресетом JSONPath: candidates.0.content.parts.1.inlineData.data.
- Хотите получить ПОЛНЫЙ data URL без плясок с expr и функциям SetVars — прямо в обычной строке шаблона.
Решение (новый синтаксис)
- Пишите: img(mime)[[МАКРОС_С_BASE64]]
- На выходе получится строка: data:&lt;mime&gt;;base64,&lt;ваш_base64&gt;
Где это работает
- Везде, где используется шаблонизатор [render_template_simple()](agentui/pipeline/templating.py:191):
- Return.text_template
- ProviderCall.template / headers / endpoint
- RawForward.extra_headers / override_path
- SetVars (mode=string)
- Реализовано в препроцессоре шаблонов: [templating.py](agentui/pipeline/templating.py)
Синтаксис подробно
1) Базовый случай (по умолчанию image/png):
- img()[[OUT1]]
→ data:image/png;base64,[[OUT1]] (после разворачивания [[OUT1]] — получится полноценный data URL)
2) Явный тип по короткому имени:
- img(png)[[...]] → image/png
- img(jpeg)[[...]] → image/jpeg (alias: jpg → image/jpeg)
- img(webp)[[...]] → image/webp
- img(gif)[[...]] → image/gif
- img(svg)[[...]] → image/svg+xml
- img(bmp)[[...]] → image/bmp
- img(tif)[[...]] / img(tiff)[[...]] → image/tiff
3) Полный MIME:
- img(image/heic)[[...]] → data:image/heic;base64,...
4) Динамический MIME через фигурные скобки:
- img({{ OUT.n1.result.candidates.0.content.parts.1.inlineData.mimeType|default('image/png') }})[[OUT1]]
- Важно: MIME в круглых скобках можно задавать фигурными {{ ... }} — после сборки строки препроцессор оставит «data:{{ ... }};base64,...», и следующий проход шаблонизатора подставит реальное значение.
Пошаговые примеры (копируйте и вставляйте)
A. Вернуть картинку как строку data URL из Return без SetVars и expr
- У вас пресет JSONPath на base64: candidates.0.content.parts.1.inlineData.data.
- В Return.text_template напишите:
img(png)[[OUT1]]
или, если [[OUT1]] — не base64, а вытащить надо конкретное поле:
img(png)[[VAR:OUT.n1.result.candidates.0.content.parts.1.inlineData.data]]
- В результате Return отдаст текст:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...
- Это удобно, когда потребитель умеет работать с data URL строками.
B. Сформировать HTML &lt;img&gt; в Return прямо в шаблоне
- В Return.text_template:
&lt;img src="img(jpeg)[[VAR:OUT.n1.result.candidates.0.content.parts.1.inlineData.data]]" alt="preview"&gt;
- На выходе получится полноценный HTML с src="data:image/jpeg;base64,...".
C. Положить data URL в переменную без expr
- SetVars → переменная IMG (mode=string):
value: img()[[VAR:OUT.n1.result.candidates.0.content.parts.1.inlineData.data]]
- Далее [[IMG]] — готовый data URL (можно вставлять куда угодно).
D. Динамический MIME из ответа провайдера
- Если в JSON есть mimeType:
img({{ OUT.n1.result.candidates.0.content.parts.1.inlineData.mimeType|default('image/png') }})[[VAR:OUT.n1.result.candidates.0.content.parts.1.inlineData.data]]
Часто задаваемые вопросы
Q1: Чем отличается от «старого» способа через SetVars + data_url()?
- Старый (через expr) остаётся и удобен, когда нужно переиспользовать значение в нескольких местах или делать дополнительную логику.
- Новый — мгновенная подстановка «в одну строку» прямо в шаблоне, без expr и функций. Он быстрее для типовой задачи «base64 → data URL».
Q2: Что будет, если внутри [[...]] вернётся не строка?
- Шаблонизатор приведёт значение к строке (для dict/list — сериализует в JSON). Но для data URL ожидается именно base64строка. Следите, чтобы путь/макрос давал строку, не объект.
Q3: Можно ли внутри скобок img(...) использовать [[...]]?
- Нет, внутри круглых скобок лучше использовать {{ ... }} (фигурные). Пример выше с mimeType показывает правильный путь. Квадратные [[...]] в круглых скобках не разбираются этим прелюдом, зато {{ ... }} спокойно подставятся на следующем шаге.
Q4: Нужно ли дописывать data:...;base64, вручную?
- Нет. Именно для этого и сделан синтаксис img(mime)[[...]]. Вы указываете только MIME (или оставляете пустым), а шаблонизатор добавляет префикс сам.
Q5: Что если мне нужен не текст data URL, а отрисованное превью?
- Блок «ЛОГИ → Data» уже показывает мини‑превью изображений для HTTPответов нод ProviderCall и RawForward, независимо от OUT1.
- Если вы хотите «превью прямо в Return», используйте HTML: &lt;img src="img(png)[[...]]"&gt; — потребитель, способный отображать HTML, увидит картинку.
Диагностика и отладка
- Если вы видите «сырую» base64строку вместо data URL, проверьте, что используете новый синтаксис img(...)[[...]] или явно дописали префикс вручную.
- Если «картинки» не видно в Return, удостоверьтесь, что получатель умеет отображать data URL напрямую. Для HTMLполучателей используйте тег &lt;img&gt;.
- Для ответов Gemini в логах панель «Data» показывает превью (мы подаём полные изображения через SSE), а в «Response» структура JSON остаётся читабельной — там триммится только base64, не нарушая остальной структуры.
Где в коде реализовано
- Препроцессор img(mime)[[...]] добавлен в [render_template_simple()](agentui/pipeline/templating.py:191); регулярное выражение и обработчик находятся в [templating.py](agentui/pipeline/templating.py).
- Общая логика развёртки [[...]] и {{ ... }} — тоже в [render_template_simple()](agentui/pipeline/templating.py:191).
- Подсветка и превью в логах — см. [static/editor.html](static/editor.html).
Короткая памятка (копируйте в нужные места шаблонов)
- По умолчанию PNG:
img()[[OUT1]]
- Явный JPEG:
img(jpeg)[[VAR:OUT.n1.result.candidates.0.content.parts.1.inlineData.data]]
- Динамический MIME:
img({{ OUT.n1.result.candidates.0.content.parts.1.inlineData.mimeType|default('image/png') }})[[OUT1]]
- HTML превью:
&lt;img src="img(png)[[OUT1]]" alt="image"&gt;
— — —
Приложение B. Что изменилось в парсинге Gemini и [[OUTx]] (20250921)
Коротко
- Что поменяли: теперь текст из ответов Gemini собирается из ВСЕХ частей parts[].text во всех candidates, пустые/пробельные строки игнорируются, результат склеивается через "\n".
- Где реализовано:
- Алгоритм besteffort для [[OUTx]]/алиасов: [\_best_text_from_outputs()](agentui/pipeline/templating.py:133)
- Явная стратегия "gemini" для нод: [\_extract_text_for_out()](agentui/pipeline/executor.py:1590)
- Что это даёт: [[OUT1]]/[[OUT2]]… и {{ OUT.nX.response_text }} больше не «пустеют», когда первая часть ответа — "\n". Возвращается весь человекочитаемый текст.
Что было и что стало
- Было: брали только первый элемент parts[0].text → если там "\n", получали пустоту.
- Стало: проходим все parts, берём непустые .text (strip) и склеиваем. Если частей нет — работает прежний «глубокий поиск текста» (fallback).
Примеры
- Ответ Gemini (упрощённо):
{
"candidates": [{
"content": { "parts": [{ "text": "\n" }, { "text": "Полезный ответ" }] }
}]}
- Раньше [[OUT…]] → "" (или "\n")
- Теперь [[OUT…]] → "Полезный ответ"
Альтернатива через пресет/JSONPath (без правок кода)
- Если хотите явно контролировать извлечение текста:
- strategy="jsonpath"
- json_path="candidates.*.content.parts.*.text"
- join_sep="\n"
- Это эквивалент новой авто‑логики для Gemini и годится, когда нужна строгая предсказуемость.
Подсказка: RawForward и auto
- Для RawForward мы иногда используем «подсказку» провайдера (auto). Сейчас этого достаточно. При желании можно расширить авто‑детекцию для ответов Gemini по ключу "candidates" в [detect_vendor()](agentui/common/vendors.py:8), но это не обязательно для текущей логики [[OUTx]].
— — —
Приложение C. Как правильно писать условия If (шпаргалка)
Где работает
- Узел If выполняет булево выражение и открывает ветку true/false. Исполнение: [IfNode.run()](agentui/pipeline/executor.py:2892)
- Парсер/оценка выражений: [eval_condition_expr()](agentui/pipeline/templating.py:382)
Что поддерживается
- Операторы:
- Логика: &&, ||, ! (вместо "not" используйте "!")
- Сравнения: ==, !=, <, <=, >, >=
- Ключевое слово: contains (подстрока для строк или membership для списков)
- Скобки: (...)
- Макросы внутри выражения:
- [[OUT1]], [[OUT:n2.result...]], [[NAME]]
- {{ OUT.nX.response_text|default('') }}, {{ params.temperature|default(0.7) }}
- Строковые литералы: "..." или '...' (следите за закрывающей кавычкой)
Семантика contains простым языком
- Строки: A contains B → строка B входит в строку A (по подстроке).
- Списки/множества: A contains B → элемент B присутствует в коллекции A.
Частые шаблоны (скопируйте)
- Проверка фразы в тексте провайдера:
[[OUT3]] contains "Stream failed to"
- Объединение условий:
([[OUT3]] contains "Stream failed to") || ([[OUT3]] contains "gemini-2.5-pro")
- Числовые сравнения с запасным значением:
{{ params.temperature|default(0.7) }} >= 0.3 && {{ params.temperature|default(0.7) }} <= 1
- Проверка «не пусто»:
{{ OUT.n2.response_text|default('') }} != ""
- Негативная проверка:
!([[OUT3]] contains "error")
- Сложная проверка с несколькими ветками:
({{ params.max_tokens|default(256) }} >= 128) && !([[OUT1]] contains "retry")
Советы по устойчивости
- Используйте |default(...) у {{ ... }}, чтобы не падать на None/пустых значениях.
- Для длинных/непредсказуемых строк проверяйте наличие ключевого фрагмента через contains.
- Оборачивайте группы условий в (...) — так легче читать и поддерживать.
- Внимание к кавычкам: "..." и '...' должны закрываться; это самая частая причина синтаксических ошибок.
Антишаблоны (чего избегать)
- Незакрытые кавычки в строковых литералах.
- Путаница с not: используйте "!" (а не слово not).
- Попытка вызывать произвольные функции — в If разрешён только contains(a, b) (под капотом), всё остальное запрещено.
Проверка на практике
- Пример из рабочего пайплайна (идея):
If.expr: (([[OUT3]] contains "Stream failed to") || ([[OUT3]] contains "gemini-2.5-pro"))
Trueветка → Return "[[OUT3]]"
Falseветка → RawForward или другой ProviderCall
Где ещё посмотреть
- Развёртка макросов [[...]] и {{ ... }}: [render_template_simple()](agentui/pipeline/templating.py:196)
- Логи If с развернутым выражением (для отладки): [IfNode.run()](agentui/pipeline/executor.py:2892)