From 59a65ad3ac7ccb9e96d0f8fafc25768729855813 Mon Sep 17 00:00:00 2001 From: Evan Mattson <35585003+moonbox3@users.noreply.github.com> Date: Wed, 11 Jun 2025 08:58:56 +0900 Subject: [PATCH 1/4] Update planner related docs now that they're removed from code. (#597) * Update planner related docs now that they're removed from code. * Fix zone pivots * Add date to planner migration guide * Provide link to lightsplugin lightmodel. * Add comment about kernel isolation for processes. --- .../process/process-best-practices.md | 8 ++ semantic-kernel/concepts/planning.md | 40 +++--- .../concepts/plugins/adding-native-plugins.md | 23 ++-- .../stepwise-planner-migration-guide.md | 130 +++++++++++++++++- 4 files changed, 169 insertions(+), 32 deletions(-) diff --git a/semantic-kernel/Frameworks/process/process-best-practices.md b/semantic-kernel/Frameworks/process/process-best-practices.md index c9325713..73ad3e57 100644 --- a/semantic-kernel/Frameworks/process/process-best-practices.md +++ b/semantic-kernel/Frameworks/process/process-best-practices.md @@ -22,6 +22,14 @@ Organizing your project files in a logical and maintainable structure is crucial An organized structure not only simplifies navigation within the project but also enhances code reusability and facilitates collaboration among team members. +### Kernel Instance Isolation + +> [!Important] +> Do not share a single Kernel instance between the main Process Framework and any of its dependencies (such as agents, tools, or external services). + +Sharing a Kernel across these components can result in unexpected recursive invocation patterns, including infinite loops, as functions registered in the Kernel may inadvertently invoke each other. For example, a Step may call a function that triggers an agent, which then re-invokes the same function, creating a non-terminating loop. + +To avoid this, instantiate separate Kernel objects for each independent agent, tool, or service used within your process. This ensures isolation between the Process Framework’s own functions and those required by dependencies, and prevents cross-invocation that could destabilize your workflow. This requirement reflects a current architectural constraint and may be revisited as the framework evolves. ### Common Pitfalls To ensure smooth implementation and operation of the Process Framework, be mindful of these common pitfalls to avoid: diff --git a/semantic-kernel/concepts/planning.md b/semantic-kernel/concepts/planning.md index fa510f94..bb6d9e93 100644 --- a/semantic-kernel/concepts/planning.md +++ b/semantic-kernel/concepts/planning.md @@ -74,6 +74,9 @@ To use automatic function calling in Semantic Kernel, you need to do the followi 2. Create an execution settings object that tells the AI to automatically call functions 3. Invoke the chat completion service with the chat history and the kernel +> [!TIP] +> The following code sample uses the `LightsPlugin` defined [here](./plugins/adding-native-plugins.md#defining-a-plugin-using-a-class). + ::: zone pivot="programming-language-csharp" ```csharp @@ -127,31 +130,25 @@ do { import asyncio from semantic_kernel import Kernel -from semantic_kernel.functions import kernel_function -from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion -from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior +from semantic_kernel.connectors.ai import FunctionChoiceBehavior from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase -from semantic_kernel.contents.chat_history import ChatHistory -from semantic_kernel.functions.kernel_arguments import KernelArguments - -from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.azure_chat_prompt_execution_settings import ( +from semantic_kernel.connectors.ai.open_ai import ( + AzureChatCompletion, AzureChatPromptExecutionSettings, ) +from semantic_kernel.contents import ChatHistory +from semantic_kernel.functions import kernel_function async def main(): # 1. Create the kernel with the Lights plugin kernel = Kernel() - kernel.add_service(AzureChatCompletion( - deployment_name="your_models_deployment_name", - api_key="your_api_key", - base_url="your_base_url", - )) + kernel.add_service(AzureChatCompletion()) kernel.add_plugin( LightsPlugin(), plugin_name="Lights", ) - chat_completion : AzureChatCompletion = kernel.get_service(type=ChatCompletionClientBase) + chat_completion: AzureChatCompletion = kernel.get_service(type=ChatCompletionClientBase) # 2. Enable automatic function calling execution_settings = AzureChatPromptExecutionSettings() @@ -173,12 +170,11 @@ async def main(): history.add_user_message(userInput) # 3. Get the response from the AI with automatic function calling - result = (await chat_completion.get_chat_message_contents( + result = await chat_completion.get_chat_message_content( chat_history=history, settings=execution_settings, kernel=kernel, - arguments=KernelArguments(), - ))[0] + ) # Print the results print("Assistant > " + str(result)) @@ -263,14 +259,16 @@ if __name__ == "__main__": When you use automatic function calling, all of the steps in the automatic planning loop are handled for you and added to the `ChatHistory` object. After the function calling loop is complete, you can inspect the `ChatHistory` object to see all of the function calls made and results provided by Semantic Kernel. -## What about the Function Calling Stepwise and Handlebars planners? +## What happened to the Stepwise and Handlebars planners? + +The Stepwise and Handlebars planners have been deprecated and removed from the Semantic Kernel package. These planners are no longer supported in either Python, .NET, or Java. -The Stepwise and Handlebars planners are still available in Semantic Kernel. However, we recommend using function calling for most tasks as it is more powerful and easier to use. Both the Stepwise and Handlebars planners will be deprecated in a future release of Semantic Kernel. +We recommend using **function calling**, which is both more powerful and easier to use for most scenarios. -Learn how to [migrate Stepwise Planner to Auto Function Calling](../support/migration/stepwise-planner-migration-guide.md). +To update existing solutions, follow our [Stepwise Planner Migration Guide](../support/migration/stepwise-planner-migration-guide.md). -> [!CAUTION] -> If you are building a new AI agent, we recommend that you _not_ use the Stepwise or Handlebars planners. Instead, use function calling as it is more powerful and easier to use. +> [!TIP] +> For new AI agents, use function calling instead of the deprecated planners. It offers better flexibility, built-in tool support, and a simpler development experience. ## Next steps Now that you understand how planners work in Semantic Kernel, you can learn more about how influence your AI agent so that they best plan and execute tasks on behalf of your users. diff --git a/semantic-kernel/concepts/plugins/adding-native-plugins.md b/semantic-kernel/concepts/plugins/adding-native-plugins.md index f2f8be4e..ee4cf5f1 100644 --- a/semantic-kernel/concepts/plugins/adding-native-plugins.md +++ b/semantic-kernel/concepts/plugins/adding-native-plugins.md @@ -34,6 +34,9 @@ Below, we'll walk through the two different ways of providing your AI agent with The easiest way to create a native plugin is to start with a class and then add methods annotated with the `KernelFunction` attribute. It is also recommended to liberally use the `Description` annotation to provide the AI agent with the necessary information to understand the function. +> [!TIP] +> The following `LightsPlugin` uses the `LightModel` defined [here](./index.md#1-define-your-plugin). + ::: zone pivot="programming-language-csharp" ```csharp @@ -81,14 +84,16 @@ public class LightsPlugin ::: zone pivot="programming-language-python" ```python -from typing import List, Optional, Annotated +from typing import Annotated + +from semantic_kernel.functions import kernel_function class LightsPlugin: - def __init__(self, lights: List[LightModel]): + def __init__(self, lights: list[LightModel]): self._lights = lights @kernel_function - async def get_lights(self) -> List[LightModel]: + async def get_lights(self) -> list[LightModel]: """Gets a list of lights and their current state.""" return self._lights @@ -96,7 +101,7 @@ class LightsPlugin: async def change_state( self, change_state: LightModel - ) -> Optional[LightModel]: + ) -> LightModel | None: """Changes the state of the light.""" for light in self._lights: if light["id"] == change_state["id"]: @@ -538,14 +543,16 @@ This approach eliminates the need to manually provide and update the return type When creating a plugin in Python, you can provide additional information about the functions in the `kernel_function` decorator. This information will be used by the AI agent to understand the functions better. ```python -from typing import List, Optional, Annotated +from typing import Annotated + +from semantic_kernel.functions import kernel_function class LightsPlugin: - def __init__(self, lights: List[LightModel]): + def __init__(self, lights: list[LightModel]): self._lights = lights @kernel_function(name="GetLights", description="Gets a list of lights and their current state") - async def get_lights(self) -> List[LightModel]: + async def get_lights(self) -> list[LightModel]: """Gets a list of lights and their current state.""" return self._lights @@ -553,7 +560,7 @@ class LightsPlugin: async def change_state( self, change_state: LightModel - ) -> Optional[LightModel]: + ) -> LightModel | None: """Changes the state of the light.""" for light in self._lights: if light["id"] == change_state["id"]: diff --git a/semantic-kernel/support/migration/stepwise-planner-migration-guide.md b/semantic-kernel/support/migration/stepwise-planner-migration-guide.md index 5b49d885..7a940761 100644 --- a/semantic-kernel/support/migration/stepwise-planner-migration-guide.md +++ b/semantic-kernel/support/migration/stepwise-planner-migration-guide.md @@ -1,17 +1,21 @@ --- -title: .NET Migrating from Stepwise Planner to Auto Function Calling +title: Migrating from Stepwise Planner to Auto Function Calling description: Describes the steps for SK caller code to migrate from Stepwise Planner to Auto Function Calling. zone_pivot_groups: programming-languages author: dmytrostruk ms.topic: conceptual ms.author: dmytrostruk +ms.date: 06/10/2025 ms.service: semantic-kernel --- -::: zone pivot="programming-language-csharp" + # Stepwise Planner Migration Guide This migration guide shows how to migrate from `FunctionCallingStepwisePlanner` to a new recommended approach for planning capability - [Auto Function Calling](../../concepts/ai-services/chat-completion/function-calling/index.md). The new approach produces the results more reliably and uses fewer tokens compared to `FunctionCallingStepwisePlanner`. ## Plan generation + +::: zone pivot="programming-language-csharp" + Following code shows how to generate a new plan with Auto Function Calling by using `FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()`. After sending a request to AI model, the plan will be located in `ChatHistory` object where a message with `Assistant` role will contain a list of functions (steps) to call. Old approach: @@ -114,7 +118,127 @@ ChatMessageContent result = await chatCompletionService.GetChatMessageContentAsy string planResult = result.Content; ``` -The code snippets above demonstrate how to migrate your code that uses Stepwise Planner to use Auto Function Calling. Learn more about [Function Calling with chat completion](../../concepts/ai-services/chat-completion/function-calling/index.md). +::: zone-end + +::: zone pivot="programming-language-python" + +The following code shows how to generate a new plan with Auto Function Calling by using `function_choice_behavior = FunctionChoiceBehavior.Auto()`. After sending a request to AI model, the plan will be located in `ChatHistory` object where a message with `Assistant` role will contain a list of functions (steps) to call. + +Old approach: +```python +from semantic_kernel import Kernel +from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion +from semantic_kernel.planners.function_calling_stepwise_planner import ( + FunctionCallingStepwisePlanner, + FunctionCallingStepwisePlannerResult, +) + +kernel = Kernel() +kernel.add_service(AzureChatCompletion()) + +# Add any plugins to the kernel that the planner will leverage +kernel.add_plugins(...) + +planner = FunctionCallingStepwisePlanner(service_id="service_id") + +result: FunctionCallingStepwisePlannerResult = await planner.invoke( + kernel=kernel, + question="Check current UTC time and return current weather in Boston city.", +) + +generated_plan = result.chat_history +``` + +New approach: + +```python +from semantic_kernel import Kernel +from semantic_kernel.connectors.ai import FunctionChoiceBehavior +from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, AzureChatPromptExecutionSettings +from semantic_kernel.contents import ChatHistory + +chat_completion_service = AzureChatCompletion() + +chat_history = ChatHistory() +chat_hitory.add_user_message("Check current UTC time and return current weather in Boston city.") + +request_settings = AzureChatPromptExecutionSettings(function_choice_behavior=FunctionChoiceBehavior.Auto()) + +# Add any plugins to the kernel that the planner will leverage +kernel = Kernel() +kernel.add_plugins(...) + +response = await chat_completion_service.get_chat_message_content( + chat_history=chat_history, + settings=request_settings, + kernel=kernel, +) +print(response) + +# The generated plan is now contained inside of `chat_history`. +``` + +## Execution of the new plan +Following code shows how to execute a new plan with Auto Function Calling by using `function_choice_behavior = FunctionChoiceBehavior.Auto()`. This approach is useful when only the result is needed without plan steps. In this case, the `Kernel` object can be used to pass a goal to the `invoke_prompt` method. The result of plan execution will be located in a `FunctionResult` object. + +Old approach: +```python +from semantic_kernel import Kernel +from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion +from semantic_kernel.planners.function_calling_stepwise_planner import ( + FunctionCallingStepwisePlanner, + FunctionCallingStepwisePlannerResult, +) + +kernel = Kernel() +kernel.add_service(AzureChatCompletion()) + +# Add any plugins to the kernel that the planner will leverage +kernel.add_plugins(...) + +planner = FunctionCallingStepwisePlanner(service_id="service_id") + +result: FunctionCallingStepwisePlannerResult = await planner.invoke( + kernel=kernel, + question="Check current UTC time and return current weather in Boston city.", +) + +print(result.final_answer) +``` + +New approach: + +```python +from semantic_kernel import Kernel +from semantic_kernel.connectors.ai import FunctionChoiceBehavior +from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, AzureChatPromptExecutionSettings +from semantic_kernel.contents import ChatHistory +from semantic_kernel.functions import KernelArguments + +kernel = Kernel() +kernel.add_service(AzureChatCompletion()) +# Add any plugins to the kernel that the planner will leverage +kernel.add_plugins(...) + +chat_history = ChatHistory() +chat_hitory.add_user_message("Check current UTC time and return current weather in Boston city.") + +request_settings = AzureChatPromptExecutionSettings(function_choice_behavior=FunctionChoiceBehavior.Auto()) + +response = await kernel.invoke_prompt( + "Check current UTC time and return current weather in Boston city.", + arguments=KernelArguments(settings=request_settings), +) +print(response) +``` ::: zone-end +::: zone pivot="programming-language-java" + +> Planners were not available in SK Java. Please use function calling directly. + +::: zone-end + +The code snippets above demonstrate how to migrate your code that uses Stepwise Planner to use Auto Function Calling. Learn more about [Function Calling with chat completion](../../concepts/ai-services/chat-completion/function-calling/index.md). + From afcd67b1dfb0aea5f0d2aab83fda078916211eb9 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Wed, 11 Jun 2025 11:12:15 -0700 Subject: [PATCH 2/4] Add .Net agent orchestration required packages --- .../Frameworks/agent/agent-orchestration/index.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/semantic-kernel/Frameworks/agent/agent-orchestration/index.md b/semantic-kernel/Frameworks/agent/agent-orchestration/index.md index bad9f02a..fe160087 100644 --- a/semantic-kernel/Frameworks/agent/agent-orchestration/index.md +++ b/semantic-kernel/Frameworks/agent/agent-orchestration/index.md @@ -97,6 +97,21 @@ await runtime.stop_when_idle() ::: zone-end +::: zone pivot="programming-language-csharp" + +## Preparing Your Development Environment + +Add the following packages to your project before you proceed: + +```pwsh +dotnet add package Microsoft.SemanticKernel.Agents.Orchestration --prerelease +dotnet add package Microsoft.SemanticKernel.Agents.Runtime.InProcess --prerelease +``` + +Depending on the agent types you use, you may also need to add the respective packages for the agents. Please refer to the [Agents Overview](../agent-architecture.md#agent-types-in-semantic-kernel) for more details. + +::: zone-end + ## Next steps > [!div class="nextstepaction"] From d5203b2472065dc1ac6cda93a29ba07f4918b69b Mon Sep 17 00:00:00 2001 From: Evan Mattson <35585003+moonbox3@users.noreply.github.com> Date: Fri, 20 Jun 2025 08:38:44 +0900 Subject: [PATCH 3/4] Update Assistant agent delete method. (#600) --- .../Frameworks/agent/agent-types/assistant-agent.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/semantic-kernel/Frameworks/agent/agent-types/assistant-agent.md b/semantic-kernel/Frameworks/agent/agent-types/assistant-agent.md index f095f14b..f33af6d2 100644 --- a/semantic-kernel/Frameworks/agent/agent-types/assistant-agent.md +++ b/semantic-kernel/Frameworks/agent/agent-types/assistant-agent.md @@ -290,7 +290,7 @@ await thread.delete() if thread else None ## Deleting an `OpenAIAssistantAgent` Since the assistant's definition is stored remotely, it will persist if not deleted. -Deleting an assistant definition may be performed directly with the `AssistantClient`. +Deleting an assistant definition may be performed directly with the client. > Note: Attempting to use an agent instance after being deleted will result in a service exception. @@ -308,9 +308,7 @@ await client.DeleteAssistantAsync(""); ::: zone pivot="programming-language-python" ```python -await agent.delete() - -is_deleted = agent._is_deleted +await client.beta.assistants.delete(agent.id) ``` ::: zone-end From e4459bede70af0833ed374287ddbf4d22031c2d8 Mon Sep 17 00:00:00 2001 From: Eduard van Valkenburg Date: Wed, 25 Jun 2025 02:35:59 +0200 Subject: [PATCH 4/4] Python: June 2025 Vector Store updates (#584) * migration guide added * small update * updates * minor updates * updated rest of docs * import updates * added hybrid search * updated method names * small tweaks in migration guide * updated version number --- .../how-to/build-your-own-connector.md | 73 ++- .../vector-store-connectors/hybrid-search.md | 45 +- .../concepts/vector-store-connectors/index.md | 100 +++- .../azure-ai-search-connector.md | 11 +- .../azure-cosmosdb-mongodb-connector.md | 18 +- .../azure-cosmosdb-nosql-connector.md | 57 +- .../chroma-connector.md | 8 +- .../faiss-connector.md | 20 +- .../inmemory-connector.md | 8 +- .../mongodb-connector.md | 8 +- .../pinecone-connector.md | 40 +- .../qdrant-connector.md | 10 +- .../redis-connector.md | 18 +- .../sql-connector.md | 12 +- .../volatile-connector.md | 6 +- .../weaviate-connector.md | 8 +- .../schema-with-record-definition.md | 4 +- .../vector-store-connectors/serialization.md | 25 +- .../vector-store-connectors/vector-search.md | 143 +---- semantic-kernel/support/migration/toc.yml | 2 + .../migration/vectorstore-python-june-2025.md | 555 ++++++++++++++++++ 21 files changed, 847 insertions(+), 324 deletions(-) create mode 100644 semantic-kernel/support/migration/vectorstore-python-june-2025.md diff --git a/semantic-kernel/concepts/vector-store-connectors/how-to/build-your-own-connector.md b/semantic-kernel/concepts/vector-store-connectors/how-to/build-your-own-connector.md index 5129dde0..38d8f9b2 100644 --- a/semantic-kernel/concepts/vector-store-connectors/how-to/build-your-own-connector.md +++ b/semantic-kernel/concepts/vector-store-connectors/how-to/build-your-own-connector.md @@ -325,6 +325,9 @@ This article provides guidance for anyone who wishes to build their own Vector S This article can be used by database providers who wish to build and maintain their own implementation, or for anyone who wishes to build and maintain an unofficial connector for a database that lacks support. +In June 2025, the setup was updated see: +1. [Vector Store Changes for Python June 2025](../../../support/migration/vectorstore-python-june-2025.md) + If you wish to contribute your connector to the Semantic Kernel code base: 1. Create an issue in the [Semantic Kernel Github repository](https://github.com/microsoft/semantic-kernel/issues). @@ -332,7 +335,7 @@ If you wish to contribute your connector to the Semantic Kernel code base: ## Overview -Vector Store connectors are implementations of the [Vector Store base classes](https://github.com/microsoft/semantic-kernel/tree/main/python/semantic_kernel/data/vector_storage) and optionally the [Vector Search base class and methods](https://github.com/microsoft/semantic-kernel/tree/main/python/semantic_kernel/data/vector_search). Some of the decisions that +Vector Store connectors are implementations of the [Vector Store base classes](https://github.com/microsoft/semantic-kernel/tree/main/python/semantic_kernel/data/vector). Some of the decisions that were made when designing the Vector Store abstraction mean that a Vector Store connector requires certain features to provide users with a good experience. @@ -344,9 +347,6 @@ It also means that a connector may need to find out certain information about th to map each of these properties. E.g. some vector databases (such as Chroma, Qdrant and Weaviate) require vectors to be stored in a specific structure and non-vectors in a different structure, or require record keys to be stored in a specific field. -At the same time, the Vector Store classes also provides a generic data model that allows a developer to work -with a database without needing to create a custom data model. - It is important for connectors to support different types of model and provide developers with flexibility around how they use the connector. The following section deep dives into each of these requirements. @@ -358,64 +358,57 @@ In order to be considered a full implementation of the Vector Store abstractions The two core classes that need to be implemented are: 1. VectorStore -1. VectorStoreRecordCollection[TKey, TModel] or VectorSearchBase[TKey, TModel] (VectorSearchBase is a subclass of VectorStoreRecordCollection) - -Then when using VectorSearchBase, the following mixins can be used, at least one otherwise search is not available: - -1. VectorTextSearchMixin[TModel] -1. VectorizableTextSearchMixin[TModel] -1. VectorizedSearchMixin[TModel] +1. VectorStoreCollection[TKey, TModel] and optionally VectorSearch[TKey, TModel] The naming convention should be: -- {database type}VectorStore -- {database type}VectorStoreRecordCollection or {database type}Collection +- {database type}Store +- {database type}Collection E.g. -- MyDBVectorStore -- MyDBVectorStoreRecordCollection or MyDBCollection +- MyDBStore +- MyDBCollection -A `VectorStoreRecordCollection` is tied to a specific collection/index name in the database, that collection name should be passed to the constructor, or the `get_collection` method of the VectorStore. +A `VectorStoreCollection` is tied to a specific collection/index name in the database, that collection name should be passed to the constructor, or the `get_collection` method of the VectorStore. These are the methods that need to be implemented: 1. VectorStore - 1. `VectorStore.get_collection` - This is a factory method that should return a new instance of the VectorStoreRecordCollection for the given collection name, it should not do checks to verify whether a collection exists or not. It should also store the collection in the internal dict `vector_record_collections` so that it can be retrieved later. + 1. `VectorStore.get_collection` - This is a factory method that should return a new instance of the VectorStoreCollection for the given collection name, it should not do checks to verify whether a collection exists or not. It should also store the collection in the internal dict `vector_record_collections` so that it can be retrieved later. 1. `VectorStore.list_collection_names` - This method should return a list of collection names that are available in the database. -1. VectorStoreRecordCollection - 1. `VectorStoreRecordCollection._inner_upsert` - This method takes a list of records and returns a list of keys that were updated or inserted, this method is called from the `upsert` and `upsert_batch` methods, those methods takes care of serialization. - 2. `VectorStoreRecordCollection._inner_get` - This method takes a list of keys and returns a list of records, this method is called from the `get` and `get_batch` methods. - 3. `VectorStoreRecordCollection._inner_delete` - This method takes a list of keys and deletes them from the database, this method is called from the `delete` and `delete_batch` methods. - 4. `VectorStoreRecordCollection._serialize_dicts_to_store_models` - This method takes a list of dicts and returns a list of objects ready to be upserted, this method is called from the `upsert` and `upsert_batch` methods, check the [Serialization docs for more info](../serialization.md). - 5. `VectorStoreRecordCollection._deserialize_store_models_to_dicts` - This method takes a list of objects from the store and returns a list of dicts, this method is called from the `get`, `get_batch` and optionally `search` methods, check the [Serialization docs for more info](../serialization.md) - 6. `VectorStoreRecordCollection.create_collection` - This method should create a collection/index in the database, it should be able to parse a `VectorStoreRecordDefinition` and create the collection/index accordingly and also allow the user to supply their own definition, ready for that store, this allows the user to leverage every feature of the store, even ones we don't. - 7. `VectorStoreRecordCollection.does_collection_exist` - This method should return a boolean indicating whether the collection exists or not. - 8. `VectorStoreRecordCollection.delete_collection` - This method should delete the collection/index from the database. -2. VectorSearchBase - 1. `VectorSearchBase._inner_search` - This method should take the options, query text or vector and `KernelSearchResults` with a `VectorSearchResult` as the internal content, the `KernelSearchResults` is a Async Iterable to allow support for paging results, as search can return a large number of results (there is a helper util to take a list of results and return a `AsyncIterable`). - 2. `VectorSearchBase._get_record_from_result` - This method should take the search result from the store and extract the actual content, this can also be as simple as returning the result. - 3. `VectorSearchBase._get_score_from_result` - This method should take the search result from the store and extract the score, this is not always present as some databases don't return a score. +1. VectorStoreCollection + 1. `VectorStoreCollection._inner_upsert` - This method takes a list of records and returns a list of keys that were updated or inserted, this method is called from the `upsert` method, those methods takes care of serialization. + 2. `VectorStoreCollection._inner_get` - This method takes a list of keys and returns a list of records, this method is called from the `get` method. + 3. `VectorStoreCollection._inner_delete` - This method takes a list of keys and deletes them from the database, this method is called from the `delete` method. + 4. `VectorStoreCollection._serialize_dicts_to_store_models` - This method takes a list of dicts and returns a list of objects ready to be upserted, this method is called from the `upsert` method, check the [Serialization docs for more info](../serialization.md), by this point a embedding is already generated when applicable. + 5. `VectorStoreCollection._deserialize_store_models_to_dicts` - This method takes a list of objects from the store and returns a list of dicts, this method is called from the `get` and optionally `search` methods, check the [Serialization docs for more info](../serialization.md) + 6. `VectorStoreCollection.ensure_collection_exists` - This method should create a collection/index in the database, it should be able to parse a `VectorStoreRecordDefinition` and create the collection/index accordingly and also allow the user to supply their own definition, ready for that store, this allows the user to leverage every feature of the store, even ones we don't. This method should first check if the collection exists, if it does not, it should create it, if it does exist, it should do nothing. + 7. `VectorStoreCollection.collection_exists` - This method should return a boolean indicating whether the collection exists or not. + 8. `VectorStoreCollection.ensure_collection_deleted` - This method should delete the collection/index from the database. +2. VectorSearch + 1. `VectorSearch._inner_search` - This method should take the query values or vector and options and search_type and return a `KernelSearchResults` with a `VectorSearchResult` as the internal content, the `KernelSearchResults` is a Async Iterable to allow support for paging results, as search can return a large number of results (there is a helper util to take a list of results and return a `AsyncIterable`). The search_type can be `vector` or `keyword_hybrid`, the first one is a pure vector search, the second one is a hybrid search that combines keyword and vector search, this is not supported in all vector stores, and the below mentioned `supported_search_types` class variable is be used to validate the search type and can also be inspected by users. There is a convenience function `_generate_vector_from_values` that can be used to generate a vector from the query values, for both search types. + 2. `VectorSearch._get_record_from_result` - This method should take the search result from the store and extract the actual content, this can also be as simple as returning the result. + 3. `VectorSearch._get_score_from_result` - This method should take the search result from the store and extract the score, this is not always present as some databases don't return a score. + 4. `VectorSearch._lambda_parser` - This method should take a lambda expression as AST (`abstract syntax tree`) and parse it into a filter expression that can be used by the store, this is called from a built-in method called `_build_filter` which takes care of parsing a lambda expression or string into a AST, and returns the results, the _inner_search method will then use the results of `_build_filter` to filter the results from the store. If you do not want to use `_build_filter` you can just implement `_lambda_parser` with a `pass`. The best way to understand more about this method is to look at the ones built, for instance in Azure AI Search or MongoDB, as they are fairly complete. Some other optional items that can be implemented are: -1. `VectorStoreRecordCollection._validate_data_model` - This method validates the data model, there is a default implementation that takes the `VectorStoreRecordDefinition` and validates the data model against it, with the values from the supported types (see below), but this can be overwritten to provide custom validation. A additional step can be added by doing `super()._validate_data_model()` to run the default validation first. -1. `VectorStoreRecordCollection.supported_key_types` - This is a `classvar`, that should be a list of supported key types, this is used to validate the key type when creating a collection. -2. `VectorStoreRecordCollection.supported_vector_types` - This is a `classvar`, that should be a list of supported vector types, this is used to validate the vector type when creating a collection. -3. `Vector...__aenter__` and `Vector...__aexit__` - These methods should be implemented to allow the use of the `async with` statement, this is useful for managing connections and resources. -4. `Vector...managed_client` - This is a helper property that can be used to indicate whether the current instance is managing the client or not, this is useful for the `__aenter__` and `__aexit__` methods and should be set based on the constructor arguments. -5. `VectorSearchBase.options_class` - This is a property that returns the search options class, by default this is the `VectorSearchOptions` but can be overwritten to provide a custom options class. The public methods perform a check of the options type and do a cast if needed. +1. `VectorStoreCollection._validate_data_model` - This method validates the data model, there is a default implementation that takes the `VectorStoreRecordDefinition` and validates the data model against it, with the values from the supported types (see below), but this can be overwritten to provide custom validation. A additional step can be added by doing `super()._validate_data_model()` to run the default validation first. +1. `VectorStoreCollection.supported_key_types` - This is a `classvar`, that should be a list of supported key types, this is used to validate the key type when creating a collection. +2. `VectorStoreCollection.supported_vector_types` - This is a `classvar`, that should be a list of supported vector types, this is used to validate the vector type when creating a collection. +3. `VectorSearch.supported_search_types` - This is a `classvar`, that should be a list of supported search types, `vector` or `keyword_hybrid`, this is used to validate the search type when searching. +3. `VectorStoreCollection.__aenter__` and `VectorStoreCollection.__aexit__` - These methods should be implemented to allow the use of the `async with` statement, this is useful for managing connections and resources. +4. `VectorStoreCollection.managed_client` - This is a helper property that can be used to indicate whether the current instance is managing the client or not, this is useful for the `__aenter__` and `__aexit__` methods and should be set based on the constructor arguments. +5. `VectorSearch.options_class` - This is a property that returns the search options class, by default this is the `VectorSearchOptions` but can be overwritten to provide a custom options class. The public methods perform a check of the options type and do a cast if needed. ### Collection / Index Creation Every store has it's own quirks when it comes to the way indexes/collections are defined and which features are supported. Most implementation use some kind of helper or util function to parse the `VectorStoreRecordDefinition` and create the collection/index definition. This includes mapping from the Semantic Kernel IndexKind and DistanceFunction to the store specific ones, and raising an error when a unsupported index or distance function is used. It is advised to use a dict to map between these so that it is easy to update and maintain over time. -There are features in Semantic Kernel that are not available in the store and vice versa, for instance a data field might be marked as full text searchable in Semantic Kernel but the store might not make that distinction, in this case that setting is ignored. The inverse where there are settings available in the store but not in Semantic Kernel, a sensible default, with a clear docstring or comment on why that default is chosen, should be used and this is exactly the type of thing that a user might want to leverage the break glass feature for (supplying their own definition to the `create_collection` method). +There are features in Semantic Kernel that are not available in the store and vice versa, for instance a data field might be marked as full text searchable in Semantic Kernel but the store might not make that distinction, in this case that setting is ignored. The inverse where there are settings available in the store but not in Semantic Kernel, a sensible default, with a clear docstring or comment on why that default is chosen, should be used and this is exactly the type of thing that a user might want to leverage the break glass feature for (supplying their own definition to the `ensure_collection_exists` method). ### Exceptions Most exceptions are raised with the Semantic Kernel specific types by the public methods, so the developer of the connector should not worry about it, this also makes sure that a user does not have to think about very specific exceptions from each connector. You should also not catch things only to re-raise, that is done once so that the stack trace does not become overly long. The vector store exceptions are all coming from the [vector_store_exceptions](https://github.com/microsoft/semantic-kernel/blob/main/python/semantic_kernel/exceptions/vector_store_exceptions.py). -### Batching -Each store and their client offers different methods and ways of working, we noticed that most either have a batch operation or it has both a single and batch operations, the only one that should be used in Semantic Kernel is the batch one because each of the _inner methods are called with a sequence of keys or records, this is to ensure that the store can optimize the operation as much as possible, without doubling the amount of code. This does mean that sometimes the _inner method will have to batch itself, this should then be done using `asyncio.gather` to ensure that the operations are done in parallel. - ::: zone-end ::: zone pivot="programming-language-java" diff --git a/semantic-kernel/concepts/vector-store-connectors/hybrid-search.md b/semantic-kernel/concepts/vector-store-connectors/hybrid-search.md index 87187276..316f380e 100644 --- a/semantic-kernel/concepts/vector-store-connectors/hybrid-search.md +++ b/semantic-kernel/concepts/vector-store-connectors/hybrid-search.md @@ -15,8 +15,49 @@ ms.service: semantic-kernel ::: zone-end ::: zone pivot="programming-language-python" -> [!WARNING] -> The Semantic Kernel Vector Store functionality is in preview, and improvements that require breaking changes may still occur in limited circumstances before release. + +There are two searches currently supported in the Semantic Kernel Vector Store abstractions: +1. `search` -> see [Search](./vector-search.md) +2. `hybrid_search` + 1. This is search based on a text value and a vector, if the vector is not supplied, it will be generated using the `embedding_generator` field on the data model or record definition, or by the vector store itself. + +All searches can take a optional set of parameters: +- `vector`: A vector used to search, can be supplied instead of the values, or in addition to the values for hybrid. +- `top`: The number of results to return, defaults to 3. +- `skip`: The number of results to skip, defaults to 0. +- `include_vectors`: Whether to include the vectors in the results, defaults to `false`. +- `filter`: A filter to apply to the results before the vector search is applied, defaults to `None`, in the form of a lambda expression: `lambda record: record.property == "value"`. +- `vector_property_name`: The name of the vector property to use for the search, defaults to the first vector property found on the data model or record definition. +- `additional_property_name`: The name of the additional field to use for the text search of the hybrid search. +- `include_total_count`: Whether to include the total count of results in the search result, defaults to `false`. + +Assuming you have a collection that already contains data, you can easily search it. Here is an example using Azure AI Search. + +```python +from semantic_kernel.connectors.azure_ai_search import AzureAISearchCollection, AzureAISearchStore + +# Create a Azure AI Search VectorStore object and choose an existing collection that already contains records. +# Hotels is the data model decorated class. +store = AzureAISearchStore() +collection: AzureAISearchCollection[str, Hotels] = store.get_collection(Hotels, collection_name="skhotels") + +search_results = await collection.hybrid_search( + query, vector_property_name="vector", additional_property_name="description" +) +hotels = [record.record async for record in search_results.results] +print(f"Found hotels: {hotels}") +``` + +> [!TIP] +> For more information on how to generate embeddings see [embedding generation](./embedding-generation.md). + +### Filters + +The `filter` parameter can be used to provide a filter for filtering the records in the chosen collection. It is defined as a lambda expression, or a string of a lambda expression, e.g. `lambda record: record.property == "value"`. + +It is important to understand that these are not executed directly, rather they are parsed into the syntax matching the vector stores, the only exception to this is the `InMemoryCollection` which does execute the filter directly. + +Given this flexibility, it is important to review the documentation of a specific store to understand which filters are supported, for instance not all vector stores support negative filters (i.e. `lambda x: not x.value`), and that won't become apparent until the search is executed. ::: zone-end ::: zone pivot="programming-language-java" diff --git a/semantic-kernel/concepts/vector-store-connectors/index.md b/semantic-kernel/concepts/vector-store-connectors/index.md index 242a6a64..9bd4cd16 100644 --- a/semantic-kernel/concepts/vector-store-connectors/index.md +++ b/semantic-kernel/concepts/vector-store-connectors/index.md @@ -16,7 +16,7 @@ ms.service: semantic-kernel ::: zone pivot="programming-language-python" > [!WARNING] -> The Semantic Kernel Vector Store functionality is in preview, and improvements that require breaking changes may still occur in limited circumstances before release. +> The Semantic Kernel Vector Store functionality is RC, and improvements that require breaking changes may still occur in limited circumstances before release. ::: zone-end ::: zone pivot="programming-language-java" @@ -174,24 +174,21 @@ public class Hotel ```python from dataclasses import dataclass, field from typing import Annotated -from semantic_kernel.data import ( +from semantic_kernel.data.vector import ( DistanceFunction, IndexKind, - VectorStoreRecordDataField, - VectorStoreRecordDefinition, - VectorStoreRecordKeyField, - VectorStoreRecordVectorField, + VectorStoreField, vectorstoremodel, ) @vectorstoremodel @dataclass class Hotel: - hotel_id: Annotated[str, VectorStoreRecordKeyField()] = field(default_factory=lambda: str(uuid4())) - hotel_name: Annotated[str, VectorStoreRecordDataField(is_filterable=True)] - description: Annotated[str, VectorStoreRecordDataField(is_full_text_searchable=True)] - description_embedding: Annotated[list[float], VectorStoreRecordVectorField(dimensions=4, distance_function=DistanceFunction.COSINE, index_kind=IndexKind.HNSW)] - tags: Annotated[list[str], VectorStoreRecordDataField(is_filterable=True)] + hotel_id: Annotated[str, VectorStoreField('key')] = field(default_factory=lambda: str(uuid4())) + hotel_name: Annotated[str, VectorStoreField('data', is_filterable=True)] + description: Annotated[str, VectorStoreField('data', is_full_text_searchable=True)] + description_embedding: Annotated[list[float], VectorStoreField('vector', dimensions=4, distance_function=DistanceFunction.COSINE, index_kind=IndexKind.HNSW)] + tags: Annotated[list[str], VectorStoreField('data', is_filterable=True)] ``` ::: zone-end @@ -289,15 +286,12 @@ Since databases support many different types of keys and records, we allow you t In our case, the type of record will be the `Hotel` class we already defined, and the type of key will be `str`, since the `HotelId` property is a `str` and Qdrant only supports `str` or `int` keys. ```python -from semantic_kernel.connectors.memory.qdrant import QdrantStore - -# Create a Qdrant VectorStore object, this will look in the environment for Qdrant related settings, and will fall back to the default, which is to run in-memory. -vector_store = QdrantStore() +from semantic_kernel.connectors.qdrant import QdrantCollection -# Choose a collection from the database and specify the type of key and record stored in it via Generic parameters. -collection = vector_store.get_collection( - collection_name="skhotels", - data_model_type=Hotel +# Create a collection specify the type of key and record stored in it via Generic parameters. +collection: QdrantCollection[str, Hotel] = QdrantCollection( + record_type=Hotel, + collection_name="skhotels" # this is optional, you can also specify the collection_name in the vectorstoremodel decorator. ) ``` ::: zone-end @@ -387,7 +381,7 @@ Hotel? retrievedHotel = await collection.GetAsync(hotelId); ```python # Create the collection if it doesn't exist yet. -await collection.create_collection_if_not_exists() +await collection.ensure_collection_exists() # Upsert a record. description = "A place where everyone can be happy." @@ -463,18 +457,74 @@ await foreach (var record in searchResult) ### Do a vector search +The search method can be used to search for records in the collection. It either takes a string, which is then vectorized using the embedding generation setup in the model or collection, or a vector that is already generated. + ```python -# Generate a vector for your search text, using your chosen embedding generation implementation. -# Just showing a placeholder method here for brevity. -search_vector = await GenerateEmbedding("I'm looking for a hotel where customer happiness is the priority."); -# Do the search. -search_result = await collection.vectorized_search(vector=searchVector, VectorSearchOptions(top = 1 )) +# Do a search. +search_result = await collection.search("I'm looking for a hotel where customer happiness is the priority.", vector_property_name="description_embedding", top=3) # Inspect the returned hotels. async for result in search_result.results: print(f"Found hotel description: {result.record.description}") ``` +### Create a search function + +To create a simple search function that can be used to search for hotels, you can use the `create_search_function` method on the collection. + +The name and description, as well as the names and descriptions of the parameters, are used to generate a function signature that is sent to the LLM when function calling is used. +This means that tweaking this can be useful to get the LLM to generate the correct function call. + +```python +collection.create_search_function( + function_name="hotel_search", + description="A hotel search engine, allows searching for hotels in specific cities, " + "you do not have to specify that you are searching for hotels, for all, use `*`." +) +``` +There are a lot of other parameters, for instance this is what a more complex version looks like, note the customization of the parameters, and the `string_mapper` function that is used to convert the record to a string. + +```python +from semantic_kernel.function import KernelParameterMetadata + +collection.create_search_function( + function_name="hotel_search", + description="A hotel search engine, allows searching for hotels in specific cities, " + "you do not have to specify that you are searching for hotels, for all, use `*`.", + search_type="keyword_hybrid", # default is "vector" + parameters=[ + KernelParameterMetadata( + name="query", + description="The terms you want to search for in the hotel database.", + type="str", + is_required=True, + type_object=str, + ), + KernelParameterMetadata( + name="tags", + description="The tags you want to search for in the hotel database, use `*` to match all.", + type="str", + type_object=str, + default_value="*", + ), + KernelParameterMetadata( + name="top", + description="Number of results to return.", + type="int", + default_value=5, + type_object=int, + ), + ], + # finally, we specify the `string_mapper` function that is used to convert the record to a string. + # This is used to make sure the relevant information from the record is passed to the LLM. + string_mapper=lambda x: f"Hotel {x.record.hotel_name}: {x.record.description}. Tags: {x.record.tags} (hotel_id: {x.record.hotel_id}) ", +) +``` + + +> [!TIP] +> For more samples, including end-to-end examples, see the [Semantic Kernel Samples repository](https://github.com/microsoft/semantic-kernel/tree/main/python/samples/concepts/memory). + ::: zone-end ::: zone pivot="programming-language-java" ```java diff --git a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/azure-ai-search-connector.md b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/azure-ai-search-connector.md index cfe0362b..0ba58015 100644 --- a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/azure-ai-search-connector.md +++ b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/azure-ai-search-connector.md @@ -199,7 +199,7 @@ You can then create a vector store instance using the `AzureAISearchStore` class ```python -from semantic_kernel.connectors.memory.azure_ai_search import AzureAISearchStore +from semantic_kernel.connectors.azure_ai_search import AzureAISearchStore vector_store = AzureAISearchStore() ``` @@ -208,7 +208,7 @@ You can also create the vector store with your own instance of the azure search ```python from azure.search.documents.indexes import SearchIndexClient -from semantic_kernel.connectors.memory.azure_ai_search import AzureAISearchStore +from semantic_kernel.connectors.azure_ai_search import AzureAISearchStore search_client = SearchIndexClient(endpoint="https://.search.windows.net", credential="") vector_store = AzureAISearchStore(search_index_client=search_client) @@ -217,9 +217,12 @@ vector_store = AzureAISearchStore(search_index_client=search_client) You can also create a collection directly. ```python -from semantic_kernel.connectors.memory.azure_ai_search import AzureAISearchCollection +from semantic_kernel.connectors.azure_ai_search import AzureAISearchCollection -collection = AzureAISearchCollection(collection_name="skhotels", data_model_type=hotel) +collection = AzureAISearchCollection( + record_type=hotel, + collection_name="skhotels" +) ``` ## Serialization diff --git a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/azure-cosmosdb-mongodb-connector.md b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/azure-cosmosdb-mongodb-connector.md index 91bc5003..511eb93c 100644 --- a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/azure-cosmosdb-mongodb-connector.md +++ b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/azure-cosmosdb-mongodb-connector.md @@ -219,20 +219,20 @@ pip install semantic-kernel[azure, mongo] You can then create the vector store. ```python -from semantic_kernel.connectors.memory.azure_cosmos_db import AzureCosmosDBforMongoDBStore +from semantic_kernel.connectors.azure_cosmos_db import CosmosMongoStore # If the right environment settings are set, namely AZURE_COSMOS_DB_MONGODB_CONNECTION_STRING and optionally AZURE_COSMOS_DB_MONGODB_DATABASE_NAME, this is enough to create the Store: -store = AzureCosmosDBforMongoDBStore() +store = CosmosMongoStore() ``` Alternatively, you can also pass in your own mongodb client if you want to have more control over the client construction: ```python from pymongo import AsyncMongoClient -from semantic_kernel.connectors.memory.azure_cosmos_db import AzureCosmosDBforMongoDBStore +from semantic_kernel.connectors.azure_cosmos_db import CosmosMongoStore client = AsyncMongoClient(...) -store = AzureCosmosDBforMongoDBStore(mongo_client=client) +store = CosmosMongoStore(mongo_client=client) ``` When a client is passed in, Semantic Kernel will not close the connection for you, so you need to ensure to close it, for instance with a `async with` statement. @@ -240,12 +240,12 @@ When a client is passed in, Semantic Kernel will not close the connection for yo You can also create a collection directly, without the store. ```python -from semantic_kernel.connectors.memory.azure_cosmos_db import AzureCosmosDBforMongoDBCollection +from semantic_kernel.connectors.azure_cosmos_db import CosmosMongoCollection -# `hotel` is a class created with the @vectorstoremodel decorator -collection = AzureCosmosDBforMongoDBCollection( - collection_name="my_collection", - data_model_type=hotel +# `Hotel` is a class created with the @vectorstoremodel decorator +collection = CosmosMongoCollection( + record_type=Hotel, + collection_name="my_collection" ) ``` diff --git a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/azure-cosmosdb-nosql-connector.md b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/azure-cosmosdb-nosql-connector.md index f306d857..5fed9e41 100644 --- a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/azure-cosmosdb-nosql-connector.md +++ b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/azure-cosmosdb-nosql-connector.md @@ -278,7 +278,7 @@ The Azure CosmosDB NoSQL Vector Store connector can be used to access and manage | Feature Area | Support | | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Collection maps to | Azure Cosmos DB NoSQL Container | -| Supported key property types |
  • string
  • AzureCosmosDBNoSQLCompositeKey
