chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run

This commit is contained in:
wehub-resource-sync
2026-07-13 13:21:23 +08:00
commit b957a53def
5423 changed files with 863745 additions and 0 deletions
@@ -0,0 +1,21 @@
# Copyright (c) Microsoft. All rights reserved.
from pydantic import ValidationError
from pytest import raises
from semantic_kernel.template_engine.blocks.block import Block
def test_init():
block = Block(content="test content")
assert block.content == "test content"
def test_content_strip():
block = Block(content=" test content ")
assert block.content == "test content"
def test_no_content():
with raises(ValidationError):
Block()
@@ -0,0 +1,581 @@
# Copyright (c) Microsoft. All rights reserved.
from pytest import mark, raises
from semantic_kernel.exceptions import (
CodeBlockRenderException,
CodeBlockSyntaxError,
CodeBlockTokenError,
FunctionIdBlockSyntaxError,
NamedArgBlockSyntaxError,
ValBlockSyntaxError,
VarBlockSyntaxError,
)
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.functions.kernel_function_decorator import kernel_function
from semantic_kernel.functions.kernel_function_from_method import KernelFunctionFromMethod
from semantic_kernel.functions.kernel_plugin import KernelPlugin
from semantic_kernel.kernel import Kernel
from semantic_kernel.template_engine.blocks.block_types import BlockTypes
from semantic_kernel.template_engine.blocks.code_block import CodeBlock
from semantic_kernel.template_engine.blocks.function_id_block import FunctionIdBlock
from semantic_kernel.template_engine.blocks.named_arg_block import NamedArgBlock
from semantic_kernel.template_engine.blocks.val_block import ValBlock
from semantic_kernel.template_engine.blocks.var_block import VarBlock
def test_init():
target = CodeBlock(
content="plugin.function 'value' arg1=$arg1",
)
assert len(target.tokens) == 3
assert target.tokens[0] == FunctionIdBlock(content="plugin.function")
assert target.tokens[1] == ValBlock(content="'value'")
assert target.tokens[2] == NamedArgBlock(content="arg1=$arg1")
assert target.type == BlockTypes.CODE
class TestCodeBlockRendering:
async def test_it_throws_if_a_plugins_are_empty(self, kernel: Kernel):
target = CodeBlock(
content="functionName",
)
assert target.tokens[0].type == BlockTypes.FUNCTION_ID
with raises(CodeBlockRenderException, match="Function `functionName` not found"):
await target.render_code(kernel, KernelArguments())
async def test_it_throws_if_a_function_doesnt_exist(self, kernel: Kernel):
target = CodeBlock(
content="functionName",
)
assert target.tokens[0].type == BlockTypes.FUNCTION_ID
kernel.add_plugin(KernelPlugin(name="test", functions=[]))
with raises(CodeBlockRenderException, match="Function `functionName` not found"):
await target.render_code(kernel, KernelArguments())
async def test_it_throws_if_a_function_call_throws(self, kernel: Kernel):
@kernel_function(name="funcName")
def invoke():
raise Exception("function exception")
function = KernelFunctionFromMethod(
method=invoke,
plugin_name="pluginName",
)
kernel.add_function(plugin_name="test", function=function)
target = CodeBlock(
content="test.funcName",
)
with raises(CodeBlockRenderException, match="test.funcName"):
await target.render_code(kernel, KernelArguments())
async def test_it_renders_code_block_consisting_of_just_a_var_block1(self, kernel: Kernel):
code_block = CodeBlock(
content="$var",
)
result = await code_block.render_code(kernel, KernelArguments(var="foo"))
assert result == "foo"
async def test_it_renders_code_block_consisting_of_just_a_val_block1(self, kernel: Kernel):
code_block = CodeBlock(
content="'ciao'",
)
result = await code_block.render_code(kernel, KernelArguments())
assert result == "ciao"
async def test_it_invokes_function_cloning_all_variables(self, kernel: Kernel):
# Set up initial context variables
arguments = KernelArguments(input="zero", var1="uno", var2="due")
# Create a FunctionIdBlock with the function name
func_id = FunctionIdBlock(content="test.funcName")
# Set up a canary dictionary to track changes in the context variables
canary = {"input": "", "var1": "", "var2": ""}
# Define the function to be invoked, which modifies the canary
# and context variables
@kernel_function(name="funcName")
def invoke(arguments: KernelArguments):
nonlocal canary
canary["input"] = arguments["input"]
canary["var1"] = arguments["var1"]
canary["var2"] = arguments["var2"]
arguments["input"] = "overridden"
arguments["var1"] = "overridden"
arguments["var2"] = "overridden"
# Create an KernelFunction with the invoke function as its delegate
function = KernelFunctionFromMethod(
method=invoke,
plugin_name="pluginName",
)
kernel.add_plugin(KernelPlugin(name="test", functions=[function]))
# Create a CodeBlock with the FunctionIdBlock and render it with the context
code_block = CodeBlock(
tokens=[func_id],
content="",
)
await code_block.render_code(kernel, arguments)
# Check that the canary values match the original context variables
assert canary["input"] == "zero"
assert canary["var1"] == "uno"
assert canary["var2"] == "due"
# Check that the original context variables were not modified
assert arguments["input"] == "zero"
assert arguments["var1"] == "uno"
assert arguments["var2"] == "due"
async def test_it_invokes_function_with_custom_variable(self, kernel: Kernel):
# Define custom variable name and value
VAR_NAME = "varName"
VAR_VALUE = "varValue"
# Set up initial context variables
arguments = KernelArguments()
arguments[VAR_NAME] = VAR_VALUE
# Create a FunctionIdBlock with the function name and a
# VarBlock with the custom variable
func_id = FunctionIdBlock(content="test.funcName")
var_block = VarBlock(content=f"${VAR_NAME}")
# Set up a canary variable to track changes in the context input
canary = ""
# Define the function to be invoked, which modifies the canary variable
@kernel_function(name="funcName")
def invoke(arguments: "KernelArguments"):
nonlocal canary
canary = arguments["varName"]
return arguments["varName"]
# Create an KernelFunction with the invoke function as its delegate
function = KernelFunctionFromMethod(
method=invoke,
plugin_name="pluginName",
)
kernel.add_plugin(KernelPlugin(name="test", functions=[function]))
# Create a CodeBlock with the FunctionIdBlock and VarBlock,
# and render it with the context
code_block = CodeBlock(
tokens=[func_id, var_block],
content="",
)
result = await code_block.render_code(kernel, arguments)
# Check that the result matches the custom variable value
assert result == VAR_VALUE
# Check that the canary value matches the custom variable value
assert canary == VAR_VALUE
async def test_it_invokes_function_with_custom_value(self, kernel: Kernel):
# Define a value to be used in the test
VALUE = "value"
# Create a FunctionIdBlock with the function name and a ValBlock with the value
func_id = FunctionIdBlock(content="test.funcName")
val_block = ValBlock(content=f"'{VALUE}'")
# Set up a canary variable to track changes in the context input
canary = ""
# Define the function to be invoked, which modifies the canary variable
@kernel_function(name="funcName")
def invoke(arguments):
nonlocal canary
canary = arguments["input"]
return arguments["input"]
# Create an KernelFunction with the invoke function as its delegate
function = KernelFunctionFromMethod(
method=invoke,
plugin_name="pluginName",
)
kernel.add_plugin(KernelPlugin(name="test", functions=[function]))
# Create a CodeBlock with the FunctionIdBlock and ValBlock,
# and render it with the context
code_block = CodeBlock(
tokens=[func_id, val_block],
content="",
)
result = await code_block.render_code(kernel, KernelArguments(input="value"))
# Check that the result matches the value
assert str(result) == VALUE
# Check that the canary value matches the value
assert canary == VALUE
async def test_it_invokes_function_with_multiple_arguments(self, kernel: Kernel):
# Define a value to be used in the test
VALUE = "value"
code_block = CodeBlock(
content=" ",
tokens=[
FunctionIdBlock(content="test.funcName", plugin_name="test", function_name="funcName", validated=True),
ValBlock(content=f'"{VALUE}"'),
NamedArgBlock(content="arg1=$arg1"),
NamedArgBlock(content='arg2="arg2"'),
],
)
# Set up a canary variable to track changes in the context input
canary = ""
# Define the function to be invoked, which modifies the canary variable
@kernel_function(name="funcName")
def invoke(input, arg1, arg2):
nonlocal canary
canary = f"{input} {arg1} {arg2}"
return input
# Create an KernelFunction with the invoke function as its delegate
function = KernelFunctionFromMethod(
method=invoke,
plugin_name="pluginName",
)
kernel.add_plugin(KernelPlugin(name="test", functions=[function]))
# Create a CodeBlock with the FunctionIdBlock and ValBlock,
# and render it with the context
result = await code_block.render_code(kernel, KernelArguments(arg1="arg1"))
# Check that the result matches the value
assert str(result) == VALUE
# Check that the canary value matches the value
assert canary == f"{VALUE} arg1 arg2"
async def test_it_invokes_function_with_only_named_arguments(self, kernel: Kernel):
code_block = CodeBlock(
content=" ",
tokens=[
FunctionIdBlock(content="test.funcName", plugin_name="test", function_name="funcName"),
NamedArgBlock(content="arg1=$arg1"),
NamedArgBlock(content='arg2="arg2"'),
],
)
# Set up a canary variable to track changes in the context input
canary = ""
# Define the function to be invoked, which modifies the canary variable
@kernel_function(name="funcName")
def invoke(arg1, arg2):
nonlocal canary
canary = f"{arg1} {arg2}"
return arg1
# Create an KernelFunction with the invoke function as its delegate
function = KernelFunctionFromMethod(
method=invoke,
plugin_name="pluginName",
)
kernel.add_plugin(KernelPlugin(name="test", functions=[function]))
# Create a CodeBlock with the FunctionIdBlock and ValBlock,
# and render it with the context
result = await code_block.render_code(kernel, KernelArguments(arg1="arg1"))
# Check that the result matches the value
assert str(result) == "arg1"
# Check that the canary value matches the value
assert canary == "arg1 arg2"
async def test_it_fails_on_function_without_args(self, kernel: Kernel):
code_block = CodeBlock(
content=" ",
tokens=[
FunctionIdBlock(content="test.funcName", plugin_name="test", function_name="funcName"),
NamedArgBlock(content="arg1=$arg1"),
NamedArgBlock(content='arg2="arg2"'),
],
)
@kernel_function(name="funcName")
def invoke():
return "function without args"
# Create an KernelFunction with the invoke function as its delegate
function = KernelFunctionFromMethod(
method=invoke,
plugin_name="test",
)
kernel.add_plugin(KernelPlugin(name="test", functions=[function]))
# Create a CodeBlock with the FunctionIdBlock and ValBlock,
# and render it with the context
with raises(
CodeBlockRenderException,
match="Function test.funcName does not take any arguments \
but it is being called in the template with 2 arguments.",
):
await code_block.render_code(kernel, KernelArguments(arg1="arg1"))
@mark.parametrize(
"token2",
[
"",
"arg2=$arg!2",
"arg2='va\"l'",
],
ids=[
"empty",
"invalid_named_arg",
"invalid_named_arg_val",
],
)
@mark.parametrize(
"token1",
[
"",
"$var!",
"\"val'",
"arg1=$arg!1",
"arg1='va\"l'",
],
ids=[
"empty",
"invalid_var",
"invalid_val",
"invalid_named_arg",
"invalid_named_arg_val",
],
)
@mark.parametrize(
"token0",
[
"plugin.func.test",
"$var!",
'"va"l"',
],
ids=[
"invalid_func",
"invalid_var",
"invalid_val",
],
)
def test_block_validation(token0, token1, token2):
with raises((
FunctionIdBlockSyntaxError,
VarBlockSyntaxError,
ValBlockSyntaxError,
NamedArgBlockSyntaxError,
CodeBlockSyntaxError,
)):
CodeBlock(
content=f"{token0} {token1} {token2}",
)
@mark.parametrize(
"token2, token2valid",
[
("", True),
("plugin.func", False),
("$var", False),
('"val"', False),
("arg1=$arg1", True),
("arg1='val'", True),
],
ids=[
"empty",
"func_invalid",
"invalid_var",
"invalid_val",
"valid_named_arg",
"valid_named_arg_val",
],
)
@mark.parametrize(
"token1, token1valid",
[
("", True),
("plugin.func", False),
("$var", True),
('"val"', True),
("arg1=$arg1", True),
("arg1='val'", True),
],
ids=[
"empty",
"func_invalid",
"var",
"val",
"valid_named_arg",
"valid_named_arg_val",
],
)
@mark.parametrize(
"token0, token0valid",
[
("func", True),
("plugin.func", True),
("$var", True),
('"val"', True),
("arg1=$arg1", False),
("arg1='val'", False),
],
ids=[
"single_name_func",
"FQN_func",
"var",
"val",
"invalid_named_arg",
"invalid_named_arg_val",
],
)
def test_positional_validation(token0, token0valid, token1, token1valid, token2, token2valid):
if not token1 and not token2valid:
mark.skipif(f"{token0} {token1} {token2}", reason="Not applicable")
return
valid = token0valid and token1valid and token2valid
if token0 in ["$var", '"val"']:
valid = True
content = f"{token0} {token1} {token2}"
if valid:
target = CodeBlock(
content=content,
)
assert target.content == content.strip()
else:
with raises(CodeBlockTokenError):
CodeBlock(
content=content,
)
@mark.parametrize(
"case, result",
[
(r"{$a", False),
],
)
def test_edge_cases(case, result):
if result:
target = CodeBlock(
content=case,
)
assert target.content == case
else:
with raises(FunctionIdBlockSyntaxError):
CodeBlock(
content=case,
)
def test_no_tokens():
with raises(CodeBlockTokenError):
CodeBlock(content="", tokens=[])
class TestNonStringArguments:
"""Test that non-string KernelArguments are preserved when passed to functions in templates."""
async def test_function_receives_int_type(self, kernel: Kernel):
"""Test that an integer argument is passed as int, not converted to string."""
received_value = None
received_type = None
@kernel_function(name="check_type")
def check_type(value: int):
nonlocal received_value, received_type
received_value = value
received_type = type(value)
return f"Received {type(value).__name__}: {value}"
function = KernelFunctionFromMethod(method=check_type, plugin_name="test")
kernel.add_plugin(KernelPlugin(name="test", functions=[function]))
code_block = CodeBlock(content="test.check_type value=$my_int")
arguments = KernelArguments(my_int=42)
await code_block.render_code(kernel, arguments)
assert received_value == 42
assert isinstance(received_value, int), f"Expected int but got {received_type}"
async def test_function_receives_list_type(self, kernel: Kernel):
"""Test that a list argument is passed as list, not converted to string."""
received_value = None
received_type = None
@kernel_function(name="check_type")
def check_type(items: list):
nonlocal received_value, received_type
received_value = items
received_type = type(items)
return f"Received {len(items)} items"
function = KernelFunctionFromMethod(method=check_type, plugin_name="test")
kernel.add_plugin(KernelPlugin(name="test", functions=[function]))
code_block = CodeBlock(content="test.check_type items=$my_list")
arguments = KernelArguments(my_list=[1, 2, 3])
await code_block.render_code(kernel, arguments)
assert received_value == [1, 2, 3]
assert isinstance(received_value, list), f"Expected list but got {received_type}"
async def test_function_receives_dict_type(self, kernel: Kernel):
"""Test that a dict argument is passed as dict, not converted to string."""
received_value = None
received_type = None
@kernel_function(name="check_type")
def check_type(data: dict):
nonlocal received_value, received_type
received_value = data
received_type = type(data)
return f"Received dict with keys: {list(data.keys())}"
function = KernelFunctionFromMethod(method=check_type, plugin_name="test")
kernel.add_plugin(KernelPlugin(name="test", functions=[function]))
code_block = CodeBlock(content="test.check_type data=$my_dict")
arguments = KernelArguments(my_dict={"key": "value", "num": 123})
await code_block.render_code(kernel, arguments)
assert received_value == {"key": "value", "num": 123}
assert isinstance(received_value, dict), f"Expected dict but got {received_type}"
async def test_named_arg_with_non_string_type(self, kernel: Kernel):
"""Test that named arguments with non-string types are preserved."""
received_count = None
received_type = None
@kernel_function(name="process")
def process(text: str, count: int):
nonlocal received_count, received_type
received_count = count
received_type = type(count)
return f"{text} x {count}"
function = KernelFunctionFromMethod(method=process, plugin_name="test")
kernel.add_plugin(KernelPlugin(name="test", functions=[function]))
code_block = CodeBlock(content="test.process 'hello' count=$repetitions")
arguments = KernelArguments(repetitions=5)
await code_block.render_code(kernel, arguments)
assert received_count == 5
assert isinstance(received_count, int), f"Expected int but got {received_type}"
@@ -0,0 +1,74 @@
# Copyright (c) Microsoft. All rights reserved.
from pytest import mark, raises
from semantic_kernel.exceptions import FunctionIdBlockSyntaxError
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.kernel import Kernel
from semantic_kernel.template_engine.blocks.block_types import BlockTypes
from semantic_kernel.template_engine.blocks.function_id_block import FunctionIdBlock
def test_init():
function_id_block = FunctionIdBlock(content="plugin.function")
assert function_id_block.content == "plugin.function"
assert function_id_block.plugin_name == "plugin"
assert function_id_block.function_name == "function"
assert function_id_block.type == BlockTypes.FUNCTION_ID
def test_init_function_only():
function_id_block = FunctionIdBlock(content="function")
assert function_id_block.content == "function"
assert not function_id_block.plugin_name
assert function_id_block.function_name == "function"
assert function_id_block.type == BlockTypes.FUNCTION_ID
def test_it_trims_spaces():
assert FunctionIdBlock(content=" aa ").content == "aa"
@mark.parametrize(
"name",
[
"0",
"1",
"a",
"_",
"01",
"01a",
"a01",
"_0",
"a01_",
"_a01",
],
)
def test_valid_syntax(name):
target = FunctionIdBlock(content=name)
assert target.content == name
@mark.parametrize(
"content",
["", "plugin.nope.function", "func-tion", "plu-in.function", ".function"],
ids=["empty", "three_parts", "invalid_function", "invalid_plugin", "no_plugin"],
)
def test_syntax_error(content):
with raises(FunctionIdBlockSyntaxError, match=rf".*{content}.*"):
FunctionIdBlock(content=content)
def test_render():
kernel = Kernel()
function_id_block = FunctionIdBlock(content="plugin.function")
rendered_value = function_id_block.render(kernel, KernelArguments())
assert rendered_value == "plugin.function"
def test_render_function_only():
kernel = Kernel()
function_id_block = FunctionIdBlock(content="function")
rendered_value = function_id_block.render(kernel, KernelArguments())
assert rendered_value == "function"
@@ -0,0 +1,115 @@
# Copyright (c) Microsoft. All rights reserved.
import logging
from pytest import mark, raises
from semantic_kernel.exceptions import NamedArgBlockSyntaxError
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.kernel import Kernel
from semantic_kernel.template_engine.blocks.block_types import BlockTypes
from semantic_kernel.template_engine.blocks.named_arg_block import NamedArgBlock
from semantic_kernel.template_engine.blocks.val_block import ValBlock
from semantic_kernel.template_engine.blocks.var_block import VarBlock
logger = logging.getLogger(__name__)
def test_init_with_var():
named_arg_block = NamedArgBlock(content="test=$test_var")
assert named_arg_block.content == "test=$test_var"
assert named_arg_block.name == "test"
assert named_arg_block.variable.name == "test_var"
assert isinstance(named_arg_block.variable, VarBlock)
def test_init_with_val():
named_arg_block = NamedArgBlock(content="test='test_val'")
assert named_arg_block.content == "test='test_val'"
assert named_arg_block.name == "test"
assert named_arg_block.value.value == "test_val"
assert isinstance(named_arg_block.value, ValBlock)
def test_type_property():
named_arg_block = NamedArgBlock(content="test=$test_var")
assert named_arg_block.type == BlockTypes.NAMED_ARG
@mark.parametrize(
"content",
[
"=$test_var",
"test=$test-var",
"test='test_val\"",
"test=''",
"test=$",
],
ids=["no_name", "invalid_var", "invalid_val", "empty_val", "empty_var"],
)
def test_syntax_error(content):
match = content.replace("$", "\\$") if "$" in content else content
with raises(NamedArgBlockSyntaxError, match=rf".*{match}.*"):
NamedArgBlock(content=content)
def test_render():
named_arg_block = NamedArgBlock(content="test=$test_var")
rendered_value = named_arg_block.render(Kernel(), KernelArguments(test_var="test_value"))
assert rendered_value == "test_value"
def test_render_variable_not_found():
named_arg_block = NamedArgBlock(content="test=$test_var")
rendered_value = named_arg_block.render(Kernel(), KernelArguments())
assert rendered_value == ""
def test_init_minimal_var():
block = NamedArgBlock(content="a=$a")
assert block.name == "a"
assert block.variable.name == "a"
def test_init_minimal_val():
block = NamedArgBlock(content="a='a'")
assert block.name == "a"
assert block.value.value == "a"
def test_init_empty():
with raises(NamedArgBlockSyntaxError, match=r".*"):
NamedArgBlock(content="")
def test_it_trims_spaces():
assert NamedArgBlock(content=" a=$x ").content == "a=$x"
def test_it_ignores_spaces_around():
target = NamedArgBlock(content=" a=$var \n ")
assert target.content == "a=$var"
def test_it_renders_to_empty_string_without_variables():
target = NamedArgBlock(content=" a=$var \n ")
result = target.render(Kernel(), None)
assert result == ""
def test_it_renders_to_empty_string_if_variable_is_missing():
target = NamedArgBlock(content=" a=$var \n ")
result = target.render(Kernel(), KernelArguments(foo="bar"))
assert result == ""
def test_it_renders_to_variable_value_when_available():
target = NamedArgBlock(content=" a=$var \n ")
result = target.render(Kernel(), KernelArguments(foo="bar", var="able"))
assert result == "able"
def test_it_renders_to_value():
target = NamedArgBlock(content=" a='var' \n ")
result = target.render(Kernel(), None)
assert result == "var"
@@ -0,0 +1,95 @@
# Copyright (c) Microsoft. All rights reserved.
import pytest
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.kernel import Kernel
from semantic_kernel.template_engine.blocks.block_types import BlockTypes
from semantic_kernel.template_engine.blocks.text_block import TextBlock
def test_init():
text_block = TextBlock.from_text(text="test text")
assert text_block.content == "test text"
def test_init_with_just_start_index():
text_block = TextBlock.from_text(text="test text", start_index=2)
assert text_block.content == "st text"
def test_init_with_just_stop_index():
text_block = TextBlock.from_text(text="test text", stop_index=2)
assert text_block.content == "te"
def test_init_with_start_index_greater_than_stop_index():
with pytest.raises(ValueError):
TextBlock.from_text(text="test text", start_index=2, stop_index=1)
def test_init_with_start_stop_indices():
text_block = TextBlock.from_text(text="test text", start_index=0, stop_index=4)
assert text_block.content == "test"
def test_init_with_start_index_less_than_zero():
with pytest.raises(ValueError):
TextBlock.from_text(text="test text", start_index=-1, stop_index=1)
def test_init_with_negative_stop_index():
text_block = TextBlock.from_text(text="test text", stop_index=-1)
assert text_block.content == "test tex"
def test_type_property():
text_block = TextBlock.from_text(text="test text")
assert text_block.type == BlockTypes.TEXT
def test_render():
text_block = TextBlock.from_text(text="test text")
rendered_value = text_block.render(Kernel(), KernelArguments())
assert rendered_value == "test text"
@pytest.mark.parametrize(
("input_", "output"),
[
(None, ""),
("", ""),
(" ", " "),
(" ", " "),
(" ", " "),
(" \n", " \n"),
(" \t", " \t"),
(" \r", " \r"),
],
ids=["None", "empty", "space", "two_spaces", "three_spaces", "space_newline", "space_tab", "space_carriage_return"],
)
def test_preserves_empty_values(input_, output):
assert output == TextBlock.from_text(text=input_).content
@pytest.mark.parametrize(
("input_", "output"),
[
(None, ""),
("", ""),
(" ", " "),
(" ", " "),
(" ", " "),
(" \n", " \n"),
(" \t", " \t"),
(" \r", " \r"),
("test", "test"),
(" \nabc", " \nabc"),
("'x'", "'x'"),
('"x"', '"x"'),
("\"'x'\"", "\"'x'\""),
],
)
def test_renders_the_content_as_it(input_, output):
assert TextBlock.from_text(text=input_).render() == output
@@ -0,0 +1,67 @@
# Copyright (c) Microsoft. All rights reserved.
from pytest import mark, raises
from semantic_kernel.exceptions import ValBlockSyntaxError
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.kernel import Kernel
from semantic_kernel.template_engine.blocks.block_types import BlockTypes
from semantic_kernel.template_engine.blocks.val_block import ValBlock
def test_init_single_quote():
val_block = ValBlock(content="'test value'")
assert val_block.content == "'test value'"
assert val_block.value == "test value"
assert val_block.quote == "'"
assert val_block.type == BlockTypes.VALUE
def test_init_double_quote():
val_block = ValBlock(content='"test value"')
assert val_block.content == '"test value"'
assert val_block.value == "test value"
assert val_block.quote == '"'
assert val_block.type == BlockTypes.VALUE
@mark.parametrize(
"content",
[
"test value",
"'test value",
"test value'",
'"test value',
'test value"',
"'test value\"",
],
ids=[
"no_quotes",
"single_quote_start",
"single_quote_end",
"double_quote_start",
"double_quote_end",
"mixed_quote",
],
)
def test_syntax_error(content):
with raises(ValBlockSyntaxError, match=rf".*{content}*"):
ValBlock(content=content)
def test_render():
val_block = ValBlock(content="'test value'")
rendered_value = val_block.render(Kernel(), KernelArguments())
assert rendered_value == "test value"
def test_escaping():
val_block = ValBlock(content="'f\\'oo'")
rendered_value = val_block.render(Kernel(), KernelArguments())
assert rendered_value == "f\\'oo"
def test_escaping2():
val_block = ValBlock(content=r"'f\'oo'")
rendered_value = val_block.render(Kernel(), KernelArguments())
assert rendered_value == r"f\'oo"
@@ -0,0 +1,87 @@
# Copyright (c) Microsoft. All rights reserved.
import logging
from pytest import mark, raises
from semantic_kernel.exceptions import VarBlockSyntaxError
from semantic_kernel.exceptions.template_engine_exceptions import VarBlockRenderError
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.kernel import Kernel
from semantic_kernel.template_engine.blocks.block_types import BlockTypes
from semantic_kernel.template_engine.blocks.var_block import VarBlock
logger = logging.getLogger(__name__)
def test_init():
var_block = VarBlock(content="$test_var")
assert var_block.content == "$test_var"
assert var_block.name == "test_var"
assert var_block.type == BlockTypes.VARIABLE
def test_it_trims_spaces():
assert VarBlock(content=" $x ").content == "$x"
@mark.parametrize(
"name",
[
"0",
"1",
"a",
"_",
"01",
"01a",
"a01",
"_0",
"a01_e",
"_a01e",
],
)
def test_valid_syntax(name):
target = VarBlock(content=f" ${name} ")
result = target.render(Kernel(), KernelArguments(**{name: "value"}))
assert target.name == name
assert result == "value"
@mark.parametrize(
"content",
["$", "$test-var", "test_var", "$a>b", "$."],
ids=["prefix_only", "invalid_characters", "no_prefix", "invalid_characters2", "invalid_characters3"],
)
def test_syntax_errors(content):
match = content.replace("$", "\\$") if "$" in content else content
with raises(VarBlockSyntaxError, match=rf".*{match}.*"):
VarBlock(content=content)
def test_render():
var_block = VarBlock(content="$test_var")
rendered_value = var_block.render(Kernel(), KernelArguments(test_var="test_value"))
assert rendered_value == "test_value"
def test_render_variable_not_found():
var_block = VarBlock(content="$test_var")
rendered_value = var_block.render(Kernel(), KernelArguments())
assert rendered_value == ""
def test_render_no_args():
target = VarBlock(content="$var")
result = target.render(Kernel())
assert result == ""
class MockNonString(str):
def __str__(self):
raise ValueError("This is not a string")
def test_not_string():
target = VarBlock(content="$var")
with raises(VarBlockRenderError):
target.render(Kernel(), KernelArguments(var=MockNonString("1")))
@@ -0,0 +1,130 @@
# Copyright (c) Microsoft. All rights reserved.
from pytest import mark, raises
from semantic_kernel.exceptions import CodeBlockSyntaxError
from semantic_kernel.template_engine.blocks.block_types import BlockTypes
from semantic_kernel.template_engine.code_tokenizer import CodeTokenizer
def test_it_parses_empty_text():
target = CodeTokenizer()
assert not target.tokenize(None)
assert not target.tokenize("")
assert not target.tokenize(" ")
assert not target.tokenize(" \n ")
@mark.parametrize(
"template, content",
[
("$foo", "$foo"),
("$foo ", "$foo"),
(" $foo", "$foo"),
(" $bar ", "$bar"),
],
)
def test_it_parses_var_blocks(template, content):
target = CodeTokenizer()
blocks = target.tokenize(template)
assert len(blocks) == 1
assert blocks[0].content == content
assert blocks[0].type == BlockTypes.VARIABLE
@mark.parametrize(
"template, content",
[
("'foo'", "'foo'"),
("'foo' ", "'foo'"),
(" 'foo'", "'foo'"),
(' "bar" ', '"bar"'),
],
)
def test_it_parses_val_blocks(template, content):
blocks = CodeTokenizer.tokenize(template)
assert len(blocks) == 1
assert blocks[0].content == content
assert blocks[0].type == BlockTypes.VALUE
@mark.parametrize(
"template, content",
[
("f", "f"),
(" x ", "x"),
("foo", "foo"),
("fo.o ", "fo.o"),
(" f.oo", "f.oo"),
(" bar ", "bar"),
],
)
def test_it_parses_function_id_blocks(template, content):
blocks = CodeTokenizer.tokenize(template)
assert len(blocks) == 1
assert blocks[0].content == content
assert blocks[0].type == BlockTypes.FUNCTION_ID
def test_it_parses_function_calls():
template1 = "x.y $foo"
template2 = "xy $foo"
template3 = "xy '$value'"
blocks1 = CodeTokenizer.tokenize(template1)
blocks2 = CodeTokenizer.tokenize(template2)
blocks3 = CodeTokenizer.tokenize(template3)
assert len(blocks1) == 2
assert len(blocks2) == 2
assert len(blocks3) == 2
assert blocks1[0].content == "x.y"
assert blocks2[0].content == "xy"
assert blocks3[0].content == "xy"
assert blocks1[0].type == BlockTypes.FUNCTION_ID
assert blocks2[0].type == BlockTypes.FUNCTION_ID
assert blocks3[0].type == BlockTypes.FUNCTION_ID
assert blocks1[1].content == "$foo"
assert blocks2[1].content == "$foo"
assert blocks3[1].content == "'$value'"
assert blocks1[1].type == BlockTypes.VARIABLE
assert blocks2[1].type == BlockTypes.VARIABLE
assert blocks3[1].type == BlockTypes.VALUE
def test_it_supports_escaping():
template = "func 'f\\'oo'"
blocks = CodeTokenizer.tokenize(template)
assert len(blocks) == 2
assert blocks[0].content == "func"
assert blocks[1].content == "'f'oo'"
def test_it_throws_when_separators_are_missing():
template1 = "call 'f\\\\'xy'"
template2 = "call 'f\\\\'x"
with raises(CodeBlockSyntaxError):
CodeTokenizer.tokenize(template1)
with raises(CodeBlockSyntaxError):
CodeTokenizer.tokenize(template2)
def test_named_args():
template = 'plugin.function "direct" arg1=$arg1 arg2="arg2"'
blocks = CodeTokenizer.tokenize(template)
assert len(blocks) == 4
assert blocks[0].content == "plugin.function"
assert blocks[1].content == '"direct"'
assert blocks[2].content == "arg1=$arg1"
assert blocks[3].content == 'arg2="arg2"'
@@ -0,0 +1,212 @@
# Copyright (c) Microsoft. All rights reserved.
from pytest import mark, raises
from semantic_kernel.exceptions import TemplateSyntaxError
from semantic_kernel.template_engine.blocks.block_types import BlockTypes
from semantic_kernel.template_engine.template_tokenizer import TemplateTokenizer
@mark.parametrize(
"text, block_type",
[
(None, BlockTypes.TEXT),
("", BlockTypes.TEXT),
(" ", BlockTypes.TEXT),
(" ", BlockTypes.TEXT),
(" {} ", BlockTypes.TEXT),
(" {{} ", BlockTypes.TEXT),
(" {{ } } } ", BlockTypes.TEXT),
(" { { }} }", BlockTypes.TEXT),
("{{}}", BlockTypes.TEXT),
("{{ }}", BlockTypes.TEXT),
("{{ }}", BlockTypes.TEXT),
("{{ '}}x", BlockTypes.TEXT),
('{{ "}}x', BlockTypes.TEXT),
],
)
def test_it_parses_text_without_code(text, block_type):
blocks = TemplateTokenizer.tokenize(text)
assert len(blocks) == 1
assert blocks[0].type == block_type
@mark.parametrize(
"text, block_type",
[
("", BlockTypes.TEXT),
(" ", BlockTypes.TEXT),
(" ", BlockTypes.TEXT),
(" aaa ", BlockTypes.TEXT),
("{{$a}}", BlockTypes.VARIABLE),
("{{ $a}}", BlockTypes.VARIABLE),
("{{ $a }}", BlockTypes.VARIABLE),
("{{ $a }}", BlockTypes.VARIABLE),
("{{code}}", BlockTypes.CODE),
("{{code }}", BlockTypes.CODE),
("{{ code }}", BlockTypes.CODE),
("{{ code }}", BlockTypes.CODE),
("{{ code }}", BlockTypes.CODE),
("{{''}}", BlockTypes.VALUE),
("{{' '}}", BlockTypes.VALUE),
("{{ ' '}}", BlockTypes.VALUE),
("{{ ' ' }}", BlockTypes.VALUE),
("{{ ' ' }}", BlockTypes.VALUE),
("{{ ' ' }}", BlockTypes.VALUE),
],
)
def test_it_parses_basic_blocks(text, block_type):
blocks = TemplateTokenizer.tokenize(text)
assert len(blocks) == 1
assert blocks[0].type == block_type
@mark.parametrize(
"template, block_count",
[
(None, 1),
("", 1),
("}}{{a}} {{b}}x", 5),
("}}{{ -a\n} } {{b}}x", 3),
],
)
def test_it_tokenizes_the_right_token_count(template, block_count):
blocks = TemplateTokenizer.tokenize(template)
assert len(blocks) == block_count
@mark.parametrize(
"template, error",
[
("}}{{{ {$a}}}} {{b}}x}}", TemplateSyntaxError),
("}}{{ -a}} {{b}}x", TemplateSyntaxError),
("}}{{ -a\n}} {{b}}x", TemplateSyntaxError),
("{{ plugin.func $va-r }}", TemplateSyntaxError),
("{{ plugin.func 'val' 'val' }}", TemplateSyntaxError),
("{{ arg=$arg }}", TemplateSyntaxError),
("{{ plugin.func 'var'arg=$arg }}", TemplateSyntaxError),
],
ids=[
"invalid_function_id",
"invalid_function_id_2",
"invalid_function_id_3",
"invalid_var",
"invalid_code_blocks",
"invalid_named_arg",
"invalid_code_block_syntax",
],
)
def test_invalid_syntax(template, error):
with raises(error):
TemplateTokenizer.tokenize(template)
def test_it_tokenizes_edge_cases_correctly_1():
blocks1 = TemplateTokenizer.tokenize("{{{{a}}")
blocks2 = TemplateTokenizer.tokenize("{{'{{a}}")
blocks3 = TemplateTokenizer.tokenize("{{'a}}")
blocks4 = TemplateTokenizer.tokenize("{{a'}}")
assert len(blocks1) == 2
assert len(blocks2) == 1
assert len(blocks3) == 1
assert len(blocks4) == 1
assert blocks1[0].type == BlockTypes.TEXT
assert blocks1[1].type == BlockTypes.CODE
assert blocks1[0].content == "{{"
assert blocks1[1].content == "a"
def test_it_tokenizes_edge_cases_correctly_3():
template = "}}{{{{$a}}}} {{b}}$x}}"
blocks = TemplateTokenizer.tokenize(template)
assert len(blocks) == 5
assert blocks[0].content == "}}{{"
assert blocks[0].type == BlockTypes.TEXT
assert blocks[1].content == "$a"
assert blocks[1].type == BlockTypes.VARIABLE
assert blocks[2].content == "}} "
assert blocks[2].type == BlockTypes.TEXT
assert blocks[3].content == "b"
assert blocks[3].type == BlockTypes.CODE
assert blocks[4].content == "$x}}"
assert blocks[4].type == BlockTypes.TEXT
@mark.parametrize(
"template",
[
("{{ asis 'f\\'oo' }}"),
],
)
def test_it_tokenizes_edge_cases_correctly_4(template):
blocks = TemplateTokenizer.tokenize(template)
assert len(blocks) == 1
assert blocks[0].type == BlockTypes.CODE
assert blocks[0].content == template[2:-2].strip()
def test_it_tokenizes_a_typical_prompt():
template = "this is a {{ $prompt }} with {{$some}} variables and {{function $calls}} {{ and 'values' }}"
blocks = TemplateTokenizer.tokenize(template)
assert len(blocks) == 8
assert blocks[0].content == "this is a "
assert blocks[0].type == BlockTypes.TEXT
assert blocks[1].content == "$prompt"
assert blocks[1].type == BlockTypes.VARIABLE
assert blocks[2].content == " with "
assert blocks[2].type == BlockTypes.TEXT
assert blocks[3].content == "$some"
assert blocks[3].type == BlockTypes.VARIABLE
assert blocks[4].content == " variables and "
assert blocks[4].type == BlockTypes.TEXT
assert blocks[5].content == "function $calls"
assert blocks[5].type == BlockTypes.CODE
assert blocks[6].content == " "
assert blocks[6].type == BlockTypes.TEXT
assert blocks[7].content == "and 'values'"
assert blocks[7].type == BlockTypes.CODE
def test_it_tokenizes_a_named_args_prompt():
template = '{{ plugin.function "direct" arg1=$arg1 arg2="arg2" }}'
blocks = TemplateTokenizer.tokenize(template)
assert len(blocks) == 1
block = blocks[0]
assert block.type == BlockTypes.CODE
assert len(block.tokens) == 4
assert block.tokens[0].type == BlockTypes.FUNCTION_ID
assert block.tokens[1].type == BlockTypes.VALUE
assert block.tokens[2].type == BlockTypes.NAMED_ARG
assert block.tokens[3].type == BlockTypes.NAMED_ARG
assert block.tokens[2].name == "arg1"
assert block.tokens[2].variable.content == "$arg1"
assert block.tokens[3].name == "arg2"
assert block.tokens[3].value.content == '"arg2"'