Files
HadTavern/tests/test_macros_vars.py

227 lines
8.1 KiB
Python
Raw Permalink 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.

import asyncio
from agentui.pipeline.executor import PipelineExecutor
from agentui.pipeline.storage import clear_var_store
from tests.utils import pp as _pp, ctx as _ctx
async def scenario_bare_vars_and_braces():
print("\n=== MACROS 1: Bare [[NAME]] и {{ NAME }} + числа/объекты без кавычек ===")
p = {
"id": "p_macros_1",
"name": "Bare and Braces",
"loop_mode": "iterative",
"nodes": [
{
"id": "n1",
"type": "SetVars",
"config": {
"variables": [
{"id": "v1", "name": "STR", "mode": "string", "value": "строка"},
{"id": "v2", "name": "NUM", "mode": "expr", "value": "42"},
{"id": "v3", "name": "OBJ", "mode": "expr", "value": '{"x": 1, "y": [2,3]}'},
]
},
"in": {}
},
{
"id": "n2",
"type": "Return",
"config": {
"target_format": "openai",
# Вставляем: строка из [[STR]], число через {{ NUM }}, и словарь через {{ OBJ }}
"text_template": "[[STR]] | {{ NUM }} | {{ OBJ }}"
},
"in": { "depends": "n1.done" }
}
]
}
out = await PipelineExecutor(p).run(_ctx())
print("OUT:", _pp(out))
async def scenario_var_path_and_defaults():
print("\n=== MACROS 2: [[VAR:path]] и {{ ...|default(...) }} (вложенные и JSON-литералы) ===")
incoming = {
"method": "POST",
"url": "http://localhost/test?foo=bar",
"path": "/test",
"query": "foo=bar",
"headers": {"authorization": "Bearer X", "x-api-key": "Y"},
"json": {"a": None}
}
p = {
"id": "p_macros_2",
"name": "VAR and defaults",
"loop_mode": "dag",
"nodes": [
{
"id": "n1",
"type": "Return",
"config": {
"target_format": "openai",
"text_template": (
"auth=[[VAR:incoming.headers.authorization]] | "
"xkey=[[VAR:incoming.headers.x-api-key]] | "
# nested default: params.a|default(123) -> если param не задан, 123
"num={{ params.a|default(123) }} | "
# deeper default chain: incoming.json.a|default(params.a|default(456))
"num2={{ incoming.json.a|default(params.a|default(456)) }} | "
# JSON literal default list/object
"lit_list={{ missing|default([1,2,3]) }} | lit_obj={{ missing2|default({\"k\":10}) }}"
)
},
"in": {}
}
]
}
out = await PipelineExecutor(p).run(_ctx(incoming=incoming, params={"temperature": 0.2}))
print("OUT:", _pp(out))
async def scenario_out_macros_full_and_short():
print("\n=== MACROS 3: [[OUT:nX...]] и короткая форма [[OUTx]] ===")
p = {
"id": "p_macros_3",
"name": "OUT full and short",
"loop_mode": "iterative",
"nodes": [
{
"id": "n1",
"type": "SetVars",
"config": {
"variables": [
{"id": "v1", "name": "MSG", "mode": "string", "value": "hello"}
]
},
"in": {}
},
{
"id": "n2",
"type": "Return",
"config": {
"target_format": "openai",
"text_template": "[[MSG]]"
},
"in": { "depends": "n1.done" }
},
{
"id": "n3",
"type": "Return",
"config": {
"target_format": "openai",
# Две формы: полная от n1.vars.MSG и короткая от n2 => [[OUT2]]
"text_template": "[[OUT:n1.vars.MSG]] + [[OUT2]]"
},
"in": { "depends": "n2.done" }
}
]
}
out = await PipelineExecutor(p).run(_ctx())
print("OUT:", _pp(out))
async def scenario_store_macros_two_runs():
print("\n=== MACROS 4: [[STORE:key]] и {{ STORE.key }} между запусками (clear_var_store=False) ===")
pid = "p_macros_4_store"
# начинаем с чистого стора
clear_var_store(pid)
p = {
"id": pid,
"name": "STORE across runs",
"loop_mode": "iterative",
"clear_var_store": False, # критично: не очищать между запусками
"nodes": [
{
"id": "n1",
"type": "SetVars",
"config": {
"variables": [
{"id": "v1", "name": "KEEP", "mode": "string", "value": "persist-me"}
]
},
"in": {}
},
{
"id": "n2",
"type": "Return",
"config": {
"target_format": "openai",
"text_template": "first-run"
},
"in": { "depends": "n1.done" }
}
]
}
# Первый запуск — кладём KEEP в STORE
out1 = await PipelineExecutor(p).run(_ctx())
print("RUN1:", _pp(out1))
# Второй запуск — читаем из STORE через макросы
p2 = {
"id": pid,
"name": "STORE read",
"loop_mode": "dag",
"clear_var_store": False,
"nodes": [
{
"id": "n1",
"type": "Return",
"config": {
"target_format": "openai",
"text_template": "[[STORE:KEEP]] | {{ STORE.KEEP }}"
},
"in": {}
}
]
}
out2 = await PipelineExecutor(p2).run(_ctx())
print("RUN2:", _pp(out2))
async def scenario_pm_prompt_blocks_to_provider_structs():
print("\n=== MACROS 5: Prompt Blocks ([[PROMPT]]) → provider-structures (OpenAI) ===")
# Проверяем, что [[PROMPT]] со списком блоков превращается в "messages":[...]
p = {
"id": "p_macros_5",
"name": "PROMPT OpenAI",
"loop_mode": "dag",
"nodes": [
{
"id": "n1",
"type": "ProviderCall",
"config": {
"provider": "openai",
"provider_configs": {
"openai": {
"base_url": "https://api.openai.com",
"endpoint": "/v1/chat/completions",
"headers": "{\"Authorization\":\"Bearer TEST\"}",
"template": "{\n \"model\": \"{{ model }}\",\n [[PROMPT]],\n \"temperature\": {{ params.temperature|default(0.7) }}\n}"
}
},
"blocks": [
{"id": "b1", "name": "sys", "role": "system", "prompt": "You are test", "enabled": True, "order": 0},
{"id": "b2", "name": "user", "role": "user", "prompt": "Say [[VAR:chat.last_user]]", "enabled": True, "order": 1}
]
},
"in": {}
}
]
}
# Сборка и запрос (ожидаемый 401), главное — валидное тело с messages:[...]
out = await PipelineExecutor(p).run(_ctx())
print("OUT:", _pp(out))
def run_all():
async def main():
await scenario_bare_vars_and_braces()
await scenario_var_path_and_defaults()
await scenario_out_macros_full_and_short()
await scenario_store_macros_two_runs()
await scenario_pm_prompt_blocks_to_provider_structs()
print("\n=== MACROS VARS SUITE: DONE ===")
asyncio.run(main())
if __name__ == "__main__":
run_all()