Files
HadTavern/docs/VARIABLES.md

434 lines
28 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: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)
— — —
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: candidates[0].content.parts[0].text
- Claude: content[].text (склейка)
- Если формат неизвестен — идёт «лучший догадчик» по глубине, чтобы найти текст
- Можно задать пресет (JSONPath) в настройках ноды или глобально в «Запуск» → «Пресеты парсинга OUTx»
Логика извлечения и пресетов находится в: [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: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)
- Определение провайдера по форме 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).