sync: UI animations, select styling, TLS verify flag via proxy second line, brand spacing
This commit is contained in:
@@ -6,9 +6,9 @@
|
||||
|
||||
Файлы, где «живет» эта магия:
|
||||
- Сервер и конечные точки: [agentui/api/server.py](agentui/api/server.py)
|
||||
- Исполнитель узлов (сердце конвейера): [PipelineExecutor.run()](agentui/pipeline/executor.py:170)
|
||||
- Ноды: [ProviderCallNode.run()](agentui/pipeline/executor.py:1631), [RawForwardNode.run()](agentui/pipeline/executor.py:1939), [ReturnNode.run()](agentui/pipeline/executor.py:2256), [IfNode.run()](agentui/pipeline/executor.py:2350), SetVars внутри того же файла
|
||||
- Шаблонизатор (подстановки [[...]] и {{ ... }}): [render_template_simple()](agentui/pipeline/templating.py:191), булевы выражения If: [eval_condition_expr()](agentui/pipeline/templating.py:336)
|
||||
- Исполнитель узлов (сердце конвейера): [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)
|
||||
|
||||
— — —
|
||||
|
||||
@@ -218,10 +218,10 @@ Return сам завернёт текст в правильную структу
|
||||
|
||||
Когда нода (ProviderCall или RawForward) получила JSON от провайдера, движок старается «вынуть» из него удобный текст:
|
||||
- OpenAI: choices[0].message.content
|
||||
- Gemini: candidates[0].content.parts[0].text
|
||||
- Gemini: все parts[].text во всех candidates склеиваются через "\n" (пустые/пробельные части игнорируются)
|
||||
- Claude: content[].text (склейка)
|
||||
- Если формат неизвестен — идёт «лучший догадчик» по глубине, чтобы найти текст
|
||||
- Можно задать пресет (JSONPath) в настройках ноды или глобально в «Запуск» → «Пресеты парсинга OUTx»
|
||||
- Можно задать пресет (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)
|
||||
|
||||
@@ -275,12 +275,12 @@ Return сам завернёт текст в правильную структу
|
||||
- Создание приложения/роутов: [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:170)
|
||||
- Узел вызова провайдера (сборка [[PROMPT]], лог HTTP): [ProviderCallNode.run()](agentui/pipeline/executor.py:1631)
|
||||
- Прямой форвард запроса: [RawForwardNode.run()](agentui/pipeline/executor.py:1939)
|
||||
- Финализация ответа под формат клиента: [ReturnNode.run()](agentui/pipeline/executor.py:2256)
|
||||
- Условия If (contains, &&, ||, ! и макросы): [IfNode.run()](agentui/pipeline/executor.py:2350), [eval_condition_expr()](agentui/pipeline/templating.py:336)
|
||||
- Шаблонизатор макросов [[...]] и {{ ... }}: [render_template_simple()](agentui/pipeline/templating.py:191)
|
||||
- Исполнитель конвейера и «волны»: [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)
|
||||
|
||||
— — —
|
||||
@@ -431,4 +431,266 @@ Return сам завернёт текст в правильную структу
|
||||
|
||||
Где посмотреть действующие значения
|
||||
- В редакторе нажмите «ПЕРЕМЕННЫЕ»: там видно 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).
|
||||
Схема работы панели описана в [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,<b64>.
|
||||
- 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-нотацию:
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- Во время исполнения 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 v2023‑06‑01):
|
||||
- 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":"<base64>"}} — для 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:
|
||||
- Опиши картинку ниже кратко.
|
||||
- 
|
||||
|
||||
Примечания
|
||||
- Никакого тримминга больших строк и 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:<mime>;base64,<ваш_base64>
|
||||
|
||||
Где это работает
|
||||
- Везде, где используется шаблонизатор [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 <img> в Return прямо в шаблоне
|
||||
- В Return.text_template:
|
||||
<img src="img(jpeg)[[VAR:OUT.n1.result.candidates.0.content.parts.1.inlineData.data]]" alt="preview">
|
||||
- На выходе получится полноценный 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: <img src="img(png)[[...]]"> — потребитель, способный отображать HTML, увидит картинку.
|
||||
|
||||
Диагностика и отладка
|
||||
- Если вы видите «сырую» base64‑строку вместо data URL, проверьте, что используете новый синтаксис img(...)[[...]] или явно дописали префикс вручную.
|
||||
- Если «картинки» не видно в Return, удостоверьтесь, что получатель умеет отображать data URL напрямую. Для HTML‑получателей используйте тег <img>.
|
||||
- Для ответов 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 превью:
|
||||
<img src="img(png)[[OUT1]]" alt="image">
|
||||
|
||||
— — —
|
||||
Приложение B. Что изменилось в парсинге Gemini и [[OUTx]] (2025‑09‑21)
|
||||
|
||||
Коротко
|
||||
- Что поменяли: теперь текст из ответов Gemini собирается из ВСЕХ частей parts[].text во всех candidates, пустые/пробельные строки игнорируются, результат склеивается через "\n".
|
||||
- Где реализовано:
|
||||
- Алгоритм best‑effort для [[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)
|
||||
|
||||
Reference in New Issue
Block a user