mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
Python: Introducing vector and text search (#9345)
### 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.
-->
This PR does the following things:
- Introduces TextSearch abstractions, including implementation for Bing
- This consists of the TextSearch class, which implements three public
search methods, and handles the internals, the search methods are:
'search' returns a string, 'get_text_search_results' returns a
TextSearchResult object and 'get_search_results' returns a object native
to the search service (i.e. BingWebPages for Bing)
- This also has a method called "create_{search_method}' which returns a
KernelFunction based on the search method. This function can be adapted
by setting the parameters and has several adaptability options and
allows you to create a RAG pipeline easily with custom names and
descriptions of both the functions and the parameters!
- Introduces VectorSearch abstractions, including implementation for
Azure AI Search
- This consists of a VectorStoreBase class which handles all the
internal and three public interfaces, vectorized_search (supply a
vector), vectorizable_text_search (supply a string that get's vectorized
downstream), vector_text_search (supply a string), each vector store
record collection can pick and choose which ones they need to support by
importing one or more next to the VectorSearchBase class.
- Introduces VectorStoreTextSearch as a way to leverage text search
against vector stores
- Since this builds on TextSearch this is now the best way to create a
super powerfull RAG setup with your own data model!
- Adds all the related classes, samples and tests for the above.
- Also reorders the data folder, which might cause some slight breaking
changes for the few stores that have the new vector store model.
- Adds additional IndexKinds and DistanceFunctions to stay in sync with
dotnet.
- Renames VolatileStore and VolatileCollection to InMemoryVectorStore
and InMemoryVectorCollection.
Closes #6832 #6833
### 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
- [ ] I didn't break anyone 😄
---------
Co-authored-by: Tao Chen <[email protected]>
This commit is contained in:
co-authored by
Tao Chen
parent
7ca11a9429
commit
c8b4094835
@@ -63,18 +63,18 @@ class KernelFunctionExtension(KernelBaseModel, ABC):
|
||||
See KernelPlugin.from_directory for more details on how the directory is parsed.
|
||||
|
||||
Args:
|
||||
plugin (KernelPlugin | Any | dict[str, Any]): The plugin to add.
|
||||
plugin: The plugin to add.
|
||||
This can be a KernelPlugin, in which case it is added straightaway and other parameters are ignored,
|
||||
a custom class that contains methods with the kernel_function decorator
|
||||
or a dictionary of functions with the kernel_function decorator for one or
|
||||
several methods.
|
||||
plugin_name (str | None): The name of the plugin, used if the plugin is not a KernelPlugin,
|
||||
plugin_name: The name of the plugin, used if the plugin is not a KernelPlugin,
|
||||
if the plugin is None and the parent_directory is set,
|
||||
KernelPlugin.from_directory is called with those parameters,
|
||||
see `KernelPlugin.from_directory` for details.
|
||||
parent_directory (str | None): The parent directory path where the plugin directory resides
|
||||
description (str | None): The description of the plugin, used if the plugin is not a KernelPlugin.
|
||||
class_init_arguments (dict[str, dict[str, Any]] | None): The class initialization arguments
|
||||
parent_directory: The parent directory path where the plugin directory resides
|
||||
description: The description of the plugin, used if the plugin is not a KernelPlugin.
|
||||
class_init_arguments: The class initialization arguments
|
||||
|
||||
Returns:
|
||||
KernelPlugin: The plugin that was added.
|
||||
|
||||
@@ -19,7 +19,7 @@ class KernelParameterMetadata(KernelBaseModel):
|
||||
is_required: bool | None = False
|
||||
type_object: Any | None = None
|
||||
schema_data: dict[str, Any] | None = None
|
||||
function_schema_include: bool | None = True
|
||||
include_in_function_choices: bool = True
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
|
||||
@@ -9,7 +9,7 @@ from collections.abc import Generator, ItemsView
|
||||
from functools import singledispatchmethod
|
||||
from glob import glob
|
||||
from types import MethodType
|
||||
from typing import TYPE_CHECKING, Annotated, Any
|
||||
from typing import TYPE_CHECKING, Annotated, Any, TypeVar
|
||||
|
||||
import httpx
|
||||
from pydantic import Field, StringConstraints
|
||||
@@ -21,6 +21,7 @@ from semantic_kernel.connectors.openai_plugin.openai_function_execution_paramete
|
||||
from semantic_kernel.connectors.openai_plugin.openai_utils import OpenAIUtils
|
||||
from semantic_kernel.connectors.openapi_plugin.openapi_manager import create_functions_from_openapi
|
||||
from semantic_kernel.connectors.utils.document_loader import DocumentLoader
|
||||
from semantic_kernel.data.text_search.text_search import TextSearch
|
||||
from semantic_kernel.exceptions import PluginInitializationError
|
||||
from semantic_kernel.exceptions.function_exceptions import FunctionInitializationError
|
||||
from semantic_kernel.functions.kernel_function import KernelFunction
|
||||
@@ -28,6 +29,7 @@ from semantic_kernel.functions.kernel_function_from_method import KernelFunction
|
||||
from semantic_kernel.functions.kernel_function_from_prompt import KernelFunctionFromPrompt
|
||||
from semantic_kernel.functions.types import KERNEL_FUNCTION_TYPE
|
||||
from semantic_kernel.kernel_pydantic import KernelBaseModel
|
||||
from semantic_kernel.kernel_types import OptionalOneOrMany
|
||||
from semantic_kernel.utils.validation import PLUGIN_NAME_REGEX
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -38,6 +40,8 @@ if TYPE_CHECKING:
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_T = TypeVar("_T", bound="KernelPlugin")
|
||||
|
||||
|
||||
class KernelPlugin(KernelBaseModel):
|
||||
"""Represents a Kernel Plugin with functions.
|
||||
@@ -95,22 +99,14 @@ class KernelPlugin(KernelBaseModel):
|
||||
self,
|
||||
name: str,
|
||||
description: str | None = None,
|
||||
functions: (
|
||||
KERNEL_FUNCTION_TYPE
|
||||
| "KernelPlugin"
|
||||
| list[KERNEL_FUNCTION_TYPE | "KernelPlugin"]
|
||||
| dict[str, KERNEL_FUNCTION_TYPE]
|
||||
| None
|
||||
) = None,
|
||||
functions: (OptionalOneOrMany[KERNEL_FUNCTION_TYPE | "KernelPlugin"] | dict[str, KERNEL_FUNCTION_TYPE]) = None,
|
||||
):
|
||||
"""Create a KernelPlugin.
|
||||
|
||||
Args:
|
||||
name: The name of the plugin. The name can be upper/lower
|
||||
case letters and underscores.
|
||||
name: The name of the plugin. The name can be upper/lower case letters and underscores.
|
||||
description: The description of the plugin.
|
||||
functions:
|
||||
The functions in the plugin, will be rewritten to a dictionary of functions.
|
||||
functions: The functions in the plugin, will be rewritten to a dictionary of functions.
|
||||
|
||||
Raises:
|
||||
ValueError: If the functions are not of the correct type.
|
||||
@@ -234,11 +230,11 @@ class KernelPlugin(KernelBaseModel):
|
||||
|
||||
@classmethod
|
||||
def from_object(
|
||||
cls,
|
||||
cls: type[_T],
|
||||
plugin_name: str,
|
||||
plugin_instance: Any | dict[str, Any],
|
||||
description: str | None = None,
|
||||
) -> "KernelPlugin":
|
||||
) -> _T:
|
||||
"""Creates a plugin that wraps the specified target object and imports it into the kernel's plugin collection.
|
||||
|
||||
Args:
|
||||
@@ -264,16 +260,16 @@ class KernelPlugin(KernelBaseModel):
|
||||
for _, candidate in candidates
|
||||
if hasattr(candidate, "__kernel_function__")
|
||||
]
|
||||
return cls(name=plugin_name, description=description, functions=functions) # type: ignore
|
||||
return cls(name=plugin_name, description=description, functions=functions)
|
||||
|
||||
@classmethod
|
||||
def from_directory(
|
||||
cls,
|
||||
cls: type[_T],
|
||||
plugin_name: str,
|
||||
parent_directory: str,
|
||||
description: str | None = None,
|
||||
class_init_arguments: dict[str, dict[str, Any]] | None = None,
|
||||
) -> "KernelPlugin":
|
||||
) -> _T:
|
||||
"""Create a plugin from a specified directory.
|
||||
|
||||
This method does not recurse into subdirectories beyond one level deep from the specified plugin directory.
|
||||
@@ -355,16 +351,16 @@ class KernelPlugin(KernelBaseModel):
|
||||
logger.warning(f"Unknown file found: {object}")
|
||||
if not functions:
|
||||
raise PluginInitializationError(f"No functions found in folder: {parent_directory}/{plugin_name}")
|
||||
return cls(name=plugin_name, description=description, functions=functions) # type: ignore
|
||||
return cls(name=plugin_name, description=description, functions=functions)
|
||||
|
||||
@classmethod
|
||||
def from_openapi(
|
||||
cls,
|
||||
cls: type[_T],
|
||||
plugin_name: str,
|
||||
openapi_document_path: str,
|
||||
execution_settings: "OpenAPIFunctionExecutionParameters | None" = None,
|
||||
description: str | None = None,
|
||||
) -> "KernelPlugin":
|
||||
) -> _T:
|
||||
"""Create a plugin from an OpenAPI document.
|
||||
|
||||
Args:
|
||||
@@ -394,13 +390,13 @@ class KernelPlugin(KernelBaseModel):
|
||||
|
||||
@classmethod
|
||||
async def from_openai(
|
||||
cls,
|
||||
cls: type[_T],
|
||||
plugin_name: str,
|
||||
plugin_url: str | None = None,
|
||||
plugin_str: str | None = None,
|
||||
execution_parameters: "OpenAIFunctionExecutionParameters | None" = None,
|
||||
description: str | None = None,
|
||||
) -> "KernelPlugin":
|
||||
) -> _T:
|
||||
"""Create a plugin from the Open AI manifest.
|
||||
|
||||
Args:
|
||||
@@ -459,12 +455,12 @@ class KernelPlugin(KernelBaseModel):
|
||||
|
||||
@classmethod
|
||||
def from_python_file(
|
||||
cls,
|
||||
cls: type[_T],
|
||||
plugin_name: str,
|
||||
py_file: str,
|
||||
description: str | None = None,
|
||||
class_init_arguments: dict[str, dict[str, Any]] | None = None,
|
||||
) -> "KernelPlugin":
|
||||
) -> _T:
|
||||
"""Create a plugin from a Python file."""
|
||||
module_name = os.path.basename(py_file).replace(".py", "")
|
||||
spec = importlib.util.spec_from_file_location(module_name, py_file)
|
||||
@@ -482,18 +478,83 @@ class KernelPlugin(KernelBaseModel):
|
||||
return cls.from_object(plugin_name=plugin_name, description=description, plugin_instance=instance)
|
||||
raise PluginInitializationError(f"No class found in file: {py_file}")
|
||||
|
||||
@classmethod
|
||||
def from_text_search_with_search(
|
||||
cls: type[_T],
|
||||
text_search: TextSearch,
|
||||
plugin_name: str,
|
||||
plugin_description: str | None = None,
|
||||
**kwargs: Any,
|
||||
) -> _T:
|
||||
"""Creates a plugin that wraps the text search "search" function.
|
||||
|
||||
Args:
|
||||
text_search: The text search to use.
|
||||
plugin_name: The name of the plugin.
|
||||
plugin_description: The description of the search plugin.
|
||||
**kwargs: The keyword arguments to use to create the search function.
|
||||
|
||||
Returns:
|
||||
a KernelPlugin.
|
||||
"""
|
||||
return cls(name=plugin_name, description=plugin_description, functions=[text_search.create_search(**kwargs)])
|
||||
|
||||
@classmethod
|
||||
def from_text_search_with_get_text_search_results(
|
||||
cls: type[_T],
|
||||
text_search: TextSearch,
|
||||
plugin_name: str,
|
||||
plugin_description: str | None = None,
|
||||
**kwargs: Any,
|
||||
) -> _T:
|
||||
"""Creates a plugin that wraps the text search "get_text_search_results" function.
|
||||
|
||||
Args:
|
||||
text_search: The text search to use.
|
||||
plugin_name: The name of the plugin.
|
||||
plugin_description: The description of the search plugin.
|
||||
**kwargs: The keyword arguments to use to create the search function.
|
||||
|
||||
Returns:
|
||||
a KernelPlugin.
|
||||
"""
|
||||
return cls(
|
||||
name=plugin_name,
|
||||
description=plugin_description,
|
||||
functions=[text_search.create_get_text_search_results(**kwargs)],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_text_search_with_get_search_results(
|
||||
cls: type[_T],
|
||||
text_search: TextSearch,
|
||||
plugin_name: str,
|
||||
plugin_description: str | None = None,
|
||||
**kwargs: Any,
|
||||
) -> _T:
|
||||
"""Creates a plugin that wraps the text search "get_search_results" function.
|
||||
|
||||
Args:
|
||||
text_search: The text search to use.
|
||||
plugin_name: The name of the plugin.
|
||||
plugin_description: The description of the search plugin.
|
||||
**kwargs: The keyword arguments to use to create the search function.
|
||||
|
||||
Returns:
|
||||
a KernelPlugin.
|
||||
"""
|
||||
return cls(
|
||||
name=plugin_name,
|
||||
description=plugin_description,
|
||||
functions=[text_search.create_get_search_results(**kwargs)],
|
||||
)
|
||||
|
||||
# endregion
|
||||
# region Internal Static Methods
|
||||
|
||||
@staticmethod
|
||||
def _validate_functions(
|
||||
functions: (
|
||||
KERNEL_FUNCTION_TYPE
|
||||
| list[KERNEL_FUNCTION_TYPE | "KernelPlugin"]
|
||||
| dict[str, KERNEL_FUNCTION_TYPE]
|
||||
| "KernelPlugin"
|
||||
| None
|
||||
),
|
||||
functions: OptionalOneOrMany[KERNEL_FUNCTION_TYPE | "KernelPlugin"] | dict[str, KERNEL_FUNCTION_TYPE],
|
||||
plugin_name: str,
|
||||
) -> dict[str, "KernelFunction"]:
|
||||
"""Validates the functions and returns a dictionary of functions."""
|
||||
@@ -521,12 +582,10 @@ class KernelPlugin(KernelBaseModel):
|
||||
function = KernelPlugin._parse_or_copy(function=function, plugin_name=plugin_name)
|
||||
functions_dict[function.name] = function
|
||||
elif isinstance(function, KernelPlugin): # type: ignore
|
||||
functions_dict.update(
|
||||
{
|
||||
name: KernelPlugin._parse_or_copy(function=function, plugin_name=plugin_name)
|
||||
for name, function in function.functions.items()
|
||||
}
|
||||
)
|
||||
functions_dict.update({
|
||||
name: KernelPlugin._parse_or_copy(function=function, plugin_name=plugin_name)
|
||||
for name, function in function.functions.items()
|
||||
})
|
||||
else:
|
||||
raise ValueError(f"Invalid type for functions in list: {function} (type: {type(function)})")
|
||||
return functions_dict
|
||||
|
||||
Reference in New Issue
Block a user