diff --git a/.openpublishing.redirection.ai.json b/.openpublishing.redirection.ai.json index 6827c469e6d96..71417c5a967ec 100644 --- a/.openpublishing.redirection.ai.json +++ b/.openpublishing.redirection.ai.json @@ -18,6 +18,11 @@ "redirect_url": "/dotnet/ai/evaluation/libraries", "redirect_document_id": true }, + { + "source_path_from_root": "/docs/ai/conceptual/vector-databases.md", + "redirect_url": "/dotnet/ai/vector-stores/overview", + "redirect_document_id": true + }, { "source_path_from_root": "/docs/ai/get-started/dotnet-ai-overview.md", "redirect_url": "/dotnet/ai/overview", @@ -35,6 +40,11 @@ "source_path_from_root": "/docs/ai/how-to/work-with-local-models.md", "redirect_url": "/dotnet/ai" }, + { + "source_path_from_root": "/docs/ai/quickstarts/build-vector-search-app.md", + "redirect_url": "/dotnet/ai/vector-stores/how-to/build-vector-search-app", + "redirect_document_id": true + }, { "source_path_from_root": "/docs/ai/quickstarts/evaluate-ai-response.md", "redirect_url": "/dotnet/ai/evaluation/evaluate-ai-response", @@ -94,5 +104,10 @@ "redirect_url": "/dotnet/ai/evaluation/evaluate-with-reporting", "redirect_document_id": true } + { + "source_path_from_root": "/docs/ai/tutorials/tutorial-ai-vector-search.md", + "redirect_url": "/dotnet/ai/vector-stores/tutorial-vector-search", + "redirect_document_id": true + } ] } diff --git a/docs/ai/conceptual/embeddings.md b/docs/ai/conceptual/embeddings.md index 4f1f0f206b0be..6706eeb7c44c6 100644 --- a/docs/ai/conceptual/embeddings.md +++ b/docs/ai/conceptual/embeddings.md @@ -49,7 +49,7 @@ You generate embeddings for your raw data by using an AI embedding model, which ### Store and process embeddings in a vector database -After you generate embeddings, you'll need a way to store them so you can later retrieve them with calls to an LLM. Vector databases are designed to store and process vectors, so they're a natural home for embeddings. Different vector databases offer different processing capabilities, so you should choose one based on your raw data and your goals. For information about your options, see [Vector databases for .NET + AI](vector-databases.md). +After you generate embeddings, you'll need a way to store them so you can later retrieve them with calls to an LLM. Vector databases are designed to store and process vectors, so they're a natural home for embeddings. Different vector databases offer different processing capabilities, so you should choose one based on your raw data and your goals. For information about your options, see [Vector databases for .NET + AI](../vector-stores/overview.md). ### Using embeddings in your LLM solution diff --git a/docs/ai/conceptual/understanding-tokens.md b/docs/ai/conceptual/understanding-tokens.md index 9d9e76d55af4b..fc1c97ff6b25a 100644 --- a/docs/ai/conceptual/understanding-tokens.md +++ b/docs/ai/conceptual/understanding-tokens.md @@ -106,4 +106,4 @@ Generative AI services might also be limited regarding the maximum number of tok - [Use Microsoft.ML.Tokenizers for text tokenization](../how-to/use-tokenizers.md) - [How generative AI and LLMs work](how-genai-and-llms-work.md) - [Understand embeddings](embeddings.md) -- [Work with vector databases](vector-databases.md) +- [Work with vector databases](../vector-stores/overview.md) diff --git a/docs/ai/conceptual/vector-databases.md b/docs/ai/conceptual/vector-databases.md deleted file mode 100644 index 2373efce66156..0000000000000 --- a/docs/ai/conceptual/vector-databases.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: "Using Vector Databases to Extend LLM Capabilities" -description: "Learn how vector databases extend LLM capabilities by storing and processing embeddings in .NET." -ms.topic: concept-article -ms.date: 05/29/2025 ---- - -# Vector databases for .NET + AI - -Vector databases are designed to store and manage vector [embeddings](embeddings.md). Embeddings are numeric representations of non-numeric data that preserve semantic meaning. Words, documents, images, audio, and other types of data can all be vectorized. You can use embeddings to help an AI model understand the meaning of inputs so that it can perform comparisons and transformations, such as summarizing text, finding contextually related data, or creating images from text descriptions. - -For example, you can use a vector database to: - -- Identify similar images, documents, and songs based on their contents, themes, sentiments, and styles. -- Identify similar products based on their characteristics, features, and user groups. -- Recommend content, products, or services based on user preferences. -- Identify the best potential options from a large pool of choices to meet complex requirements. -- Identify data anomalies or fraudulent activities that are dissimilar from predominant or normal patterns. - -## Understand vector search - -Vector databases provide vector search capabilities to find similar items based on their data characteristics rather than by exact matches on a property field. Vector search works by analyzing the vector representations of your data that you created using an AI embedding model such the [Azure OpenAI embedding models](/azure/ai-services/openai/concepts/models#embeddings-models). The search process measures the distance between the data vectors and your query vector. The data vectors that are closest to your query vector are the ones that are found to be most similar semantically. - -Some services such as [Azure Cosmos DB for MongoDB vCore](/azure/cosmos-db/mongodb/vcore/vector-search) provide native vector search capabilities for your data. Other databases can be enhanced with vector search by indexing the stored data using a service such as Azure AI Search, which can scan and index your data to provide vector search capabilities. - -## Vector search workflows with .NET and OpenAI - -Vector databases and their search features are especially useful in [RAG pattern](rag.md) workflows with Azure OpenAI. This pattern allows you to augment or enhance your AI model with additional semantically rich knowledge of your data. A common AI workflow using vector databases might include the following steps: - -1. Create embeddings for your data using an OpenAI embedding model. -1. Store and index the embeddings in a vector database or search service. -1. Convert user prompts from your application to embeddings. -1. Run a vector search across your data, comparing the user prompt embedding to the embeddings your database. -1. Use a language model such as GPT-35 or GPT-4 to assemble a user friendly completion from the vector search results. - -Visit the [Implement Azure OpenAI with RAG using vector search in a .NET app](../tutorials/tutorial-ai-vector-search.md) tutorial for a hands-on example of this flow. - -Other benefits of the RAG pattern include: - -- Generate contextually relevant and accurate responses to user prompts from AI models. -- Overcome LLM tokens limits - the heavy lifting is done through the database vector search. -- Reduce the costs from frequent fine-tuning on updated data. - -## Related content - -- [Implement Azure OpenAI with RAG using vector search in a .NET app](../tutorials/tutorial-ai-vector-search.md) diff --git a/docs/ai/dotnet-ai-ecosystem.md b/docs/ai/dotnet-ai-ecosystem.md index ac6dcdcc5259e..97979820b7d6b 100644 --- a/docs/ai/dotnet-ai-ecosystem.md +++ b/docs/ai/dotnet-ai-ecosystem.md @@ -20,7 +20,7 @@ The .NET ecosystem provides many powerful tools, libraries, and services to deve ## Other AI-related Microsoft.Extensions libraries -The [📦 Microsoft.Extensions.VectorData.Abstractions package](https://www.nuget.org/packages/Microsoft.Extensions.VectorData.Abstractions/) provides a unified layer of abstractions for interacting with a variety of vector stores. It lets you store processed chunks in vector stores such as Qdrant, Azure SQL, CosmosDB, MongoDB, ElasticSearch, and many more. For more information, see [Build a .NET AI vector search app](quickstarts/build-vector-search-app.md). +The [📦 Microsoft.Extensions.VectorData.Abstractions package](https://www.nuget.org/packages/Microsoft.Extensions.VectorData.Abstractions/) provides a unified layer of abstractions for interacting with a variety of vector stores. It lets you store processed chunks in vector stores such as Qdrant, Azure SQL, CosmosDB, MongoDB, ElasticSearch, and many more. For more information, see [Build a .NET AI vector search app](vector-stores/how-to/build-vector-search-app.md). The [📦 Microsoft.Extensions.DataIngestion package](https://www.nuget.org/packages/Microsoft.Extensions.DataIngestion) provides foundational .NET building blocks for data ingestion. It enables developers to read, process, and prepare documents for AI and machine learning workflows, especially retrieval-augmented generation (RAG) scenarios. For more information, see [Data ingestion](conceptual/data-ingestion.md). diff --git a/docs/ai/overview.md b/docs/ai/overview.md index ed23e161a95cd..fbf5273042704 100644 --- a/docs/ai/overview.md +++ b/docs/ai/overview.md @@ -53,7 +53,7 @@ We recommend the following sequence of tutorials and articles for an introductio |-----------------------------|-------------------------------------------------------------------------| | Create a chat application | [Build an Azure AI chat app with .NET](./quickstarts/build-chat-app.md) | | Summarize text | [Summarize text using Azure AI chat app](./quickstarts/prompt-model.md) | -| Chat with your data | [Get insight about your data from a .NET Azure AI chat app](./quickstarts/build-vector-search-app.md) | +| Chat with your data | [Get insight about your data from a .NET Azure AI chat app](./vector-stores/how-to/build-vector-search-app.md) | | Call .NET functions with AI | [Extend Azure AI using tools and execute a local function with .NET](./quickstarts/use-function-calling.md) | | Generate images | [Generate images from text](./quickstarts/text-to-image.md) | | Train your own model | [ML.NET tutorial](https://dotnet.microsoft.com/learn/ml-dotnet/get-started-tutorial/intro) | diff --git a/docs/ai/toc.yml b/docs/ai/toc.yml index 9e0de45d8ab5f..a72d5c3917d53 100644 --- a/docs/ai/toc.yml +++ b/docs/ai/toc.yml @@ -48,8 +48,6 @@ items: href: conceptual/understanding-tokens.md - name: Embeddings href: conceptual/embeddings.md - - name: Vector databases - href: conceptual/vector-databases.md - name: Data ingestion href: conceptual/data-ingestion.md - name: Prompt engineering @@ -81,7 +79,7 @@ items: - name: Get started with the RAG sample href: get-started-app-chat-template.md - name: Implement RAG using vector search - href: tutorials/tutorial-ai-vector-search.md + href: vector-stores/tutorial-vector-search.md - name: Scale Azure OpenAI with Azure Container Apps href: get-started-app-chat-scaling-with-azure-container-apps.md - name: MCP client/server @@ -96,6 +94,30 @@ items: items: - name: Use Microsoft.ML.Tokenizers href: how-to/use-tokenizers.md +- name: Vector stores + items: + - name: Vector databases overview + href: vector-stores/overview.md + - name: How-to + items: + - name: Use vector stores + href: vector-stores/how-to/use-vector-stores.md + - name: Build a vector search app + href: vector-stores/how-to/build-vector-search-app.md + - name: Ingest data into a vector store + href: vector-stores/how-to/vector-store-data-ingestion.md + - name: Define your data model + href: vector-stores/defining-your-data-model.md + - name: Define schema with record definitions + href: vector-stores/schema-with-record-definition.md + - name: Dynamic data model + href: vector-stores/dynamic-data-model.md + - name: Generate embeddings + href: vector-stores/embedding-generation.md + - name: Vector search + href: vector-stores/vector-search.md + - name: Hybrid search + href: vector-stores/hybrid-search.md - name: Security and content safety items: - name: Authentication for Azure-hosted apps and services diff --git a/docs/ai/tutorials/tutorial-ai-vector-search.md b/docs/ai/tutorials/tutorial-ai-vector-search.md deleted file mode 100644 index 3ec82474108d1..0000000000000 --- a/docs/ai/tutorials/tutorial-ai-vector-search.md +++ /dev/null @@ -1,296 +0,0 @@ ---- -title: Tutorial - Integrate OpenAI with the RAG pattern and vector search using Azure Cosmos DB for MongoDB -description: Create a simple recipe app using the RAG pattern and vector search using Azure Cosmos DB for MongoDB. -ms.date: 08/26/2025 -ms.topic: tutorial -author: alexwolfmsft -ms.author: alexwolf ---- - -# Implement Azure OpenAI with RAG using vector search in a .NET app - -This tutorial explores integration of the RAG pattern using Open AI models and vector search capabilities in a .NET app. The sample application performs vector searches on custom data stored in Azure Cosmos DB for MongoDB and further refines the responses using generative AI models, such as GPT-35 and GPT-4. In the sections that follow, you'll set up a sample application and explore key code examples that demonstrate these concepts. - -## Prerequisites - -- [.NET 8.0](https://dotnet.microsoft.com/) -- An [Azure Account](https://azure.microsoft.com/free) -- An [Azure Cosmos DB for MongoDB vCore](/azure/cosmos-db/mongodb/vcore/introduction) service -- An [Azure Open AI](/azure/ai-services/openai/overview) service - - Deploy `text-embedding-ada-002` model for embeddings - - Deploy `gpt-35-turbo` model for chat completions - -## App overview - -The Cosmos Recipe Guide app allows you to perform vector and AI driven searches against a set of recipe data. You can search directly for available recipes or prompt the app with ingredient names to find related recipes. The app and the sections ahead guide you through the following workflow to demonstrate this type of functionality: - -1. Upload sample data to an Azure Cosmos DB for MongoDB database. -1. Create embeddings and a vector index for the uploaded sample data using the Azure OpenAI `text-embedding-ada-002` model. -1. Perform vector similarity search based on the user prompts. -1. Use the Azure OpenAI `gpt-35-turbo` completions model to compose more meaningful answers based on the search results data. - - :::image type="content" source="../media/get-started-app-chat-template/contoso-recipes.png" alt-text="A screenshot showing the running sample app."::: - -## Get started - -1. Clone the following GitHub repository: - - ```bash - git clone https://github.com/microsoft/AzureDataRetrievalAugmentedGenerationSamples.git - ``` - -1. In the _C#/CosmosDB-MongoDBvCore_ folder, open the **CosmosRecipeGuide.sln** file. - -1. In the _appsettings.json_ file, replace the following config values with your Azure OpenAI and Azure CosmosDB for MongoDb values: - - ```json - "OpenAIEndpoint": "https://.openai.azure.com/", - "OpenAIKey": "", - "OpenAIEmbeddingDeployment": "", - "OpenAIcompletionsDeployment": "", - "MongoVcoreConnection": "" - ``` - -1. Launch the app by pressing the **Start** button at the top of Visual Studio. - -## Explore the app - -When you run the app for the first time, it connects to Azure Cosmos DB and reports that there are no recipes available yet. Follow the steps displayed by the app to begin the core workflow. - -1. Select **Upload recipe(s) to Cosmos DB** and press Enter. This command reads sample JSON files from the local project and uploads them to the Cosmos DB account. - - The code from the _Utility.cs_ class parses the local JSON files. - - ``` C# - public static List ParseDocuments(string Folderpath) - { - List recipes = new List(); - - Directory.GetFiles(Folderpath) - .ToList() - .ForEach(f => - { - var jsonString= System.IO.File.ReadAllText(f); - Recipe recipe = JsonConvert.DeserializeObject(jsonString); - recipe.id = recipe.name.ToLower().Replace(" ", ""); - recipes.Add(recipe); - } - ); - - return recipes; - } - ``` - - The `UpsertVectorAsync` method in the _VCoreMongoService.cs_ file uploads the documents to Azure Cosmos DB for MongoDB. - - ```C# - public async Task UpsertVectorAsync(Recipe recipe) - { - BsonDocument document = recipe.ToBsonDocument(); - - if (!document.Contains("_id")) - { - Console.WriteLine("UpsertVectorAsync: Document does not contain _id."); - throw new ArgumentException("UpsertVectorAsync: Document does not contain _id."); - } - - string? _idValue = document["_id"].ToString(); - - try - { - var filter = Builders.Filter.Eq("_id", _idValue); - var options = new ReplaceOptions { IsUpsert = true }; - await _recipeCollection.ReplaceOneAsync(filter, document, options); - } - catch (Exception ex) - { - Console.WriteLine($"Exception: UpsertVectorAsync(): {ex.Message}"); - throw; - } - } - ``` - -1. Select **Vectorize the recipe(s) and store them in Cosmos DB**. - - The JSON items uploaded to Cosmos DB do not contain embeddings and therefore are not optimized for RAG via vector search. An embedding is an information-dense, numerical representation of the semantic meaning of a piece of text. Vector searches are able to find items with contextually similar embeddings. - - The `GetEmbeddingsAsync` method in the _OpenAIService.cs_ file creates an embedding for each item in the database. - - ```C# - public async Task GetEmbeddingsAsync(dynamic data) - { - try - { - EmbeddingsOptions options = new EmbeddingsOptions(data) - { - Input = data - }; - - var response = await _openAIClient.GetEmbeddingsAsync(openAIEmbeddingDeployment, options); - - Embeddings embeddings = response.Value; - float[] embedding = embeddings.Data[0].Embedding.ToArray(); - - return embedding; - } - catch (Exception ex) - { - Console.WriteLine($"GetEmbeddingsAsync Exception: {ex.Message}"); - return null; - } - } - ``` - - The `CreateVectorIndexIfNotExists` in the _VCoreMongoService.cs_ file creates a vector index, which enables you to perform vector similarity searches. - - ```C# - public void CreateVectorIndexIfNotExists(string vectorIndexName) - { - try - { - //Find if vector index exists in vectors collection - using (IAsyncCursor indexCursor = _recipeCollection.Indexes.List()) - { - bool vectorIndexExists = indexCursor.ToList().Any(x => x["name"] == vectorIndexName); - if (!vectorIndexExists) - { - BsonDocumentCommand command = new BsonDocumentCommand( - BsonDocument.Parse(@" - { createIndexes: 'Recipe', - indexes: [{ - name: 'vectorSearchIndex', - key: { embedding: 'cosmosSearch' }, - cosmosSearchOptions: { - kind: 'vector-ivf', - numLists: 5, - similarity: 'COS', - dimensions: 1536 } - }] - }")); - - BsonDocument result = _database.RunCommand(command); - if (result["ok"] != 1) - { - Console.WriteLine("CreateIndex failed with response: " + result.ToJson()); - } - } - } - } - catch (MongoException ex) - { - Console.WriteLine("MongoDbService InitializeVectorIndex: " + ex.Message); - throw; - } - } - ``` - -1. Select the **Ask AI Assistant (search for a recipe by name or description, or ask a question)** option in the application to run a user query. - - The user query is converted to an embedding using the OpenAI service and the embedding model. The embedding is then sent to Azure Cosmos DB for MongoDB and is used to perform a vector search. The `VectorSearchAsync` method in the _VCoreMongoService.cs_ file performs a vector search to find vectors that are close to the supplied vector and returns a list of documents from Azure Cosmos DB for MongoDB vCore. - - ```C# - public async Task> VectorSearchAsync(float[] queryVector) - { - List retDocs = new List(); - string resultDocuments = string.Empty; - - try - { - //Search Azure Cosmos DB for MongoDB vCore collection for similar embeddings - //Project the fields that are needed - BsonDocument[] pipeline = new BsonDocument[] - { - BsonDocument.Parse( - @$"{{$search: {{ - cosmosSearch: - {{ vector: [{string.Join(',', queryVector)}], - path: 'embedding', - k: {_maxVectorSearchResults}}}, - returnStoredSource:true - }} - }}"), - BsonDocument.Parse($"{{$project: {{embedding: 0}}}}"), - }; - - var bsonDocuments = await _recipeCollection - .Aggregate(pipeline).ToListAsync(); - - var recipes = bsonDocuments - .ToList() - .ConvertAll(bsonDocument => - BsonSerializer.Deserialize(bsonDocument)); - return recipes; - } - catch (MongoException ex) - { - Console.WriteLine($"Exception: VectorSearchAsync(): {ex.Message}"); - throw; - } - } - ``` - - The `GetChatCompletionAsync` method generates an improved chat completion response based on the user prompt and the related vector search results. - - ``` C# - public async Task<(string response, int promptTokens, int responseTokens)> GetChatCompletionAsync(string userPrompt, string documents) - { - try - { - ChatMessage systemMessage = new ChatMessage( - ChatRole.System, _systemPromptRecipeAssistant + documents); - ChatMessage userMessage = new ChatMessage( - ChatRole.User, userPrompt); - - ChatCompletionsOptions options = new() - { - Messages = - { - systemMessage, - userMessage - }, - MaxTokens = openAIMaxTokens, - Temperature = 0.5f, //0.3f, - NucleusSamplingFactor = 0.95f, - FrequencyPenalty = 0, - PresencePenalty = 0 - }; - - Azure.Response completionsResponse = - await openAIClient.GetChatCompletionsAsync(openAICompletionDeployment, options); - ChatCompletions completions = completionsResponse.Value; - - return ( - response: completions.Choices[0].Message.Content, - promptTokens: completions.Usage.PromptTokens, - responseTokens: completions.Usage.CompletionTokens - ); - - } - catch (Exception ex) - { - string message = $"OpenAIService.GetChatCompletionAsync(): {ex.Message}"; - Console.WriteLine(message); - throw; - } - } - ``` - - The app also uses prompt engineering to ensure OpenAI service limits and formats the response for supplied recipes. - - ```C# - //System prompts to send with user prompts to instruct the model for chat session - private readonly string _systemPromptRecipeAssistant = @" - You are an intelligent assistant for Contoso Recipes. - You are designed to provide helpful answers to user questions about - recipes, cooking instructions provided in JSON format below. - - Instructions: - - Only answer questions related to the recipe provided below. - - Don't reference any recipe not provided below. - - If you're unsure of an answer, say ""I don't know"" and recommend users search themselves. - - Your response should be complete. - - List the Name of the Recipe at the start of your response followed by step by step cooking instructions. - - Assume the user is not an expert in cooking. - - Format the content so that it can be printed to the Command Line console. - - In case there is more than one recipe you find, let the user pick the most appropriate recipe."; - ``` diff --git a/docs/ai/vector-stores/defining-your-data-model.md b/docs/ai/vector-stores/defining-your-data-model.md new file mode 100644 index 0000000000000..c3e206daf4cce --- /dev/null +++ b/docs/ai/vector-stores/defining-your-data-model.md @@ -0,0 +1,76 @@ +--- +title: Define your Vector Store data model +description: Describes how to create a data model with Microsoft.Extensions.VectorData to use when writing to or reading from a Vector Store. +ms.topic: reference +ms.date: 02/28/2026 +--- +# Define your data model + +The Vector Store connectors use a model-first approach to interacting with databases. + +All methods to upsert or get records use strongly typed model classes. The properties on these classes are decorated with attributes that indicate the purpose of each property. + +> [!TIP] +> +> - For an alternative to using attributes, see [Define your storage schema using a record definition](./schema-with-record-definition.md). +> - For an alternative to defining your own data model, see [Use Vector Store abstractions without defining your own data model](./dynamic-data-model.md). + +Here's an example of a model that's decorated with these attributes. + +:::code language="csharp" source="./snippets/conceptual/defining-your-data-model.cs" id="Overview"::: + +## Attributes + +### VectorStoreKeyAttribute + +Use the attribute to indicate that your property is the key of the record. + +:::code language="csharp" source="./snippets/conceptual/defining-your-data-model.cs" id="VectorStoreKeyAttribute"::: + +#### VectorStoreKeyAttribute parameters + +| Parameter | Required | Description | +|---------------|:--------:|-------------| +| | No | Can be used to supply an alternative name for the property in the database. This parameter isn't supported by all connectors, for example, where alternatives like `JsonPropertyNameAttribute` are supported. | + +### VectorStoreDataAttribute + +Use the attribute to indicate that your property contains general data that is not a key or a vector. + +:::code language="csharp" source="./snippets/conceptual/defining-your-data-model.cs" id="VectorStoreDataAttribute"::: + +#### VectorStoreDataAttribute parameters + +| Parameter | Required | Description | +|-------------|:--------:|-------------| +| | No | Indicates whether the property should be indexed for filtering in cases where a database requires opting in to indexing per property. The default is `false`. | +| | No | Indicates whether the property should be indexed for full text search for databases that support full text search. The default is `false`. | +| | No | Can be used to supply an alternative name for the property in the database. This parameter is not supported by all connectors, for example, where alternatives like `JsonPropertyNameAttribute` are supported. | + +### VectorStoreVectorAttribute + +Use the attribute to indicate that your property contains a vector. + +:::code language="csharp" source="./snippets/conceptual/defining-your-data-model.cs" id="VectorStoreVectorAttribute1"::: + +It's also possible to use on properties that don't have a vector type, for example, a property of type `string`. When a property is decorated in this way, you need to provide an instance to the vector store. When upserting the record, the text that's in the `string` property is automatically converted and stored as a vector in the database. (It's not possible to retrieve a vector using this mechanism.) + +```csharp +[VectorStoreVector(Dimensions: 4, DistanceFunction = DistanceFunction.CosineSimilarity, IndexKind = IndexKind.Hnsw)] +public string DescriptionEmbedding { get; set; } +``` + +> [!TIP] +> For more information on how to use built-in embedding generation, see [Let the Vector Store generate embeddings](embedding-generation.md#let-the-vector-store-generate-embeddings). + +#### VectorStoreVectorAttribute parameters + +| Parameter | Required | Description | +|--------------|:--------:|-------------| +| `Dimensions` | Yes | The number of dimensions that the vector has. This is required when creating a vector index for a collection. | +| | No | The type of index to index the vector with. Default varies by vector store type. | +| | No | The type of function to use when doing vector comparison during vector search over this vector. Default varies by vector store type. | +| | No | Can be used to supply an alternative name for the property in the database. This parameter is not supported by all connectors, for example, where alternatives like `JsonPropertyNameAttribute` is supported. | + +Common index kinds and distance function types are supplied as static values on the and classes. +Individual Vector Store implementations might also use their own index kinds and distance functions, where the database supports unusual types. diff --git a/docs/ai/vector-stores/dynamic-data-model.md b/docs/ai/vector-stores/dynamic-data-model.md new file mode 100644 index 0000000000000..4a73663576f7d --- /dev/null +++ b/docs/ai/vector-stores/dynamic-data-model.md @@ -0,0 +1,26 @@ +--- +title: Using Vector Store abstractions without defining your own data model +description: Describes how to use Vector Store abstractions without defining your own data model. +ms.topic: reference +ms.date: 02/28/2026 +--- +# Use Vector Store abstractions without defining your own data model + +The Vector Store connectors use a model-first approach to interact with databases. This makes using the connectors easy and simple, since your data model reflects the schema of your database records. To add any additional schema information, you can simply add attributes to your data model properties. + +However, there are cases where it isn't desirable or possible to define your own data model. For example, imagine that you don't know at compile time what your database schema looks like, and the schema is only provided via configuration. Creating a data model that reflects the schema would be impossible in this case. Instead, you can use a `Dictionary` for the record type. Properties are added to the `Dictionary` with key as the property name and the value as the property value. + +## Supply schema information when using `Dictionary` + +When using a `Dictionary`, connectors still need to know what the database schema looks like. Without the schema information, the connector would not be able to create a collection or know how to map to and from the storage representation that each database uses. + +A record definition can be used to provide the schema information. Unlike a data model, a record definition can be created from configuration at *runtime* when schema information isn't known at compile time. + +> [!TIP] +> For information about creating a record definition, see [Defining your schema with a record definition](./schema-with-record-definition.md). + +## Example + +To use the `Dictionary` with a connector, specify it as your data model when you create a collection. Also provide a record definition. + +:::code language="csharp" source="./snippets/conceptual/dynamic-data-model.cs" id="Example1"::: diff --git a/docs/ai/vector-stores/embedding-generation.md b/docs/ai/vector-stores/embedding-generation.md new file mode 100644 index 0000000000000..0fc4cb45ac2db --- /dev/null +++ b/docs/ai/vector-stores/embedding-generation.md @@ -0,0 +1,92 @@ +--- +title: Generate embeddings for Vector Store connectors +description: Describes how you can generate embeddings to use with Vector Store connectors. +ms.topic: concept-article +ms.date: 02/28/2026 +--- + +# Generate embeddings for Vector Store connectors + +Vector Store connectors support multiple ways of generating embeddings. Embeddings can be generated by the developer and passed as part of a record when using a . Or they can be generated internally to the . + +## Let the Vector Store generate embeddings + +You can configure an embedding generator on your vector store, allowing embeddings to be automatically generated during both upsert and search operations, eliminating the need for manual preprocessing. + +To enable generating vectors automatically on upsert, the vector property on your data model is defined as the source type, for example, string but still decorated with a . + +:::code language="csharp" source="./snippets/conceptual/embedding-generation.cs" id="LetTheVectorStoreGenerateEmbeddings"::: + +Before upsert, the `Embedding` property should contain the string from which a vector should be generated. The type of the vector stored in the database (for example, float32, float16, etc.) will be derived from the configured embedding generator. + +> [!IMPORTANT] +> These vector properties do not support retrieving either the generated vector or the original text that the vector was generated from. They also do not store the original text. If the original text needs to be stored, add a separate Data property to store it. + +Embedding generators that implement the `Microsoft.Extensions.AI` abstractions are supported and can be configured at various levels: + +- **On the Vector Store**: + + You can set a default embedding generator for the entire vector store. This generator will be used for all collections and properties unless overridden. + + :::code language="csharp" source="./snippets/conceptual/embedding-generation.cs" id="OnTheVectorStore"::: + +- **On a Collection**: + + You can configure an embedding generator for a specific collection, overriding the store-level generator. + + :::code language="csharp" source="./snippets/conceptual/embedding-generation.cs" id="OnACollection"::: + +- **On a Record Definition**: + + When defining properties programmatically using , you can specify an embedding generator for all properties. + + :::code language="csharp" source="./snippets/conceptual/embedding-generation.cs" id="OnARecordDefinition"::: + +- **On a Vector Property Definition**: + + When defining properties programmatically, you can set an embedding generator directly on the property. + + :::code language="csharp" source="./snippets/conceptual/embedding-generation.cs" id="OnAVectorPropertyDefinition"::: + +### Example usage + +The following example demonstrates how to use the embedding generator to automatically generate vectors during both upsert and search operations. This approach simplifies workflows by eliminating the need to precompute embeddings manually. + +:::code language="csharp" source="./snippets/conceptual/embedding-generation.cs" id="ExampleUsage"::: + +## Generate embeddings yourself + +### Construct an embedding generator + +For information on how to construct `Microsoft.Extensions.AI` embedding generators, see [Embeddings in .NET](../conceptual/embeddings.md). + +### Generate embeddings on upsert with `IEmbeddingGenerator` + +:::code language="csharp" source="./snippets/conceptual/embedding-generation.cs" id="GenerateEmbeddingsOnUpsertWithIEmbedding"::: + +### Generate embeddings on search with `IEmbeddingGenerator` + +:::code language="csharp" source="./snippets/conceptual/embedding-generation.cs" id="GenerateEmbeddingsOnSearchWithIEmbedding"::: + +## Embedding dimensions + +Vector databases typically require you to specify the number of dimensions that each vector has when creating the collection. Different embedding models typically support generating vectors with various dimension sizes. For example, OpenAI `text-embedding-ada-002` generates vectors with 1536 dimensions. Some models also allow you to choose the number of dimensions you want in the output vector. For example, Google `text-embedding-004` produces vectors with 768 dimensions by default, but allows you to choose any number of dimensions between 1 and 768. + +It's important to ensure that the vectors generated by the embedding model have the same number of dimensions as the matching vector in the database. + +If you create a collection using the Vector Store abstractions, you need to specify the number of dimensions required for each vector property either via *annotations* or via the *record definition*. The following code shows examples of setting the number of dimensions to 1536 using both mechanisms. + +```csharp +[VectorStoreVector(Dimensions: 1536)] +public ReadOnlyMemory? DescriptionEmbedding { get; set; } + +new VectorStoreVectorProperty( + "DescriptionEmbedding", + typeof(float), + dimensions: 1536); +``` + +## See also + +- [Defining your data model](./defining-your-data-model.md) +- [Defining your schema with a record definition](./schema-with-record-definition.md) diff --git a/docs/ai/quickstarts/build-vector-search-app.md b/docs/ai/vector-stores/how-to/build-vector-search-app.md similarity index 79% rename from docs/ai/quickstarts/build-vector-search-app.md rename to docs/ai/vector-stores/how-to/build-vector-search-app.md index 49fe37004dc0d..c9e5172d1bcde 100644 --- a/docs/ai/quickstarts/build-vector-search-app.md +++ b/docs/ai/vector-stores/how-to/build-vector-search-app.md @@ -1,7 +1,7 @@ --- title: Quickstart - Build a minimal .NET AI RAG app description: Create an AI powered app to search and integrate with vector stores using embeddings and the Microsoft.Extensions.VectorData package for .NET -ms.date: 05/29/2025 +ms.date: 02/28/2026 ms.topic: quickstart zone_pivot_groups: openai-library --- @@ -25,13 +25,13 @@ The app uses the and attributes, such as , influence how each property is handled when used in a vector store. The `Vector` property stores a generated embedding that represents the semantic meaning of the `Description` value for vector searches. + The attributes, such as , influence how each property is handled when used in a vector store. The `Vector` property stores a generated embedding that represents the semantic meaning of the `Description` value for vector searches. 1. In the `Program.cs` file, add the following code to create a data set that describes a collection of cloud services: - :::code language="csharp" source="snippets/chat-with-data/azure-openai/program.cs" id="DataSet"::: + :::code language="csharp" source="../snippets/chat-with-data/azure-openai/program.cs" id="DataSet"::: 1. Create and configure an `IEmbeddingGenerator` implementation to send requests to an embedding AI model: - :::zone target="docs" pivot="azure-openai" + :::zone target="docs" pivot="azure-openai" - :::code language="csharp" source="snippets/chat-with-data/azure-openai/program.cs" id="EmbeddingGenerator"::: + :::code language="csharp" source="../snippets/chat-with-data/azure-openai/program.cs" id="EmbeddingGenerator"::: - > [!NOTE] - > searches for authentication credentials from your local tooling. You'll need to assign the `Azure AI Developer` role to the account you used to sign in to Visual Studio or the Azure CLI. For more information, see [Authenticate to Foundry Tools with .NET](../azure-ai-services-authentication.md). + > [!NOTE] + > searches for authentication credentials from your local tooling. You'll need to assign the `Azure AI Developer` role to the account you used to sign in to Visual Studio or the Azure CLI. For more information, see [Authenticate to Foundry Tools with .NET](../../azure-ai-services-authentication.md). - :::zone-end + :::zone-end - :::zone target="docs" pivot="openai" + :::zone target="docs" pivot="openai" - :::code language="csharp" source="snippets/chat-with-data/openai/program.cs" id="EmbeddingGenerator"::: + :::code language="csharp" source="../snippets/chat-with-data/openai/program.cs" id="EmbeddingGenerator"::: - :::zone-end + :::zone-end 1. Create and populate a vector store with the cloud service data. Use the `IEmbeddingGenerator` implementation to create and assign an embedding vector for each record in the cloud service data: - :::code language="csharp" source="snippets/chat-with-data/azure-openai/program.cs" id="VectorStore"::: + :::code language="csharp" source="../snippets/chat-with-data/azure-openai/program.cs" id="VectorStore"::: The embeddings are numerical representations of the semantic meaning for each data record, which makes them compatible with vector search features. 1. Create an embedding for a search query and use it to perform a vector search on the vector store: - :::code language="csharp" source="snippets/chat-with-data/azure-openai/program.cs" id="Search"::: + :::code language="csharp" source="../snippets/chat-with-data/azure-openai/program.cs" id="Search"::: 1. Use the `dotnet run` command to run the app: - ```dotnetcli - dotnet run - ``` + ```dotnetcli + dotnet run + ``` - The app prints out the top result of the vector search, which is the cloud service that's most relevant to the original query. You can modify the query to try different search scenarios. + The app prints out the top result of the vector search, which is the cloud service that's most relevant to the original query. You can modify the query to try different search scenarios. :::zone target="docs" pivot="azure-openai" @@ -194,5 +194,5 @@ If you no longer need them, delete the Azure OpenAI resource and model deploymen ## Next steps -- [Quickstart - Chat with a local AI model](chat-local-model.md) -- [Generate images from text using AI](text-to-image.md) +- [Quickstart - Chat with a local AI model](../../quickstarts/chat-local-model.md) +- [Generate images from text using AI](../../quickstarts/text-to-image.md) diff --git a/docs/ai/vector-stores/how-to/use-vector-stores.md b/docs/ai/vector-stores/how-to/use-vector-stores.md new file mode 100644 index 0000000000000..60f48e3164885 --- /dev/null +++ b/docs/ai/vector-stores/how-to/use-vector-stores.md @@ -0,0 +1,177 @@ +--- +title: Use vector stores in .NET AI apps +description: Learn how to use Microsoft.Extensions.VectorData to store, search, and manage embeddings in vector databases for .NET AI applications. +ms.topic: how-to +ms.date: 02/28/2026 +ai-usage: ai-generated +--- + +# Use vector stores in .NET AI apps + +The [📦 Microsoft.Extensions.VectorData.Abstractions](https://www.nuget.org/packages/Microsoft.Extensions.VectorData.Abstractions) package provides a unified API for working with vector stores in .NET. You can use the same code to store and search embeddings across different vector database providers. + +This article shows you how to use the key features of the library. + +## Prerequisites + +- [.NET 9 SDK](https://dotnet.microsoft.com/download/dotnet/9.0) or later +- An understanding of [embeddings](../../conceptual/embeddings.md) and [vector databases](../overview.md) + +## Install the packages + +Install the `Microsoft.Extensions.VectorData.Abstractions` package and a connector for your vector database. The following example uses the in-memory connector for development and testing: + +```dotnetcli +dotnet add package Microsoft.Extensions.VectorData.Abstractions +dotnet add package Microsoft.SemanticKernel.Connectors.InMemory --prerelease +``` + +For production scenarios, replace `Microsoft.SemanticKernel.Connectors.InMemory` with the connector for your database. For a full list of available connectors, see [Available vector store connectors](../overview.md#available-vector-store-connectors). + +## Define a data model + +Define a .NET class to represent the records you want to store in the vector store. Use the following attributes from the namespace to describe the properties: + +- : The primary key of each record. +- : A data property that gets stored. Set `IsIndexed = true` to enable filtering on the property, or `IsFullTextIndexed = true` to enable full-text search on it. +- : An embedding vector property. Set the `Dimensions` parameter to match your embedding model's output size. + +:::code language="csharp" source="../snippets/how-to/Hotel.cs" id="DataModel"::: + +The `Dimensions` parameter in `[VectorStoreVector]` must match the output size of the embedding model you use. Common values include 1536 for `text-embedding-3-small` and 3072 for `text-embedding-3-large`. + +### Attribute parameters + +The following tables describe all available parameters for each attribute. + +#### VectorStoreKeyAttribute parameters + +| Parameter | Required | Description | +|---|---|---| +| | No | An alternative name for the property in storage. Not supported by all connectors. | + +#### VectorStoreDataAttribute parameters + +| Parameter | Required | Description | +|---|---|---| +| | No | Whether to index this property for filtering. Default is `false`. | +| | No | Whether to index this property for full-text search. Default is `false`. | +| | No | An alternative name for the property in storage. Not supported by all connectors. | + +#### VectorStoreVectorAttribute parameters + +| Parameter | Required | Description | +|---|---|---| +| | Yes (for collection creation) | The number of dimensions in the vector. Must match your embedding model. | +| | No | The type of index to use. Defaults vary by connector. Common values: `Hnsw`, `Flat`. | +| | No | The distance function for similarity comparisons. Defaults vary by connector. Common values: `CosineSimilarity`, `DotProductSimilarity`, `EuclideanDistance`. | +| | No | An alternative name for the property in storage. Not supported by all connectors. | + +## Define a schema programmatically + +As an alternative to using attributes, you can define your schema programmatically using a . This approach is useful when you want to use the same data model with different configurations, or when you can't add attributes to the data model class. + +For more information, see [Define your storage schema using a record definition](../schema-with-record-definition.md). + +## Create a vector store + +Create an instance of the implementation for your chosen database. The following example creates an in-memory vector store: + +:::code language="csharp" source="../snippets/how-to/Program.cs" id="CreateVectorStore"::: + +## Get a collection + +Call on the to get a typed reference. Then call to create the collection if it doesn't already exist: + +:::code language="csharp" source="../snippets/how-to/Program.cs" id="GetCollection"::: + +The collection name maps to the underlying storage concept for your database (for example, a table in SQL Server, an index in Azure AI Search, or a container in Cosmos DB). + +## Upsert records + +Use to insert or update records in the collection. If a record with the same key already exists, it gets updated: + +:::code language="csharp" source="../snippets/how-to/Program.cs" id="UpsertRecords"::: + +> [!IMPORTANT] +> In a real app, generate the embedding vectors using an `IEmbeddingGenerator` before storing the records. For a working example with real embeddings, see [Build a .NET AI vector search app](build-vector-search-app.md). + +## Get records + +Use to retrieve a single record by its key. To retrieve multiple records, pass an `IEnumerable` to : + +:::code language="csharp" source="../snippets/how-to/Program.cs" id="GetRecord"::: + +To retrieve multiple records at once: + +:::code language="csharp" source="../snippets/how-to/Program.cs" id="GetBatch"::: + +## Perform vector search + +Use to find records that are semantically similar to a query. Pass the embedding vector for your query and the number of results to return: + +:::code language="csharp" source="../snippets/how-to/Program.cs" id="SearchBasic"::: + +Each contains the matching record and a similarity score. Higher scores indicate a closer semantic match. + +## Filter search results + +Use to filter search results before the vector comparison. You can filter on any property marked with `IsIndexed = true`: + +:::code language="csharp" source="../snippets/how-to/Program.cs" id="SearchWithFilter"::: + +Filters are expressed as LINQ expressions. The supported operations vary by connector, but all connectors support common comparisons like equality, inequality, and logical `&&` and `||`. + +## Control search behavior with VectorSearchOptions + +Use to control various aspects of vector search behavior: + +:::code language="csharp" source="../snippets/how-to/Program.cs" id="SearchOptions"::: + +The following table describes the available options: + +| Option | Description | +|------------------|------------------------------------------------------------------------------------------------| +| `Filter` | A LINQ expression to filter records before vector comparison. | +| `VectorProperty` | The vector property to search on. Required when the data model has multiple vector properties. | +| `Skip` | Number of results to skip before returning. Useful for paging. Default is `0`. | +| `IncludeVectors` | Whether to include vector data in the returned records. Omitting vectors reduces data transfer. Default is `false`. | + +For more information, see [Vector search options](../vector-search.md#vector-search-options). + +## Use built-in embedding generation + +Instead of generating embeddings manually before each upsert, you can configure an `IEmbeddingGenerator` on the vector store or collection. When you do, declare your vector property as a `string` type (the source text) and the store generates the embedding automatically. + +For more information, see [Let the Vector Store generate embeddings](../embedding-generation.md#let-the-vector-store-generate-embeddings). + +## Hybrid search + +Some vector stores support *hybrid search*, which combines vector similarity with keyword matching. This approach can improve result relevance compared to vector-only search. + +To use hybrid search, check whether your collection implements . Only connectors for databases that support this feature implement this interface. + +For more information, see [Hybrid search using Vector Store connectors](../hybrid-search.md). + +## Delete records + +To delete a single record by key, use : + +:::code language="csharp" source="../snippets/how-to/Program.cs" id="DeleteRecord"::: + +## Delete a collection + +To remove an entire collection from the vector store, use : + +:::code language="csharp" source="../snippets/how-to/Program.cs" id="DeleteCollection"::: + +## Switch vector store connectors + +Because all connectors implement the same abstract class, you can switch between them by changing the concrete type at startup. Your collection and search code remains the same. This approach lets you develop and test locally without any external services, then deploy to a production database with minimal changes. + +## Related content + +- [Vector databases for .NET AI apps](../overview.md) +- [Build a .NET AI vector search app](build-vector-search-app.md) +- [Data ingestion](../../conceptual/data-ingestion.md) +- [Embeddings in .NET](../../conceptual/embeddings.md) diff --git a/docs/ai/vector-stores/how-to/vector-store-data-ingestion.md b/docs/ai/vector-stores/how-to/vector-store-data-ingestion.md new file mode 100644 index 0000000000000..1203eb59121cd --- /dev/null +++ b/docs/ai/vector-stores/how-to/vector-store-data-ingestion.md @@ -0,0 +1,102 @@ +--- +title: How to ingest data into a Vector Store +description: Step by step instructions on how to ingest data into a Vector Store. +ms.topic: tutorial +ms.date: 02/28/2026 +--- +# How to ingest data into a Vector Store + +This article demonstrates how to create an application to: + +1. Take text from each paragraph in a Microsoft Word document. +2. Generate an embedding for each paragraph. +3. Upsert the text, embedding, and a reference to the original location into a Redis instance. + +## Prerequisites + +For this sample you need: + +- An embedding generation model hosted in Azure or another provider of your choice. +- An instance of Redis or Docker Desktop so that you can run Redis locally. +- A Word document to parse and load. + +## Set up Redis + +If you already have a Redis instance, you can use that. If you prefer to test your project locally, +you can easily start a Redis container using Docker: + +```docker +docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest +``` + +To verify that it's running successfully, navigate to [http://localhost:8001/redis-stack/browser](http://localhost:8001/redis-stack/browser) in your browser. + +The rest of these instructions assume that you're using a container with these settings. + +## Create your project + +Create a new project and add NuGet package references for the Redis connector, the OpenXml package to read the Word document, and the Azure OpenAI packages for generating embeddings. + +```dotnetcli +dotnet new console --framework net8.0 --name SKVectorIngest +cd SKVectorIngest +dotnet add package Azure.AI.OpenAI +dotnet add package Microsoft.Extensions.AI.OpenAI +dotnet add package Microsoft.SemanticKernel.Connectors.Redis --prerelease +dotnet add package DocumentFormat.OpenXml +``` + +## Add a data model + +To upload data, you must first describe what format the data should have in the database. +You can do this by creating a data model with attributes that describe the function of each property. + +Add a new file to the project called `TextParagraph.cs` and add the following model to it. + +:::code language="csharp" source="./snippets/conceptual/DataIngestion.cs" id="AddADataModel"::: + +The value `1536`, which is the dimension size of the vector, is passed to the . This value must match the size of vector that your chosen embedding generator produces. + +> [!TIP] +> For more information on how to annotate your data model and what additional options are available for each attribute, see [defining your data model](../defining-your-data-model.md). + +## Read the paragraphs in the document + +Next, you add the code to read the Word document and find the text of each paragraph in it. + +Add a new file to the project called `DocumentReader.cs` and add the following class to read the paragraphs from a document. + +:::code language="csharp" source="./snippets/conceptual/DataIngestion.cs" id="ReadTheParagraphsInTheDocument"::: + +## Generate embeddings and upload the data + +Next, you add a new class to generate embeddings and upload the paragraphs to Redis. + +Add a new file called `DataUploader.cs` with the following contents: + +:::code language="csharp" source="./snippets/conceptual/DataIngestion.cs" id="GenerateEmbeddingsAndUploadTheData"::: + +## Put it all together + +Finally, you put together the different pieces. In this example, you use standard .NET dependency injection to register the Redis vector store and the embedding generator. + +Add the following code to your `Program.cs` file to set up the container, register the Redis vector store, and register the embedding service. Replace the text embedding generation settings with your own values. + +:::code language="csharp" source="./snippets/conceptual/DataIngestion.cs" id="PutItAllTogether1"::: + +Lastly, add code to read the paragraphs from the Word document and call the data uploader to generate the embeddings and upload the paragraphs. + +:::code language="csharp" source="./snippets/conceptual/DataIngestion.cs" id="PutItAllTogether2"::: + +## See your data in Redis + +Navigate to the Redis stack browser, for example, [http://localhost:8001/redis-stack/browser](http://localhost:8001/redis-stack/browser), where you should now be able to see your uploaded paragraphs. Following is an example of what you should see for one of the uploaded paragraphs. + +```json +{ + "DocumentUri" : "file:///c:/vector-store-data-ingestion-input.docx", + "ParagraphId" : "14CA7304", + "Text" : "Version 1.0+ support across C#, Python, and Java means it’s reliable, committed to non breaking changes. Any existing chat-based APIs are easily expanded to support additional modalities like voice and video.", + "TextEmbedding" : [...] +} +``` diff --git a/docs/ai/vector-stores/hybrid-search.md b/docs/ai/vector-stores/hybrid-search.md new file mode 100644 index 0000000000000..3fe601149a8c8 --- /dev/null +++ b/docs/ai/vector-stores/hybrid-search.md @@ -0,0 +1,92 @@ +--- +title: Hybrid search using Vector Store connectors +description: Describes the different options you can use when doing a hybrid search using Vector Store connectors. +ms.topic: concept-article +ms.date: 02/28/2026 +--- +# Hybrid search using Vector Store connectors + +The library provides hybrid search capabilities, including filtering, as part of its Vector Store abstractions. + +The hybrid search is based on a vector search and a keyword search, both of which are executed in parallel. The hybrid search returns a union of the two result sets. (Sparse vector–based hybrid search isn't currently supported.) + +To execute a hybrid search, your database schema needs to have a vector field and a string field with full-text search capabilities enabled. If you're creating a collection using the Vector Store connectors, enable the option on the string field that you want to target for the keyword search. + +> [!TIP] +> For more information on how to enable , see [VectorStoreDataAttribute parameters](./defining-your-data-model.md#vectorstoredataattribute-parameters) or [VectorStoreDataProperty configuration settings](./schema-with-record-definition.md#vectorstoredataproperty-configuration-settings) + +## Hybrid search + +The method searches using a vector and an `ICollection` of string keywords. It also takes an optional `HybridSearchOptions` class as input. + +Only connectors for databases that support vector plus keyword hybrid search implement [the interface](xref:Microsoft.Extensions.VectorData.IKeywordHybridSearchable`1) that provides this method. + +The following example shows how to perform a hybrid search on a collection in a Qdrant database. + +:::code language="csharp" source="./snippets/conceptual/hybrid-search.cs" id="HybridSearch"::: + +> [!TIP] +> For more information on how to generate embeddings, see [embedding generation](./embedding-generation.md). + +## Supported vector types + + takes a generic type as the vector parameter. +The types of vectors supported by each data store vary. + +It's also important for the search vector type to match the target vector that's being searched. For example, if you have two vectors +on the same record with different vector types, make sure that the search vector you supply matches the type of the specific vector +you're targeting. For information on how to pick a target vector if you have more than one per record, see [VectorProperty and AdditionalProperty](#vectorproperty-and-additionalproperty). + +## Hybrid search options + +The following options can be provided using the class. + +### VectorProperty and AdditionalProperty + +Use the and options to specify the vector property and full-text search property to target during the search. + +When no `VectorProperty` is provided: + +- If the data model contains only one vector, that vector is used. +- If the data model contains no vector or multiple vectors, the search method throws an exception. + +When no `AdditionalProperty` is provided: + +- If the data model contains only one full-text search property, that property is used. +- If the data model contains no full-text search property or multiple full-text search properties, the search method throws an exception. + +:::code language="csharp" source="./snippets/conceptual/hybrid-search.cs" id="VectorPropertyAndAdditionalProperty"::: + +### `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 result set. You can use `Top` and `Skip` to do paging if you want to retrieve a large number of results using separate calls. + +:::code language="csharp" source="./snippets/conceptual/hybrid-search.cs" id="TopAndSkip"::: + +The default value for `Skip` is 0. + +### IncludeVectors + +The `IncludeVectors` option allows you to specify whether you want to return vectors in the search results. If `false`, the vector properties on the returned model are 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 `IncludeVectors` is `false`. + +:::code language="csharp" source="./snippets/conceptual/hybrid-search.cs" id="IncludeVectors"::: + +### Filter + +Use the vector search filter option 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 result set for by excluding data that the user shouldn't have access to. This can be useful for access-control purposes. + +For fields to be used for filtering, many vector stores require them to be indexed first. Some vector stores allow filtering using any field, but might optionally allow indexing to improve filtering performance. + +If you're creating a collection via the Vector Store abstractions and you want to enable filtering on a field, set the property to `true` when defining your data model or when creating your record definition. + +> [!TIP] +> For more information on how to set the property, see [VectorStoreDataAttribute parameters](./defining-your-data-model.md#vectorstoredataattribute-parameters) or [VectorStoreDataProperty configuration settings](./schema-with-record-definition.md). + +Filters are expressed using LINQ expressions based on the type of the data model. The set of LINQ expressions supported varies depending on the functionality supported by each database, but all databases support a broad base of common expressions, for example, `equals`, `not equals`, `and`, and `or`. + +:::code language="csharp" source="./snippets/conceptual/hybrid-search.cs" id="Filter"::: diff --git a/docs/ai/vector-stores/overview.md b/docs/ai/vector-stores/overview.md new file mode 100644 index 0000000000000..b54923352beef --- /dev/null +++ b/docs/ai/vector-stores/overview.md @@ -0,0 +1,81 @@ +--- +title: "Vector databases for .NET AI apps" +description: "Learn how vector databases extend LLM capabilities by storing and processing embeddings in .NET, and how to use Microsoft.Extensions.VectorData to build semantic search features." +ms.topic: concept-article +ms.date: 02/28/2026 +ai-usage: ai-assisted +--- + +# Vector databases for .NET AI apps + +Vector databases store and manage vector [*embeddings*](../conceptual/embeddings.md). Embeddings are numeric representations of data that preserve semantic meaning. Words, documents, images, audio, and other types of data can all be vectorized. You can use embeddings to help an AI model understand the meaning of inputs so that it can perform comparisons and transformations, such as summarizing text, finding contextually related data, or creating images from text descriptions. + +For example, you can use a vector database to: + +- Identify similar images, documents, and songs based on their contents, themes, sentiments, and styles. +- Identify similar products based on their characteristics, features, and user groups. +- Recommend content, products, or services based on user preferences. +- Identify the best potential options from a large pool of choices to meet complex requirements. +- Identify data anomalies or fraudulent activities that are dissimilar from predominant or normal patterns. + +## Understand vector search + +Vector databases provide vector search capabilities to find similar items based on their data characteristics rather than by exact matches on a property field. Vector search works by analyzing the vector representations of your data that you created using an AI embedding model such as the [Azure OpenAI embedding models](/azure/ai-services/openai/concepts/models#embeddings-models). The search process measures the distance between the data vectors and your query vector. The data vectors that are closest to your query vector are the ones that are most similar semantically. + +Some services, such as [Azure Cosmos DB for MongoDB vCore](/azure/cosmos-db/mongodb/vcore/vector-search), provide native vector search capabilities for your data. Other databases can be enhanced with vector search by indexing the stored data using a service such as Azure AI Search, which can scan and index your data to provide vector search capabilities. + +## Vector search workflows with .NET and OpenAI + +Vector databases and their search features are especially useful in [RAG pattern](../conceptual/rag.md) workflows with Azure OpenAI. This pattern allows you to augment or enhance your AI model with additional semantically rich knowledge of your data. A common AI workflow using vector databases includes the following steps: + +1. Create embeddings for your data using an OpenAI embedding model. +1. Store and index the embeddings in a vector database or search service. +1. Convert user prompts from your application to embeddings. +1. Run a vector search across your data, comparing the user prompt embedding to the embeddings in your database. +1. Use a language model such as GPT-4 to assemble a user-friendly completion from the vector search results. + +For a hands-on example of this flow, see the [Implement Azure OpenAI with RAG using vector search in a .NET app](tutorial-vector-search.md) tutorial. + +Other benefits of the RAG pattern include: + +- Generate contextually relevant and accurate responses to user prompts from AI models. +- Overcome LLM token limits—the heavy lifting is done through the database vector search. +- Reduce the costs from frequent fine-tuning on updated data. + +## The Microsoft.Extensions.VectorData library + +The [📦 Microsoft.Extensions.VectorData.Abstractions](https://www.nuget.org/packages/Microsoft.Extensions.VectorData.Abstractions) package provides a unified layer of abstractions for interacting with vector stores in .NET. These abstractions let you write code against a single API and swap out the underlying vector store with minimal changes to your application. + +The library provides the following key capabilities: + +- **Unified data model**: Define your data model once using .NET attributes and use it across any supported vector store. +- **CRUD operations**: Create, read, update, and delete records in a vector store. +- **Vector and text search**: Query records by semantic similarity using vector search, or by keyword using text search. +- **Collection management**: Create, list, and delete collections (tables or indices) in a vector store. + +### Key abstractions + +The `Microsoft.Extensions.VectorData.Abstractions` library exposes the following main abstract classes: + +- : The top-level class for a vector database. Use it to retrieve and manage collections. +- : Represents a named collection of records within a vector store. Use it to perform CRUD and search operations. Also implements `IVectorSearchable`. +- `IKeywordHybridSearchable`: Implemented by collections that support hybrid search, combining vector similarity with keyword matching. + +For a step-by-step guide covering data model definition, CRUD operations, vector search, filtering, hybrid search, and embedding generation, see [Use vector stores in .NET AI apps](how-to/use-vector-stores.md). + +## Available vector store connectors + +The `Microsoft.Extensions.VectorData.Abstractions` package defines the abstractions, and separate connector packages implement them for specific vector databases. Choose the connector that matches your vector database, for example, [Microsoft.SemanticKernel.Connectors.AzureAISearch](https://www.nuget.org/packages/Microsoft.SemanticKernel.Connectors.AzureAISearch). + +All connectors implement the same and abstract classes, so you can switch between them without changing your application logic. + +> [!TIP] +> Use the in-memory connector ([Microsoft.SemanticKernel.Connectors.InMemory](https://www.nuget.org/packages/Microsoft.SemanticKernel.Connectors.InMemory)) during development and testing. It doesn't require any external service or configuration, and you can swap it out for a production connector later. + +## Related content + +- [Use vector stores in .NET AI apps](how-to/use-vector-stores.md) +- [Build a .NET AI vector search app](how-to/build-vector-search-app.md) +- [Implement Azure OpenAI with RAG using vector search in a .NET app](tutorial-vector-search.md) +- [Data ingestion](../conceptual/data-ingestion.md) +- [Embeddings in .NET](../conceptual/embeddings.md) diff --git a/docs/ai/vector-stores/schema-with-record-definition.md b/docs/ai/vector-stores/schema-with-record-definition.md new file mode 100644 index 0000000000000..480fa322871f1 --- /dev/null +++ b/docs/ai/vector-stores/schema-with-record-definition.md @@ -0,0 +1,74 @@ +--- +title: Defining your storage schema using a record definition +description: Describes how to create a record definition to use when writing to or reading from a Vector Store. +ms.topic: reference +ms.date: 02/28/2026 +--- +# Define your storage schema using a record definition + +The Vector Store connectors use a model first approach to interacting with databases and allows annotating models with information that is needed for creating indexes or mapping data to the database schema. + +Another way of providing this information is via record definitions, which can be defined and supplied separately to the data model. This can be useful in multiple scenarios: + +- You want to use the same data model with more than one configuration. +- You want to use a built-in type, like a `Dictionary`, or an optimized format, like a dataframe, and still wants to leverage the vector store functionality. + +Here's an example of how to create a record definition: + +:::code language="csharp" source="./snippets/conceptual/schema-with-record-definition.cs" id="UseRecordDefinition"::: + +When you create a definition, you always have to provide a name and type for each property in your schema, since this is required for index creation and data mapping. + +To use the definition, pass it to the `GetCollection` method. + +:::code language="csharp" source="./snippets/conceptual/schema-with-record-definition.cs" id="DefineYourStorageSchemaUsingARecordDefin2"::: + +## Record property configuration classes + +### VectorStoreKeyProperty + +Use the class to indicate that your property is the key of the record. + +:::code language="csharp" source="./snippets/conceptual/schema-with-record-definition.cs" id="VectorStoreKeyProperty"::: + +#### VectorStoreKeyProperty configuration settings + +| Parameter | Required | Description | +|-----------|:--------:|-------------| +| `Name` | Yes | The name of the property on the data model. Used by the mapper to automatically map between the storage schema and data model and for creating indexes. | +| `Type` | No | The type of the property on the data model. Used by the mapper to automatically map between the storage schema and data model and for creating indexes. | +| `StorageName` | No | Can be used to supply an alternative name for the property in the database. This parameter is not supported by all connectors, for example, where alternatives like `JsonPropertyNameAttribute` is supported. | + +### VectorStoreDataProperty + +Use the class to indicate that your property contains general data that isn't a key or a vector. + +:::code language="csharp" source="./snippets/conceptual/schema-with-record-definition.cs" id="VectorStoreDataProperty"::: + +#### VectorStoreDataProperty configuration settings + +| Parameter | Required | Description | +|-----------|:--------:|-------------| +| `Name` | Yes | The name of the property on the data model. Used by the mapper to automatically map between the storage schema and data model and for creating indexes. | +| `Type` | No | The type of the property on the data model. Used by the mapper to automatically map between the storage schema and data model and for creating indexes. | +| `IsIndexed` | No | Indicates whether the property should be indexed for filtering in cases where a database requires opting in to indexing per property. Default is false. | +| `IsFullTextIndexed` | No | Indicates whether the property should be indexed for full text search for databases that support full text search. Default is false. | +| `StorageName` | No | Can be used to supply an alternative name for the property in the database. This parameter is not supported by all connectors, for example, where alternatives like `JsonPropertyNameAttribute` is supported. | + +### VectorStoreVectorProperty + +Use the class to indicate that your property contains a vector. + +:::code language="csharp" source="./snippets/conceptual/schema-with-record-definition.cs" id="VectorStoreVectorProperty"::: + +#### VectorStoreVectorProperty configuration settings + +| Parameter | Required | Description | +|-----------|:--------:|-------------| +| `Name` | Yes | The name of the property on the data model. Used by the mapper to automatically map between the storage schema and data model and for creating indexes. | +| `Type` | No | The type of the property on the data model. Used by the mapper to automatically map between the storage schema and data model and for creating indexes. | +| `Dimensions` | Yes | The number of dimensions that the vector has. This is required for creating a vector index for a collection. | +| `IndexKind` | No | The type of index to index the vector with. Default varies by vector store type. | +| `DistanceFunction` | No | The type of function to use when doing vector comparison during vector search over this vector. Default varies by vector store type. | +| `StorageName` | No | Can be used to supply an alternative name for the property in the database. This parameter is not supported by all connectors, for example, where alternatives like `JsonPropertyNameAttribute` is supported. | +| `EmbeddingGenerator` | No | Allows specifying a `Microsoft.Extensions.AI.IEmbeddingGenerator` instance to use for generating embeddings automatically for the decorated property. | diff --git a/docs/ai/quickstarts/snippets/chat-with-data/azure-openai/CloudService.cs b/docs/ai/vector-stores/snippets/chat-with-data/azure-openai/CloudService.cs similarity index 100% rename from docs/ai/quickstarts/snippets/chat-with-data/azure-openai/CloudService.cs rename to docs/ai/vector-stores/snippets/chat-with-data/azure-openai/CloudService.cs diff --git a/docs/ai/quickstarts/snippets/chat-with-data/azure-openai/Program.cs b/docs/ai/vector-stores/snippets/chat-with-data/azure-openai/Program.cs similarity index 100% rename from docs/ai/quickstarts/snippets/chat-with-data/azure-openai/Program.cs rename to docs/ai/vector-stores/snippets/chat-with-data/azure-openai/Program.cs diff --git a/docs/ai/quickstarts/snippets/chat-with-data/azure-openai/VectorDataAI.csproj b/docs/ai/vector-stores/snippets/chat-with-data/azure-openai/VectorDataAI.csproj similarity index 100% rename from docs/ai/quickstarts/snippets/chat-with-data/azure-openai/VectorDataAI.csproj rename to docs/ai/vector-stores/snippets/chat-with-data/azure-openai/VectorDataAI.csproj diff --git a/docs/ai/quickstarts/snippets/chat-with-data/openai/CloudService.cs b/docs/ai/vector-stores/snippets/chat-with-data/openai/CloudService.cs similarity index 100% rename from docs/ai/quickstarts/snippets/chat-with-data/openai/CloudService.cs rename to docs/ai/vector-stores/snippets/chat-with-data/openai/CloudService.cs diff --git a/docs/ai/quickstarts/snippets/chat-with-data/openai/Program.cs b/docs/ai/vector-stores/snippets/chat-with-data/openai/Program.cs similarity index 100% rename from docs/ai/quickstarts/snippets/chat-with-data/openai/Program.cs rename to docs/ai/vector-stores/snippets/chat-with-data/openai/Program.cs diff --git a/docs/ai/quickstarts/snippets/chat-with-data/openai/VectorDataAI.csproj b/docs/ai/vector-stores/snippets/chat-with-data/openai/VectorDataAI.csproj similarity index 100% rename from docs/ai/quickstarts/snippets/chat-with-data/openai/VectorDataAI.csproj rename to docs/ai/vector-stores/snippets/chat-with-data/openai/VectorDataAI.csproj diff --git a/docs/ai/vector-stores/snippets/conceptual/VectorStoreSnippets.csproj b/docs/ai/vector-stores/snippets/conceptual/VectorStoreSnippets.csproj new file mode 100644 index 0000000000000..8da7f054d84df --- /dev/null +++ b/docs/ai/vector-stores/snippets/conceptual/VectorStoreSnippets.csproj @@ -0,0 +1,22 @@ + + + + net10.0 + enable + enable + $(NoWarn);CS8019;CS0219;CS1591;CS8602 + + + + + + + + + + + + + + + diff --git a/docs/ai/vector-stores/snippets/conceptual/defining-your-data-model.cs b/docs/ai/vector-stores/snippets/conceptual/defining-your-data-model.cs new file mode 100644 index 0000000000000..f1a03abf786ca --- /dev/null +++ b/docs/ai/vector-stores/snippets/conceptual/defining-your-data-model.cs @@ -0,0 +1,27 @@ +using Microsoft.Extensions.VectorData; + +// +public class Hotel +{ + // + [VectorStoreKey] + public ulong HotelId { get; set; } + // + + // + [VectorStoreData(IsIndexed = true)] + public required string HotelName { get; set; } + // + + [VectorStoreData(IsFullTextIndexed = true)] + public required string Description { get; set; } + + // + [VectorStoreVector(Dimensions: 4, DistanceFunction = DistanceFunction.CosineSimilarity, IndexKind = IndexKind.Hnsw)] + public ReadOnlyMemory? DescriptionEmbedding { get; set; } + // + + [VectorStoreData(IsIndexed = true)] + public required string[] Tags { get; set; } +} +// diff --git a/docs/ai/vector-stores/snippets/conceptual/dynamic-data-model.cs b/docs/ai/vector-stores/snippets/conceptual/dynamic-data-model.cs new file mode 100644 index 0000000000000..c4c8f4fc499fd --- /dev/null +++ b/docs/ai/vector-stores/snippets/conceptual/dynamic-data-model.cs @@ -0,0 +1,39 @@ +using Microsoft.Extensions.VectorData; +using Microsoft.SemanticKernel.Connectors.Qdrant; +using Qdrant.Client; + +public class DynamicDataModel +{ + public static async Task RunAsync() + { + VectorStore vectorStore = new QdrantVectorStore(new QdrantClient("localhost"), ownsClient: true); + + // + VectorStoreCollectionDefinition definition = new() + { + Properties = + [ + new VectorStoreKeyProperty("Key", typeof(string)), + new VectorStoreDataProperty("Term", typeof(string)), + new VectorStoreDataProperty("Definition", typeof(string)), + new VectorStoreVectorProperty("DefinitionEmbedding", typeof(ReadOnlyMemory), dimensions: 1536) + ] + }; + + // Use GetDynamicCollection instead of the regular GetCollection method + // to get an instance of a collection using Dictionary. + VectorStoreCollection> dynamicDataModelCollection = + vectorStore.GetDynamicCollection("glossary", definition); + + // Since schema information is available from the record definition, + // it's possible to create a collection with the right vectors, + // dimensions, indexes, and distance functions. + await dynamicDataModelCollection.EnsureCollectionExistsAsync(); + + // When retrieving a record from the collection, + // access key, data, and vector values via the dictionary entries. + Dictionary? record = await dynamicDataModelCollection.GetAsync("SK"); + Console.WriteLine(record["Definition"]); + // + } +} diff --git a/docs/ai/vector-stores/snippets/conceptual/embedding-generation.cs b/docs/ai/vector-stores/snippets/conceptual/embedding-generation.cs new file mode 100644 index 0000000000000..5cccf1bbf1d1e --- /dev/null +++ b/docs/ai/vector-stores/snippets/conceptual/embedding-generation.cs @@ -0,0 +1,191 @@ +using Microsoft.Extensions.AI; +using Microsoft.Extensions.VectorData; +using Microsoft.SemanticKernel.Connectors.InMemory; +using Microsoft.SemanticKernel.Connectors.Qdrant; +using OpenAI; +using Qdrant.Client; + +public class EmbeddingExamples +{ + // + [VectorStoreVector(1536)] + public required string Embedding { get; set; } + // + + public static void ConfigureEmbeddingGeneration() + { + IEmbeddingGenerator embeddingGenerator = new OpenAIClient("your key") + .GetEmbeddingClient("your chosen model") + .AsIEmbeddingGenerator(); + + // + VectorStore vectorStore = new QdrantVectorStore( + new QdrantClient("localhost"), + ownsClient: true, + new QdrantVectorStoreOptions + { + EmbeddingGenerator = embeddingGenerator + }); + // + + // + var collectionOptions = new QdrantCollectionOptions + { + EmbeddingGenerator = embeddingGenerator + }; + + var collection = new QdrantCollection( + new QdrantClient("localhost"), + "myCollection", + ownsClient: true, + collectionOptions); + // + + // + var definition = new VectorStoreCollectionDefinition + { + EmbeddingGenerator = embeddingGenerator, + Properties = + [ + new VectorStoreKeyProperty("Key", typeof(ulong)), + new VectorStoreVectorProperty("DescriptionEmbedding", typeof(string), dimensions: 1536) + ] + }; + + collectionOptions = new QdrantCollectionOptions + { + Definition = definition + }; + + collection = new QdrantCollection( + new QdrantClient("localhost"), + "myCollection", + ownsClient: true, + collectionOptions); + // + + // + VectorStoreVectorProperty vectorProperty = new( + "DescriptionEmbedding", + typeof(string), + dimensions: 1536) + { + EmbeddingGenerator = embeddingGenerator + }; + // + } + + private class MyRecord { } +} + +public class ExampleUsage +{ + // + + // The data model. + internal class FinanceInfo + { + [VectorStoreKey] + public string Key { get; set; } = string.Empty; + + [VectorStoreData] + public string Text { get; set; } = string.Empty; + + // Note that the vector property is typed as a string, and + // its value is derived from the Text property. The string + // value will however be converted to a vector on upsert and + // stored in the database as a vector. + [VectorStoreVector(1536)] + public string Embedding => Text; + } + + public static async Task RunAsync() + { + // Create an OpenAI embedding generator. + var embeddingGenerator = new OpenAIClient("your key") + .GetEmbeddingClient("your chosen model") + .AsIEmbeddingGenerator(); + + // Use the embedding generator with the vector store. + VectorStore vectorStore = new InMemoryVectorStore(new() + { EmbeddingGenerator = embeddingGenerator } + ); + InMemoryCollection collection = + (InMemoryCollection)vectorStore.GetCollection("finances"); + await collection.EnsureCollectionExistsAsync(); + + // Create some test data. + string[] budgetInfo = + [ + "The budget for 2020 is EUR 100 000", + "The budget for 2021 is EUR 120 000", + "The budget for 2022 is EUR 150 000", + "The budget for 2023 is EUR 200 000", + "The budget for 2024 is EUR 364 000" + ]; + + // Embeddings are generated automatically on upsert. + IEnumerable records = budgetInfo.Select( + (input, index) => new FinanceInfo { Key = index.ToString(), Text = input } + ); + await collection.UpsertAsync(records); + + // Embeddings for the search is automatically generated on search. + IAsyncEnumerable> searchResult = + collection.SearchAsync("What is my budget for 2024?", top: 1); + + // Output the matching result. + await foreach (VectorSearchResult result in searchResult) + { + Console.WriteLine($"Key: {result.Record.Key}, Text: {result.Record.Text}"); + } + } + // + async Task GenerateEmbeddingsAndUpsertAsync( + IEmbeddingGenerator> embeddingGenerator, + VectorStoreCollection collection) + { + // Upsert a record. + string descriptionText = "A place where everyone can be happy."; + ulong hotelId = 1; + + // Generate the embedding. + ReadOnlyMemory embedding = + (await embeddingGenerator.GenerateAsync(descriptionText)).Vector; + + // Create a record and upsert with the already generated embedding. + await collection.UpsertAsync(new Hotel + { + HotelId = hotelId, + HotelName = "Hotel Happy", + Description = descriptionText, + DescriptionEmbedding = embedding, + Tags = ["luxury", "pool"] + }); + } + // + + // + async Task GenerateEmbeddingsAndSearchAsync( + IEmbeddingGenerator> embeddingGenerator, + VectorStoreCollection collection) + { + // Upsert a record. + string descriptionText = "Find me a hotel with happiness in mind."; + + // Generate the embedding. + ReadOnlyMemory searchEmbedding = + (await embeddingGenerator.GenerateAsync(descriptionText)).Vector; + + // Search using the already generated embedding. + IAsyncEnumerable> searchResult = collection.SearchAsync(searchEmbedding, top: 1); + List> resultItems = await searchResult.ToListAsync(); + + // Print the first search result. + Console.WriteLine("Score for first result: " + resultItems.FirstOrDefault()?.Score); + Console.WriteLine("Hotel description for first result: " + resultItems.FirstOrDefault()?.Record.Description); + } + // +} diff --git a/docs/ai/vector-stores/snippets/conceptual/hybrid-search.cs b/docs/ai/vector-stores/snippets/conceptual/hybrid-search.cs new file mode 100644 index 0000000000000..b8d86c32a5b8f --- /dev/null +++ b/docs/ai/vector-stores/snippets/conceptual/hybrid-search.cs @@ -0,0 +1,188 @@ +using Microsoft.SemanticKernel.Connectors.Qdrant; +using Microsoft.Extensions.VectorData; +using Qdrant.Client; + +public class HybridSearchExample +{ + public async static void Run() + { + // + // Create a Qdrant VectorStore object and choose + // an existing collection that already contains records. + VectorStore vectorStore = new QdrantVectorStore(new QdrantClient("localhost"), ownsClient: true); + IKeywordHybridSearchable collection = + (IKeywordHybridSearchable)vectorStore.GetCollection("skhotels"); + + // Generate a vector for your search text, using + // your chosen embedding generation implementation. + ReadOnlyMemory searchVector = await GenerateAsync("I'm looking for a hotel where customer happiness is the priority."); + + // Do the search, passing an options object with a Top value to limit results to the single top match. + IAsyncEnumerable> searchResult = + collection.HybridSearchAsync(searchVector, ["happiness", "hotel", "customer"], top: 1); + + // Inspect the returned hotel. + await foreach (VectorSearchResult record in searchResult) + { + Console.WriteLine($"Found hotel description: {record.Record.Description}"); + Console.WriteLine($"Found record score: {record.Score}"); + } + // + } + + private static async Task> GenerateAsync(string v) => throw new NotImplementedException(); + + public class VPAndAddlPropertyExample + { + private object searchVector; + + // + public async Task Run() + { + var vectorStore = new QdrantVectorStore(new QdrantClient("localhost"), ownsClient: true); + var collection = (IKeywordHybridSearchable)vectorStore.GetCollection("skproducts"); + + // Create the hybrid search options and indicate that you want + // to search the DescriptionEmbedding vector property and the + // Description full text search property. + var hybridSearchOptions = new HybridSearchOptions + { + VectorProperty = r => r.DescriptionEmbedding, + AdditionalProperty = r => r.Description + }; + + // This snippet assumes searchVector is already provided, having been created using the embedding model of your choice. + IAsyncEnumerable> searchResult = + collection.HybridSearchAsync(searchVector, ["happiness", "hotel", "customer"], top: 3, hybridSearchOptions); + } + } + + public sealed class Product + { + [VectorStoreKey] + public int Key { get; set; } + + [VectorStoreData(IsFullTextIndexed = true)] + public required string Name { get; set; } + + [VectorStoreData(IsFullTextIndexed = true)] + public required string Description { get; set; } + + [VectorStoreData] + public required List FeatureList { get; set; } + + [VectorStoreVector(1536)] + public ReadOnlyMemory DescriptionEmbedding { get; set; } + + [VectorStoreVector(1536)] + public ReadOnlyMemory FeatureListEmbedding { get; set; } + } + // + + public class TopAndSkipExample + { + private object searchVector; + private IKeywordHybridSearchable collection; + + public async Task TopAndSkipSearch() + { + // + // Create the vector search options and indicate that you want to skip the first 40 results and then pass 20 to search to get the next 20. + HybridSearchOptions hybridSearchOptions = new() + { + Skip = 40 + }; + + // This snippet assumes searchVector is already provided, + // having been created using the embedding model of your choice. + IAsyncEnumerable> searchResult = + collection.HybridSearchAsync( + searchVector, + ["happiness", "hotel", "customer"], + top: 20, + hybridSearchOptions); + + // Iterate over the search results. + await foreach (VectorSearchResult result in searchResult) + { + Console.WriteLine(result.Record.Description); + } + // + + // + // Create the hybrid search options and indicate that you want to include vectors in the search results. + hybridSearchOptions = new HybridSearchOptions() + { + IncludeVectors = true + }; + + // This snippet assumes searchVector is already provided, having been created using the embedding model of your choice. + searchResult = collection.HybridSearchAsync( + searchVector, + ["happiness", "hotel", "customer"], + top: 3, + hybridSearchOptions); + + // Iterate over the search results. + await foreach (VectorSearchResult result in searchResult) + { + Console.WriteLine(result.Record.FeatureList); + } + // + } + } + + public class FilterExample + { + private object searchVector; + private IKeywordHybridSearchable collection; + + // + public async Task FilterSearch() + { + // Create the hybrid search options and set the filter on the options. + HybridSearchOptions searchOptions = new() + { + Filter = r => r.Category == "External Definitions" && r.Tags.Contains("memory") + }; + + // This snippet assumes searchVector is already provided, + // having been created using the embedding model of your choice. + IAsyncEnumerable> searchResult = + collection.HybridSearchAsync( + searchVector, + ["happiness", "hotel", "customer"], + top: 3, + searchOptions); + + // Iterate over the search results. + await foreach (VectorSearchResult result in searchResult) + { + Console.WriteLine(result.Record.Definition); + } + } + + sealed class Glossary + { + [VectorStoreKey] + public ulong Key { get; set; } + + // Category is marked as indexed, since you want to filter using this property. + [VectorStoreData(IsIndexed = true)] + public required string Category { get; set; } + + // Tags is marked as indexed, since you want to filter using this property. + [VectorStoreData(IsIndexed = true)] + public required List Tags { get; set; } + [VectorStoreData] + public required string Term { get; set; } + + [VectorStoreData] + public required string Definition { get; set; } + + [VectorStoreVector(1536)] + public ReadOnlyMemory DefinitionEmbedding { get; set; } + } + // + } +} diff --git a/docs/ai/vector-stores/snippets/conceptual/schema-with-record-definition.cs b/docs/ai/vector-stores/snippets/conceptual/schema-with-record-definition.cs new file mode 100644 index 0000000000000..f11f4bbcd98e8 --- /dev/null +++ b/docs/ai/vector-stores/snippets/conceptual/schema-with-record-definition.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.VectorData; + +public class RecordDefinitionExample +{ + public void CreateHotelDefinition() + { + // + VectorStoreCollectionDefinition hotelDefinition = new() + { + Properties = + [ + new VectorStoreKeyProperty("HotelId", typeof(ulong)), + new VectorStoreDataProperty("HotelName", typeof(string)) { IsIndexed = true }, + new VectorStoreDataProperty("Description", typeof(string)) { IsFullTextIndexed = true }, + new VectorStoreVectorProperty("DescriptionEmbedding", typeof(float), dimensions: 4) { DistanceFunction = DistanceFunction.CosineSimilarity, IndexKind = IndexKind.Hnsw }, + ] + }; + // + } +} diff --git a/docs/ai/vector-stores/snippets/conceptual/vector-search.cs b/docs/ai/vector-stores/snippets/conceptual/vector-search.cs new file mode 100644 index 0000000000000..1ddc3e25838bd --- /dev/null +++ b/docs/ai/vector-stores/snippets/conceptual/vector-search.cs @@ -0,0 +1,177 @@ +using System.Security.Cryptography.X509Certificates; +using Microsoft.Extensions.VectorData; +using Microsoft.SemanticKernel.Connectors.InMemory; +using Microsoft.SemanticKernel.Connectors.Qdrant; +using Qdrant.Client; + +public class VectorSearchExample +{ + private object searchVector; + private VectorStoreCollection collection; + + // + public async Task SearchAsync() + { + // Create a Qdrant VectorStore object and choose + // an existing collection that already contains records. + VectorStore vectorStore = new QdrantVectorStore(new QdrantClient("localhost"), ownsClient: true); + VectorStoreCollection collection = + vectorStore.GetCollection("skhotels"); + + // Generate a vector for your search text, using + // your chosen embedding generation implementation. + ReadOnlyMemory searchVector = + await GenerateAsync("I'm looking for a hotel where customer happiness is the priority."); + + // Do the search, passing an options object with + // a Top value to limit results to the single top match. + IAsyncEnumerable> searchResult = + collection.SearchAsync(searchVector, top: 1); + + // Inspect the returned hotel. + await foreach (VectorSearchResult record in searchResult) + { + Console.WriteLine("Found hotel description: " + record.Record.Description); + Console.WriteLine("Found record score: " + record.Score); + } + } + // + + private async Task> GenerateAsync(string v) => + throw new NotImplementedException(); + + // + public async Task VectorPropertySearch() + { + var vectorStore = new InMemoryVectorStore(); + InMemoryCollection collection = + vectorStore.GetCollection("skproducts"); + + // Create the vector search options and indicate that you want to search the FeatureListEmbedding property. + var vectorSearchOptions = new VectorSearchOptions + { + VectorProperty = r => r.FeatureListEmbedding + }; + + // This snippet assumes searchVector is already provided, having been created using the embedding model of your choice. + IAsyncEnumerable> searchResult = + collection.SearchAsync(searchVector, top: 3, vectorSearchOptions); + } + + public sealed class Product + { + [VectorStoreKey] + public int Key { get; set; } + + [VectorStoreData] + public required string Description { get; set; } + + [VectorStoreData] + public required List FeatureList { get; set; } + + [VectorStoreVector(1536)] + public ReadOnlyMemory DescriptionEmbedding { get; set; } + + [VectorStoreVector(1536)] + public ReadOnlyMemory FeatureListEmbedding { get; set; } + } + // + // Create the vector search options and indicate + // that you want to skip the first 40 results. + VectorSearchOptions vectorSearchOptions = new() + { + Skip = 40 + }; + + // This snippet assumes searchVector is already provided, + // having been created using the embedding model of your choice. + // Pass 'top: 20' to indicate that you want to retrieve + // the next 20 results after skipping the first 40. + IAsyncEnumerable> searchResult = + collection.SearchAsync(searchVector, top: 20, vectorSearchOptions); + + // Iterate over the search results. + await foreach (VectorSearchResult result in searchResult) + { + Console.WriteLine(result.Record.FeatureList); + } + // + } + + public async Task IncludeVectorsSearch() + { + // + // Create the vector search options and indicate that you want to include vectors in the search results. + var vectorSearchOptions = new VectorSearchOptions + { + IncludeVectors = true + }; + + // This snippet assumes searchVector is already provided, + // having been created using the embedding model of your choice. + IAsyncEnumerable> searchResult = + collection.SearchAsync(searchVector, top: 3, vectorSearchOptions); + + // Iterate over the search results. + await foreach (VectorSearchResult result in searchResult) + { + Console.WriteLine(result.Record.FeatureList); + } + // + } +} + +public class FilterExample +{ + private static object searchVector; + private static VectorStoreCollection collection; + + // + public static async Task FilteredSearchAsync() + { + // Create the vector search options and set the filter on the options. + VectorSearchOptions vectorSearchOptions = new() + { + Filter = r => r.Category == "External Definitions" && r.Tags.Contains("memory") + }; + + // This snippet assumes searchVector is already provided, + // having been created using the embedding model of your choice. + IAsyncEnumerable> searchResult = + collection.SearchAsync(searchVector, top: 3, vectorSearchOptions); + + // Iterate over the search results. + await foreach (VectorSearchResult result in searchResult) + { + Console.WriteLine(result.Record.Definition); + + } + } + + sealed class Glossary + { + [VectorStoreKey] + public ulong Key { get; set; } + + // Category is marked as indexed, since you want to filter using this property. + [VectorStoreData(IsIndexed = true)] + public required string Category { get; set; } + + // Tags is marked as indexed, since you want to filter using this property. + [VectorStoreData(IsIndexed = true)] + public required List Tags { get; set; } + [VectorStoreData] + public required string Term { get; set; } + + [VectorStoreData] + public required string Definition { get; set; } + + [VectorStoreVector(1536)] + public ReadOnlyMemory DefinitionEmbedding { get; set; } + } + // +} diff --git a/docs/ai/vector-stores/snippets/how-to/DataIngestion.cs b/docs/ai/vector-stores/snippets/how-to/DataIngestion.cs new file mode 100644 index 0000000000000..7f2ae3797123a --- /dev/null +++ b/docs/ai/vector-stores/snippets/how-to/DataIngestion.cs @@ -0,0 +1,135 @@ +using System.Text; +using System.Xml; +using DocumentFormat.OpenXml.Packaging; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.VectorData; + +namespace VectorIngest; + +// +internal class TextParagraph +{ + /// A unique key for the text paragraph. + [VectorStoreKey] + public required string Key { get; init; } + + /// A URI that points at the original location of the document containing the text. + [VectorStoreData] + public required string DocumentUri { get; init; } + + /// The ID of the paragraph from the document containing the text. + [VectorStoreData] + public required string ParagraphId { get; init; } + + /// The text of the paragraph. + [VectorStoreData] + public required string Text { get; init; } + + /// The embedding generated from the text. + [VectorStoreVector(1536)] + public ReadOnlyMemory TextEmbedding { get; set; } +} +// + +// +internal class DocumentReader +{ + public static IEnumerable ReadParagraphs(Stream documentContents, string documentUri) + { + // Open the document. + using WordprocessingDocument wordDoc = WordprocessingDocument.Open(documentContents, false); + if (wordDoc.MainDocumentPart == null) + { + yield break; + } + + // Create an XmlDocument to hold the document contents + // and load the document contents into the XmlDocument. + XmlDocument xmlDoc = new(); + XmlNamespaceManager nsManager = new(xmlDoc.NameTable); + nsManager.AddNamespace("w", "http://schemas.openxmlformats.org/wordprocessingml/2006/main"); + nsManager.AddNamespace("w14", "http://schemas.microsoft.com/office/word/2010/wordml"); + + xmlDoc.Load(wordDoc.MainDocumentPart.GetStream()); + + // Select all paragraphs in the document and break if none found. + XmlNodeList? paragraphs = xmlDoc.SelectNodes("//w:p", nsManager); + if (paragraphs == null) + { + yield break; + } + + // Iterate over each paragraph. + foreach (XmlNode paragraph in paragraphs) + { + // Select all text nodes in the paragraph and continue if none found. + XmlNodeList? texts = paragraph.SelectNodes(".//w:t", nsManager); + if (texts == null) + { + continue; + } + + // Combine all non-empty text nodes into a single string. + var textBuilder = new StringBuilder(); + foreach (XmlNode text in texts) + { + if (!string.IsNullOrWhiteSpace(text.InnerText)) + { + textBuilder.Append(text.InnerText); + } + } + + // Yield a new TextParagraph if the combined text is not empty. + string combinedText = textBuilder.ToString(); + if (!string.IsNullOrWhiteSpace(combinedText)) + { + Console.WriteLine("Found paragraph:"); + Console.WriteLine(combinedText); + Console.WriteLine(); + + yield return new TextParagraph + { + Key = Guid.NewGuid().ToString(), + DocumentUri = documentUri, + ParagraphId = paragraph.Attributes?["w14:paraId"]?.Value ?? string.Empty, + Text = combinedText + }; + } + } + } +} +// + +// +internal class DataUploader( + VectorStore vectorStore, + IEmbeddingGenerator> embeddingGenerator) +{ + /// + /// Generate an embedding for each text paragraph and upload it to the specified collection. + /// + /// The name of the collection to upload the text paragraphs to. + /// The text paragraphs to upload. + /// An async task. + public async Task GenerateEmbeddingsAndUpload(string collectionName, IEnumerable textParagraphs) + { + VectorStoreCollection collection = + vectorStore.GetCollection(collectionName); + await collection.EnsureCollectionExistsAsync(); + + foreach (TextParagraph paragraph in textParagraphs) + { + // Generate the text embedding. + Console.WriteLine($"Generating embedding for paragraph: {paragraph.ParagraphId}"); + paragraph.TextEmbedding = (await embeddingGenerator.GenerateAsync(paragraph.Text)).Vector; + + // Upload the text paragraph. + Console.WriteLine($"Upserting paragraph: {paragraph.ParagraphId}"); + await collection.UpsertAsync(paragraph); + + Console.WriteLine(); + } + } +} +// diff --git a/docs/ai/vector-stores/snippets/how-to/Hotel.cs b/docs/ai/vector-stores/snippets/how-to/Hotel.cs new file mode 100644 index 0000000000000..632bffd5fe3ca --- /dev/null +++ b/docs/ai/vector-stores/snippets/how-to/Hotel.cs @@ -0,0 +1,21 @@ +// +using Microsoft.Extensions.VectorData; + +public record class Hotel +{ + [VectorStoreKey] + public int HotelId { get; set; } + + [VectorStoreData(IsIndexed = true)] + public string? HotelName { get; set; } + + [VectorStoreData(IsFullTextIndexed = true)] + public string? Description { get; set; } + + [VectorStoreVector(Dimensions: 1536, DistanceFunction = DistanceFunction.CosineSimilarity)] + public ReadOnlyMemory? DescriptionEmbedding { get; set; } + + [VectorStoreData(IsIndexed = true)] + public string[]? Tags { get; set; } +} +// diff --git a/docs/ai/vector-stores/snippets/how-to/HowToSnippets.csproj b/docs/ai/vector-stores/snippets/how-to/HowToSnippets.csproj new file mode 100644 index 0000000000000..76610bbb55fa4 --- /dev/null +++ b/docs/ai/vector-stores/snippets/how-to/HowToSnippets.csproj @@ -0,0 +1,22 @@ + + + + Exe + net10.0 + enable + enable + $(NoWarn);CS8019;CS0219;CS1591;CS8602 + + + + + + + + + + + + + + diff --git a/docs/ai/vector-stores/snippets/how-to/Program.cs b/docs/ai/vector-stores/snippets/how-to/Program.cs new file mode 100644 index 0000000000000..9031980080dcc --- /dev/null +++ b/docs/ai/vector-stores/snippets/how-to/Program.cs @@ -0,0 +1,145 @@ +using Microsoft.Extensions.VectorData; +using Microsoft.SemanticKernel.Connectors.InMemory; + +// +// Create an in-memory vector store (no external service required). +// For production, replace this with a connector for your preferred database. +var vectorStore = new InMemoryVectorStore(); +// + +// +// Get a reference to a collection named "hotels". +VectorStoreCollection collection = + vectorStore.GetCollection("hotels"); + +// Ensure the collection exists in the database. +await collection.EnsureCollectionExistsAsync(); +// + +// +// Upsert records into the collection. +// In a real app, generate embeddings using an IEmbeddingGenerator. +// The CreateFakeEmbedding helper at the bottom of this file generates +// placeholder vectors for demonstration purposes only. +var hotels = new List +{ + new() + { + HotelId = 1, + HotelName = "Seaside Retreat", + Description = "A peaceful hotel on the coast with stunning ocean views.", + DescriptionEmbedding = CreateFakeEmbedding(1), + Tags = ["beach", "ocean", "relaxation"] + }, + new() + { + HotelId = 2, + HotelName = "Mountain Lodge", + Description = "A cozy lodge in the mountains with hiking trails nearby.", + DescriptionEmbedding = CreateFakeEmbedding(2), + Tags = ["mountain", "hiking", "nature"] + }, + new() + { + HotelId = 3, + HotelName = "City Centre Hotel", + Description = "A modern hotel in the heart of the city, close to attractions.", + DescriptionEmbedding = CreateFakeEmbedding(3), + Tags = ["city", "business", "urban"] + } +}; + +foreach (Hotel h in hotels) +{ + await collection.UpsertAsync(h); +} +// + +// +// Get a specific record by its key. +Hotel? hotel = await collection.GetAsync(1); +if (hotel is not null) +{ + Console.WriteLine($"Hotel: {hotel.HotelName}"); + Console.WriteLine($"Description: {hotel.Description}"); +} +// + +// +// Get multiple records by their keys. +IAsyncEnumerable hotelBatch = collection.GetAsync([1, 2, 3]); +await foreach (Hotel h in hotelBatch) +{ + Console.WriteLine($"Batch hotel: {h.HotelName}"); +} +// + +ReadOnlyMemory queryEmbedding = CreateFakeEmbedding(1); + +// +// Search for the top 2 hotels most similar to the query embedding. +IAsyncEnumerable> searchResults = + collection.SearchAsync(queryEmbedding, top: 2); + +await foreach (VectorSearchResult result in searchResults) +{ + Console.WriteLine($"Found: {result.Record.HotelName} (score: {result.Score:F4})"); +} +// + +// +// Filter results before the vector comparison. +// Only properties marked with IsIndexed = true can be used in filters. +var searchOptions = new VectorSearchOptions +{ + Filter = h => h.HotelName == "Seaside Retreat" +}; + +IAsyncEnumerable> filteredResults = + collection.SearchAsync(queryEmbedding, top: 2, searchOptions); + +await foreach (VectorSearchResult result in filteredResults) +{ + Console.WriteLine($"Filtered: {result.Record.HotelName} (score: {result.Score:F4})"); +} +// + +// +// Use VectorSearchOptions to control paging and vector inclusion. +var pagedOptions = new VectorSearchOptions +{ + Skip = 1, // Skip the first result (useful for paging). + IncludeVectors = false // Don't include vector data in results (default). +}; + +IAsyncEnumerable> pagedResults = + collection.SearchAsync(queryEmbedding, top: 2, pagedOptions); + +await foreach (VectorSearchResult result in pagedResults) +{ + Console.WriteLine($"Paged: {result.Record.HotelName}"); +} +// + +// +// Delete a record by its key. +await collection.DeleteAsync(3); +// + +// +// Delete the entire collection from the vector store. +await collection.EnsureCollectionDeletedAsync(); +// + +// Helper to create a predictable fake embedding for demonstration. +// In a real app, use IEmbeddingGenerator to generate real embeddings. +static ReadOnlyMemory CreateFakeEmbedding(int seed) +{ + var random = new Random(seed); + float[] vector = new float[1536]; + for (int i = 0; i < vector.Length; i++) + { + vector[i] = (float)random.NextDouble(); + } + return new ReadOnlyMemory(vector); +} diff --git a/docs/ai/vector-stores/snippets/how-to/PutItAllTogether.cs b/docs/ai/vector-stores/snippets/how-to/PutItAllTogether.cs new file mode 100644 index 0000000000000..d174c2bb45683 --- /dev/null +++ b/docs/ai/vector-stores/snippets/how-to/PutItAllTogether.cs @@ -0,0 +1,50 @@ +using Azure; +using Azure.AI.OpenAI; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.VectorData; +using Microsoft.SemanticKernel.Connectors.Redis; +using VectorIngest; + +public class PutItAllTogether +{ + public static async Task Main() + { + // + // Replace with your values. + string deploymentName = "text-embedding-ada-002"; + string endpoint = "https://sksample.openai.azure.com/"; + string apiKey = "your-api-key"; + + // Register Azure OpenAI embedding generator and Redis vector store. + var services = new ServiceCollection(); + services.AddSingleton>>( + new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(apiKey)) + .GetEmbeddingClient(deploymentName) + .AsIEmbeddingGenerator()); + + services.AddRedisVectorStore("localhost:6379"); + + // Register the data uploader. + services.AddSingleton(); + + // Build the service provider and get the data uploader. + ServiceProvider serviceProvider = services.BuildServiceProvider(); + DataUploader dataUploader = serviceProvider.GetRequiredService(); + // + + // + // Load the data. + IEnumerable textParagraphs = DocumentReader.ReadParagraphs( + new FileStream( + "vector-store-data-ingestion-input.docx", + FileMode.Open), + "file:///c:/vector-store-data-ingestion-input.docx"); + + await dataUploader.GenerateEmbeddingsAndUpload( + "sk-documentation", + textParagraphs); + + // + } +} diff --git a/docs/ai/vector-stores/tutorial-vector-search.md b/docs/ai/vector-stores/tutorial-vector-search.md new file mode 100644 index 0000000000000..e5268cf7c08d4 --- /dev/null +++ b/docs/ai/vector-stores/tutorial-vector-search.md @@ -0,0 +1,296 @@ +--- +title: Tutorial - Integrate OpenAI with the RAG pattern and vector search using Azure Cosmos DB for MongoDB +description: Create a simple recipe app using the RAG pattern and vector search using Azure Cosmos DB for MongoDB. +ms.date: 02/28/2026 +ms.topic: tutorial +author: alexwolfmsft +ms.author: alexwolf +--- + +# Implement Azure OpenAI with RAG using vector search in a .NET app + +This tutorial explores integration of the RAG pattern using OpenAI models and vector search capabilities in a .NET app. The sample application performs vector searches on custom data stored in Azure Cosmos DB for MongoDB and further refines the responses using generative AI models, such as GPT-35 and GPT-4. In the sections that follow, you'll set up a sample application and explore key code examples that demonstrate these concepts. + +## Prerequisites + +- [.NET 8.0](https://dotnet.microsoft.com/) +- An [Azure Account](https://azure.microsoft.com/free) +- An [Azure Cosmos DB for MongoDB vCore](/azure/cosmos-db/mongodb/vcore/introduction) service +- An [Azure OpenAI](/azure/ai-services/openai/overview) service + - Deploy `text-embedding-ada-002` model for embeddings + - Deploy `gpt-35-turbo` model for chat completions + +## App overview + +The Cosmos Recipe Guide app allows you to perform vector and AI driven searches against a set of recipe data. You can search directly for available recipes or prompt the app with ingredient names to find related recipes. The app and the sections ahead guide you through the following workflow to demonstrate this type of functionality: + +1. Upload sample data to an Azure Cosmos DB for MongoDB database. +1. Create embeddings and a vector index for the uploaded sample data using the Azure OpenAI `text-embedding-ada-002` model. +1. Perform vector similarity search based on the user prompts. +1. Use the Azure OpenAI `gpt-35-turbo` completions model to compose more meaningful answers based on the search results data. + + :::image type="content" source="../media/get-started-app-chat-template/contoso-recipes.png" alt-text="A screenshot showing the running sample app."::: + +## Get started + +1. Clone the following GitHub repository: + + ```bash + git clone https://github.com/microsoft/AzureDataRetrievalAugmentedGenerationSamples.git + ``` + +1. In the _C#/CosmosDB-MongoDBvCore_ folder, open the **CosmosRecipeGuide.sln** file. + +1. In the _appsettings.json_ file, replace the following config values with your Azure OpenAI and Azure CosmosDB for MongoDb values: + + ```json + "OpenAIEndpoint": "https://.openai.azure.com/", + "OpenAIKey": "", + "OpenAIEmbeddingDeployment": "", + "OpenAIcompletionsDeployment": "", + "MongoVcoreConnection": "" + ``` + +1. Launch the app by pressing the **Start** button at the top of Visual Studio. + +## Explore the app + +When you run the app for the first time, it connects to Azure Cosmos DB and reports that there are no recipes available yet. Follow the steps displayed by the app to begin the core workflow. + +1. Select **Upload recipe(s) to Cosmos DB** and press Enter. This command reads sample JSON files from the local project and uploads them to the Cosmos DB account. + + The code from the _Utility.cs_ class parses the local JSON files. + + ```csharp + public static List ParseDocuments(string Folderpath) + { + List recipes = new List(); + + Directory.GetFiles(Folderpath) + .ToList() + .ForEach(f => + { + var jsonString= System.IO.File.ReadAllText(f); + Recipe recipe = JsonConvert.DeserializeObject(jsonString); + recipe.id = recipe.name.ToLower().Replace(" ", ""); + recipes.Add(recipe); + } + ); + + return recipes; + } + ``` + + The `UpsertVectorAsync` method in the _VCoreMongoService.cs_ file uploads the documents to Azure Cosmos DB for MongoDB. + + ```csharp + public async Task UpsertVectorAsync(Recipe recipe) + { + BsonDocument document = recipe.ToBsonDocument(); + + if (!document.Contains("_id")) + { + Console.WriteLine("UpsertVectorAsync: Document does not contain _id."); + throw new ArgumentException("UpsertVectorAsync: Document does not contain _id."); + } + + string? _idValue = document["_id"].ToString(); + + try + { + var filter = Builders.Filter.Eq("_id", _idValue); + var options = new ReplaceOptions { IsUpsert = true }; + await _recipeCollection.ReplaceOneAsync(filter, document, options); + } + catch (Exception ex) + { + Console.WriteLine($"Exception: UpsertVectorAsync(): {ex.Message}"); + throw; + } + } + ``` + +1. Select **Vectorize the recipe(s) and store them in Cosmos DB**. + + The JSON items uploaded to Cosmos DB do not contain embeddings and therefore are not optimized for RAG via vector search. An embedding is an information-dense, numerical representation of the semantic meaning of a piece of text. Vector searches are able to find items with contextually similar embeddings. + + The `GetEmbeddingsAsync` method in the _OpenAIService.cs_ file creates an embedding for each item in the database. + + ```csharp + public async Task GetEmbeddingsAsync(dynamic data) + { + try + { + EmbeddingsOptions options = new EmbeddingsOptions(data) + { + Input = data + }; + + var response = await _openAIClient.GetEmbeddingsAsync(openAIEmbeddingDeployment, options); + + Embeddings embeddings = response.Value; + float[] embedding = embeddings.Data[0].Embedding.ToArray(); + + return embedding; + } + catch (Exception ex) + { + Console.WriteLine($"GetEmbeddingsAsync Exception: {ex.Message}"); + return null; + } + } + ``` + + The `CreateVectorIndexIfNotExists` in the _VCoreMongoService.cs_ file creates a vector index, which enables you to perform vector similarity searches. + + ```csharp + public void CreateVectorIndexIfNotExists(string vectorIndexName) + { + try + { + //Find if vector index exists in vectors collection + using (IAsyncCursor indexCursor = _recipeCollection.Indexes.List()) + { + bool vectorIndexExists = indexCursor.ToList().Any(x => x["name"] == vectorIndexName); + if (!vectorIndexExists) + { + BsonDocumentCommand command = new BsonDocumentCommand( + BsonDocument.Parse(@" + { createIndexes: 'Recipe', + indexes: [{ + name: 'vectorSearchIndex', + key: { embedding: 'cosmosSearch' }, + cosmosSearchOptions: { + kind: 'vector-ivf', + numLists: 5, + similarity: 'COS', + dimensions: 1536 } + }] + }")); + + BsonDocument result = _database.RunCommand(command); + if (result["ok"] != 1) + { + Console.WriteLine("CreateIndex failed with response: " + result.ToJson()); + } + } + } + } + catch (MongoException ex) + { + Console.WriteLine("MongoDbService InitializeVectorIndex: " + ex.Message); + throw; + } + } + ``` + +1. Select the **Ask AI Assistant (search for a recipe by name or description, or ask a question)** option in the application to run a user query. + + The user query is converted to an embedding using the OpenAI service and the embedding model. The embedding is then sent to Azure Cosmos DB for MongoDB and is used to perform a vector search. The method in the _VCoreMongoService.cs_ file performs a vector search to find vectors that are close to the supplied vector and returns a list of documents from Azure Cosmos DB for MongoDB vCore. + + ```csharp + public async Task> VectorSearchAsync(float[] queryVector) + { + List retDocs = new List(); + string resultDocuments = string.Empty; + + try + { + //Search Azure Cosmos DB for MongoDB vCore collection for similar embeddings + //Project the fields that are needed + BsonDocument[] pipeline = new BsonDocument[] + { + BsonDocument.Parse( + @$"{{$search: {{ + cosmosSearch: + {{ vector: [{string.Join(',', queryVector)}], + path: 'embedding', + k: {_maxVectorSearchResults}}}, + returnStoredSource:true + }} + }}"), + BsonDocument.Parse($"{{$project: {{embedding: 0}}}}"), + }; + + var bsonDocuments = await _recipeCollection + .Aggregate(pipeline).ToListAsync(); + + var recipes = bsonDocuments + .ToList() + .ConvertAll(bsonDocument => + BsonSerializer.Deserialize(bsonDocument)); + return recipes; + } + catch (MongoException ex) + { + Console.WriteLine($"Exception: VectorSearchAsync(): {ex.Message}"); + throw; + } + } + ``` + + The `GetChatCompletionAsync` method generates an improved chat completion response based on the user prompt and the related vector search results. + + ```csharp + public async Task<(string response, int promptTokens, int responseTokens)> GetChatCompletionAsync(string userPrompt, string documents) + { + try + { + ChatMessage systemMessage = new ChatMessage( + ChatRole.System, _systemPromptRecipeAssistant + documents); + ChatMessage userMessage = new ChatMessage( + ChatRole.User, userPrompt); + + ChatCompletionsOptions options = new() + { + Messages = + { + systemMessage, + userMessage + }, + MaxTokens = openAIMaxTokens, + Temperature = 0.5f, //0.3f, + NucleusSamplingFactor = 0.95f, + FrequencyPenalty = 0, + PresencePenalty = 0 + }; + + Azure.Response completionsResponse = + await openAIClient.GetChatCompletionsAsync(openAICompletionDeployment, options); + ChatCompletions completions = completionsResponse.Value; + + return ( + response: completions.Choices[0].Message.Content, + promptTokens: completions.Usage.PromptTokens, + responseTokens: completions.Usage.CompletionTokens + ); + + } + catch (Exception ex) + { + string message = $"OpenAIService.GetChatCompletionAsync(): {ex.Message}"; + Console.WriteLine(message); + throw; + } + } + ``` + + The app also uses prompt engineering to ensure OpenAI service limits and formats the response for supplied recipes. + + ```csharp + //System prompts to send with user prompts to instruct the model for chat session + private readonly string _systemPromptRecipeAssistant = @" + You are an intelligent assistant for Contoso Recipes. + You are designed to provide helpful answers to user questions about + recipes, cooking instructions provided in JSON format below. + + Instructions: + - Only answer questions related to the recipe provided below. + - Don't reference any recipe not provided below. + - If you're unsure of an answer, say ""I don't know"" and recommend users search themselves. + - Your response should be complete. + - List the Name of the Recipe at the start of your response followed by step by step cooking instructions. + - Assume the user is not an expert in cooking. + - Format the content so that it can be printed to the Command Line console. + - In case there is more than one recipe you find, let the user pick the most appropriate recipe."; + ``` diff --git a/docs/ai/vector-stores/vector-search.md b/docs/ai/vector-stores/vector-search.md new file mode 100644 index 0000000000000..1b7370fc95797 --- /dev/null +++ b/docs/ai/vector-stores/vector-search.md @@ -0,0 +1,80 @@ +--- +title: Vector search using Vector Store connectors +description: Describes the different options you can use when doing a vector search using Vector Store connectors. +ms.topic: concept-article +ms.date: 02/28/2026 +--- +# Vector search using Vector Store connectors + +The library provides vector search capabilities as part of its Vector Store abstractions. These capabilities include filtering and many other options. + +> [!TIP] +> To see how you can search without generating embeddings yourself, see [Let the Vector Store generate embeddings](./embedding-generation.md#let-the-vector-store-generate-embeddings). + +## Vector search + +The method allows searching using data that has already been vectorized. This method takes a vector and an optional class as input. is available on the following types: + +- +- + +Note that implements `IVectorSearchable`. + +Assuming you have a collection that already contains data, you can easily search it. Here is an example using Qdrant. + +:::code language="csharp" source="./snippets/conceptual/vector-search.cs" id="VectorSearch"::: + +> [!TIP] +> For more information on how to generate embeddings see [embedding generation](./embedding-generation.md). + +## Supported vector types + + takes a generic type as the vector parameter. The types of vectors supported by each data store vary. + +It's also important for the search vector type to match the target vector that is being searched, for example, if you have two vectors on the same record with different vector types, make sure that the search vector you supply matches the type of the specific vector you are targeting. For information on how to pick a target vector if you have more than one per record, see [VectorProperty](#vectorproperty). + +## Vector search options + +The following options can be provided using the `VectorSearchOptions` class. + +### VectorProperty + +Use the `VectorProperty` option to specify the vector property to target during the search. If none is provided and the data model contains only one vector, that vector will be used. If the data model contains no vector or multiple vectors and `VectorProperty` is not provided, the search method throws an exceptoin. + +:::code language="csharp" source="./snippets/conceptual/vector-search.cs" id="VectorProperty"::: + +### `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. You can use `Top` and `Skip` to do paging if you want to retrieve a large number of results using separate calls. + +:::code language="csharp" source="./snippets/conceptual/vector-search.cs" id="TopAndSkip"::: + +The default value for `Skip` is 0. + +### IncludeVectors + +The `IncludeVectors` option allows you to specify whether you want to return vectors in the search results. +If `false`, the vector properties on the returned model are 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 `IncludeVectors` is `false`. + +:::code language="csharp" source="./snippets/conceptual/vector-search.cs" id="IncludeVectors"::: + +### Filter + +The vector search 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 for example, access control purposes, by excluding data that the user shouldn't have access to. + +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 might optionally allow indexing to improve filtering performance. + +If you're creating a collection via the Vector Store abstractions and you want to enable filtering on a field, set the property to `true` when defining your data model or when creating your record definition. + +> [!TIP] +> For more information on how to set the property, see [VectorStoreDataAttribute parameters](./defining-your-data-model.md#vectorstoredataattribute-parameters) or [VectorStoreDataProperty configuration settings](./schema-with-record-definition.md#vectorstoredataproperty-configuration-settings). + +Filters are expressed using LINQ expressions based on the type of the data model. The set of LINQ expressions supported will vary depending on the functionality supported by each database, but all databases support a broad base of common expressions, for example, equals, not equals, `and`, and `or`. + +:::code language="csharp" source="./snippets/conceptual/vector-search.cs" id="Filter":::