mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
### Motivation and Context <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> Implements dealing with unsafe content, by doing HTML parsing on variables and function results. Closes: #5889 ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> Adds parameter `allow_dangerously_set_content` to: - InputVariable - PromptTemplateConfig - PromptTemplateBase The behavior is that if the flag is set to True on the template itself (KernelPromptTemplate, Jinja2PromptTemplate or HandlebarsPromptTemplate) the behavior is the same, no encoding is done on inputs. Otherwise: - variables are encoded by default, this can be switched off using the InputVariables class for that variable. - function output is encoded by default, this can be switched off using the flag in the PromptTemplateConfig, this is not yet possible to do on a per function basis. ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [x] The code builds clean without any errors or warnings - [x] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone 😄
348 lines
13 KiB
Python
348 lines
13 KiB
Python
import os
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion import OpenAIChatCompletion
|
|
from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion import OpenAITextCompletion
|
|
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
|
|
from semantic_kernel.const import METADATA_EXCEPTION_KEY
|
|
from semantic_kernel.contents.chat_message_content import ChatMessageContent
|
|
from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent
|
|
from semantic_kernel.contents.text_content import TextContent
|
|
from semantic_kernel.exceptions import FunctionInitializationError
|
|
from semantic_kernel.functions.kernel_function_from_prompt import KernelFunctionFromPrompt
|
|
from semantic_kernel.kernel import Kernel
|
|
from semantic_kernel.prompt_template.input_variable import InputVariable
|
|
from semantic_kernel.prompt_template.kernel_prompt_template import KernelPromptTemplate
|
|
from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig
|
|
|
|
|
|
def test_init_minimal_prompt():
|
|
function = KernelFunctionFromPrompt(
|
|
function_name="test",
|
|
plugin_name="test",
|
|
prompt="test",
|
|
)
|
|
|
|
assert function.name == "test"
|
|
assert function.plugin_name == "test"
|
|
assert function.description is None
|
|
assert function.prompt_template.prompt_template_config.template == "test"
|
|
|
|
|
|
def test_init_minimal_prompt_template():
|
|
function = KernelFunctionFromPrompt(
|
|
function_name="test",
|
|
plugin_name="test",
|
|
prompt_template=KernelPromptTemplate(prompt_template_config=PromptTemplateConfig(template="test")),
|
|
)
|
|
|
|
assert function.name == "test"
|
|
assert function.plugin_name == "test"
|
|
assert function.description is None
|
|
assert function.prompt_template.prompt_template_config.template == "test"
|
|
|
|
|
|
def test_init_minimal_prompt_template_config():
|
|
function = KernelFunctionFromPrompt(
|
|
function_name="test", plugin_name="test", prompt_template_config=PromptTemplateConfig(template="test")
|
|
)
|
|
|
|
assert function.name == "test"
|
|
assert function.plugin_name == "test"
|
|
assert function.description is None
|
|
assert function.prompt_template.prompt_template_config.template == "test"
|
|
|
|
|
|
def test_init_no_prompt():
|
|
with pytest.raises(FunctionInitializationError):
|
|
KernelFunctionFromPrompt(
|
|
function_name="test",
|
|
plugin_name="test",
|
|
)
|
|
|
|
|
|
def test_init_invalid_name():
|
|
with pytest.raises(FunctionInitializationError):
|
|
KernelFunctionFromPrompt(function_name="test func", plugin_name="test", prompt="test")
|
|
|
|
|
|
def test_init_prompt_execution_settings_none():
|
|
function = KernelFunctionFromPrompt(
|
|
function_name="test",
|
|
plugin_name="test",
|
|
prompt="test",
|
|
prompt_execution_settings=None,
|
|
)
|
|
|
|
assert function.name == "test"
|
|
assert function.plugin_name == "test"
|
|
assert function.description is None
|
|
assert function.prompt_template.prompt_template_config.template == "test"
|
|
|
|
|
|
def test_init_prompt_execution_settings_none_with_prompt_template():
|
|
function = KernelFunctionFromPrompt(
|
|
function_name="test",
|
|
plugin_name="test",
|
|
prompt_template=KernelPromptTemplate(
|
|
prompt_template_config=PromptTemplateConfig(template="test", execution_settings={})
|
|
),
|
|
prompt_execution_settings=None,
|
|
)
|
|
|
|
assert function.name == "test"
|
|
assert function.plugin_name == "test"
|
|
assert function.description is None
|
|
assert function.prompt_template.prompt_template_config.template == "test"
|
|
|
|
|
|
def test_init_prompt_execution_settings():
|
|
function = KernelFunctionFromPrompt(
|
|
function_name="test",
|
|
plugin_name="test",
|
|
prompt="test",
|
|
prompt_execution_settings=PromptExecutionSettings(service_id="test"),
|
|
)
|
|
|
|
assert function.name == "test"
|
|
assert function.plugin_name == "test"
|
|
assert function.description is None
|
|
assert function.prompt_template.prompt_template_config.template == "test"
|
|
|
|
|
|
def test_init_prompt_execution_settings_list():
|
|
function = KernelFunctionFromPrompt(
|
|
function_name="test",
|
|
plugin_name="test",
|
|
prompt="test",
|
|
prompt_execution_settings=[PromptExecutionSettings(service_id="test")],
|
|
)
|
|
|
|
assert function.name == "test"
|
|
assert function.plugin_name == "test"
|
|
assert function.description is None
|
|
assert function.prompt_template.prompt_template_config.template == "test"
|
|
|
|
|
|
def test_init_prompt_execution_settings_dict():
|
|
function = KernelFunctionFromPrompt(
|
|
function_name="test",
|
|
plugin_name="test",
|
|
prompt="test",
|
|
prompt_execution_settings={"test": PromptExecutionSettings(service_id="test")},
|
|
)
|
|
|
|
assert function.name == "test"
|
|
assert function.plugin_name == "test"
|
|
assert function.description is None
|
|
assert function.prompt_template.prompt_template_config.template == "test"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_invoke_chat_stream(openai_unit_test_env):
|
|
kernel = Kernel()
|
|
kernel.add_service(OpenAIChatCompletion(service_id="test", ai_model_id="test"))
|
|
function = KernelFunctionFromPrompt(
|
|
function_name="test",
|
|
plugin_name="test",
|
|
prompt="test",
|
|
prompt_execution_settings=PromptExecutionSettings(service_id="test"),
|
|
)
|
|
|
|
# This part remains unchanged - for synchronous mocking example
|
|
with patch(
|
|
"semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion.OpenAIChatCompletion.complete_chat"
|
|
) as mock:
|
|
mock.return_value = [ChatMessageContent(role="assistant", content="test", metadata={})]
|
|
result = await function.invoke(kernel=kernel)
|
|
assert str(result) == "test"
|
|
|
|
with patch(
|
|
"semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion.OpenAIChatCompletion.complete_chat_stream"
|
|
) as mock:
|
|
mock.__iter__.return_value = [
|
|
StreamingChatMessageContent(choice_index=0, role="assistant", content="test", metadata={})
|
|
]
|
|
async for result in function.invoke_stream(kernel=kernel):
|
|
assert str(result) == "test"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_invoke_exception(openai_unit_test_env):
|
|
kernel = Kernel()
|
|
kernel.add_service(OpenAIChatCompletion(service_id="test", ai_model_id="test"))
|
|
function = KernelFunctionFromPrompt(
|
|
function_name="test",
|
|
plugin_name="test",
|
|
prompt="test",
|
|
prompt_execution_settings=PromptExecutionSettings(service_id="test"),
|
|
)
|
|
with patch(
|
|
"semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion.OpenAIChatCompletion.complete_chat",
|
|
side_effect=Exception,
|
|
) as mock:
|
|
mock.return_value = [ChatMessageContent(role="assistant", content="test", metadata={})]
|
|
result = await function.invoke(kernel=kernel)
|
|
assert isinstance(result.metadata[METADATA_EXCEPTION_KEY], Exception)
|
|
|
|
with patch(
|
|
"semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion.OpenAIChatCompletion.complete_chat_stream",
|
|
side_effect=Exception,
|
|
) as mock:
|
|
mock.__iter__.return_value = [
|
|
StreamingChatMessageContent(choice_index=0, role="assistant", content="test", metadata={})
|
|
]
|
|
async for result in function.invoke_stream(kernel=kernel):
|
|
assert isinstance(result.metadata[METADATA_EXCEPTION_KEY], Exception)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_invoke_text(openai_unit_test_env):
|
|
kernel = Kernel()
|
|
kernel.add_service(OpenAITextCompletion(service_id="test", ai_model_id="test"))
|
|
function = KernelFunctionFromPrompt(
|
|
function_name="test",
|
|
plugin_name="test",
|
|
prompt="test",
|
|
prompt_execution_settings=PromptExecutionSettings(service_id="test"),
|
|
)
|
|
with patch(
|
|
"semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion.OpenAITextCompletion.complete",
|
|
) as mock:
|
|
mock.return_value = [TextContent(text="test", metadata={})]
|
|
result = await function.invoke(kernel=kernel)
|
|
assert str(result) == "test"
|
|
|
|
with patch(
|
|
"semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion.OpenAITextCompletion.complete_stream",
|
|
) as mock:
|
|
mock.__iter__.return_value = [TextContent(text="test", metadata={})]
|
|
async for result in function.invoke_stream(kernel=kernel):
|
|
assert str(result) == "test"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_invoke_exception_text(openai_unit_test_env):
|
|
kernel = Kernel()
|
|
kernel.add_service(OpenAITextCompletion(service_id="test", ai_model_id="test"))
|
|
function = KernelFunctionFromPrompt(
|
|
function_name="test",
|
|
plugin_name="test",
|
|
prompt="test",
|
|
prompt_execution_settings=PromptExecutionSettings(service_id="test"),
|
|
)
|
|
with patch(
|
|
"semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion.OpenAITextCompletion.complete",
|
|
side_effect=Exception,
|
|
) as mock:
|
|
mock.return_value = [TextContent(text="test", metadata={})]
|
|
result = await function.invoke(kernel=kernel)
|
|
assert isinstance(result.metadata[METADATA_EXCEPTION_KEY], Exception)
|
|
|
|
with patch(
|
|
"semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion.OpenAITextCompletion.complete_stream",
|
|
side_effect=Exception,
|
|
) as mock:
|
|
mock.__iter__.return_value = []
|
|
async for result in function.invoke_stream(kernel=kernel):
|
|
assert isinstance(result.metadata[METADATA_EXCEPTION_KEY], Exception)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_invoke_defaults(openai_unit_test_env):
|
|
kernel = Kernel()
|
|
kernel.add_service(OpenAIChatCompletion(service_id="test", ai_model_id="test"))
|
|
function = KernelFunctionFromPrompt(
|
|
function_name="test",
|
|
plugin_name="test",
|
|
prompt_template_config=PromptTemplateConfig(
|
|
template="{{$input}}",
|
|
input_variables=[InputVariable(name="input", type="str", default="test", is_required=False)],
|
|
),
|
|
prompt_execution_settings=PromptExecutionSettings(service_id="test"),
|
|
)
|
|
with patch(
|
|
"semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion.OpenAIChatCompletion.complete_chat"
|
|
) as mock:
|
|
mock.return_value = [ChatMessageContent(role="assistant", content="test", metadata={})]
|
|
result = await function.invoke(kernel=kernel)
|
|
assert str(result) == "test"
|
|
|
|
|
|
def test_create_with_multiple_settings():
|
|
function = KernelFunctionFromPrompt(
|
|
function_name="test",
|
|
plugin_name="test",
|
|
prompt_template_config=PromptTemplateConfig(
|
|
template="test",
|
|
execution_settings=[
|
|
PromptExecutionSettings(service_id="test", temperature=0.0),
|
|
PromptExecutionSettings(service_id="test2", temperature=1.0),
|
|
],
|
|
),
|
|
)
|
|
assert (
|
|
function.prompt_template.prompt_template_config.execution_settings["test"].extension_data["temperature"] == 0.0
|
|
)
|
|
assert (
|
|
function.prompt_template.prompt_template_config.execution_settings["test2"].extension_data["temperature"] == 1.0
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_with_multiple_settings_one_service_registered(openai_unit_test_env):
|
|
kernel = Kernel()
|
|
kernel.add_service(OpenAIChatCompletion(service_id="test2", ai_model_id="test"))
|
|
function = KernelFunctionFromPrompt(
|
|
function_name="test",
|
|
plugin_name="test",
|
|
prompt_template_config=PromptTemplateConfig(
|
|
template="test",
|
|
execution_settings=[
|
|
PromptExecutionSettings(service_id="test", temperature=0.0),
|
|
PromptExecutionSettings(service_id="test2", temperature=1.0),
|
|
],
|
|
),
|
|
)
|
|
with patch(
|
|
"semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion.OpenAIChatCompletion.complete_chat"
|
|
) as mock:
|
|
mock.return_value = [ChatMessageContent(role="assistant", content="test", metadata={})]
|
|
result = await function.invoke(kernel=kernel)
|
|
assert str(result) == "test"
|
|
|
|
|
|
def test_from_yaml_fail():
|
|
with pytest.raises(FunctionInitializationError):
|
|
KernelFunctionFromPrompt.from_yaml("template_format: something_else")
|
|
|
|
|
|
def test_from_directory_prompt_only():
|
|
with pytest.raises(FunctionInitializationError):
|
|
KernelFunctionFromPrompt.from_directory(
|
|
path=os.path.join(
|
|
os.path.dirname(__file__),
|
|
"../../assets",
|
|
"test_plugins",
|
|
"TestPlugin",
|
|
"TestFunctionPromptOnly",
|
|
),
|
|
plugin_name="test",
|
|
)
|
|
|
|
|
|
def test_from_directory_config_only():
|
|
with pytest.raises(FunctionInitializationError):
|
|
KernelFunctionFromPrompt.from_directory(
|
|
path=os.path.join(
|
|
os.path.dirname(__file__),
|
|
"../../assets",
|
|
"test_plugins",
|
|
"TestPlugin",
|
|
"TestFunctionConfigOnly",
|
|
),
|
|
plugin_name="test",
|
|
)
|