| +| Supported key property types |
  • string
  • CosmosNoSqlCompositeKey
| | Supported data property types |
  • string
  • int
  • long
  • double
  • float
  • bool
  • DateTimeOffset
  • *and iterables of each of these types*
| | Supported vector property types |
  • list[float]
  • list[int]
  • ndarray
| | Supported index types |
  • Flat
  • QuantizedFlat
  • DiskAnn
| @@ -309,17 +309,17 @@ And optionally: When this is not set, a `AsyncDefaultAzureCredential` is used to authenticate. ```python -from semantic_kernel.connectors.memory.azure_cosmos_db import AzureCosmosDBNoSQLStore +from semantic_kernel.connectors.azure_cosmos_db import CosmosNoSqlStore -vector_store = AzureCosmosDBNoSQLStore() +vector_store = CosmosNoSqlStore() ``` You can also supply these values in the constructor: ```python -from semantic_kernel.connectors.memory.azure_cosmos_db import AzureCosmosDBNoSQLStore +from semantic_kernel.connectors.azure_cosmos_db import CosmosNoSqlStore -vector_store = AzureCosmosDBNoSQLStore( +vector_store = CosmosNoSqlStore( url="https://.documents.azure.com:443/", key="", database_name="" @@ -329,14 +329,14 @@ vector_store = AzureCosmosDBNoSQLStore( And you can pass in a CosmosClient instance, just make sure it is a async client. ```python -from semantic_kernel.connectors.memory.azure_cosmos_db import AzureCosmosDBNoSQLStore +from semantic_kernel.connectors.azure_cosmos_db import CosmosNoSqlStore from azure.cosmos.aio import CosmosClient client = CosmosClient( url="https://.documents.azure.com:443/", credential="" or AsyncDefaultAzureCredential() ) -vector_store = AzureCosmosDBNoSQLStore( +vector_store = CosmosNoSqlStore( client=client, database_name="" ) @@ -347,20 +347,20 @@ The next step needs a data model, a variable called Hotels is used in the exampl With a store, you can get a collection: ```python -from semantic_kernel.connectors.memory.azure_cosmos_db import AzureCosmosDBNoSQLStore +from semantic_kernel.connectors.azure_cosmos_db import CosmosNoSqlStore -vector_store = AzureCosmosDBNoSQLStore() -collection = vector_store.get_collection(collection_name="skhotels", data_model=Hotel) +vector_store = CosmosNoSqlStore() +collection = vector_store.get_collection(collection_name="skhotels", record_type=Hotel) ``` It is possible to construct a direct reference to a named collection, this uses the same environment variables as above. ```python -from semantic_kernel.connectors.memory.azure_cosmos_db import AzureCosmosDBNoSQLCollection +from semantic_kernel.connectors.azure_cosmos_db import CosmosNoSqlCollection -collection = AzureCosmosDBNoSQLCollection( +collection = CosmosNoSqlCollection( + record_type=Hotel, collection_name="skhotels", - data_model_type=Hotel, ) ``` @@ -369,11 +369,11 @@ collection = AzureCosmosDBNoSQLCollection( In the Azure Cosmos DB for NoSQL connector, the partition key property defaults to the key property - `id`. You can also supply a value for the partition key in the constructor. ```python -from semantic_kernel.connectors.memory.azure_cosmos_db import AzureCosmosDBNoSQLCollection +from semantic_kernel.connectors.azure_cosmos_db import CosmosNoSqlCollection -collection = AzureCosmosDBNoSQLCollection( +collection = CosmosNoSqlCollection( + record_type=Hotel, collection_name="skhotels", - data_model_type=Hotel, partition_key="hotel_name" ) ``` @@ -381,40 +381,39 @@ collection = AzureCosmosDBNoSQLCollection( This can be a more complex key, when using the `PartitionKey` object: ```python -from semantic_kernel.connectors.memory.azure_cosmos_db import AzureCosmosDBNoSQLCollection +from semantic_kernel.connectors.azure_cosmos_db import CosmosNoSqlCollection from azure.cosmos import PartitionKey partition_key = PartitionKey(path="/hotel_name") -collection = AzureCosmosDBNoSQLCollection( +collection = CosmosNoSqlCollection( + record_type=Hotel, collection_name="skhotels", - data_model_type=Hotel, partition_key=partition_key ) ``` -The `AzureCosmosDBNoSQLVectorStoreRecordCollection` class supports two key types: `string` and `AzureCosmosDBNoSQLCompositeKey`. The `AzureCosmosDBNoSQLCompositeKey` consists of `key` and `partition_key`. +The `CosmosNoSqlVectorStoreRecordCollection` class supports two key types: `string` and `CosmosNoSqlCompositeKey`. The `CosmosNoSqlCompositeKey` consists of `key` and `partition_key`. -If the partition key property is not set (and the default key property is used), `string` keys can be used for operations with database records. However, if a partition key property is specified, it is recommended to use `AzureCosmosDBNoSQLCompositeKey` to provide both the key and partition key values to the `get` and `delete` methods. +If the partition key property is not set (and the default key property is used), `string` keys can be used for operations with database records. However, if a partition key property is specified, it is recommended to use `CosmosNoSqlCompositeKey` to provide both the key and partition key values to the `get` and `delete` methods. ```python -from semantic_kernel.connectors.memory.azure_cosmos_db import AzureCosmosDBNoSQLCollection -from semantic_kernel.connectors.memory.azure_cosmos_db import AzureCosmosDBNoSQLCompositeKey -from semantic_kernel.data import VectorStoreRecordDataField +from semantic_kernel.connectors.azure_cosmos_db import CosmosNoSqlCollection, CosmosNoSqlCompositeKey +from semantic_kernel.data.vector import VectorStoreField @vectorstoremodel -class data_model_type: - id: Annotated[str, VectorStoreRecordKeyField] - product_type: Annotated[str, VectorStoreRecordDataField()] +class Record: + id: Annotated[str, VectorStoreField("key")] + product_type: Annotated[str, VectorStoreField("data")] ... collection = store.get_collection( + record_type=Record, collection_name=collection_name, - data_model=data_model_type, partition_key=PartitionKey(path="/product_type"), ) # when there is data in the collection -composite_key = AzureCosmosDBNoSQLCompositeKey( +composite_key = CosmosNoSqlCompositeKey( key='key value', partition_key='partition key value' ) # get a record, with the partition key diff --git a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/chroma-connector.md b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/chroma-connector.md index 9cfc7ee8..be1becd7 100644 --- a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/chroma-connector.md +++ b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/chroma-connector.md @@ -61,7 +61,7 @@ pip install semantic-kernel[chroma] You can then create the vector store. ```python -from semantic_kernel.connectors.memory.chroma import ChromaStore +from semantic_kernel.connectors.chroma import ChromaStore store = ChromaStore() ``` @@ -70,7 +70,7 @@ Alternatively, you can also pass in your own mongodb client if you want to have ```python from chromadb import Client -from semantic_kernel.connectors.memory.chroma import ChromaStore +from semantic_kernel.connectors.chroma import ChromaStore client = Client(...) store = ChromaStore(client=client) @@ -79,12 +79,12 @@ store = ChromaStore(client=client) You can also create a collection directly, without the store. ```python -from semantic_kernel.connectors.memory.chroma import ChromaCollection +from semantic_kernel.connectors.chroma import ChromaCollection # `hotel` is a class created with the @vectorstoremodel decorator collection = ChromaCollection( + record_type=hotel, collection_name="my_collection", - data_model_type=hotel ) ``` diff --git a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/faiss-connector.md b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/faiss-connector.md index 7fc2c4a2..67aa2356 100644 --- a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/faiss-connector.md +++ b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/faiss-connector.md @@ -1,5 +1,5 @@ --- -title: Using the Semantic Kernel Faiss Vector Store connector (Preview) +title: Using the Semantic Kernel Faiss VectorYou can then create a vector store instance using the `FaissStore` class. description: Contains information on how to use a Semantic Kernel Vector store connector to access and manipulate data in an in-memory Faiss vector store. zone_pivot_groups: programming-languages author: eavanvalkenburg @@ -50,7 +50,7 @@ pip install semantic-kernel[faiss] In the snippets below, it is assumed that you have a data model class defined named 'DataModel'. ```python -from semantic_kernel.connectors.memory.faiss import FaissStore +from semantic_kernel.connectors.faiss import FaissStore vector_store = FaissStore() vector_collection = vector_store.get_collection("collection_name", DataModel) @@ -59,9 +59,9 @@ vector_collection = vector_store.get_collection("collection_name", DataModel) It is possible to construct a direct reference to a named collection. ```python -from semantic_kernel.connectors.memory.faiss import FaissCollection +from semantic_kernel.connectors.faiss import FaissCollection -vector_collection = FaissCollection("collection_name", DataModel) +vector_collection = FaissCollection(DataModel, collection_name="collection_name") ``` ## Custom indexes @@ -80,12 +80,12 @@ To pass in your custom index, use either: import faiss -from semantic_kernel.connectors.memory.faiss import FaissCollection +from semantic_kernel.connectors.faiss import FaissCollection index = faiss.IndexHNSW(d=768, M=16, efConstruction=200) # or some other index vector_collection = FaissCollection( + record_type=DataModel, collection_name="collection_name", - data_model_type=DataModel, indexes={"vector_field_name": index} ) ``` @@ -96,18 +96,18 @@ or: import faiss -from semantic_kernel.connectors.memory.faiss import FaissCollection +from semantic_kernel.connectors.faiss import FaissCollection index = faiss.IndexHNSW(d=768, M=16, efConstruction=200) # or some other index vector_collection = FaissCollection( + record_type=DataModel, collection_name="collection_name", - data_model_type=DataModel, ) -await vector_collection.create_collection( +await vector_collection.ensure_collection_exists( indexes={"vector_field_name": index} ) # or when you have only one vector field: -await vector_collection.create_collection( +await vector_collection.ensure_collection_exists( index=index ) diff --git a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/inmemory-connector.md b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/inmemory-connector.md index 1ad0b1ef..51086da7 100644 --- a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/inmemory-connector.md +++ b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/inmemory-connector.md @@ -135,18 +135,18 @@ You can create the store and collections from there, or create the collections d In the snippets below, it is assumed that you have a data model class defined named 'DataModel'. ```python -from semantic_kernel.connectors.memory.in_memory import InMemoryVectorStore +from semantic_kernel.connectors.in_memory import InMemoryVectorStore vector_store = InMemoryVectorStore() -vector_collection = vector_store.get_collection("collection_name", DataModel) +vector_collection = vector_store.get_collection(record_type=DataModel, collection_name="collection_name") ``` It is possible to construct a direct reference to a named collection. ```python -from semantic_kernel.connectors.memory.in_memory import InMemoryCollection +from semantic_kernel.connectors.in_memory import InMemoryCollection -vector_collection = InMemoryCollection("collection_name", DataModel) +vector_collection = InMemoryCollection(record_type=DataModel, collection_name="collection_name") ``` ::: zone-end diff --git a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/mongodb-connector.md b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/mongodb-connector.md index 8f7609d7..ba104e75 100644 --- a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/mongodb-connector.md +++ b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/mongodb-connector.md @@ -188,7 +188,7 @@ pip install semantic-kernel[mongo] You can then create the vector store. ```python -from semantic_kernel.connectors.memory.mongodb_atlas import MongoDBAtlasStore +from semantic_kernel.connectors.mongodb import MongoDBAtlasStore # If the right environment settings are set, namely MONGODB_ATLAS_CONNECTION_STRING and optionally MONGODB_ATLAS_DATABASE_NAME and MONGODB_ATLAS_INDEX_NAME, this is enough to create the Store: store = MongoDBAtlasStore() @@ -198,7 +198,7 @@ Alternatively, you can also pass in your own mongodb client if you want to have ```python from pymongo import AsyncMongoClient -from semantic_kernel.connectors.memory.mongodb_atlas import MongoDBAtlasStore +from semantic_kernel.connectors.mongodb import MongoDBAtlasStore client = AsyncMongoClient(...) store = MongoDBAtlasStore(mongo_client=client) @@ -209,12 +209,12 @@ When a client is passed in, Semantic Kernel will not close the connection for yo You can also create a collection directly, without the store. ```python -from semantic_kernel.connectors.memory.mongodb_atlas import MongoDBAtlasCollection +from semantic_kernel.connectors.mongodb import MongoDBAtlasCollection # `hotel` is a class created with the @vectorstoremodel decorator collection = MongoDBAtlasCollection( + record_type=hotel, collection_name="my_collection", - data_model_type=hotel ) ``` diff --git a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/pinecone-connector.md b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/pinecone-connector.md index fc054d40..b392fd67 100644 --- a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/pinecone-connector.md +++ b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/pinecone-connector.md @@ -225,30 +225,30 @@ You can then create a PineconeStore instance and use it to create a collection. This will read the Pinecone API key from the environment variable `PINECONE_API_KEY`. ```python -from semantic_kernel.connectors.memory.pinecone import PineconeStore +from semantic_kernel.connectors.pinecone import PineconeStore store = PineconeStore() -collection = store.get_collection(collection_name="collection_name", data_model=DataModel) +collection = store.get_collection(collection_name="collection_name", record_type=DataModel) ``` It is possible to construct a direct reference to a named collection. ```python -from semantic_kernel.connectors.memory.pinecone import PineconeCollection +from semantic_kernel.connectors.pinecone import PineconeCollection -collection = PineconeCollection(collection_name="collection_name", data_model=DataModel) +collection = PineconeCollection(collection_name="collection_name", record_type=DataModel) ``` You can also create your own Pinecone client and pass it into the constructor. The client needs to be either `PineconeAsyncio` or `PineconeGRPC` (see [GRPC Support](#grpc-support)). ```python -from semantic_kernel.connectors.memory.pinecone import PineconeStore, PineconeCollection +from semantic_kernel.connectors.pinecone import PineconeStore, PineconeCollection from pinecone import PineconeAsyncio client = PineconeAsyncio(api_key="your_api_key") store = PineconeStore(client=client) -collection = store.get_collection(collection_name="collection_name", data_model=DataModel) +collection = store.get_collection(collection_name="collection_name", record_type=DataModel) ``` ### GRPC support @@ -256,20 +256,20 @@ collection = store.get_collection(collection_name="collection_name", data_model= We also support two options on the collection constructor, the first is to enable GRPC support: ```python -from semantic_kernel.connectors.memory.pinecone import PineconeCollection +from semantic_kernel.connectors.pinecone import PineconeCollection -collection = PineconeCollection(collection_name="collection_name", data_model=DataModel, use_grpc=True) +collection = PineconeCollection(collection_name="collection_name", record_type=DataModel, use_grpc=True) ``` Or with your own client: ```python -from semantic_kernel.connectors.memory.pinecone import PineconeStore +from semantic_kernel.connectors.pinecone import PineconeStore from pinecone.grpc import PineconeGRPC client = PineconeGRPC(api_key="your_api_key") store = PineconeStore(client=client) -collection = store.get_collection(collection_name="collection_name", data_model=DataModel) +collection = store.get_collection(collection_name="collection_name", record_type=DataModel) ``` ### Integrated Embeddings @@ -279,27 +279,27 @@ The second is to use the integrated embeddings of Pinecone, this will check for See [Pinecone docs](https://docs.pinecone.io/guides/indexes/create-an-index) and then the `Use integrated embeddings` sections. ```python -from semantic_kernel.connectors.memory.pinecone import PineconeCollection +from semantic_kernel.connectors.pinecone import PineconeCollection -collection = PineconeCollection(collection_name="collection_name", data_model=DataModel) +collection = PineconeCollection(collection_name="collection_name", record_type=DataModel) ``` Alternatively, when not settings the environment variable, you can pass the embed settings into the constructor: ```python -from semantic_kernel.connectors.memory.pinecone import PineconeCollection +from semantic_kernel.connectors.pinecone import PineconeCollection -collection = PineconeCollection(collection_name="collection_name", data_model=DataModel, embed_settings={"model": "multilingual-e5-large"}) +collection = PineconeCollection(collection_name="collection_name", record_type=DataModel, embed_settings={"model": "multilingual-e5-large"}) ``` This can include other details about the vector setup, like metric and field mapping. -You can also pass the embed settings into the `create_collection` method, this will override the default settings set during initialization. +You can also pass the embed settings into the `ensure_collection_exists` method, this will override the default settings set during initialization. ```python -from semantic_kernel.connectors.memory.pinecone import PineconeCollection +from semantic_kernel.connectors.pinecone import PineconeCollection -collection = PineconeCollection(collection_name="collection_name", data_model=DataModel) -await collection.create_collection(embed_settings={"model": "multilingual-e5-large"}) +collection = PineconeCollection(collection_name="collection_name", record_type=DataModel) +await collection.ensure_collection_exists(embed_settings={"model": "multilingual-e5-large"}) ``` > Important: GRPC and Integrated embeddings cannot be used together. @@ -313,11 +313,11 @@ By default the Pinecone connector will pass `''` as the namespace for all operat Pinecone collection when constructing it and use this instead for all operations. ```python -from semantic_kernel.connectors.memory.pinecone import PineconeCollection +from semantic_kernel.connectors.pinecone import PineconeCollection collection = PineconeCollection( collection_name="collection_name", - data_model=DataModel, + record_type=DataModel, namespace="seasidehotels" ) ``` diff --git a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/qdrant-connector.md b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/qdrant-connector.md index 591fceb2..475ddca2 100644 --- a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/qdrant-connector.md +++ b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/qdrant-connector.md @@ -216,7 +216,7 @@ You can then create a vector store instance using the `QdrantStore` class, this ```python -from semantic_kernel.connectors.memory.qdrant import QdrantStore +from semantic_kernel.connectors.qdrant import QdrantStore vector_store = QdrantStore() ``` @@ -225,7 +225,7 @@ You can also create the vector store with your own instance of the qdrant client ```python from qdrant_client.async_qdrant_client import AsyncQdrantClient -from semantic_kernel.connectors.memory.qdrant import QdrantStore +from semantic_kernel.connectors.qdrant import QdrantStore client = AsyncQdrantClient(host='localhost', port=6333) vector_store = QdrantStore(client=client) @@ -234,9 +234,9 @@ vector_store = QdrantStore(client=client) You can also create a collection directly. ```python -from semantic_kernel.connectors.memory.qdrant import QdrantCollection +from semantic_kernel.connectors.qdrant import QdrantCollection -collection = QdrantCollection(collection_name="skhotels", data_model_type=hotel) +collection = QdrantCollection(collection_name="skhotels", record_type=hotel) ``` ## Serialization @@ -415,7 +415,7 @@ from semantic_kernel.connectors.memory.qdrant import QdrantCollection collection = QdrantCollection( collection_name="skhotels", - data_model_type=Hotel, + record_type=Hotel, named_vectors=False, ) ``` diff --git a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/redis-connector.md b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/redis-connector.md index f8f68cb4..14263850 100644 --- a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/redis-connector.md +++ b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/redis-connector.md @@ -164,7 +164,7 @@ You can then create a vector store instance using the `RedisStore` class, this w ```python -from semantic_kernel.connectors.memory.redis import RedisStore +from semantic_kernel.connectors.redis import RedisStore vector_store = RedisStore() ``` @@ -173,7 +173,7 @@ You can also create the vector store with your own instance of the redis databas ```python from redis.asyncio.client import Redis -from semantic_kernel.connectors.memory.redis import RedisStore +from semantic_kernel.connectors.redis import RedisStore redis_database = Redis.from_url(url="https://") vector_store = RedisStore(redis_database=redis_database) @@ -182,21 +182,21 @@ vector_store = RedisStore(redis_database=redis_database) You can also create a collection directly, but there are two types of collections, one for Hashes and one for JSON. ```python -from semantic_kernel.connectors.memory.redis import RedisHashsetCollection, RedisJsonCollection +from semantic_kernel.connectors.redis import RedisHashsetCollection, RedisJsonCollection -hash_collection = RedisHashsetCollection(collection_name="skhotels", data_model_type=Hotel) -json_collection = RedisJsonCollection(collection_name="skhotels", data_model_type=Hotel) +hash_collection = RedisHashsetCollection(collection_name="skhotels", record_type=Hotel) +json_collection = RedisJsonCollection(collection_name="skhotels", record_type=Hotel) ``` When creating a collection from the vector store, you can pass in the collection type, as a enum: `RedisCollectionTypes`, the default is a hash collection. ```python -from semantic_kernel.connectors.memory.redis import RedisStore, RedisCollectionTypes +from semantic_kernel.connectors.redis import RedisStore, RedisCollectionTypes vector_store = RedisStore() collection = vector_store.get_collection( collection_name="skhotels", - data_model_type=Hotel, + record_type=Hotel, collection_type=RedisCollectionTypes.JSON, ) @@ -296,9 +296,9 @@ await collection.GetAsync("myprefix_h1"); ::: zone-end ::: zone pivot="programming-language-python" ```python -from semantic_kernel.connectors.memory.redis import RedisJsonCollection +from semantic_kernel.connectors.redis import RedisJsonCollection -collection = RedisJsonCollection(collection_name="skhotels", data_model_type=hotel, prefix_collection_name_to_key_names=False) +collection = RedisJsonCollection(collection_name="skhotels", record_type=hotel, prefix_collection_name_to_key_names=False) await collection.get("myprefix_h1") ``` diff --git a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/sql-connector.md b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/sql-connector.md index a927fbf5..d7eb0b12 100644 --- a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/sql-connector.md +++ b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/sql-connector.md @@ -168,7 +168,7 @@ In order for the store and collection to work, it needs a connection string, thi In the snippets below, it is assumed that you have a data model class defined named 'DataModel'. ```python -from semantic_kernel.connectors.memory.sql_server import SqlServerStore +from semantic_kernel.connectors.sql_server import SqlServerStore vector_store = SqlServerStore() @@ -182,7 +182,7 @@ vector_collection = vector_store.get_collection("dbo.table_name", DataModel) It is possible to construct a direct reference to a named collection. ```python -from semantic_kernel.connectors.memory.sql_server import SqlServerCollection +from semantic_kernel.connectors.sql_server import SqlServerCollection vector_collection = SqlServerCollection("dbo.table_name", DataModel) ``` @@ -192,7 +192,7 @@ vector_collection = SqlServerCollection("dbo.table_name", DataModel) When you have specific requirements for the connection, you can also pass in a `pyodbc.Connection` object to the `SqlServerStore` constructor. This allows you to use a custom connection string or other connection options: ```python -from semantic_kernel.connectors.memory.sql_server import SqlServerStore +from semantic_kernel.connectors.sql_server import SqlServerStore import pyodbc # Create a connection to the SQL Server database @@ -207,18 +207,18 @@ You will have to make sure to close the connection yourself, as the store or col The SQL Server connector is limited to the Flat index type. -The `create_collection` method on the `SqlServerCollection` allows you to pass in a single or multiple custom queries to create the collection. The queries are executed in the order they are passed in, no results are returned. +The `ensure_collection_exists` method on the `SqlServerCollection` allows you to pass in a single or multiple custom queries to create the collection. The queries are executed in the order they are passed in, no results are returned. If this is done, there is no guarantee that the other methods still work as expected. The connector is not aware of the custom queries and will not validate them. If the `DataModel` has `id`, `content`, and `vector` as fields, then for instance you could create the table like this in order to also create a index on the content field: ```python -from semantic_kernel.connectors.memory.sql_server import SqlServerCollection +from semantic_kernel.connectors.sql_server import SqlServerCollection # Create a collection with a custom query async with SqlServerCollection("dbo.table_name", DataModel) as collection: - collection.create_collection( + collection.ensure_collection_exists( queries=["CREATE TABLE dbo.table_name (id INT PRIMARY KEY, content NVARCHAR(3000) NULL, vector VECTOR(1536) NULL ) PRIMARY KEY (id);", "CREATE INDEX idx_content ON dbo.table_name (content);"] ) diff --git a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/volatile-connector.md b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/volatile-connector.md index 1d5b7300..4043dae9 100644 --- a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/volatile-connector.md +++ b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/volatile-connector.md @@ -109,7 +109,7 @@ You can then create a vector store instance using the `VolatileStore` class. ```python -from semantic_kernel.connectors.memory.volatile import VolatileStore +from semantic_kernel.connectors.volatile import VolatileStore vector_store = VolatileStore() ``` @@ -117,9 +117,9 @@ vector_store = VolatileStore() You can also create a collection directly. ```python -from semantic_kernel.connectors.memory.volatile import VolatileCollection +from semantic_kernel.connectors.volatile import VolatileCollection -collection = VolatileCollection(collection_name="skhotels", data_model_type=Hotel) +collection = VolatileCollection(collection_name="skhotels", record_type=Hotel) ``` ## Serialization diff --git a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/weaviate-connector.md b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/weaviate-connector.md index 1d4d6870..f5f23616 100644 --- a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/weaviate-connector.md +++ b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/weaviate-connector.md @@ -252,7 +252,7 @@ If you want to use embedded: These should be set exclusively, so only one set of the above is present, otherwise it will raise an exception. ```python -from semantic_kernel.connectors.memory.weaviate import WeaviateStore +from semantic_kernel.connectors.weaviate import WeaviateStore store = WeaviateStore() ``` @@ -261,7 +261,7 @@ Alternatively, you can also pass in your own mongodb client if you want to have ```python import weaviate -from semantic_kernel.connectors.memory.weaviate import WeaviateStore +from semantic_kernel.connectors.weaviate import WeaviateStore client = weaviate.WeaviateAsyncClient(...) store = WeaviateStore(async_client=client) @@ -270,12 +270,12 @@ store = WeaviateStore(async_client=client) You can also create a collection directly, without the store. ```python -from semantic_kernel.connectors.memory.weaviate import WeaviateCollection +from semantic_kernel.connectors.weaviate import WeaviateCollection # `hotel` is a class created with the @vectorstoremodel decorator collection = WeaviateCollection( collection_name="my_collection", - data_model_type=hotel + record_type=hotel ) ``` diff --git a/semantic-kernel/concepts/vector-store-connectors/schema-with-record-definition.md b/semantic-kernel/concepts/vector-store-connectors/schema-with-record-definition.md index 11358d03..e0401502 100644 --- a/semantic-kernel/concepts/vector-store-connectors/schema-with-record-definition.md +++ b/semantic-kernel/concepts/vector-store-connectors/schema-with-record-definition.md @@ -171,8 +171,8 @@ To use the definition, pass it to the GetCollection method or a collection const ```python collection = vector_store.get_collection( collection_name="skhotels", - data_model_type=pd.DataFrame, - data_model_definition=hotel_definition, + record_type=pd.DataFrame, + definition=hotel_definition, ) ``` diff --git a/semantic-kernel/concepts/vector-store-connectors/serialization.md b/semantic-kernel/concepts/vector-store-connectors/serialization.md index 35897169..89f75aac 100644 --- a/semantic-kernel/concepts/vector-store-connectors/serialization.md +++ b/semantic-kernel/concepts/vector-store-connectors/serialization.md @@ -61,6 +61,9 @@ Depending on what kind of data model you have, the steps are done in different w 3. check if the record is a Pydantic model and use the `model_dump` of the model, see the note below for more info. 4. loop through the fields in the definition and create the dictionary +#### Optional: Embedding +When you have a data model with a `embedding_generator` field, or the collection has an `embedding_generator` field, the embedding will be generated and added to the dictionary before it is serialized to the store model. + #### Serialization Step 2: Dict to Store Model A method has to be supplied by the connector for converting the dictionary to the store model. This is done by the developer of the connector and is different for each store. @@ -81,28 +84,6 @@ The deserialization is done in the reverse order, it tries these options: > #### Using Pydantic with built-in serialization > When you define you model using a Pydantic BaseModel, it will use the `model_dump` and `model_validate` methods to serialize and deserialize the data model to and from a dict. This is done by using the model_dump method without any parameters, if you want to control that, consider implementing the `ToDictMethodProtocol` on your data model, as that is tried first. -## Serialization of vectors - -When you have a vector in your data model, it needs to either be a list of floats or list of ints, since that is what most stores need, if you want your class to store the vector in a different format, you can use the `serialize_function` and `deserialize_function` defined in the `VectorStoreRecordVectorField` annotation. For instance for a numpy array you can use the following annotation: - -```python -import numpy as np - -vector: Annotated[ - np.ndarray | None, - VectorStoreRecordVectorField( - dimensions=1536, - serialize_function=np.ndarray.tolist, - deserialize_function=np.array, - ), -] = None -``` - -If you do use a vector store that can handle native numpy arrays and you don't want to have them converted back and forth, you should setup the [direct serialization and deserialization](#direct-serialization-data-model-to-store-model) methods for the model and that store. - -> [!NOTE] -> This is only used when using the built-in serialization, when using the direct serialization you can handle the vector in any way you want. - ::: zone-end ::: zone pivot="programming-language-java" diff --git a/semantic-kernel/concepts/vector-store-connectors/vector-search.md b/semantic-kernel/concepts/vector-store-connectors/vector-search.md index 59869750..cf27d4f8 100644 --- a/semantic-kernel/concepts/vector-store-connectors/vector-search.md +++ b/semantic-kernel/concepts/vector-store-connectors/vector-search.md @@ -255,35 +255,32 @@ sealed class Glossary ## Vector Search -There are three searches currently supported in the Semantic Kernel Vector Store abstractions: -1. `vectorized_search` - 1. This is search based on a vector created in your code, the vector is passed in and used to search. -2. `vectorizable_text_search` - 1. This is search based on a text string that is vectorized by the vector store as part of the search, this is not always supported and often requires a specific setup of either the vector store or the index. -3. `vector_text_search` - 1. This is text search directly against the vector store, most stores support this and depending on the store it can be as simple as comparing values or more advanced keyword search. - -All searches can take a optional `VectorSearchOptions` instance as input. Each of the three searches have a Mixin class that needs to be part of it to surface the search methods and this always should be combined with the `VectorSearchBase[TKey, TRecord]` parent. - -Note that `VectorSearchBase` inherits from `VectorStoreRecordCollection`, as it uses some of the same methods, for instance for serialization and deserialization. +There are two searches currently supported in the Semantic Kernel Vector Store abstractions: +1. `search` (vector search) + 1. This is search based on a value that can be vectorized, by the `embedding_generator` field on the data model or record definition, or by the vector store itself. Or by directly supplying a vector. +2. `hybrid_search` -> see [Hybrid Search](./hybrid-search.md) + +All searches can take a optional set of parameters: +- `vector`: A vector used to search, can be supplied instead of the values, or in addition to the values for hybrid. +- `top`: The number of results to return, defaults to 3. +- `skip`: The number of results to skip, defaults to 0. +- `include_vectors`: Whether to include the vectors in the results, defaults to `false`. +- `filter`: A filter to apply to the results before the vector search is applied, defaults to `None`, in the form of a lambda expression: `lambda record: record.property == "value"`. +- `vector_property_name`: The name of the vector property to use for the search, defaults to the first vector property found on the data model or record definition. +- `include_total_count`: Whether to include the total count of results in the search result, defaults to `false`. Assuming you have a collection that already contains data, you can easily search it. Here is an example using Azure AI Search. ```python -from semantic_kernel.connectors.memory.azure_ai_search import AzureAISearchCollection, AzureAISearchStore -from semantic_kernel.data.vector_search import VectorSearchOptions +from semantic_kernel.connectors.azure_ai_search import AzureAISearchCollection, AzureAISearchStore # Create a Azure AI Search VectorStore object and choose an existing collection that already contains records. # Hotels is the data model decorated class. store = AzureAISearchStore() -collection: AzureAISearchCollection = store.get_collection("skhotels", Hotels) +collection: AzureAISearchCollection[str, Hotels] = store.get_collection(Hotels, collection_name="skhotels") -# Generate a vector for your search text. -# Just showing a placeholder method here for brevity. -vector = await generate_vector("I'm looking for a hotel where customer happiness is the priority.") - -search_results = await collection.vectorized_search( - vector=vector, options=VectorSearchOptions(vector_field_name="vector") +search_results = await collection.search( + query, vector_property_name="vector" ) hotels = [record.record async for record in search_results.results] print(f"Found hotels: {hotels}") @@ -292,111 +289,13 @@ print(f"Found hotels: {hotels}") > [!TIP] > For more information on how to generate embeddings see [embedding generation](./embedding-generation.md). -## Vector Search Options - -The following options can be provided using the `VectorSearchOptions` class. - -### Vector Field Name - -The `vector_field_name` option can be used to specify the name of the vector property to target during the search. -If none is provided, the first vector found on the data model or specified in the record definition will be used. - -Note that when specifying the `vector_field_name`, use the name of the property as defined on the data model or in the record definition. -Use this property name even if the property may be stored under a different name in the vector store. The storage name may e.g. be different -because of custom serialization settings. - -```Python - -from semantic_kernel.data.vector_search import VectorSearchOptions -from semantic_kernel.connectors.memory.in_memory import InMemoryVectorStore - -vector_store = InMemoryVectorStore() -collection = vector_store.get_collection("skproducts", Product) - -# Create the vector search options and indicate that we want to search the FeatureListEmbedding property. -vector_search_options = VectorSearchOptions(vector_field_name="feature_list_embedding") - -# This snippet assumes search_vector is already provided, having been created using the embedding model of your choice. -search_result = await collection.vectorized_search(vector=search_vector, options=vector_search_options) -products = [record async for record in search_result.results] - -``` - -### Top and Skip - -The `top` and `skip` options allow you to limit the number of results to the Top n results and -to skip a number of results from the top of the resultset. -Top and Skip can be used to do paging if you wish to retrieve a large number of results using separate calls. - -```python -# Create the vector search options and indicate that we want to skip the first 40 results and then get the next 20. -vector_search_options = VectorSearchOptions(top=20, skip=40) - -# This snippet assumes `vector` is already provided, having been created using the embedding model of your choice. -search_result = await collection.vectorized_search(vector=vector, options=vector_search_options) -async for result in search_result.results: - print(result.record) -``` - -The default values for `top` is 3 and `skip` is 0. - -### Include Vectors - -The `include_vectors` option allows you to specify whether you wish to return vectors in the search results. -If `false`, the vector properties on the returned model will be left null. -Using `false` can significantly reduce the amount of data retrieved from the vector store during search, -making searches more efficient. - -The default value for `include_vectors` is `false`. - -> [!TIP] -> Make sure your data model allows the vector fields to be None! If not and you keep this to the default it might raise an error. - -### Filter - -The `filter` option can be used to provide a filter for filtering the records in the chosen collection -before applying the vector search. - -This has multiple benefits: - -- Reduce latency and processing cost, since only records remaining after filtering need to be compared with the search vector and therefore fewer vector comparisons have to be done. -- Limit the resultset for e.g. access control purposes, by excluding data that the user shouldn't have access to. - -Note that in order for fields to be used for filtering, many vector stores require those fields to be indexed first. -Some vector stores will allow filtering using any field, but may optionally allow indexing to improve filtering performance. - -If creating a collection via the Semantic Kernel vector store abstractions and you wish to enable filtering on a field, -set the `is_filterable` property to true when defining your data model or when creating your record definition. - -> [!TIP] -> For more information on how to set the `is_filterable` property, refer to [VectorStoreRecordDataAttribute parameters](./defining-your-data-model.md#vectorstorerecorddatafield-parameters) or [VectorStoreRecordDataField configuration settings](./schema-with-record-definition.md). - -To create a filter use the `VectorSearchFilter` class. You can combine multiple filter clauses together in one `VectorSearchFilter`. -All filter clauses are combined with `and`. -Note that when providing a property name when constructing the filter, use the name of the property as defined on the data model or in the record definition. -Use this property name even if the property may be stored under a different name in the vector store. The storage name may e.g. be different -because of custom serialization settings. - -```python -# Filter where category == 'External Definitions' and tags contain 'memory'. -filter = VectorSearchFilter.equal_to('category', "External Definitions").any_tag_equal_to('tags', "memory") - -# Create the vector search options and set the filter on the options. -vector_search_options = VectorSearchOptions(filter=filter) - -# This snippet assumes search_vector is already provided, having been created using the embedding model of your choice. -search_result = await collection.vectorized_search(vector=search_vector, options=vector_search_options) -``` -You can chain any number of filters together as shown above, and they will be combined with `and`. - -#### EqualTo filter clause +### Filters -Use `EqualTo` (class) or `.equal_to` ((class)method on the filter object) for a direct comparison between property and value. +The `filter` parameter can be used to provide a filter for filtering the records in the chosen collection. It is defined as a lambda expression, or a string of a lambda expression, e.g. `lambda record: record.property == "value"`. -#### AnyTagEqualTo filter clause +It is important to understand that these are not executed directly, rather they are parsed into the syntax matching the vector stores, the only exception to this is the `InMemoryCollection` which does execute the filter directly. -Use `AnyTagEqualTo`/`.any_tag_equal_to` to check if any of the strings, stored in a tag property in the vector store, contains a provided value. -For a property to be considered a tag property, it needs to be a List, array or other enumerable of string. +Given this flexibility, it is important to review the documentation of a specific store to understand which filters are supported, for instance not all vector stores support negative filters (i.e. `lambda x: not x.value`), and that won't become apparent until the search is executed. ::: zone-end ::: zone pivot="programming-language-java" diff --git a/semantic-kernel/support/migration/toc.yml b/semantic-kernel/support/migration/toc.yml index 689727a3..b4ecf9c4 100644 --- a/semantic-kernel/support/migration/toc.yml +++ b/semantic-kernel/support/migration/toc.yml @@ -20,6 +20,8 @@ href: vectorstore-april-2025.md - name: Vector Store changes - May 2025 href: vectorstore-may-2025.md +- name: Vector Store changes for Python - June 2025 + href: vectorstore-python-june-2025.md - name: Sessions Python Plugin Migration Guide - May 2025 href: sessions-python-plugin-migration-guide-2025.md - name: Functions.Markdown to Functions.Yaml Package Migration Guide diff --git a/semantic-kernel/support/migration/vectorstore-python-june-2025.md b/semantic-kernel/support/migration/vectorstore-python-june-2025.md new file mode 100644 index 00000000..d4662c80 --- /dev/null +++ b/semantic-kernel/support/migration/vectorstore-python-june-2025.md @@ -0,0 +1,555 @@ +--- +title: Vector Store changes for Python - June 2025 +description: Describes the changes included in the June 2025 Vector Store release and how to migrate +author: edvan +ms.topic: conceptual +ms.author: edvan +ms.date: 01/06/2025 +ms.service: semantic-kernel +--- +# Semantic Kernel Python Vector Store Migration Guide + +## Overview + +This guide covers the major vector store updates introduced in Semantic Kernel version 1.34, which represents a significant overhaul of the vector store implementation to align with the .NET SDK and provide a more unified, intuitive API. The changes consolidate everything under `semantic_kernel.data.vector` and improve the connector architecture. + +## Key Improvements Summary + +- **Unified Field Model**: Single `VectorStoreField` class replaces multiple field types +- **Integrated Embeddings**: Direct embedding generation in vector field specifications +- **Simplified Search**: Easy creation of search functions directly on collections +- **Consolidated Structure**: Everything under `semantic_kernel.data.vector` and `semantic_kernel.connectors` +- **Enhanced Text Search**: Improved text search capabilities with streamlined connectors +- **Deprecation**: Old `memory_stores` are deprecated in favor of the new vector store architecture + +## 1. Integrated Embeddings and Vector Store Models/Fields Updates + +There are a number of changes to the way you define your vector store model, the biggest is that we now support integrated embeddings directly in the vector store field definitions. This means that when you specify a field to be a vector, the content of that field is automatically embedded using the specified embedding generator, such as OpenAI's text embedding model. This simplifies the process of creating and managing vector fields. + +When you define that field, you need to make sure of three things, especially when using a Pydantic model: +1. **typing**: The field will likely have three types, `list[float]`, `str` or something else for the input to the embedding generator, and `None` for when the field is unset. +2. **default value**: The field must have a default value of `None` or something else, so that there is no error when getting records from `get` or `search` with `include_vectors=False` which is the default now. + +There are two concerns here, the first is that when decorating a class with `vectorstoremodel`, the first type annotation of the field is used to fill the `type` parameter of the `VectorStoreField` class, so you need to make sure that the first type annotation is the right type for the vector store collection to be created with, often `list[float]`. By default, the `get` and `search` methods do not include_vectors in the results, so the field needs a default value, and the typing needs to correspond to that, hence often `None` is allowed, and the default is set to `None`. When the field is created, the values that need to be embedded are in this field, often strings, so `str` also needs to be included. The reason for this change is to allow more flexibility in what is embedded and what is actually stored in data fields, this would be a common setup: + +```python +from semantic_kernel.data.vector import VectorStoreField, vectorstoremodel +from typing import Annotated +from dataclasses import dataclass + +@vectorstoremodel +@dataclass +class MyRecord: + content: Annotated[str, VectorStoreField('data', is_indexed=True, is_full_text_indexed=True)] + title: Annotated[str, VectorStoreField('data', is_indexed=True, is_full_text_indexed=True)] + id: Annotated[str, VectorStoreField('key')] + vector: Annotated[list[float] | str | None, VectorStoreField( + 'vector', + dimensions=1536, + distance_function="cosine", + embedding_generator=OpenAITextEmbedding(ai_model_id="text-embedding-3-small"), + )] = None + + def __post_init__(self): + if self.vector is None: + self.vector = f"Title: {self.title}, Content: {self.content}" +``` + +Note the __post_init__ method, this creates a value that get's embedded, which is more then a single field. The three types are also present. + +### Before: Separate Field Classes + +```python +from semantic_kernel.data import ( + VectorStoreRecordKeyField, + VectorStoreRecordDataField, + VectorStoreRecordVectorField +) + +# Old approach with separate field classes +fields = [ + VectorStoreRecordKeyField(name="id"), + VectorStoreRecordDataField(name="text", is_filterable=True, is_full_text_searchable=True), + VectorStoreRecordVectorField(name="vector", dimensions=1536, distance_function="cosine") +] +``` + +### After: Unified VectorStoreField with Integrated Embeddings + +```python +from semantic_kernel.data.vector import VectorStoreField +from semantic_kernel.connectors.ai.open_ai import OpenAITextEmbedding + +# New unified approach with integrated embeddings +embedding_service = OpenAITextEmbedding( + ai_model_id="text-embedding-3-small" +) + +fields = [ + VectorStoreField( + "key", + name="id", + ), + VectorStoreField( + "data", + name="text", + is_indexed=True, # Previously is_filterable + is_full_text_indexed=True # Previously is_full_text_searchable + ), + VectorStoreField( + "vector", + name="vector", + dimensions=1536, + distance_function="cosine", + embedding_generator=embedding_service # Integrated embedding generation + ) +] +``` + +### Key Changes in Field Definition + +1. **Single Field Class**: `VectorStoreField` replaces all previous field types +2. **Field Type Specification**: Use `field_type: Literal["key", "data", "vector"]` parameter, this can be a positional parameter, so `VectorStoreField("key")` is valid. +3. **Enhanced properties**: + - `storage_name` has been added, when set, that is used as the field name in the vector store, otherwise the `name` parameter is used. + - `dimensions` is now a required parameter for vector fields. + - `distance_function` and `index_kind` are both optional and will be set to `DistanceFunction.DEFAULT` and `IndexKind.DEFAULT` respectively if not specified and only for vector fields, each vector store implementation has logic that chooses a Default for that store. +4. **Property Renames**: + - `property_type` → `type_` as an attribute and `type` in constructors + - `is_filterable` → `is_indexed` + - `is_full_text_searchable` → `is_full_text_indexed` +5. **Integrated Embeddings**: Add `embedding_generator` directly to vector fields, alternatively you can set the `embedding_generator` on the vector store collection itself, which will be used for all vector fields in that store, this value takes precedence over the collection level embedding generator. + +## 2. New Methods on Stores and Collections + +### Enhanced Store Interface + +```python +from semantic_kernel.connectors.in_memory import InMemoryStore + +# Before: Limited collection methods +collection = InMemoryStore.get_collection("my_collection", record_type=MyRecord) + +# After: Slimmer collection interface with new methods +collection = InMemoryStore.get_collection(MyRecord) +# if the record type has the `vectorstoremodel` decorator it can contain both the collection_name and the definition for the collection. + +# New methods for collection management +await store.collection_exists("my_collection") +await store.ensure_collection_deleted("my_collection") +# both of these methods, create a simple model to streamline doing collection management tasks. +# they both call the underlying `VectorStoreCollection` methods, see below. +``` + +### Enhanced Collection Interface +```python +from semantic_kernel.connectors.in_memory import InMemoryCollection + +collection = InMemoryCollection( + record_type=MyRecord, + embedding_generator=OpenAITextEmbedding(ai_model_id="text-embedding-3-small") # Optional, if there is no embedding generator set on the record type +) +# If both the collection and the record type have an embedding generator set, the record type's embedding generator will be used for the collection. If neither is set, it is assumed the vector store itself can create embeddings, or that vectors are included in the records already, if that is not the case, it will likely raise. + +# Enhanced collection operations +await collection.collection_exists() +await collection.ensure_collection_exists() +await collection.ensure_collection_deleted() + +# CRUD methods +# Removed batch operations, all CRUD operations can now take both a single record or a list of records +records = [ + MyRecord(id="1", text="First record"), + MyRecord(id="2", text="Second record") +] +ids = ["1", "2"] +# this method adds vectors automatically +await collection.upsert(records) + +# You can do get with one or more ids, and it will return a list of records +await collection.get(ids) # Returns a list of records +# you can also do a get without ids, with top, skip and order_by parameters +await collection.get(top=10, skip=0, order_by='id') +# the order_by parameter can be a string or a dict, with the key being the field name and the value being True for ascending or False for descending order. +# At this time, not all vector stores support this method. + +# Delete also allows for single or multiple ids +await collection.delete(ids) + +query = "search term" +# New search methods, these use the built-in embedding generator to take the value and create a vector +results = await collection.search(query, top=10) +results = await collection.hybrid_search(query, top=10) + +# You can also supply a vector directly +query_vector = [0.1, 0.2, 0.3] # Example vector +results = await collection.search(vector=query_vector, top=10) +results = await collection.hybrid_search(query, vector=query_vector, top=10) + +``` + +## 3. Enhanced Filters for search + +The new vector store implementation moves from string-based FilterClause objects to more powerful and type-safe lambda expressions or callable filters. + +### Before: FilterClause Objects + +```python +from semantic_kernel.data.text_search import SearchFilter, EqualTo, AnyTagsEqualTo +from semantic_kernel.data.vector_search import VectorSearchFilter + +# Creating filters using FilterClause objects +text_filter = SearchFilter() +text_filter.equal_to("category", "AI") +text_filter.equal_to("status", "active") + +# Vector search filters +vector_filter = VectorSearchFilter() +vector_filter.equal_to("category", "AI") +vector_filter.any_tag_equal_to("tags", "important") + +# Using in search +results = await collection.search( + "query text", + options=VectorSearchOptions(filter=vector_filter) +) +``` + +### After: Lambda Expression Filters + +```python +# When defining the collection with the generic type hints, most IDE's will be able to infer the type of the record, so you can use the record type directly in the lambda expressions. +collection = InMemoryCollection[str, MyRecord](MyRecord) + +# Using lambda expressions for more powerful and type-safe filtering +# The code snippets below work on a data model with more fields then defined earlier. + +# Direct lambda expressions +results = await collection.search( + "query text", + filter=lambda record: record.category == "AI" and record.status == "active" +) + +# Complex filtering with multiple conditions +results = await collection.search( + "query text", + filter=lambda record: ( + record.category == "AI" and + record.score > 0.8 and + "important" in record.tags + ) +) + +# Combining conditions with boolean operators +results = await collection.search( + "query text", + filter=lambda record: ( + record.category == "AI" or record.category == "ML" + ) and record.published_date >= datetime(2024, 1, 1) +) + +# Range filtering (now possible with lambda expressions) +results = await collection.search( + "query text", + filter=lambda record: 0.5 <= record.confidence_score <= 0.9 +) +``` + +### Migration Tips for Filters + +1. **Simple equality**: `filter.equal_to("field", "value")` becomes `lambda r: r.field == "value"` +2. **Multiple conditions**: Chain with `and`/`or` operators instead of multiple filter calls +3. **Tag/array containment**: `filter.any_tag_equal_to("tags", "value")` becomes `lambda r: "value" in r.tags` +4. **Enhanced capabilities**: Support for range queries, complex boolean logic, and custom predicates + + +## 4. Improved Ease of Creating Search Functions + +### Before: Search Function Creation with VectorStoreTextSearch + +```python +from semantic_kernel.connectors.in_memory import InMemoryCollection +from semantic_kernel.data import VectorStoreTextSearch + +collection = InMemoryCollection(collection_name='collection', record_type=MyRecord) +search = VectorStoreTextSearch.from_vectorized_search(vectorized_search=collection, embedding_generator=OpenAITextEmbedding(ai_model_id="text-embedding-3-small")) + +search_function = search.create_search( + function_name='search', + ... +) + +``` + +### After: Direct Search Function Creation + +```python +collection = InMemoryCollection(MyRecord) +# Create search function directly on collection +search_function = collection.create_search_function( + function_name="search", + search_type="vector", # or "keyword_hybrid" + top=10, + vector_property_name="vector", # Name of the vector field +) + +# Add to kernel directly +kernel.add_function(plugin_name="memory", function=search_function) +``` + +## 5. Connector Renames and Import Changes + +### Import Path Consolidation + +```python +# Before: Scattered imports +from semantic_kernel.connectors.memory.azure_cognitive_search import AzureCognitiveSearchMemoryStore +from semantic_kernel.connectors.memory.chroma import ChromaMemoryStore +from semantic_kernel.connectors.memory.pinecone import PineconeMemoryStore +from semantic_kernel.connectors.memory.qdrant import QdrantMemoryStore + +# After: Consolidated under connectors +from semantic_kernel.connectors.azure_ai_search import AzureAISearchStore +from semantic_kernel.connectors.chroma import ChromaVectorStore +from semantic_kernel.connectors.pinecone import PineconeVectorStore +from semantic_kernel.connectors.qdrant import QdrantVectorStore + +# Alternative after: Consolidated with lazy loading: +from semantic_kernel.connectors.memory import ( + AzureAISearchStore, + ChromaVectorStore, + PineconeVectorStore, + QdrantVectorStore, + WeaviateVectorStore, + RedisVectorStore +) + +``` + +### Connector Class Renames + +| Old Name | New Name | +|----------|----------| +| AzureCosmosDBforMongoDB* | CosmosMongo* | +| AzureCosmosDBForNoSQL* | CosmosNoSql* | + + +## 6. Text Search Improvements and Removed Bing Connector + +### Bing Connector Removed and Enhanced Text Search Interface + +The Bing text search connector has been removed. Migrate to alternative search providers: + +```python +# Before: Bing Connector (REMOVED) +from semantic_kernel.connectors.search.bing import BingConnector + +bing_search = BingConnector(api_key="your-bing-key") + +# After: Use Brave Search or other providers +from semantic_kernel.connectors.brave import BraveSearch +# or +from semantic_kernel.connectors.search import BraveSearch + +brave_search = BraveSearch() + +# Create text search function +text_search_function = brave_search.create_search_function( + function_name="web_search", + query_parameter_name="query", + description="Search the web for information" +) + +kernel.add_function(plugin_name="search", function=text_search_function) + +``` + +### Improved Search Methods + +### Before: Three separate search methods with different return types + +```python +from semantic_kernel.connectors.brave import BraveSearch +brave_search = BraveSearch() +# Before: Separate search methods +search_results: KernelSearchResult[str] = await brave_search.search( + query="semantic kernel python", + top=5, +) + +search_results: KernelSearchResult[TextSearchResult] = await brave_search.get_text_search_results( + query="semantic kernel python", + top=5, +) + +search_results: KernelSearchResult[BraveWebPage] = await brave_search.get_search_results( + query="semantic kernel python", + top=5, +) +``` + +### After: Unified search method with output type parameter + +```python +from semantic_kernel.data.text_search import SearchOptions +# Enhanced search results with metadata +search_results: KernelSearchResult[str] = await brave_search.search( + query="semantic kernel python", + output_type=str, # can also be TextSearchResult or anything else for search engine specific results, default is `str` + top=5, + filter=lambda result: result.country == "NL", # Example filter +) + +async for result in search_results.results: + assert isinstance(result, str) # or TextSearchResult if using that type + print(f"Result: {result}") + print(f"Metadata: {search_results.metadata}") +``` + +## 7. Deprecation of Old Memory Stores + +All the old memory stores, based on `MemoryStoreBase` have been moved into `semantic_kernel.connectors.memory_stores` and are now marked as deprecated. Most of them have a equivalent new implementation based on VectorStore and VectorStoreCollection, which can be found in `semantic_kernel.connectors.memory`. + +These connectors will be removed completely: +- `AstraDB` +- `Milvus` +- `Usearch` + +If you need any of these still, either, make sure to take over the code from the deprecated module and the `semantic_kernel.memory` folder, or [implement your own vector store collection](../../concepts/vector-store-connectors/how-to/build-your-own-connector.md) based on the new `VectorStoreCollection` class. + +If there is a large demand based on github feedback, we will consider bringing them back, but for now, they are not maintained and will be removed in the future. + +### Migration from SemanticTextMemory + +```python +# Before: SemanticTextMemory (DEPRECATED) +from semantic_kernel.memory import SemanticTextMemory +from semantic_kernel.connectors.ai.open_ai import OpenAITextEmbeddingGenerationService + +embedding_service = OpenAITextEmbeddingGenerationService(ai_model_id="text-embedding-3-small") +memory = SemanticTextMemory(storage=vector_store, embeddings_generator=embedding_service) + +# Store memory +await memory.save_information(collection="docs", text="Important information", id="doc1") + +# Search memory +results = await memory.search(collection="docs", query="important", limit=5) +``` + +```python +# After: Direct Vector Store Usage +from semantic_kernel.data.vector import VectorStoreField, vectorstoremodel +from semantic_kernel.connectors.in_memory import InMemoryCollection + +# Define data model +@vectorstoremodel +@dataclass +class MemoryRecord: + id: Annotated[str, VectorStoreField('key')] + text: Annotated[str, VectorStoreField('data', is_full_text_indexed=True)] + embedding: Annotated[list[float] | str | None, VectorStoreField('vector', dimensions=1536, distance_function="cosine", embedding_generator=OpenAITextEmbedding(ai_model_id="text-embedding-3-small"))] = None + +# Create vector store with integrated embeddings +collection = InMemoryCollection( + record_type=MemoryRecord, + embedding_generator=OpenAITextEmbedding(ai_model_id="text-embedding-3-small") # Optional, if not set on the record type +) + +# Store with automatic embedding generation +record = MemoryRecord(id="doc1", text="Important information", embedding='Important information') +await collection.upsert(record) + +# Search with built-in function +search_function = collection.create_search_function( + function_name="search_docs", + search_type="vector" +) +``` + +### Memory Plugin Migration + +When you want to have a plugin that can also save information, then you can easily create that like this: + +```python +# Before: TextMemoryPlugin (DEPRECATED) +from semantic_kernel.core_plugins import TextMemoryPlugin + +memory_plugin = TextMemoryPlugin(memory) +kernel.add_plugin(memory_plugin, "memory") +``` + +```python +# After: Custom plugin using vector store search functions +from semantic_kernel.functions import kernel_function + +class VectorMemoryPlugin: + def __init__(self, collection: VectorStoreCollection): + self.collection = collection + + @kernel_function(name="save") + async def save_memory(self, text: str, key: str) -> str: + record = MemoryRecord(id=key, text=text, embedding=text) + await self.collection.upsert(record) + return f"Saved to {self.collection.collection_name}" + + @kernel_function(name="search") + async def search_memory(self, query: str, limit: int = 5) -> str: + results = await self.collection.search( + query, top=limit, vector_property_name="embedding" + ) + return "\n".join([r.record.text async for r in results.results]) + +# Register the new plugin +memory_plugin = VectorMemoryPlugin(collection) +kernel.add_plugin(memory_plugin, "memory") +``` + +## Migration Checklist for Vector Search + +### Step 1: Update Imports +- [ ] Replace memory store imports with vector store equivalents +- [ ] Update field imports to use `VectorStoreField` +- [ ] Remove Bing connector imports + +### Step 2: Update Field Definitions +- [ ] Convert to unified `VectorStoreField` class +- [ ] Update property names (`is_filterable` → `is_indexed`) +- [ ] Add integrated embedding generators to vector fields + +### Step 3: Update Collection Usage +- [ ] Replace memory operations with vector store methods +- [ ] Use new batch operations where applicable +- [ ] Implement new search function creation + +### Step 4: Update Search Implementation +- [ ] Replace manual search functions with `create_search_function` +- [ ] Update text search to use new providers +- [ ] Implement hybrid search where beneficial +- [ ] Migrate from `FilterClause` to `lambda` expressions for filtering + +### Step 5: Remove Deprecated Code +- [ ] Remove `SemanticTextMemory` usage +- [ ] Remove `TextMemoryPlugin` dependencies + +## Performance and Feature Benefits + +### Performance Improvements +- **Batch Operations**: New batch upsert/delete methods improve throughput +- **Integrated Embeddings**: Eliminates separate embedding generation steps +- **Optimized Search**: Built-in search functions are optimized for each store type + +### Feature Enhancements +- **Hybrid Search**: Combines vector and text search for better results +- **Advanced Filtering**: Enhanced filter expressions and indexing + +### Developer Experience +- **Simplified API**: Fewer classes and methods to learn +- **Consistent Interface**: Unified approach across all vector stores +- **Better Documentation**: Clear examples and migration paths +- **Future-Proof**: Aligned with .NET SDK for consistent cross-platform development + +## Conclusion + +The vector store updates discussed above represent a significant improvement in the Semantic Kernel Python SDK. The new unified architecture provides better performance, enhanced features, and a more intuitive developer experience. While migration requires updating imports and refactoring existing code, the benefits in maintainability and functionality make this upgrade highly recommended. + +For additional help with migration, refer to the updated samples in the `samples/concepts/memory/` directory and the comprehensive API documentation.