Files
semantic-kernel/python/tests/unit/functions/test_kernel_function_from_prompt.py
T
Eduard van Valkenburg 08bcc803ef Python: Unsafe input handling (#6003)
### 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 😄
2024-05-16 13:02:54 +00:00

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",
)