chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from pydantic import BaseModel
|
||||
|
||||
from semantic_kernel import Kernel
|
||||
from semantic_kernel.data.text_search import (
|
||||
DEFAULT_DESCRIPTION,
|
||||
DEFAULT_FUNCTION_NAME,
|
||||
KernelSearchResults,
|
||||
SearchOptions,
|
||||
TextSearch,
|
||||
TextSearchResult,
|
||||
create_options,
|
||||
)
|
||||
from semantic_kernel.data.vector import VectorSearchOptions
|
||||
from semantic_kernel.exceptions import TextSearchException
|
||||
from semantic_kernel.functions import KernelArguments, KernelParameterMetadata
|
||||
from semantic_kernel.utils.list_handler import desync_list
|
||||
|
||||
|
||||
def test_text_search():
|
||||
search_base_class = TextSearch()
|
||||
assert search_base_class is not None
|
||||
assert search_base_class.options_class == SearchOptions
|
||||
|
||||
|
||||
class TestSearch(TextSearch):
|
||||
async def search(self, **kwargs) -> KernelSearchResults[Any]:
|
||||
"""Test search function."""
|
||||
output_type = kwargs.pop("output_type", str)
|
||||
|
||||
async def generator() -> AsyncGenerator[str | TextSearchResult | Any, None]:
|
||||
yield "test" if output_type is not TextSearchResult else TextSearchResult(value="test")
|
||||
|
||||
return KernelSearchResults(results=generator(), metadata=kwargs)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("output_type", [str, TextSearchResult, "Any"])
|
||||
async def test_create_kernel_function(output_type: str, kernel: Kernel):
|
||||
test_search = TestSearch()
|
||||
kernel_function = test_search._create_kernel_function(output_type=output_type)
|
||||
assert kernel_function is not None
|
||||
assert kernel_function.name == DEFAULT_FUNCTION_NAME
|
||||
assert kernel_function.description == DEFAULT_DESCRIPTION
|
||||
assert len(kernel_function.parameters) == 3
|
||||
assert kernel_function.return_parameter == KernelParameterMetadata(
|
||||
name="results",
|
||||
description="The search results.",
|
||||
type="list[str]",
|
||||
type_object=list,
|
||||
is_required=True,
|
||||
)
|
||||
results = await kernel_function.invoke(kernel, KernelArguments(query="query"))
|
||||
assert results is not None
|
||||
assert results.value == (
|
||||
['{"name":null,"value":"test","link":null}'] if output_type is TextSearchResult else ["test"]
|
||||
)
|
||||
|
||||
|
||||
def test_create_kernel_function_fail():
|
||||
test_search = TestSearch()
|
||||
with pytest.raises(TextSearchException):
|
||||
test_search.create_search_function(
|
||||
function_name="search",
|
||||
description="description",
|
||||
output_type="random",
|
||||
parameters=None,
|
||||
return_parameter=None,
|
||||
string_mapper=None,
|
||||
)
|
||||
|
||||
|
||||
async def test_create_kernel_function_inner(kernel: Kernel):
|
||||
test_search = TestSearch()
|
||||
|
||||
kernel_function = test_search._create_kernel_function(
|
||||
output_type=str,
|
||||
options=None,
|
||||
parameters=[],
|
||||
return_parameter=None,
|
||||
function_name="search",
|
||||
description="description",
|
||||
string_mapper=None,
|
||||
)
|
||||
results = await kernel_function.invoke(kernel, None)
|
||||
assert results is not None
|
||||
assert results.value == ["test"]
|
||||
|
||||
|
||||
async def test_create_kernel_function_inner_with_options(kernel: Kernel):
|
||||
test_search = TestSearch()
|
||||
|
||||
kernel_function = test_search._create_kernel_function(
|
||||
output_type=str,
|
||||
options=SearchOptions(),
|
||||
parameters=[
|
||||
KernelParameterMetadata(
|
||||
name="city",
|
||||
description="The city that you want to search for a hotel in.",
|
||||
type="str",
|
||||
is_required=False,
|
||||
type_object=str,
|
||||
)
|
||||
],
|
||||
return_parameter=None,
|
||||
function_name="search",
|
||||
description="description",
|
||||
string_mapper=None,
|
||||
)
|
||||
results = await kernel_function.invoke(kernel, KernelArguments(city="city"))
|
||||
assert results is not None
|
||||
assert results.value == ["test"]
|
||||
|
||||
|
||||
async def test_create_kernel_function_inner_with_other_options_type(kernel: Kernel):
|
||||
test_search = TestSearch()
|
||||
|
||||
kernel_function = test_search._create_kernel_function(
|
||||
output_type=str,
|
||||
options=VectorSearchOptions(),
|
||||
parameters=[
|
||||
KernelParameterMetadata(
|
||||
name="test_field",
|
||||
description="The test field.",
|
||||
type="str",
|
||||
is_required=False,
|
||||
type_object=str,
|
||||
)
|
||||
],
|
||||
return_parameter=None,
|
||||
function_name="search",
|
||||
description="description",
|
||||
string_mapper=None,
|
||||
)
|
||||
results = await kernel_function.invoke(kernel, KernelArguments(test_field="city"))
|
||||
assert results is not None
|
||||
assert results.value == ["test"]
|
||||
|
||||
|
||||
async def test_create_kernel_function_inner_no_results(kernel: Kernel):
|
||||
test_search = TestSearch()
|
||||
|
||||
kernel_function = test_search._create_kernel_function(
|
||||
output_type=str,
|
||||
options=None,
|
||||
parameters=[],
|
||||
return_parameter=None,
|
||||
function_name="search",
|
||||
description="description",
|
||||
string_mapper=None,
|
||||
)
|
||||
with (
|
||||
patch.object(test_search, "search") as mock_search,
|
||||
pytest.raises(TextSearchException),
|
||||
):
|
||||
mock_search.side_effect = Exception("fail")
|
||||
await kernel_function.invoke(kernel, None)
|
||||
|
||||
|
||||
async def test_default_map_to_string():
|
||||
test_search = TestSearch()
|
||||
assert (await test_search._map_results(results=KernelSearchResults(results=desync_list(["test"])))) == ["test"]
|
||||
|
||||
class TestClass(BaseModel):
|
||||
test: str
|
||||
|
||||
assert (
|
||||
await test_search._map_results(results=KernelSearchResults(results=desync_list([TestClass(test="test")])))
|
||||
) == ['{"test":"test"}']
|
||||
|
||||
|
||||
async def test_custom_map_to_string():
|
||||
test_search = TestSearch()
|
||||
|
||||
class TestClass(BaseModel):
|
||||
test: str
|
||||
|
||||
assert (
|
||||
await test_search._map_results(
|
||||
results=KernelSearchResults(results=desync_list([TestClass(test="test")])), string_mapper=lambda x: x.test
|
||||
)
|
||||
) == ["test"]
|
||||
|
||||
|
||||
def test_create_options():
|
||||
options = SearchOptions()
|
||||
options_class = VectorSearchOptions
|
||||
new_options = create_options(options_class, options, top=1)
|
||||
assert new_options is not None
|
||||
assert isinstance(new_options, options_class)
|
||||
assert new_options.top == 1
|
||||
|
||||
|
||||
def test_create_options_none():
|
||||
options = None
|
||||
options_class = VectorSearchOptions
|
||||
new_options = create_options(options_class, options, top=1)
|
||||
assert new_options is not None
|
||||
assert isinstance(new_options, options_class)
|
||||
assert new_options.top == 1
|
||||
|
||||
|
||||
def test_create_options_from_dict():
|
||||
options = {"skip": 1}
|
||||
options_class = SearchOptions
|
||||
new_options = create_options(options_class, options, top=1) # type: ignore
|
||||
assert new_options is not None
|
||||
assert isinstance(new_options, options_class)
|
||||
assert new_options.top == 1
|
||||
# if a non SearchOptions object is passed in, it should be ignored
|
||||
assert new_options.skip == 0
|
||||
|
||||
|
||||
def test_public_create_functions_search():
|
||||
test_search = TestSearch()
|
||||
function = test_search.create_search_function()
|
||||
assert function is not None
|
||||
assert function.name == "search"
|
||||
assert (
|
||||
function.description == "Perform a search for content related to the specified query and return string results"
|
||||
)
|
||||
assert len(function.parameters) == 3
|
||||
Reference in New Issue
Block a user