diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props
index 69548fef9058..282a4a768b59 100644
--- a/dotnet/Directory.Packages.props
+++ b/dotnet/Directory.Packages.props
@@ -87,6 +87,7 @@
+
diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln
index acf61a114486..6517eeca2b29 100644
--- a/dotnet/SK-dotnet.sln
+++ b/dotnet/SK-dotnet.sln
@@ -107,11 +107,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{958AD708-F04
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Diagnostics", "Diagnostics", "{29E7D971-1308-4171-9872-E8E4669A1134}"
ProjectSection(SolutionItems) = preProject
+ src\InternalUtilities\src\Diagnostics\ActivityExtensions.cs = src\InternalUtilities\src\Diagnostics\ActivityExtensions.cs
src\InternalUtilities\src\Diagnostics\CompilerServicesAttributes.cs = src\InternalUtilities\src\Diagnostics\CompilerServicesAttributes.cs
src\InternalUtilities\src\Diagnostics\DynamicallyAccessedMembersAttribute.cs = src\InternalUtilities\src\Diagnostics\DynamicallyAccessedMembersAttribute.cs
src\InternalUtilities\src\Diagnostics\ExceptionExtensions.cs = src\InternalUtilities\src\Diagnostics\ExceptionExtensions.cs
src\InternalUtilities\src\Diagnostics\ExperimentalAttribute.cs = src\InternalUtilities\src\Diagnostics\ExperimentalAttribute.cs
src\InternalUtilities\src\Diagnostics\IsExternalInit.cs = src\InternalUtilities\src\Diagnostics\IsExternalInit.cs
+ src\InternalUtilities\src\Diagnostics\KernelVerify.cs = src\InternalUtilities\src\Diagnostics\KernelVerify.cs
+ src\InternalUtilities\src\Diagnostics\LoggingExtensions.cs = src\InternalUtilities\src\Diagnostics\LoggingExtensions.cs
src\InternalUtilities\src\Diagnostics\NullableAttributes.cs = src\InternalUtilities\src\Diagnostics\NullableAttributes.cs
src\InternalUtilities\src\Diagnostics\RequiresDynamicCodeAttribute.cs = src\InternalUtilities\src\Diagnostics\RequiresDynamicCodeAttribute.cs
src\InternalUtilities\src\Diagnostics\RequiresUnreferencedCodeAttribute.cs = src\InternalUtilities\src\Diagnostics\RequiresUnreferencedCodeAttribute.cs
@@ -547,6 +550,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Runtime.InProcess", "src\Ag
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Runtime.InProcess.Tests", "src\Agents\Runtime\InProcess.Tests\Runtime.InProcess.Tests.csproj", "{DA6B4ED4-ED0B-D25C-889C-9F940E714891}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VectorData.UnitTests", "src\Connectors\VectorData.UnitTests\VectorData.UnitTests.csproj", "{AAC7B5E8-CC4E-49D0-AF6A-2B4F7B43BD84}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1508,6 +1513,12 @@ Global
{DA6B4ED4-ED0B-D25C-889C-9F940E714891}.Publish|Any CPU.Build.0 = Release|Any CPU
{DA6B4ED4-ED0B-D25C-889C-9F940E714891}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DA6B4ED4-ED0B-D25C-889C-9F940E714891}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AAC7B5E8-CC4E-49D0-AF6A-2B4F7B43BD84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AAC7B5E8-CC4E-49D0-AF6A-2B4F7B43BD84}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AAC7B5E8-CC4E-49D0-AF6A-2B4F7B43BD84}.Publish|Any CPU.ActiveCfg = Debug|Any CPU
+ {AAC7B5E8-CC4E-49D0-AF6A-2B4F7B43BD84}.Publish|Any CPU.Build.0 = Debug|Any CPU
+ {AAC7B5E8-CC4E-49D0-AF6A-2B4F7B43BD84}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AAC7B5E8-CC4E-49D0-AF6A-2B4F7B43BD84}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1713,6 +1724,7 @@ Global
{A4F05541-7D23-A5A9-033D-382F1E13D0FE} = {A70ED5A7-F8E1-4A57-9455-3C05989542DA}
{CCC909E4-5269-A31E-0BFD-4863B4B29BBB} = {A70ED5A7-F8E1-4A57-9455-3C05989542DA}
{DA6B4ED4-ED0B-D25C-889C-9F940E714891} = {A70ED5A7-F8E1-4A57-9455-3C05989542DA}
+ {AAC7B5E8-CC4E-49D0-AF6A-2B4F7B43BD84} = {5A7028A7-4DDF-4E4F-84A9-37CE8F8D7E89}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83}
diff --git a/dotnet/docs/EXPERIMENTS.md b/dotnet/docs/EXPERIMENTS.md
index 99fd9b56afb4..1e143695e0f0 100644
--- a/dotnet/docs/EXPERIMENTS.md
+++ b/dotnet/docs/EXPERIMENTS.md
@@ -25,6 +25,8 @@ You can use the following diagnostic IDs to ignore warnings or errors for a part
| SKEXP0100 | Advanced Semantic Kernel features |
| SKEXP0110 | Semantic Kernel Agents |
| SKEXP0120 | Native-AOT |
+| MEVD9000 | Microsoft.Extensions.VectorData experimental user-facing APIs |
+| MEVD9001 | Microsoft.Extensions.VectorData experimental connector-facing APIs |
## Experimental Features Tracking
diff --git a/dotnet/samples/Concepts/Caching/SemanticCachingWithFilters.cs b/dotnet/samples/Concepts/Caching/SemanticCachingWithFilters.cs
index 78c54df49434..4d9eb15d36a2 100644
--- a/dotnet/samples/Concepts/Caching/SemanticCachingWithFilters.cs
+++ b/dotnet/samples/Concepts/Caching/SemanticCachingWithFilters.cs
@@ -199,8 +199,8 @@ public async Task OnPromptRenderAsync(PromptRenderContext context, Funcfalse
true
- $(NoWarn);CS8618,IDE0009,IDE1006,CA1051,CA1050,CA1707,CA1054,CA2007,VSTHRD111,CS1591,RCS1110,RCS1243,CA5394,SKEXP0001,SKEXP0010,SKEXP0020,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0070,SKEXP0101,SKEXP0110,OPENAI001,CA1724
+ $(NoWarn);CS8618,IDE0009,IDE1006,CA1051,CA1050,CA1707,CA1054,CA2007,VSTHRD111,CS1591,RCS1110,RCS1243,CA5394,SKEXP0001,SKEXP0010,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0070,SKEXP0101,SKEXP0110,OPENAI001,CA1724,MEVD9000Library5ee045b0-aea3-4f08-8d31-32d1a6f8fed0
diff --git a/dotnet/samples/Concepts/Memory/HuggingFace_TextEmbeddingCustomHttpHandler.cs b/dotnet/samples/Concepts/Memory/HuggingFace_TextEmbeddingCustomHttpHandler.cs
index 744274d4c527..d9ebbf568c3a 100644
--- a/dotnet/samples/Concepts/Memory/HuggingFace_TextEmbeddingCustomHttpHandler.cs
+++ b/dotnet/samples/Concepts/Memory/HuggingFace_TextEmbeddingCustomHttpHandler.cs
@@ -15,6 +15,7 @@ namespace Memory;
/// For example, the cointegrated/LaBSE-en-ru model returns results as a 1 * 1 * 4 * 768 matrix, which is different from Hugging Face embedding generation service implementation.
/// To address this, a custom can be used to modify the response before sending it back.
///
+[Obsolete("The IMemoryStore abstraction is being obsoleted")]
public class HuggingFace_TextEmbeddingCustomHttpHandler(ITestOutputHelper output) : BaseTest(output)
{
public async Task RunInferenceApiEmbeddingCustomHttpHandlerAsync()
diff --git a/dotnet/samples/Concepts/Memory/MemoryStore_CustomReadOnly.cs b/dotnet/samples/Concepts/Memory/MemoryStore_CustomReadOnly.cs
deleted file mode 100644
index e8994db01afd..000000000000
--- a/dotnet/samples/Concepts/Memory/MemoryStore_CustomReadOnly.cs
+++ /dev/null
@@ -1,239 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using System.Numerics.Tensors;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Text.Json;
-using Microsoft.SemanticKernel.Memory;
-
-namespace Memory;
-
-///
-/// This sample provides a custom implementation of that is read only.
-/// In this sample, the data is stored in a JSON string and deserialized into an
-/// . For this specific sample, the implementation
-/// of has a single collection, and thus does not need to be named.
-/// It also assumes that the JSON formatted data can be deserialized into objects.
-///
-public class MemoryStore_CustomReadOnly(ITestOutputHelper output) : BaseTest(output)
-{
- [Fact]
- public async Task RunAsync()
- {
- var store = new ReadOnlyMemoryStore(s_jsonVectorEntries);
-
- var embedding = new ReadOnlyMemory([22, 4, 6]);
-
- Console.WriteLine("Reading data from custom read-only memory store");
- var memoryRecord = await store.GetAsync("collection", "key3");
- if (memoryRecord is not null)
- {
- Console.WriteLine($"ID = {memoryRecord.Metadata.Id}, Embedding = {string.Join(", ", MemoryMarshal.ToEnumerable(memoryRecord.Embedding))}");
- }
-
- Console.WriteLine($"Getting most similar vector to {string.Join(", ", MemoryMarshal.ToEnumerable(embedding))}");
- var result = await store.GetNearestMatchAsync("collection", embedding, 0.0);
- if (result.HasValue)
- {
- Console.WriteLine($"ID = {string.Join(", ", MemoryMarshal.ToEnumerable(result.Value.Item1.Embedding))}, Embedding = {result.Value.Item2}");
- }
- }
-
- private sealed class ReadOnlyMemoryStore : IMemoryStore
- {
- private readonly MemoryRecord[]? _memoryRecords = null;
- private readonly int _vectorSize = 3;
-
- public ReadOnlyMemoryStore(string valueString)
- {
- s_jsonVectorEntries = s_jsonVectorEntries.Replace("\n", string.Empty, StringComparison.Ordinal);
- s_jsonVectorEntries = s_jsonVectorEntries.Replace(" ", string.Empty, StringComparison.Ordinal);
- this._memoryRecords = JsonSerializer.Deserialize(valueString);
-
- if (this._memoryRecords is null)
- {
- throw new Exception("Unable to deserialize memory records");
- }
- }
-
- public Task CreateCollectionAsync(string collectionName, CancellationToken cancellationToken = default)
- {
- throw new System.NotImplementedException();
- }
-
- public Task DeleteCollectionAsync(string collectionName, CancellationToken cancellationToken = default)
- {
- throw new System.NotImplementedException();
- }
-
- public Task DoesCollectionExistAsync(string collectionName, CancellationToken cancellationToken = default)
- {
- throw new System.NotImplementedException();
- }
-
- public Task GetAsync(string collectionName, string key, bool withEmbedding = false, CancellationToken cancellationToken = default)
- {
- // Note: with this simple implementation, the MemoryRecord will always contain the embedding.
- return Task.FromResult(this._memoryRecords?.FirstOrDefault(x => x.Key == key));
- }
-
- public async IAsyncEnumerable GetBatchAsync(string collectionName, IEnumerable keys, bool withEmbeddings = false, [EnumeratorCancellation] CancellationToken cancellationToken = default)
- {
- // Note: with this simple implementation, the MemoryRecord will always contain the embedding.
- if (this._memoryRecords is not null)
- {
- foreach (var memoryRecord in this._memoryRecords)
- {
- if (keys.Contains(memoryRecord.Key))
- {
- yield return memoryRecord;
- }
- }
- }
- }
-
- public IAsyncEnumerable GetCollectionsAsync(CancellationToken cancellationToken = default)
- {
- throw new System.NotImplementedException();
- }
-
- public async Task<(MemoryRecord, double)?> GetNearestMatchAsync(string collectionName, ReadOnlyMemory embedding, double minRelevanceScore = 0,
- bool withEmbedding = false, CancellationToken cancellationToken = default)
- {
- // Note: with this simple implementation, the MemoryRecord will always contain the embedding.
- await foreach (var item in this.GetNearestMatchesAsync(
- collectionName: collectionName,
- embedding: embedding,
- limit: 1,
- minRelevanceScore: minRelevanceScore,
- withEmbeddings: withEmbedding,
- cancellationToken: cancellationToken).ConfigureAwait(false))
- {
- return item;
- }
-
- return default;
- }
-
- public async IAsyncEnumerable<(MemoryRecord, double)> GetNearestMatchesAsync(string collectionName, ReadOnlyMemory embedding, int limit,
- double minRelevanceScore = 0, bool withEmbeddings = false, [EnumeratorCancellation] CancellationToken cancellationToken = default)
- {
- // Note: with this simple implementation, the MemoryRecord will always contain the embedding.
- if (this._memoryRecords is null || this._memoryRecords.Length == 0)
- {
- yield break;
- }
-
- if (embedding.Length != this._vectorSize)
- {
- throw new Exception($"Embedding vector size {embedding.Length} does not match expected size of {this._vectorSize}");
- }
-
- List<(MemoryRecord Record, double Score)> embeddings = [];
-
- foreach (var item in this._memoryRecords)
- {
- double similarity = TensorPrimitives.CosineSimilarity(embedding.Span, item.Embedding.Span);
- if (similarity >= minRelevanceScore)
- {
- embeddings.Add(new(item, similarity));
- }
- }
-
- foreach (var item in embeddings.OrderByDescending(l => l.Score).Take(limit))
- {
- yield return (item.Record, item.Score);
- }
- }
-
- public Task RemoveAsync(string collectionName, string key, CancellationToken cancellationToken = default)
- {
- throw new System.NotImplementedException();
- }
-
- public Task RemoveBatchAsync(string collectionName, IEnumerable keys, CancellationToken cancellationToken = default)
- {
- throw new System.NotImplementedException();
- }
-
- public Task UpsertAsync(string collectionName, MemoryRecord record, CancellationToken cancellationToken = default)
- {
- throw new System.NotImplementedException();
- }
-
- public IAsyncEnumerable UpsertBatchAsync(string collectionName, IEnumerable records, CancellationToken cancellationToken = default)
- {
- throw new System.NotImplementedException();
- }
- }
-
- private static string s_jsonVectorEntries = """
- [
- {
- "embedding": [0, 0, 0],
- "metadata": {
- "is_reference": false,
- "external_source_name": "externalSourceName",
- "id": "Id1",
- "description": "description",
- "text": "text",
- "additional_metadata" : "value:"
- },
- "key": "key1",
- "timestamp": null
- },
- {
- "embedding": [0, 0, 10],
- "metadata": {
- "is_reference": false,
- "external_source_name": "externalSourceName",
- "id": "Id2",
- "description": "description",
- "text": "text",
- "additional_metadata" : "value:"
- },
- "key": "key2",
- "timestamp": null
- },
- {
- "embedding": [1, 2, 3],
- "metadata": {
- "is_reference": false,
- "external_source_name": "externalSourceName",
- "id": "Id3",
- "description": "description",
- "text": "text",
- "additional_metadata" : "value:"
- },
- "key": "key3",
- "timestamp": null
- },
- {
- "embedding": [-1, -2, -3],
- "metadata": {
- "is_reference": false,
- "external_source_name": "externalSourceName",
- "id": "Id4",
- "description": "description",
- "text": "text",
- "additional_metadata" : "value:"
- },
- "key": "key4",
- "timestamp": null
- },
- {
- "embedding": [12, 8, 4],
- "metadata": {
- "is_reference": false,
- "external_source_name": "externalSourceName",
- "id": "Id5",
- "description": "description",
- "text": "text",
- "additional_metadata" : "value:"
- },
- "key": "key5",
- "timestamp": null
- }
- ]
- """;
-}
diff --git a/dotnet/samples/Concepts/Memory/SemanticTextMemory_Building.cs b/dotnet/samples/Concepts/Memory/SemanticTextMemory_Building.cs
deleted file mode 100644
index 72cb44af516a..000000000000
--- a/dotnet/samples/Concepts/Memory/SemanticTextMemory_Building.cs
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using Microsoft.SemanticKernel.Connectors.AzureAISearch;
-using Microsoft.SemanticKernel.Connectors.OpenAI;
-using Microsoft.SemanticKernel.Memory;
-
-namespace Memory;
-
-/* The files contains two examples about SK Semantic Memory.
- *
- * 1. Memory using Azure AI Search.
- * 2. Memory using a custom embedding generator and vector engine.
- *
- * Semantic Memory allows to store your data like traditional DBs,
- * adding the ability to query it using natural language.
- */
-public class SemanticTextMemory_Building(ITestOutputHelper output) : BaseTest(output)
-{
- private const string MemoryCollectionName = "SKGitHub";
-
- [Fact]
- public async Task RunAsync()
- {
- Console.WriteLine("==============================================================");
- Console.WriteLine("======== Semantic Memory using Azure AI Search ========");
- Console.WriteLine("==============================================================");
-
- /* This example leverages Azure AI Search to provide SK with Semantic Memory.
- *
- * Azure AI Search automatically indexes your data semantically, so you don't
- * need to worry about embedding generation.
- */
-
- var memoryWithACS = new MemoryBuilder()
- .WithOpenAITextEmbeddingGeneration("text-embedding-ada-002", TestConfiguration.OpenAI.ApiKey)
- .WithMemoryStore(new AzureAISearchMemoryStore(TestConfiguration.AzureAISearch.Endpoint, TestConfiguration.AzureAISearch.ApiKey))
- .Build();
-
- await RunExampleAsync(memoryWithACS);
-
- Console.WriteLine("====================================================");
- Console.WriteLine("======== Semantic Memory (volatile, in RAM) ========");
- Console.WriteLine("====================================================");
-
- /* You can build your own semantic memory combining an Embedding Generator
- * with a Memory storage that supports search by similarity (ie semantic search).
- *
- * In this example we use a volatile memory, a local simulation of a vector DB.
- *
- * You can replace VolatileMemoryStore with Qdrant (see QdrantMemoryStore connector)
- * or implement your connectors for Pinecone, Vespa, Postgres + pgvector, SQLite VSS, etc.
- */
-
- var memoryWithCustomDb = new MemoryBuilder()
- .WithOpenAITextEmbeddingGeneration("text-embedding-ada-002", TestConfiguration.OpenAI.ApiKey)
- .WithMemoryStore(new VolatileMemoryStore())
- .Build();
-
- // Uncomment the following line to use GoogleAI embeddings
- // var memoryWithCustomDb = new MemoryBuilder()
- // .WithGoogleAITextEmbeddingGeneration(TestConfiguration.GoogleAI.EmbeddingModelId, TestConfiguration.GoogleAI.ApiKey)
- // .WithMemoryStore(new VolatileMemoryStore())
- // .Build();
-
- await RunExampleAsync(memoryWithCustomDb);
- }
-
- private async Task RunExampleAsync(ISemanticTextMemory memory)
- {
- await StoreMemoryAsync(memory);
-
- await SearchMemoryAsync(memory, "How do I get started?");
-
- /*
- Output:
-
- Query: How do I get started?
-
- Result 1:
- URL: : https://github.com/microsoft/semantic-kernel/blob/main/README.md
- Title : README: Installation, getting started, and how to contribute
-
- Result 2:
- URL: : https://github.com/microsoft/semantic-kernel/blob/main/samples/dotnet-jupyter-notebooks/00-getting-started.ipynb
- Title : Jupyter notebook describing how to get started with the Semantic Kernel
-
- */
-
- await SearchMemoryAsync(memory, "Can I build a chat with SK?");
-
- /*
- Output:
-
- Query: Can I build a chat with SK?
-
- Result 1:
- URL: : https://github.com/microsoft/semantic-kernel/tree/main/prompt_template_samples/ChatPlugin/ChatGPT
- Title : Sample demonstrating how to create a chat plugin interfacing with ChatGPT
-
- Result 2:
- URL: : https://github.com/microsoft/semantic-kernel/blob/main/samples/apps/chat-summary-webapp-react/README.md
- Title : README: README associated with a sample chat summary react-based webapp
-
- */
- }
-
- private async Task SearchMemoryAsync(ISemanticTextMemory memory, string query)
- {
- Console.WriteLine("\nQuery: " + query + "\n");
-
- var memoryResults = memory.SearchAsync(MemoryCollectionName, query, limit: 2, minRelevanceScore: 0.5);
-
- int i = 0;
- await foreach (MemoryQueryResult memoryResult in memoryResults)
- {
- Console.WriteLine($"Result {++i}:");
- Console.WriteLine(" URL: : " + memoryResult.Metadata.Id);
- Console.WriteLine(" Title : " + memoryResult.Metadata.Description);
- Console.WriteLine(" Relevance: " + memoryResult.Relevance);
- Console.WriteLine();
- }
-
- Console.WriteLine("----------------------");
- }
-
- private async Task StoreMemoryAsync(ISemanticTextMemory memory)
- {
- /* Store some data in the semantic memory.
- *
- * When using Azure AI Search the data is automatically indexed on write.
- *
- * When using the combination of VolatileStore and Embedding generation, SK takes
- * care of creating and storing the index
- */
-
- Console.WriteLine("\nAdding some GitHub file URLs and their descriptions to the semantic memory.");
- var githubFiles = SampleData();
- var i = 0;
- foreach (var entry in githubFiles)
- {
- await memory.SaveReferenceAsync(
- collection: MemoryCollectionName,
- externalSourceName: "GitHub",
- externalId: entry.Key,
- description: entry.Value,
- text: entry.Value);
-
- Console.Write($" #{++i} saved.");
- }
-
- Console.WriteLine("\n----------------------");
- }
-
- private static Dictionary SampleData()
- {
- return new Dictionary
- {
- ["https://github.com/microsoft/semantic-kernel/blob/main/README.md"]
- = "README: Installation, getting started, and how to contribute",
- ["https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/02-running-prompts-from-file.ipynb"]
- = "Jupyter notebook describing how to pass prompts from a file to a semantic plugin or function",
- ["https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/00-getting-started.ipynb"]
- = "Jupyter notebook describing how to get started with the Semantic Kernel",
- ["https://github.com/microsoft/semantic-kernel/tree/main/prompt_template_samples/ChatPlugin/ChatGPT"]
- = "Sample demonstrating how to create a chat plugin interfacing with ChatGPT",
- ["https://github.com/microsoft/semantic-kernel/blob/main/dotnet/src/Plugins/Plugins.Memory/VolatileMemoryStore.cs"]
- = "C# class that defines a volatile embedding store",
- };
- }
-}
diff --git a/dotnet/samples/Concepts/Memory/TextMemoryPlugin_GeminiEmbeddingGeneration.cs b/dotnet/samples/Concepts/Memory/TextMemoryPlugin_GeminiEmbeddingGeneration.cs
deleted file mode 100644
index 0313370782e0..000000000000
--- a/dotnet/samples/Concepts/Memory/TextMemoryPlugin_GeminiEmbeddingGeneration.cs
+++ /dev/null
@@ -1,279 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using Microsoft.SemanticKernel;
-using Microsoft.SemanticKernel.Connectors.Google;
-using Microsoft.SemanticKernel.Embeddings;
-using Microsoft.SemanticKernel.Memory;
-
-namespace Memory;
-
-///
-/// Represents an example class for Gemini Embedding Generation with volatile memory store.
-///
-public sealed class TextMemoryPlugin_GeminiEmbeddingGeneration(ITestOutputHelper output) : BaseTest(output)
-{
- private const string MemoryCollectionName = "aboutMe";
-
- [Fact]
- public async Task GoogleAIAsync()
- {
- Console.WriteLine("============= Google AI - Gemini Embedding Generation =============");
-
- Assert.NotNull(TestConfiguration.GoogleAI.ApiKey);
- Assert.NotNull(TestConfiguration.GoogleAI.EmbeddingModelId);
-
- Kernel kernel = Kernel.CreateBuilder()
- .AddGoogleAIGeminiChatCompletion(
- modelId: TestConfiguration.GoogleAI.EmbeddingModelId,
- apiKey: TestConfiguration.GoogleAI.ApiKey)
- .AddGoogleAIEmbeddingGeneration(
- modelId: TestConfiguration.GoogleAI.EmbeddingModelId,
- apiKey: TestConfiguration.GoogleAI.ApiKey)
- .Build();
-
- await this.RunSimpleSampleAsync(kernel);
- await this.RunTextMemoryPluginSampleAsync(kernel);
- }
-
- [Fact]
- public async Task VertexAIAsync()
- {
- Console.WriteLine("============= Vertex AI - Gemini Embedding Generation =============");
-
- Assert.NotNull(TestConfiguration.VertexAI.BearerKey);
- Assert.NotNull(TestConfiguration.VertexAI.Location);
- Assert.NotNull(TestConfiguration.VertexAI.ProjectId);
- Assert.NotNull(TestConfiguration.VertexAI.Gemini.ModelId);
- Assert.NotNull(TestConfiguration.VertexAI.EmbeddingModelId);
-
- Kernel kernel = Kernel.CreateBuilder()
- .AddVertexAIGeminiChatCompletion(
- modelId: TestConfiguration.VertexAI.Gemini.ModelId,
- bearerKey: TestConfiguration.VertexAI.BearerKey,
- location: TestConfiguration.VertexAI.Location,
- projectId: TestConfiguration.VertexAI.ProjectId)
- .AddVertexAIEmbeddingGeneration(
- modelId: TestConfiguration.VertexAI.EmbeddingModelId,
- bearerKey: TestConfiguration.VertexAI.BearerKey,
- location: TestConfiguration.VertexAI.Location,
- projectId: TestConfiguration.VertexAI.ProjectId)
- .Build();
-
- // To generate bearer key, you need installed google sdk or use google web console with command:
- //
- // gcloud auth print-access-token
- //
- // Above code pass bearer key as string, it is not recommended way in production code,
- // especially if IChatCompletionService and IEmbeddingGenerationService will be long lived, tokens generated by google sdk lives for 1 hour.
- // You should use bearer key provider, which will be used to generate token on demand:
- //
- // Example:
- //
- // Kernel kernel = Kernel.CreateBuilder()
- // .AddVertexAIGeminiChatCompletion(
- // modelId: TestConfiguration.VertexAI.Gemini.ModelId,
- // bearerKeyProvider: () =>
- // {
- // // This is just example, in production we recommend using Google SDK to generate your BearerKey token.
- // // This delegate will be called on every request,
- // // when providing the token consider using caching strategy and refresh token logic when it is expired or close to expiration.
- // return GetBearerKey();
- // },
- // location: TestConfiguration.VertexAI.Location,
- // projectId: TestConfiguration.VertexAI.ProjectId)
- // .AddVertexAIEmbeddingGeneration(
- // modelId: embeddingModelId,
- // bearerKeyProvider: () =>
- // {
- // // This is just example, in production we recommend using Google SDK to generate your BearerKey token.
- // // This delegate will be called on every request,
- // // when providing the token consider using caching strategy and refresh token logic when it is expired or close to expiration.
- // return GetBearerKey();
- // },
- // location: geminiLocation,
- // projectId: geminiProject);
-
- await this.RunSimpleSampleAsync(kernel);
- await this.RunTextMemoryPluginSampleAsync(kernel);
- }
-
- private async Task RunSimpleSampleAsync(Kernel kernel)
- {
- Console.WriteLine("== Simple Sample: Generating Embeddings ==");
-
- // Obtain an embedding generator.
- var embeddingGenerator = kernel.GetRequiredService();
-
- var generatedEmbeddings = await embeddingGenerator.GenerateEmbeddingAsync("My name is Andrea");
- Console.WriteLine($"Generated Embeddings count: {generatedEmbeddings.Length}, " +
- $"First five: {string.Join(", ", generatedEmbeddings[..5])}...");
- Console.WriteLine();
- }
-
- private async Task RunTextMemoryPluginSampleAsync(Kernel kernel)
- {
- Console.WriteLine("== Complex Sample: TextMemoryPlugin ==");
-
- var memoryStore = new VolatileMemoryStore();
-
- // Obtain an embedding generator to use for semantic memory.
- var embeddingGenerator = kernel.GetRequiredService();
-
- // The combination of the text embedding generator and the memory store makes up the 'SemanticTextMemory' object used to
- // store and retrieve memories.
- Microsoft.SemanticKernel.Memory.SemanticTextMemory textMemory = new(memoryStore, embeddingGenerator);
-
- /////////////////////////////////////////////////////////////////////////////////////////////////////
- // PART 1: Store and retrieve memories using the ISemanticTextMemory (textMemory) object.
- //
- // This is a simple way to store memories from a code perspective, without using the Kernel.
- /////////////////////////////////////////////////////////////////////////////////////////////////////
- Console.WriteLine("== PART 1: Saving Memories through the ISemanticTextMemory object ==");
-
- Console.WriteLine("Saving memory with key 'info1': \"My name is Andrea\"");
- await textMemory.SaveInformationAsync(MemoryCollectionName, id: "info1", text: "My name is Andrea");
-
- Console.WriteLine("Saving memory with key 'info2': \"I work as a tourist operator\"");
- await textMemory.SaveInformationAsync(MemoryCollectionName, id: "info2", text: "I work as a tourist operator");
-
- Console.WriteLine("Saving memory with key 'info3': \"I've been living in Seattle since 2005\"");
- await textMemory.SaveInformationAsync(MemoryCollectionName, id: "info3", text: "I've been living in Seattle since 2005");
-
- Console.WriteLine("Saving memory with key 'info4': \"I visited France and Italy five times since 2015\"");
- await textMemory.SaveInformationAsync(MemoryCollectionName, id: "info4", text: "I visited France and Italy five times since 2015");
-
- Console.WriteLine();
-
- /////////////////////////////////////////////////////////////////////////////////////////////////////
- // PART 2: Create TextMemoryPlugin, store memories through the Kernel.
- //
- // This enables prompt functions and the AI (via Planners) to access memories
- /////////////////////////////////////////////////////////////////////////////////////////////////////
-
- Console.WriteLine("== PART 2: Saving Memories through the Kernel with TextMemoryPlugin and the 'Save' function ==");
-
- // Import the TextMemoryPlugin into the Kernel for other functions
- var memoryPlugin = kernel.ImportPluginFromObject(new Microsoft.SemanticKernel.Plugins.Memory.TextMemoryPlugin(textMemory));
-
- // Save a memory with the Kernel
- Console.WriteLine("Saving memory with key 'info5': \"My family is from New York\"");
- await kernel.InvokeAsync(memoryPlugin["Save"], new()
- {
- [Microsoft.SemanticKernel.Plugins.Memory.TextMemoryPlugin.InputParam] = "My family is from New York",
- [Microsoft.SemanticKernel.Plugins.Memory.TextMemoryPlugin.CollectionParam] = MemoryCollectionName,
- [Microsoft.SemanticKernel.Plugins.Memory.TextMemoryPlugin.KeyParam] = "info5",
- });
-
- Console.WriteLine();
-
- /////////////////////////////////////////////////////////////////////////////////////////////////////
- // PART 3: Recall similar ideas with semantic search
- //
- // Uses AI Embeddings for fuzzy lookup of memories based on intent, rather than a specific key.
- /////////////////////////////////////////////////////////////////////////////////////////////////////
-
- Console.WriteLine("== PART 3: Recall (similarity search) with AI Embeddings ==");
-
- Console.WriteLine("== PART 3a: Recall (similarity search) with ISemanticTextMemory ==");
- Console.WriteLine("Ask: live in Seattle?");
-
- await foreach (var answer in textMemory.SearchAsync(
- collection: MemoryCollectionName,
- query: "live in Seattle?",
- limit: 2,
- minRelevanceScore: 0.79,
- withEmbeddings: true))
- {
- Console.WriteLine($"Answer: {answer.Metadata.Text}");
- }
-
- /* Possible output:
- Answer: I've been living in Seattle since 2005
- */
-
- Console.WriteLine("== PART 3b: Recall (similarity search) with Kernel and TextMemoryPlugin 'Recall' function ==");
- Console.WriteLine("Ask: my family is from?");
-
- var result = await kernel.InvokeAsync(memoryPlugin["Recall"], new()
- {
- [Microsoft.SemanticKernel.Plugins.Memory.TextMemoryPlugin.InputParam] = "Ask: my family is from?",
- [Microsoft.SemanticKernel.Plugins.Memory.TextMemoryPlugin.CollectionParam] = MemoryCollectionName,
- [Microsoft.SemanticKernel.Plugins.Memory.TextMemoryPlugin.LimitParam] = "2",
- [Microsoft.SemanticKernel.Plugins.Memory.TextMemoryPlugin.RelevanceParam] = "0.79",
- });
-
- Console.WriteLine($"Answer: {result.GetValue()}");
- Console.WriteLine();
-
- /* Possible output:
- Answer: ["My family is from New York"]
- */
-
- /////////////////////////////////////////////////////////////////////////////////////////////////////
- // PART 4: TextMemoryPlugin Recall in a Prompt Function
- //
- // Looks up related memories when rendering a prompt template, then sends the rendered prompt to
- // the text generation model to answer a natural language query.
- /////////////////////////////////////////////////////////////////////////////////////////////////////
-
- Console.WriteLine("== PART 4: Using TextMemoryPlugin 'Recall' function in a Prompt Function ==");
-
- // Build a prompt function that uses memory to find facts
- const string RecallFunctionDefinition = @"
-Consider only the facts below when answering questions:
-
-BEGIN FACTS
-About me: {{recall 'live in Seattle?'}}
-About me: {{recall 'my family is from?'}}
-END FACTS
-
-Question: {{$input}}
-
-Answer:
-";
-
- result = await kernel.InvokePromptAsync(RecallFunctionDefinition, new(new GeminiPromptExecutionSettings { MaxTokens = 1000 })
- {
- [Microsoft.SemanticKernel.Plugins.Memory.TextMemoryPlugin.InputParam] = "Where are my family from?",
- [Microsoft.SemanticKernel.Plugins.Memory.TextMemoryPlugin.CollectionParam] = MemoryCollectionName,
- [Microsoft.SemanticKernel.Plugins.Memory.TextMemoryPlugin.LimitParam] = "2",
- [Microsoft.SemanticKernel.Plugins.Memory.TextMemoryPlugin.RelevanceParam] = "0.79",
- });
-
- Console.WriteLine("Ask: Where are my family from?");
- Console.WriteLine($"Answer: {result.GetValue()}");
-
- /* Possible output:
- Answer: New York
- */
-
- Console.WriteLine();
-
- /////////////////////////////////////////////////////////////////////////////////////////////////////
- // PART 5: Cleanup, deleting database collection
- //
- /////////////////////////////////////////////////////////////////////////////////////////////////////
-
- Console.WriteLine("== PART 5: Cleanup, deleting database collection ==");
-
- Console.WriteLine("Printing Collections in DB...");
- var collections = memoryStore.GetCollectionsAsync();
- await foreach (var collection in collections)
- {
- Console.WriteLine(collection);
- }
-
- Console.WriteLine();
-
- Console.WriteLine($"Removing Collection {MemoryCollectionName}");
- await memoryStore.DeleteCollectionAsync(MemoryCollectionName);
- Console.WriteLine();
-
- Console.WriteLine($"Printing Collections in DB (after removing {MemoryCollectionName})...");
- collections = memoryStore.GetCollectionsAsync();
- await foreach (var collection in collections)
- {
- Console.WriteLine(collection);
- }
- }
-}
diff --git a/dotnet/samples/Concepts/Memory/TextMemoryPlugin_MultipleMemoryStore.cs b/dotnet/samples/Concepts/Memory/TextMemoryPlugin_MultipleMemoryStore.cs
deleted file mode 100644
index 0c0f4da85bff..000000000000
--- a/dotnet/samples/Concepts/Memory/TextMemoryPlugin_MultipleMemoryStore.cs
+++ /dev/null
@@ -1,336 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using Microsoft.SemanticKernel;
-using Microsoft.SemanticKernel.Connectors.AzureAISearch;
-using Microsoft.SemanticKernel.Connectors.Chroma;
-using Microsoft.SemanticKernel.Connectors.DuckDB;
-using Microsoft.SemanticKernel.Connectors.Kusto;
-using Microsoft.SemanticKernel.Connectors.MongoDB;
-using Microsoft.SemanticKernel.Connectors.OpenAI;
-using Microsoft.SemanticKernel.Connectors.Pinecone;
-using Microsoft.SemanticKernel.Connectors.Postgres;
-using Microsoft.SemanticKernel.Connectors.Qdrant;
-using Microsoft.SemanticKernel.Connectors.Redis;
-using Microsoft.SemanticKernel.Connectors.Sqlite;
-using Microsoft.SemanticKernel.Connectors.Weaviate;
-using Microsoft.SemanticKernel.Memory;
-using Microsoft.SemanticKernel.Plugins.Memory;
-using Npgsql;
-using StackExchange.Redis;
-
-namespace Memory;
-
-public class TextMemoryPlugin_MultipleMemoryStore(ITestOutputHelper output) : BaseTest(output)
-{
- private const string MemoryCollectionName = "aboutMe";
-
- [Theory]
- [InlineData("Volatile")]
- [InlineData("AzureAISearch")]
- public async Task RunAsync(string provider)
- {
- // Volatile Memory Store - an in-memory store that is not persisted
- IMemoryStore store = provider switch
- {
- "AzureAISearch" => CreateSampleAzureAISearchMemoryStore(),
- _ => new VolatileMemoryStore(),
- };
-
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- // INSTRUCTIONS: uncomment one of the following lines to select a different memory store to use. //
- ///////////////////////////////////////////////////////////////////////////////////////////////////
-
- // Sqlite Memory Store - a file-based store that persists data in a Sqlite database
- // store = await CreateSampleSqliteMemoryStoreAsync();
-
- // DuckDB Memory Store - a file-based store that persists data in a DuckDB database
- // store = await CreateSampleDuckDbMemoryStoreAsync();
-
- // MongoDB Memory Store - a store that persists data in a MongoDB database
- // store = CreateSampleMongoDBMemoryStore();
-
- // Azure AI Search Memory Store - a store that persists data in a hosted Azure AI Search database
- // store = CreateSampleAzureAISearchMemoryStore();
-
- // Qdrant Memory Store - a store that persists data in a local or remote Qdrant database
- // store = CreateSampleQdrantMemoryStore();
-
- // Chroma Memory Store
- // store = CreateSampleChromaMemoryStore();
-
- // Pinecone Memory Store - a store that persists data in a hosted Pinecone database
- // store = CreateSamplePineconeMemoryStore();
-
- // Weaviate Memory Store
- // store = CreateSampleWeaviateMemoryStore();
-
- // Redis Memory Store
- // store = await CreateSampleRedisMemoryStoreAsync();
-
- // Postgres Memory Store
- // store = CreateSamplePostgresMemoryStore();
-
- // Kusto Memory Store
- // store = CreateSampleKustoMemoryStore();
-
- await RunWithStoreAsync(store);
- }
-
- private async Task CreateSampleSqliteMemoryStoreAsync()
- {
- IMemoryStore store = await SqliteMemoryStore.ConnectAsync("memories.sqlite");
- return store;
- }
-
- private async Task CreateSampleDuckDbMemoryStoreAsync()
- {
- IMemoryStore store = await DuckDBMemoryStore.ConnectAsync("memories.duckdb", 1536);
- return store;
- }
-
- private IMemoryStore CreateSampleMongoDBMemoryStore()
- {
- IMemoryStore store = new MongoDBMemoryStore(TestConfiguration.MongoDB.ConnectionString, "memoryPluginExample");
- return store;
- }
-
- private IMemoryStore CreateSampleAzureAISearchMemoryStore()
- {
- IMemoryStore store = new AzureAISearchMemoryStore(TestConfiguration.AzureAISearch.Endpoint, TestConfiguration.AzureAISearch.ApiKey);
- return store;
- }
-
- private IMemoryStore CreateSampleChromaMemoryStore()
- {
- IMemoryStore store = new ChromaMemoryStore(TestConfiguration.Chroma.Endpoint, this.LoggerFactory);
- return store;
- }
-
- private IMemoryStore CreateSampleQdrantMemoryStore()
- {
- IMemoryStore store = new QdrantMemoryStore(TestConfiguration.Qdrant.Endpoint, 1536, this.LoggerFactory);
- return store;
- }
-
- private IMemoryStore CreateSamplePineconeMemoryStore()
- {
- IMemoryStore store = new PineconeMemoryStore(TestConfiguration.Pinecone.Environment, TestConfiguration.Pinecone.ApiKey, this.LoggerFactory);
- return store;
- }
-
- private IMemoryStore CreateSampleWeaviateMemoryStore()
- {
- IMemoryStore store = new WeaviateMemoryStore(TestConfiguration.Weaviate.Endpoint, TestConfiguration.Weaviate.ApiKey);
- return store;
- }
-
- private async Task CreateSampleRedisMemoryStoreAsync()
- {
- string configuration = TestConfiguration.Redis.Configuration;
- ConnectionMultiplexer connectionMultiplexer = await ConnectionMultiplexer.ConnectAsync(configuration);
- IDatabase database = connectionMultiplexer.GetDatabase();
- IMemoryStore store = new RedisMemoryStore(database, vectorSize: 1536);
- return store;
- }
-
- private static IMemoryStore CreateSamplePostgresMemoryStore()
- {
- NpgsqlDataSourceBuilder dataSourceBuilder = new(TestConfiguration.Postgres.ConnectionString);
- dataSourceBuilder.UseVector();
- NpgsqlDataSource dataSource = dataSourceBuilder.Build();
- IMemoryStore store = new PostgresMemoryStore(dataSource, vectorSize: 1536, schema: "public");
- return store;
- }
-
- private static IMemoryStore CreateSampleKustoMemoryStore()
- {
- var connectionString = new Kusto.Data.KustoConnectionStringBuilder(TestConfiguration.Kusto.ConnectionString).WithAadUserPromptAuthentication();
- IMemoryStore store = new KustoMemoryStore(connectionString, "MyDatabase");
- return store;
- }
-
- private async Task RunWithStoreAsync(IMemoryStore memoryStore)
- {
- var kernel = Kernel.CreateBuilder()
- .AddOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
- .AddOpenAITextEmbeddingGeneration(TestConfiguration.OpenAI.EmbeddingModelId, TestConfiguration.OpenAI.ApiKey)
- .Build();
-
- // Create an embedding generator to use for semantic memory.
- var embeddingGenerator = new OpenAITextEmbeddingGenerationService(TestConfiguration.OpenAI.EmbeddingModelId, TestConfiguration.OpenAI.ApiKey);
-
- // The combination of the text embedding generator and the memory store makes up the 'SemanticTextMemory' object used to
- // store and retrieve memories.
- SemanticTextMemory textMemory = new(memoryStore, embeddingGenerator);
-
- /////////////////////////////////////////////////////////////////////////////////////////////////////
- // PART 1: Store and retrieve memories using the ISemanticTextMemory (textMemory) object.
- //
- // This is a simple way to store memories from a code perspective, without using the Kernel.
- /////////////////////////////////////////////////////////////////////////////////////////////////////
- Console.WriteLine("== PART 1a: Saving Memories through the ISemanticTextMemory object ==");
-
- Console.WriteLine("Saving memory with key 'info1': \"My name is Andrea\"");
- await textMemory.SaveInformationAsync(MemoryCollectionName, id: "info1", text: "My name is Andrea");
-
- Console.WriteLine("Saving memory with key 'info2': \"I work as a tourist operator\"");
- await textMemory.SaveInformationAsync(MemoryCollectionName, id: "info2", text: "I work as a tourist operator");
-
- Console.WriteLine("Saving memory with key 'info3': \"I've been living in Seattle since 2005\"");
- await textMemory.SaveInformationAsync(MemoryCollectionName, id: "info3", text: "I've been living in Seattle since 2005");
-
- Console.WriteLine("Saving memory with key 'info4': \"I visited France and Italy five times since 2015\"");
- await textMemory.SaveInformationAsync(MemoryCollectionName, id: "info4", text: "I visited France and Italy five times since 2015");
-
- // Retrieve a memory
- Console.WriteLine("== PART 1b: Retrieving Memories through the ISemanticTextMemory object ==");
- MemoryQueryResult? lookup = await textMemory.GetAsync(MemoryCollectionName, "info1");
- Console.WriteLine("Memory with key 'info1':" + lookup?.Metadata.Text ?? "ERROR: memory not found");
- Console.WriteLine();
-
- /////////////////////////////////////////////////////////////////////////////////////////////////////
- // PART 2: Create TextMemoryPlugin, store and retrieve memories through the Kernel.
- //
- // This enables prompt functions and the AI (via Planners) to access memories
- /////////////////////////////////////////////////////////////////////////////////////////////////////
-
- Console.WriteLine("== PART 2a: Saving Memories through the Kernel with TextMemoryPlugin and the 'Save' function ==");
-
- // Import the TextMemoryPlugin into the Kernel for other functions
- var memoryPlugin = kernel.ImportPluginFromObject(new TextMemoryPlugin(textMemory));
-
- // Save a memory with the Kernel
- Console.WriteLine("Saving memory with key 'info5': \"My family is from New York\"");
- await kernel.InvokeAsync(memoryPlugin["Save"], new()
- {
- [TextMemoryPlugin.InputParam] = "My family is from New York",
- [TextMemoryPlugin.CollectionParam] = MemoryCollectionName,
- [TextMemoryPlugin.KeyParam] = "info5",
- });
-
- // Retrieve a specific memory with the Kernel
- Console.WriteLine("== PART 2b: Retrieving Memories through the Kernel with TextMemoryPlugin and the 'Retrieve' function ==");
- var result = await kernel.InvokeAsync(memoryPlugin["Retrieve"], new KernelArguments()
- {
- [TextMemoryPlugin.CollectionParam] = MemoryCollectionName,
- [TextMemoryPlugin.KeyParam] = "info5"
- });
-
- Console.WriteLine("Memory with key 'info5':" + result.GetValue() ?? "ERROR: memory not found");
- Console.WriteLine();
-
- /////////////////////////////////////////////////////////////////////////////////////////////////////
- // PART 3: Recall similar ideas with semantic search
- //
- // Uses AI Embeddings for fuzzy lookup of memories based on intent, rather than a specific key.
- /////////////////////////////////////////////////////////////////////////////////////////////////////
-
- Console.WriteLine("== PART 3: Recall (similarity search) with AI Embeddings ==");
-
- Console.WriteLine("== PART 3a: Recall (similarity search) with ISemanticTextMemory ==");
- Console.WriteLine("Ask: where did I grow up?");
-
- await foreach (var answer in textMemory.SearchAsync(
- collection: MemoryCollectionName,
- query: "where did I grow up?",
- limit: 2,
- minRelevanceScore: 0.79,
- withEmbeddings: true))
- {
- Console.WriteLine($"Answer: {answer.Metadata.Text}");
- }
-
- Console.WriteLine("== PART 3b: Recall (similarity search) with Kernel and TextMemoryPlugin 'Recall' function ==");
- Console.WriteLine("Ask: where do I live?");
-
- result = await kernel.InvokeAsync(memoryPlugin["Recall"], new()
- {
- [TextMemoryPlugin.InputParam] = "Ask: where do I live?",
- [TextMemoryPlugin.CollectionParam] = MemoryCollectionName,
- [TextMemoryPlugin.LimitParam] = "2",
- [TextMemoryPlugin.RelevanceParam] = "0.79",
- });
-
- Console.WriteLine($"Answer: {result.GetValue()}");
- Console.WriteLine();
-
- /*
- Output:
-
- Ask: where did I grow up?
- Answer:
- ["My family is from New York","I\u0027ve been living in Seattle since 2005"]
-
- Ask: where do I live?
- Answer:
- ["I\u0027ve been living in Seattle since 2005","My family is from New York"]
- */
-
- /////////////////////////////////////////////////////////////////////////////////////////////////////
- // PART 4: TextMemoryPlugin Recall in a Prompt Function
- //
- // Looks up related memories when rendering a prompt template, then sends the rendered prompt to
- // the text generation model to answer a natural language query.
- /////////////////////////////////////////////////////////////////////////////////////////////////////
-
- Console.WriteLine("== PART 4: Using TextMemoryPlugin 'Recall' function in a Prompt Function ==");
-
- // Build a prompt function that uses memory to find facts
- const string RecallFunctionDefinition = @"
-Consider only the facts below when answering questions:
-
-BEGIN FACTS
-About me: {{recall 'where did I grow up?'}}
-About me: {{recall 'where do I live now?'}}
-END FACTS
-
-Question: {{$input}}
-
-Answer:
-";
-
- var aboutMeOracle = kernel.CreateFunctionFromPrompt(RecallFunctionDefinition, new OpenAIPromptExecutionSettings() { MaxTokens = 100 });
-
- result = await kernel.InvokeAsync(aboutMeOracle, new()
- {
- [TextMemoryPlugin.InputParam] = "Do I live in the same town where I grew up?",
- [TextMemoryPlugin.CollectionParam] = MemoryCollectionName,
- [TextMemoryPlugin.LimitParam] = "2",
- [TextMemoryPlugin.RelevanceParam] = "0.79",
- });
-
- Console.WriteLine("Ask: Do I live in the same town where I grew up?");
- Console.WriteLine($"Answer: {result.GetValue()}");
-
- /*
- Approximate Output:
- Answer: No, I do not live in the same town where I grew up since my family is from New York and I have been living in Seattle since 2005.
- */
-
- /////////////////////////////////////////////////////////////////////////////////////////////////////
- // PART 5: Cleanup, deleting database collection
- //
- /////////////////////////////////////////////////////////////////////////////////////////////////////
-
- Console.WriteLine("== PART 5: Cleanup, deleting database collection ==");
-
- Console.WriteLine("Printing Collections in DB...");
- var collections = memoryStore.GetCollectionsAsync();
- await foreach (var collection in collections)
- {
- Console.WriteLine(collection);
- }
- Console.WriteLine();
-
- Console.WriteLine($"Removing Collection {MemoryCollectionName}");
- await memoryStore.DeleteCollectionAsync(MemoryCollectionName);
- Console.WriteLine();
-
- Console.WriteLine($"Printing Collections in DB (after removing {MemoryCollectionName})...");
- collections = memoryStore.GetCollectionsAsync();
- await foreach (var collection in collections)
- {
- Console.WriteLine(collection);
- }
- }
-}
diff --git a/dotnet/samples/Concepts/Memory/TextMemoryPlugin_RecallJsonSerializationWithOptions.cs b/dotnet/samples/Concepts/Memory/TextMemoryPlugin_RecallJsonSerializationWithOptions.cs
deleted file mode 100644
index 883195b68df9..000000000000
--- a/dotnet/samples/Concepts/Memory/TextMemoryPlugin_RecallJsonSerializationWithOptions.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using System.Text.Encodings.Web;
-using System.Text.Json;
-using System.Text.Unicode;
-using Microsoft.SemanticKernel;
-using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
-using Microsoft.SemanticKernel.Memory;
-using Microsoft.SemanticKernel.Plugins.Memory;
-
-namespace Memory;
-
-///
-/// This example shows how to use custom when serializing multiple results during recall using .
-///
-///
-/// When multiple results are returned during recall, has to turn these results into a string to pass back to the kernel.
-/// The uses to turn the results into a string.
-/// In some cases though, the default serialization options may not work, e.g. if the memories contain non-latin text,
-/// will escape these characters by default. In this case, you can provide custom to the to control how the memories are serialized.
-///
-public class TextMemoryPlugin_RecallJsonSerializationWithOptions(ITestOutputHelper output) : BaseTest(output)
-{
- [Fact]
- public async Task RunAsync()
- {
- // Create a Kernel.
- var kernelWithoutOptions = Kernel.CreateBuilder()
- .Build();
-
- // Create an embedding generator to use for semantic memory.
- var embeddingGenerator = new AzureOpenAITextEmbeddingGenerationService(TestConfiguration.AzureOpenAIEmbeddings.DeploymentName, TestConfiguration.AzureOpenAIEmbeddings.Endpoint, TestConfiguration.AzureOpenAIEmbeddings.ApiKey);
-
- // Using an in memory store for this example.
- var memoryStore = new VolatileMemoryStore();
-
- // The combination of the text embedding generator and the memory store makes up the 'SemanticTextMemory' object used to
- // store and retrieve memories.
- SemanticTextMemory textMemory = new(memoryStore, embeddingGenerator);
- await textMemory.SaveInformationAsync("samples", "First example of some text in Thai and Bengali: วรรณยุกต์ চলিতভাষা", "test-record-1");
- await textMemory.SaveInformationAsync("samples", "Second example of some text in Thai and Bengali: วรรณยุกต์ চলিতভাষা", "test-record-2");
-
- // Import the TextMemoryPlugin into the Kernel without any custom JsonSerializerOptions.
- var memoryPluginWithoutOptions = kernelWithoutOptions.ImportPluginFromObject(new TextMemoryPlugin(textMemory));
-
- // Retrieve the memories using the TextMemoryPlugin.
- var resultWithoutOptions = await kernelWithoutOptions.InvokeAsync(memoryPluginWithoutOptions["Recall"], new()
- {
- [TextMemoryPlugin.InputParam] = "Text examples",
- [TextMemoryPlugin.CollectionParam] = "samples",
- [TextMemoryPlugin.LimitParam] = "2",
- [TextMemoryPlugin.RelevanceParam] = "0.79",
- });
-
- // The recall operation returned the following text, where the Thai and Bengali text was escaped:
- // ["Second example of some text in Thai and Bengali: \u0E27\u0E23\u0E23\u0E13\u0E22\u0E38\u0E01\u0E15\u0E4C \u099A\u09B2\u09BF\u09A4\u09AD\u09BE\u09B7\u09BE","First example of some text in Thai and Bengali: \u0E27\u0E23\u0E23\u0E13\u0E22\u0E38\u0E01\u0E15\u0E4C \u099A\u09B2\u09BF\u09A4\u09AD\u09BE\u09B7\u09BE"]
- Console.WriteLine(resultWithoutOptions.GetValue());
-
- // Create a Kernel.
- var kernelWithOptions = Kernel.CreateBuilder()
- .Build();
-
- // Import the TextMemoryPlugin into the Kernel with custom JsonSerializerOptions that allow Thai and Bengali script to be serialized unescaped.
- var options = new JsonSerializerOptions { Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.Thai, UnicodeRanges.Bengali) };
- var memoryPluginWithOptions = kernelWithOptions.ImportPluginFromObject(new TextMemoryPlugin(textMemory, jsonSerializerOptions: options));
-
- // Retrieve the memories using the TextMemoryPlugin.
- var result = await kernelWithOptions.InvokeAsync(memoryPluginWithOptions["Recall"], new()
- {
- [TextMemoryPlugin.InputParam] = "Text examples",
- [TextMemoryPlugin.CollectionParam] = "samples",
- [TextMemoryPlugin.LimitParam] = "2",
- [TextMemoryPlugin.RelevanceParam] = "0.79",
- });
-
- // The recall operation returned the following text, where the Thai and Bengali text was not escaped:
- // ["Second example of some text in Thai and Bengali: วรรณยุกต์ চলিতভাষা","First example of some text in Thai and Bengali: วรรณยุกต์ চলিতভাষা"]
- Console.WriteLine(result.GetValue());
- }
-}
diff --git a/dotnet/samples/Concepts/Memory/VectorStoreEmbeddingGeneration/GenerateTextEmbeddingAttribute.cs b/dotnet/samples/Concepts/Memory/VectorStoreEmbeddingGeneration/GenerateTextEmbeddingAttribute.cs
deleted file mode 100644
index 9a8e6b17aa27..000000000000
--- a/dotnet/samples/Concepts/Memory/VectorStoreEmbeddingGeneration/GenerateTextEmbeddingAttribute.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-namespace Memory.VectorStoreEmbeddingGeneration;
-
-///
-/// An attribute that can be used for an embedding property to indicate that it should
-/// be generated from one or more text properties located on the same class.
-///
-///
-/// This class is part of the sample.
-///
-[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
-public sealed class GenerateTextEmbeddingAttribute : Attribute
-{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The name of the property that the embedding should be generated from.
-#pragma warning disable CA1019 // Define accessors for attribute arguments
- public GenerateTextEmbeddingAttribute(string sourcePropertyName)
-#pragma warning restore CA1019 // Define accessors for attribute arguments
- {
- this.SourcePropertyNames = [sourcePropertyName];
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The names of the properties that the embedding should be generated from.
- public GenerateTextEmbeddingAttribute(string[] sourcePropertyNames)
- {
- this.SourcePropertyNames = sourcePropertyNames;
- }
-
- ///
- /// Gets the name of the property to use as the source for generating the embedding.
- ///
- public string[] SourcePropertyNames { get; }
-}
diff --git a/dotnet/samples/Concepts/Memory/VectorStoreEmbeddingGeneration/TextEmbeddingVectorStore.cs b/dotnet/samples/Concepts/Memory/VectorStoreEmbeddingGeneration/TextEmbeddingVectorStore.cs
deleted file mode 100644
index 6848b38af48f..000000000000
--- a/dotnet/samples/Concepts/Memory/VectorStoreEmbeddingGeneration/TextEmbeddingVectorStore.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using Microsoft.Extensions.VectorData;
-using Microsoft.SemanticKernel.Embeddings;
-
-namespace Memory.VectorStoreEmbeddingGeneration;
-
-///
-/// Decorator for a that generates embeddings for records on upsert.
-///
-///
-/// This class is part of the sample.
-///
-public class TextEmbeddingVectorStore : IVectorStore
-{
- /// The decorated .
- private readonly IVectorStore _decoratedVectorStore;
-
- /// The service to use for generating the embeddings.
- private readonly ITextEmbeddingGenerationService _textEmbeddingGenerationService;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The decorated .
- /// The service to use for generating the embeddings.
- public TextEmbeddingVectorStore(IVectorStore decoratedVectorStore, ITextEmbeddingGenerationService textEmbeddingGenerationService)
- {
- // Verify & Assign.
- this._decoratedVectorStore = decoratedVectorStore ?? throw new ArgumentNullException(nameof(decoratedVectorStore));
- this._textEmbeddingGenerationService = textEmbeddingGenerationService ?? throw new ArgumentNullException(nameof(textEmbeddingGenerationService));
- }
-
- ///
- public IVectorStoreRecordCollection GetCollection(string name, VectorStoreRecordDefinition? vectorStoreRecordDefinition = null)
- where TKey : notnull
- {
- var collection = this._decoratedVectorStore.GetCollection(name, vectorStoreRecordDefinition);
- var embeddingStore = new TextEmbeddingVectorStoreRecordCollection(collection, this._textEmbeddingGenerationService);
- return embeddingStore;
- }
-
- ///
- public IAsyncEnumerable ListCollectionNamesAsync(CancellationToken cancellationToken = default)
- {
- return this._decoratedVectorStore.ListCollectionNamesAsync(cancellationToken);
- }
-}
diff --git a/dotnet/samples/Concepts/Memory/VectorStoreEmbeddingGeneration/TextEmbeddingVectorStoreExtensions.cs b/dotnet/samples/Concepts/Memory/VectorStoreEmbeddingGeneration/TextEmbeddingVectorStoreExtensions.cs
deleted file mode 100644
index e1b6c779fdb8..000000000000
--- a/dotnet/samples/Concepts/Memory/VectorStoreEmbeddingGeneration/TextEmbeddingVectorStoreExtensions.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using Microsoft.Extensions.VectorData;
-using Microsoft.SemanticKernel.Embeddings;
-
-namespace Memory.VectorStoreEmbeddingGeneration;
-
-///
-/// Contains extension methods to help add text embedding generation to a or
-///
-///
-/// This class is part of the sample.
-///
-public static class TextEmbeddingVectorStoreExtensions
-{
- ///
- /// Add text embedding generation to a .
- ///
- /// The to add text embedding generation to.
- /// The service to use for generating text embeddings.
- /// The with text embedding added.
- public static IVectorStore UseTextEmbeddingGeneration(this IVectorStore vectorStore, ITextEmbeddingGenerationService textEmbeddingGenerationService)
- {
- return new TextEmbeddingVectorStore(vectorStore, textEmbeddingGenerationService);
- }
-
- ///
- /// Add text embedding generation to a .
- ///
- /// The to add text embedding generation to.
- /// The service to use for generating text embeddings.
- /// The data type of the record key.
- /// The record data model to use for adding, updating and retrieving data from the store.
- /// The with text embedding added.
- public static IVectorStoreRecordCollection UseTextEmbeddingGeneration(this IVectorStoreRecordCollection vectorStoreRecordCollection, ITextEmbeddingGenerationService textEmbeddingGenerationService)
- where TKey : notnull
- {
- return new TextEmbeddingVectorStoreRecordCollection(vectorStoreRecordCollection, textEmbeddingGenerationService);
- }
-}
diff --git a/dotnet/samples/Concepts/Memory/VectorStoreEmbeddingGeneration/TextEmbeddingVectorStoreRecordCollection.cs b/dotnet/samples/Concepts/Memory/VectorStoreEmbeddingGeneration/TextEmbeddingVectorStoreRecordCollection.cs
deleted file mode 100644
index 000cb1ebba07..000000000000
--- a/dotnet/samples/Concepts/Memory/VectorStoreEmbeddingGeneration/TextEmbeddingVectorStoreRecordCollection.cs
+++ /dev/null
@@ -1,193 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using Microsoft.Extensions.VectorData;
-using Microsoft.SemanticKernel.Embeddings;
-
-namespace Memory.VectorStoreEmbeddingGeneration;
-
-///
-/// Decorator for a that generates embeddings for records on upsert and when using .
-///
-///
-/// This class is part of the sample.
-///
-/// The data type of the record key.
-/// The record data model to use for adding, updating and retrieving data from the store.
-#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
-public class TextEmbeddingVectorStoreRecordCollection : IVectorStoreRecordCollection, IVectorizableTextSearch
-#pragma warning restore CA1711 // Identifiers should not have incorrect suffix
- where TKey : notnull
-{
- /// The decorated .
- private readonly IVectorStoreRecordCollection _decoratedVectorStoreRecordCollection;
-
- /// The service to use for generating the embeddings.
- private readonly ITextEmbeddingGenerationService _textEmbeddingGenerationService;
-
- /// Optional configuration options for this class.
- private readonly IEnumerable<(PropertyInfo EmbeddingPropertyInfo, IList SourcePropertiesInfo)> _embeddingPropertiesWithSourceProperties;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The decorated .
- /// The service to use for generating the embeddings.
- /// Thrown when embedding properties are referencing data source properties that do not exist.
- /// Thrown when required parameters are null.
- public TextEmbeddingVectorStoreRecordCollection(IVectorStoreRecordCollection decoratedVectorStoreRecordCollection, ITextEmbeddingGenerationService textEmbeddingGenerationService)
- {
- // Assign.
- this._decoratedVectorStoreRecordCollection = decoratedVectorStoreRecordCollection ?? throw new ArgumentNullException(nameof(decoratedVectorStoreRecordCollection));
- this._textEmbeddingGenerationService = textEmbeddingGenerationService ?? throw new ArgumentNullException(nameof(textEmbeddingGenerationService));
-
- // Find all the embedding properties to generate embeddings for.
- this._embeddingPropertiesWithSourceProperties = FindDataPropertiesWithEmbeddingProperties(typeof(TRecord));
- }
-
- ///
- public string CollectionName => this._decoratedVectorStoreRecordCollection.CollectionName;
-
- ///
- public Task CollectionExistsAsync(CancellationToken cancellationToken = default)
- {
- return this._decoratedVectorStoreRecordCollection.CollectionExistsAsync(cancellationToken);
- }
-
- ///
- public Task CreateCollectionAsync(CancellationToken cancellationToken = default)
- {
- return this._decoratedVectorStoreRecordCollection.CreateCollectionAsync(cancellationToken);
- }
-
- ///
- public async Task CreateCollectionIfNotExistsAsync(CancellationToken cancellationToken = default)
- {
- if (!await this.CollectionExistsAsync(cancellationToken).ConfigureAwait(false))
- {
- await this.CreateCollectionAsync(cancellationToken).ConfigureAwait(false);
- }
- }
-
- ///
- public Task DeleteCollectionAsync(CancellationToken cancellationToken = default)
- {
- return this._decoratedVectorStoreRecordCollection.DeleteCollectionAsync(cancellationToken);
- }
-
- ///
- public Task DeleteAsync(TKey key, CancellationToken cancellationToken = default)
- {
- return this._decoratedVectorStoreRecordCollection.DeleteAsync(key, cancellationToken);
- }
-
- ///
- public Task DeleteBatchAsync(IEnumerable keys, CancellationToken cancellationToken = default)
- {
- return this._decoratedVectorStoreRecordCollection.DeleteBatchAsync(keys, cancellationToken);
- }
-
- ///
- public Task GetAsync(TKey key, GetRecordOptions? options = null, CancellationToken cancellationToken = default)
- {
- return this._decoratedVectorStoreRecordCollection.GetAsync(key, options, cancellationToken);
- }
-
- ///
- public IAsyncEnumerable GetBatchAsync(IEnumerable keys, GetRecordOptions? options = null, CancellationToken cancellationToken = default)
- {
- return this._decoratedVectorStoreRecordCollection.GetBatchAsync(keys, options, cancellationToken);
- }
-
- ///
- public async Task UpsertAsync(TRecord record, CancellationToken cancellationToken = default)
- {
- var recordWithEmbeddings = await this.AddEmbeddingsAsync(record, cancellationToken).ConfigureAwait(false);
- return await this._decoratedVectorStoreRecordCollection.UpsertAsync(recordWithEmbeddings, cancellationToken).ConfigureAwait(false);
- }
-
- ///
- public async IAsyncEnumerable UpsertBatchAsync(IEnumerable records, [EnumeratorCancellation] CancellationToken cancellationToken = default)
- {
- var recordWithEmbeddingsTasks = records.Select(r => this.AddEmbeddingsAsync(r, cancellationToken));
- var recordWithEmbeddings = await Task.WhenAll(recordWithEmbeddingsTasks).ConfigureAwait(false);
- var upsertResults = this._decoratedVectorStoreRecordCollection.UpsertBatchAsync(recordWithEmbeddings, cancellationToken);
- await foreach (var upsertResult in upsertResults.ConfigureAwait(false))
- {
- yield return upsertResult;
- }
- }
-
- ///
- public Task> VectorizedSearchAsync(TVector vector, VectorSearchOptions? options = null, CancellationToken cancellationToken = default)
- {
- return this._decoratedVectorStoreRecordCollection.VectorizedSearchAsync(vector, options, cancellationToken);
- }
-
- ///
- public async Task> VectorizableTextSearchAsync(string searchText, VectorSearchOptions? options = null, CancellationToken cancellationToken = default)
- {
- var embeddingValue = await this._textEmbeddingGenerationService.GenerateEmbeddingAsync(searchText, cancellationToken: cancellationToken).ConfigureAwait(false);
- return await this.VectorizedSearchAsync(embeddingValue, options, cancellationToken).ConfigureAwait(false);
- }
-
- ///
- /// Generate and add embeddings for each embedding field that has a on the provided record.
- ///
- /// The record to generate embeddings for.
- /// The to monitor for cancellation requests.
- /// The record with embeddings added.
- private async Task AddEmbeddingsAsync(TRecord record, CancellationToken cancellationToken)
- {
- foreach (var (embeddingPropertyInfo, sourcePropertiesInfo) in this._embeddingPropertiesWithSourceProperties)
- {
- var sourceValues = sourcePropertiesInfo.Select(x => x.GetValue(record)).Cast().Where(x => !string.IsNullOrWhiteSpace(x));
- var sourceString = string.Join("\n", sourceValues);
-
- var embeddingValue = await this._textEmbeddingGenerationService.GenerateEmbeddingAsync(sourceString, cancellationToken: cancellationToken).ConfigureAwait(false);
- embeddingPropertyInfo.SetValue(record, embeddingValue);
- }
-
- return record;
- }
-
- ///
- /// Get the list of properties with from the data model.
- ///
- /// The type of the data model to find
- /// The list of properties with with the properties from which the embedding can be generated.
- private static IEnumerable<(PropertyInfo EmbeddingPropertyInfo, IList SourcePropertiesInfo)> FindDataPropertiesWithEmbeddingProperties(Type dataModelType)
- {
- var allProperties = dataModelType.GetProperties();
- var propertiesDictionary = allProperties.ToDictionary(p => p.Name);
-
- // Loop through all the properties to find the ones that have the GenerateTextEmbeddingAttribute.
- foreach (var property in allProperties)
- {
- var attribute = property.GetCustomAttribute();
- if (attribute is not null)
- {
- // Find the source properties that the embedding should be generated from.
- var sourcePropertiesInfo = new List();
- foreach (var sourcePropertyName in attribute.SourcePropertyNames)
- {
- if (!propertiesDictionary.TryGetValue(sourcePropertyName, out var sourcePropertyInfo))
- {
- throw new ArgumentException($"The source property '{sourcePropertyName}' as referenced by embedding property '{property.Name}' does not exist in the record model.");
- }
- else if (sourcePropertyInfo.PropertyType != typeof(string))
- {
- throw new ArgumentException($"The source property '{sourcePropertyName}' as referenced by embedding property '{property.Name}' has type {sourcePropertyInfo.PropertyType} but must be a string.");
- }
- else
- {
- sourcePropertiesInfo.Add(sourcePropertyInfo);
- }
- }
-
- yield return (property, sourcePropertiesInfo);
- }
- }
- }
-}
diff --git a/dotnet/samples/Concepts/Memory/VectorStoreExtensions.cs b/dotnet/samples/Concepts/Memory/VectorStoreExtensions.cs
index 3d54787aee79..3a2183ba34ee 100644
--- a/dotnet/samples/Concepts/Memory/VectorStoreExtensions.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStoreExtensions.cs
@@ -45,6 +45,7 @@ internal static async Task> CreateCo
ITextEmbeddingGenerationService embeddingGenerationService,
CreateRecordFromString createRecord)
where TKey : notnull
+ where TRecord : notnull
{
// Get and create collection if it doesn't exist.
var collection = vectorStore.GetCollection(collectionName);
@@ -80,6 +81,7 @@ internal static async Task> CreateCo
ITextEmbeddingGenerationService embeddingGenerationService,
CreateRecordFromTextSearchResult createRecord)
where TKey : notnull
+ where TRecord : notnull
{
// Get and create collection if it doesn't exist.
var collection = vectorStore.GetCollection(collectionName);
diff --git a/dotnet/samples/Concepts/Memory/VectorStoreLangchainInterop/AzureAISearchFactory.cs b/dotnet/samples/Concepts/Memory/VectorStoreLangchainInterop/AzureAISearchFactory.cs
deleted file mode 100644
index 2bf0cb763a7a..000000000000
--- a/dotnet/samples/Concepts/Memory/VectorStoreLangchainInterop/AzureAISearchFactory.cs
+++ /dev/null
@@ -1,128 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using System.Text.Json;
-using System.Text.Json.Nodes;
-using System.Text.Json.Serialization;
-using Azure.Search.Documents.Indexes;
-using Microsoft.Extensions.VectorData;
-using Microsoft.SemanticKernel.Connectors.AzureAISearch;
-
-namespace Memory.VectorStoreLangchainInterop;
-
-///
-/// Contains a factory method that can be used to create an Azure AI Search vector store that is compatible with datasets ingested using Langchain.
-///
-///
-/// This class is used with the sample.
-///
-public static class AzureAISearchFactory
-{
- ///
- /// Record definition that matches the storage format used by Langchain for Azure AI Search.
- ///
- private static readonly VectorStoreRecordDefinition s_recordDefinition = new()
- {
- Properties = new List
- {
- new VectorStoreRecordKeyProperty("id", typeof(string)),
- new VectorStoreRecordDataProperty("content", typeof(string)),
- new VectorStoreRecordDataProperty("metadata", typeof(string)),
- new VectorStoreRecordVectorProperty("content_vector", typeof(ReadOnlyMemory)) { Dimensions = 1536 }
- }
- };
-
- ///
- /// Create a new Azure AI Search-backed that can be used to read data that was ingested using Langchain.
- ///
- /// Azure AI Search client that can be used to manage the list of indices in an Azure AI Search Service.
- /// The .
- public static IVectorStore CreateQdrantLangchainInteropVectorStore(SearchIndexClient searchIndexClient)
- => new AzureAISearchLangchainInteropVectorStore(searchIndexClient);
-
- private sealed class AzureAISearchLangchainInteropVectorStore(SearchIndexClient searchIndexClient, AzureAISearchVectorStoreOptions? options = default)
- : AzureAISearchVectorStore(searchIndexClient, options)
- {
- private readonly SearchIndexClient _searchIndexClient = searchIndexClient;
-
- public override IVectorStoreRecordCollection GetCollection(string name, VectorStoreRecordDefinition? vectorStoreRecordDefinition = null)
- {
- if (typeof(TKey) != typeof(string) || typeof(TRecord) != typeof(LangchainDocument))
- {
- throw new NotSupportedException("This VectorStore is only usable with string keys and LangchainDocument record types");
- }
-
- // Create an Azure AI Search collection. To be compatible with Langchain
- // we need to use a custom record definition that matches the
- // schema used by Langchain. We also need to use a custom mapper
- // since the Langchain schema includes a metadata field that is
- // a JSON string containing the source property. Parsing this
- // string and extracting the source is not supported by the default mapper.
- return (new AzureAISearchVectorStoreRecordCollection(
- _searchIndexClient,
- name,
- new()
- {
- VectorStoreRecordDefinition = s_recordDefinition,
- JsonObjectCustomMapper = new LangchainInteropMapper() as IVectorStoreRecordMapper
- }) as IVectorStoreRecordCollection)!;
- }
- }
-
- ///
- /// Custom mapper to map the metadata string field, since it contains JSON as a string and this is not supported
- /// automatically by the built in mapper.
- ///
- private sealed class LangchainInteropMapper : IVectorStoreRecordMapper, JsonObject>
- {
- public JsonObject MapFromDataToStorageModel(LangchainDocument dataModel)
- {
- var storageDocument = new AzureAISearchLangchainDocument()
- {
- Key = dataModel.Key,
- Content = dataModel.Content,
- Metadata = $"{{\"source\": \"{dataModel.Source}\"}}",
- Embedding = dataModel.Embedding
- };
-
- return JsonSerializer.SerializeToNode(storageDocument)!.AsObject();
- }
-
- public LangchainDocument MapFromStorageToDataModel(JsonObject storageModel, StorageToDataModelMapperOptions options)
- {
- var storageDocument = JsonSerializer.Deserialize(storageModel)!;
- var metadataDocument = JsonSerializer.Deserialize(storageDocument.Metadata);
- var source = metadataDocument?["source"]?.AsValue()?.ToString();
-
- return new LangchainDocument()
- {
- Key = storageDocument.Key,
- Content = storageDocument.Content,
- Source = source!,
- Embedding = storageDocument.Embedding
- };
- }
- }
-
- ///
- /// Model class that matches the storage format used by Langchain for Azure AI Search.
- ///
- private sealed class AzureAISearchLangchainDocument
- {
- [JsonPropertyName("id")]
- public string Key { get; set; }
-
- [JsonPropertyName("content")]
- public string Content { get; set; }
-
- ///
- /// The storage format used by Langchain stores the source information
- /// in the metadata field as a JSON string.
- /// E.g. {"source": "my-doc"}
- ///
- [JsonPropertyName("metadata")]
- public string Metadata { get; set; }
-
- [JsonPropertyName("content_vector")]
- public ReadOnlyMemory Embedding { get; set; }
- }
-}
diff --git a/dotnet/samples/Concepts/Memory/VectorStoreLangchainInterop/MappingVectorStoreRecordCollection.cs b/dotnet/samples/Concepts/Memory/VectorStoreLangchainInterop/MappingVectorStoreRecordCollection.cs
deleted file mode 100644
index 1951f3a6dbee..000000000000
--- a/dotnet/samples/Concepts/Memory/VectorStoreLangchainInterop/MappingVectorStoreRecordCollection.cs
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-// TODO: Commented out as part of implementing LINQ-based filtering, since MappingVectorStoreRecordCollection is no longer easy/feasible.
-// TODO: The user provides an expression tree accepting a TPublicRecord, but we require an expression tree accepting a TInternalRecord.
-// TODO: This is something that the user must provide, and is quite advanced.
-
-#if DISABLED
-
-using System.Runtime.CompilerServices;
-using Microsoft.Extensions.VectorData;
-
-namespace Memory.VectorStoreLangchainInterop;
-
-///
-/// Decorator class that allows conversion of keys and records between public and internal representations.
-///
-///
-/// This class is useful if a vector store implementation exposes keys or records in a way that is not
-/// suitable for the user of the vector store. E.g. let's say that the vector store supports Guid keys
-/// but you want to work with string keys that contain Guids. This class allows you to map between the
-/// public string Guids and the internal Guids.
-///
-/// The type of the key that the user of this class will use.
-/// The type of the key that the internal collection exposes.
-/// The type of the record that the user of this class will use.
-/// The type of the record that the internal collection exposes.
-internal sealed class MappingVectorStoreRecordCollection : IVectorStoreRecordCollection
- where TPublicKey : notnull
- where TInternalKey : notnull
-{
- private readonly IVectorStoreRecordCollection _collection;
- private readonly Func _publicToInternalKeyMapper;
- private readonly Func _internalToPublicKeyMapper;
- private readonly Func _publicToInternalRecordMapper;
- private readonly Func _internalToPublicRecordMapper;
-
- public MappingVectorStoreRecordCollection(
- IVectorStoreRecordCollection collection,
- Func publicToInternalKeyMapper,
- Func internalToPublicKeyMapper,
- Func publicToInternalRecordMapper,
- Func internalToPublicRecordMapper)
- {
- this._collection = collection;
- this._publicToInternalKeyMapper = publicToInternalKeyMapper;
- this._internalToPublicKeyMapper = internalToPublicKeyMapper;
- this._publicToInternalRecordMapper = publicToInternalRecordMapper;
- this._internalToPublicRecordMapper = internalToPublicRecordMapper;
- }
-
- ///
- public string CollectionName => this._collection.CollectionName;
-
- ///
- public Task CollectionExistsAsync(CancellationToken cancellationToken = default)
- {
- return this._collection.CollectionExistsAsync(cancellationToken);
- }
-
- ///
- public Task CreateCollectionAsync(CancellationToken cancellationToken = default)
- {
- return this._collection.CreateCollectionAsync(cancellationToken);
- }
-
- ///
- public Task CreateCollectionIfNotExistsAsync(CancellationToken cancellationToken = default)
- {
- return this._collection.CreateCollectionIfNotExistsAsync(cancellationToken);
- }
-
- ///
- public Task DeleteAsync(TPublicKey key, CancellationToken cancellationToken = default)
- {
- return this._collection.DeleteAsync(this._publicToInternalKeyMapper(key), cancellationToken);
- }
-
- ///
- public Task DeleteBatchAsync(IEnumerable keys, CancellationToken cancellationToken = default)
- {
- return this._collection.DeleteBatchAsync(keys.Select(this._publicToInternalKeyMapper), cancellationToken);
- }
-
- ///
- public Task DeleteCollectionAsync(CancellationToken cancellationToken = default)
- {
- return this._collection.DeleteCollectionAsync(cancellationToken);
- }
-
- ///
- public async Task GetAsync(TPublicKey key, GetRecordOptions? options = null, CancellationToken cancellationToken = default)
- {
- var internalRecord = await this._collection.GetAsync(this._publicToInternalKeyMapper(key), options, cancellationToken).ConfigureAwait(false);
- if (internalRecord == null)
- {
- return default;
- }
-
- return this._internalToPublicRecordMapper(internalRecord);
- }
-
- ///
- public IAsyncEnumerable GetBatchAsync(IEnumerable keys, GetRecordOptions? options = null, CancellationToken cancellationToken = default)
- {
- var internalRecords = this._collection.GetBatchAsync(keys.Select(this._publicToInternalKeyMapper), options, cancellationToken);
- return internalRecords.Select(this._internalToPublicRecordMapper);
- }
-
- ///
- public async Task UpsertAsync(TPublicRecord record, CancellationToken cancellationToken = default)
- {
- var internalRecord = this._publicToInternalRecordMapper(record);
- var internalKey = await this._collection.UpsertAsync(internalRecord, cancellationToken).ConfigureAwait(false);
- return this._internalToPublicKeyMapper(internalKey);
- }
-
- ///
- public async IAsyncEnumerable UpsertBatchAsync(IEnumerable records, [EnumeratorCancellation] CancellationToken cancellationToken = default)
- {
- var internalRecords = records.Select(this._publicToInternalRecordMapper);
- var internalKeys = this._collection.UpsertBatchAsync(internalRecords, cancellationToken);
- await foreach (var internalKey in internalKeys.ConfigureAwait(false))
- {
- yield return this._internalToPublicKeyMapper(internalKey);
- }
- }
-
- ///
- public async Task> VectorizedSearchAsync(TVector vector, VectorSearchOptions? options = null, CancellationToken cancellationToken = default)
- {
- var searchResults = await this._collection.VectorizedSearchAsync(vector, options, cancellationToken).ConfigureAwait(false);
- var publicResultRecords = searchResults.Results.Select(result => new VectorSearchResult(this._internalToPublicRecordMapper(result.Record), result.Score));
-
- return new VectorSearchResults(publicResultRecords)
- {
- TotalCount = searchResults.TotalCount,
- Metadata = searchResults.Metadata,
- };
- }
-}
-
-#endif
diff --git a/dotnet/samples/Concepts/Memory/VectorStoreLangchainInterop/PineconeFactory.cs b/dotnet/samples/Concepts/Memory/VectorStoreLangchainInterop/PineconeFactory.cs
index 2f878199b62a..6e391fffc16a 100644
--- a/dotnet/samples/Concepts/Memory/VectorStoreLangchainInterop/PineconeFactory.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStoreLangchainInterop/PineconeFactory.cs
@@ -24,7 +24,7 @@ public static class PineconeFactory
new VectorStoreRecordKeyProperty("Key", typeof(string)),
new VectorStoreRecordDataProperty("Content", typeof(string)) { StoragePropertyName = "text" },
new VectorStoreRecordDataProperty("Source", typeof(string)) { StoragePropertyName = "source" },
- new VectorStoreRecordVectorProperty("Embedding", typeof(ReadOnlyMemory)) { StoragePropertyName = "embedding", Dimensions = 1536 }
+ new VectorStoreRecordVectorProperty("Embedding", typeof(ReadOnlyMemory), 1536) { StoragePropertyName = "embedding" }
}
};
@@ -34,14 +34,18 @@ public static class PineconeFactory
/// Pinecone client that can be used to manage the collections and points in a Pinecone store.
/// The .
public static IVectorStore CreatePineconeLangchainInteropVectorStore(Sdk.PineconeClient pineconeClient)
- => new PineconeLangchainInteropVectorStore(pineconeClient);
+ => new PineconeLangchainInteropVectorStore(new PineconeVectorStore(pineconeClient), pineconeClient);
- private sealed class PineconeLangchainInteropVectorStore(Sdk.PineconeClient pineconeClient)
- : PineconeVectorStore(pineconeClient)
+ private sealed class PineconeLangchainInteropVectorStore(
+ IVectorStore innerStore,
+ Sdk.PineconeClient pineconeClient)
+ : IVectorStore
{
private readonly Sdk.PineconeClient _pineconeClient = pineconeClient;
- public override IVectorStoreRecordCollection GetCollection(string name, VectorStoreRecordDefinition? vectorStoreRecordDefinition = null)
+ public IVectorStoreRecordCollection GetCollection(string name, VectorStoreRecordDefinition? vectorStoreRecordDefinition = null)
+ where TKey : notnull
+ where TRecord : notnull
{
if (typeof(TKey) != typeof(string) || typeof(TRecord) != typeof(LangchainDocument))
{
@@ -51,7 +55,7 @@ public override IVectorStoreRecordCollection GetCollection(
+ return (new PineconeVectorStoreRecordCollection(
_pineconeClient,
name,
new()
@@ -59,5 +63,13 @@ public override IVectorStoreRecordCollection GetCollection)!;
}
+
+ public object? GetService(Type serviceType, object? serviceKey = null) => innerStore.GetService(serviceType, serviceKey);
+
+ public IAsyncEnumerable ListCollectionNamesAsync(CancellationToken cancellationToken = default) => innerStore.ListCollectionNamesAsync(cancellationToken);
+
+ public Task CollectionExistsAsync(string name, CancellationToken cancellationToken = default) => innerStore.CollectionExistsAsync(name, cancellationToken);
+
+ public Task DeleteCollectionAsync(string name, CancellationToken cancellationToken = default) => innerStore.DeleteCollectionAsync(name, cancellationToken);
}
}
diff --git a/dotnet/samples/Concepts/Memory/VectorStoreLangchainInterop/QdrantFactory.cs b/dotnet/samples/Concepts/Memory/VectorStoreLangchainInterop/QdrantFactory.cs
deleted file mode 100644
index 53f0b399af82..000000000000
--- a/dotnet/samples/Concepts/Memory/VectorStoreLangchainInterop/QdrantFactory.cs
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using Microsoft.Extensions.VectorData;
-using Microsoft.SemanticKernel.Connectors.Qdrant;
-using Qdrant.Client;
-using Qdrant.Client.Grpc;
-
-namespace Memory.VectorStoreLangchainInterop;
-
-///
-/// Contains a factory method that can be used to create a Qdrant vector store that is compatible with datasets ingested using Langchain.
-///
-///
-/// This class is used with the sample.
-///
-public static class QdrantFactory
-{
- ///
- /// Record definition that matches the storage format used by Langchain for Qdrant.
- /// There is no need to list the data fields, since they have no indexing requirements and Qdrant
- /// doesn't require individual fields to be defined on index creation.
- ///
- private static readonly VectorStoreRecordDefinition s_recordDefinition = new()
- {
- Properties = new List
- {
- new VectorStoreRecordKeyProperty("Key", typeof(Guid)),
- new VectorStoreRecordVectorProperty("Embedding", typeof(ReadOnlyMemory)) { StoragePropertyName = "embedding", Dimensions = 1536 }
- }
- };
-
- ///
- /// Create a new Qdrant-backed that can be used to read data that was ingested using Langchain.
- ///
- /// Qdrant client that can be used to manage the collections and points in a Qdrant store.
- /// The .
- public static IVectorStore CreateQdrantLangchainInteropVectorStore(QdrantClient qdrantClient)
- => new QdrantLangchainInteropVectorStore(qdrantClient);
-
- private sealed class QdrantLangchainInteropVectorStore(QdrantClient qdrantClient)
- : QdrantVectorStore(qdrantClient)
- {
- private readonly QdrantClient _qdrantClient = qdrantClient;
-
- public override IVectorStoreRecordCollection GetCollection(string name, VectorStoreRecordDefinition? vectorStoreRecordDefinition = null)
- {
- // Create a Qdrant collection. To be compatible with Langchain
- // we need to use a custom record definition that matches the
- // schema used by Langchain. We also need to use a custom mapper
- // since the Langchain schema includes a metadata field that is
- // a struct and this isn't supported by the default mapper.
- // Since langchain creates collections without named vector support
- // we should set HasNamedVectors to false.
- var collection = new QdrantVectorStoreRecordCollection>(
- _qdrantClient,
- name,
- new()
- {
- HasNamedVectors = false,
- VectorStoreRecordDefinition = s_recordDefinition,
- PointStructCustomMapper = new LangchainInteropMapper()
- });
-
- // If the user asked for a guid key, we can return the collection as is.
- if (typeof(TKey) == typeof(Guid) && typeof(TRecord) == typeof(LangchainDocument))
- {
- return (collection as IVectorStoreRecordCollection)!;
- }
-
-#if DISABLED_FOR_NOW // TODO: See note on MappingVectorStoreRecordCollection
- // If the user asked for a string key, we can add a decorator which converts back and forth between string and guid.
- // The string that the user provides will still need to contain a valid guid, since the Langchain created collection
- // uses guid keys.
- // Supporting string keys like this is useful since it means you can work with the collection in the same way as with
- // collections from other vector stores that support string keys.
- if (typeof(TKey) == typeof(string) && typeof(TRecord) == typeof(LangchainDocument))
- {
- var stringKeyCollection = new MappingVectorStoreRecordCollection, LangchainDocument>(
- collection,
- p => Guid.Parse(p),
- i => i.ToString("D"),
- p => new LangchainDocument { Key = Guid.Parse(p.Key), Content = p.Content, Source = p.Source, Embedding = p.Embedding },
- i => new LangchainDocument { Key = i.Key.ToString("D"), Content = i.Content, Source = i.Source, Embedding = i.Embedding });
-
- return (stringKeyCollection as IVectorStoreRecordCollection)!;
- }
-#endif
-
- throw new NotSupportedException("This VectorStore is only usable with Guid keys and LangchainDocument record types or string keys and LangchainDocument record types");
- }
- }
-
- ///
- /// A custom mapper that is required to map the metadata struct. While the other
- /// fields in the record can be mapped by the default Qdrant mapper, the default
- /// mapper doesn't support complex types like metadata, which is a Qdrant struct
- /// containing a source field.
- ///
- private sealed class LangchainInteropMapper : IVectorStoreRecordMapper, PointStruct>
- {
- public PointStruct MapFromDataToStorageModel(LangchainDocument dataModel)
- {
- var metadataStruct = new Struct()
- {
- Fields = { ["source"] = dataModel.Source }
- };
-
- var pointStruct = new PointStruct()
- {
- Id = new PointId() { Uuid = dataModel.Key.ToString("D") },
- Vectors = new Vectors() { Vector = dataModel.Embedding.ToArray() },
- Payload =
- {
- ["page_content"] = dataModel.Content,
- ["metadata"] = new Value() { StructValue = metadataStruct }
- },
- };
-
- return pointStruct;
- }
-
- public LangchainDocument MapFromStorageToDataModel(PointStruct storageModel, StorageToDataModelMapperOptions options)
- {
- return new LangchainDocument()
- {
- Key = new Guid(storageModel.Id.Uuid),
- Content = storageModel.Payload["page_content"].StringValue,
- Source = storageModel.Payload["metadata"].StructValue.Fields["source"].StringValue,
- Embedding = options.IncludeVectors ? storageModel.Vectors.Vector.Data.ToArray() : null
- };
- }
- }
-}
diff --git a/dotnet/samples/Concepts/Memory/VectorStoreLangchainInterop/RedisFactory.cs b/dotnet/samples/Concepts/Memory/VectorStoreLangchainInterop/RedisFactory.cs
index 23fd026401b4..86e54937bdf6 100644
--- a/dotnet/samples/Concepts/Memory/VectorStoreLangchainInterop/RedisFactory.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStoreLangchainInterop/RedisFactory.cs
@@ -24,7 +24,7 @@ public static class RedisFactory
new VectorStoreRecordKeyProperty("Key", typeof(string)),
new VectorStoreRecordDataProperty("Content", typeof(string)) { StoragePropertyName = "text" },
new VectorStoreRecordDataProperty("Source", typeof(string)) { StoragePropertyName = "source" },
- new VectorStoreRecordVectorProperty("Embedding", typeof(ReadOnlyMemory)) { StoragePropertyName = "embedding", Dimensions = 1536 }
+ new VectorStoreRecordVectorProperty("Embedding", typeof(ReadOnlyMemory), 1536) { StoragePropertyName = "embedding" }
}
};
@@ -34,14 +34,18 @@ public static class RedisFactory
/// The redis database to read/write from.
/// The .
public static IVectorStore CreateRedisLangchainInteropVectorStore(IDatabase database)
- => new RedisLangchainInteropVectorStore(database);
+ => new RedisLangchainInteropVectorStore(new RedisVectorStore(database), database);
- private sealed class RedisLangchainInteropVectorStore(IDatabase database)
- : RedisVectorStore(database)
+ private sealed class RedisLangchainInteropVectorStore(
+ IVectorStore innerStore,
+ IDatabase database)
+ : IVectorStore
{
private readonly IDatabase _database = database;
- public override IVectorStoreRecordCollection GetCollection(string name, VectorStoreRecordDefinition? vectorStoreRecordDefinition = null)
+ public IVectorStoreRecordCollection GetCollection(string name, VectorStoreRecordDefinition? vectorStoreRecordDefinition = null)
+ where TKey : notnull
+ where TRecord : notnull
{
if (typeof(TKey) != typeof(string) || typeof(TRecord) != typeof(LangchainDocument))
{
@@ -52,7 +56,7 @@ public override IVectorStoreRecordCollection GetCollection(
+ return (new RedisHashSetVectorStoreRecordCollection(
_database,
name,
new()
@@ -60,5 +64,13 @@ public override IVectorStoreRecordCollection GetCollection)!;
}
+
+ public object? GetService(Type serviceType, object? serviceKey = null) => innerStore.GetService(serviceType, serviceKey);
+
+ public IAsyncEnumerable ListCollectionNamesAsync(CancellationToken cancellationToken = default) => innerStore.ListCollectionNamesAsync(cancellationToken);
+
+ public Task CollectionExistsAsync(string name, CancellationToken cancellationToken = default) => innerStore.CollectionExistsAsync(name, cancellationToken);
+
+ public Task DeleteCollectionAsync(string name, CancellationToken cancellationToken = default) => innerStore.DeleteCollectionAsync(name, cancellationToken);
}
}
diff --git a/dotnet/samples/Concepts/Memory/VectorStore_ConsumeFromMemoryStore_AzureAISearch.cs b/dotnet/samples/Concepts/Memory/VectorStore_ConsumeFromMemoryStore_AzureAISearch.cs
index da4ae9cf7a76..12ce70374a0b 100644
--- a/dotnet/samples/Concepts/Memory/VectorStore_ConsumeFromMemoryStore_AzureAISearch.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStore_ConsumeFromMemoryStore_AzureAISearch.cs
@@ -26,6 +26,7 @@ namespace Memory;
/// dotnet user-secrets set "AzureAISearch:Endpoint" "https://myazureaisearchinstance.search.windows.net"
/// dotnet user-secrets set "AzureAISearch:ApiKey" "samplesecret"
///
+[Obsolete("The IMemoryStore abstraction is being obsoleted")]
public class VectorStore_ConsumeFromMemoryStore_AzureAISearch(ITestOutputHelper output, VectorStoreQdrantContainerFixture qdrantFixture) : BaseTest(output), IClassFixture
{
private const int VectorSize = 1536;
diff --git a/dotnet/samples/Concepts/Memory/VectorStore_ConsumeFromMemoryStore_Common.cs b/dotnet/samples/Concepts/Memory/VectorStore_ConsumeFromMemoryStore_Common.cs
index 772327889f49..50782b075af6 100644
--- a/dotnet/samples/Concepts/Memory/VectorStore_ConsumeFromMemoryStore_Common.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStore_ConsumeFromMemoryStore_Common.cs
@@ -19,6 +19,7 @@ namespace Memory;
///
///
///
+[Obsolete("The IMemoryStore abstraction is being obsoleted")]
public static class VectorStore_ConsumeFromMemoryStore_Common
{
public static async Task CreateCollectionAndAddSampleDataAsync(IMemoryStore memoryStore, string collectionName, ITextEmbeddingGenerationService textEmbeddingService)
diff --git a/dotnet/samples/Concepts/Memory/VectorStore_ConsumeFromMemoryStore_Qdrant.cs b/dotnet/samples/Concepts/Memory/VectorStore_ConsumeFromMemoryStore_Qdrant.cs
index 1f21c404e312..00b85bb6b494 100644
--- a/dotnet/samples/Concepts/Memory/VectorStore_ConsumeFromMemoryStore_Qdrant.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStore_ConsumeFromMemoryStore_Qdrant.cs
@@ -23,6 +23,7 @@ namespace Memory;
/// To run this sample, you need a local instance of Docker running, since the associated fixture
/// will try and start a Qdrant container in the local docker instance to run against.
///
+[Obsolete("The IMemoryStore abstraction is being obsoleted")]
public class VectorStore_ConsumeFromMemoryStore_Qdrant(ITestOutputHelper output, VectorStoreQdrantContainerFixture qdrantFixture) : BaseTest(output), IClassFixture
{
private const int VectorSize = 1536;
diff --git a/dotnet/samples/Concepts/Memory/VectorStore_ConsumeFromMemoryStore_Redis.cs b/dotnet/samples/Concepts/Memory/VectorStore_ConsumeFromMemoryStore_Redis.cs
index 91ecae46c124..669f5d2dfa7e 100644
--- a/dotnet/samples/Concepts/Memory/VectorStore_ConsumeFromMemoryStore_Redis.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStore_ConsumeFromMemoryStore_Redis.cs
@@ -22,6 +22,7 @@ namespace Memory;
/// To run this sample, you need a local instance of Docker running, since the associated fixture
/// will try and start a Redis container in the local docker instance to run against.
///
+[Obsolete("The IMemoryStore abstraction is being obsoleted")]
public class VectorStore_ConsumeFromMemoryStore_Redis(ITestOutputHelper output, VectorStoreRedisContainerFixture redisFixture) : BaseTest(output), IClassFixture
{
private const int VectorSize = 1536;
diff --git a/dotnet/samples/Concepts/Memory/VectorStore_DataIngestion_CustomMapper.cs b/dotnet/samples/Concepts/Memory/VectorStore_DataIngestion_CustomMapper.cs
deleted file mode 100644
index 3f86c763acbb..000000000000
--- a/dotnet/samples/Concepts/Memory/VectorStore_DataIngestion_CustomMapper.cs
+++ /dev/null
@@ -1,203 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using System.Text.Json;
-using System.Text.Json.Nodes;
-using Azure.Identity;
-using Memory.VectorStoreFixtures;
-using Microsoft.Extensions.VectorData;
-using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
-using Microsoft.SemanticKernel.Connectors.Redis;
-using Microsoft.SemanticKernel.Embeddings;
-using StackExchange.Redis;
-
-namespace Memory;
-
-///
-/// An example showing how to ingest data into a vector store using with a custom mapper.
-/// In this example, the storage model differs significantly from the data model, so a custom mapper is used to map between the two.
-/// A is used to define the schema of the storage model, and this means that the connector
-/// will not try and infer the schema from the data model.
-/// In storage the data is stored as a JSON object that looks similar to this:
-///
-/// {
-/// "Term": "API",
-/// "Definition": "Application Programming Interface. A set of rules and specifications that allow software components to communicate and exchange data.",
-/// "DefinitionEmbedding": [ ... ]
-/// }
-///
-/// However, the data model is a class with a property for key and two dictionaries for the data (Term and Definition) and vector (DefinitionEmbedding).
-///
-/// The example shows the following steps:
-/// 1. Create an embedding generator.
-/// 2. Create a Redis Vector Store using a custom factory for creating collections.
-/// When constructing a collection, the factory injects a custom mapper that maps between the data model and the storage model if required.
-/// 3. Ingest some data into the vector store.
-/// 4. Read the data back from the vector store.
-///
-/// You need a local instance of Docker running, since the associated fixture will try and start a Redis container in the local docker instance to run against.
-///
-public class VectorStore_DataIngestion_CustomMapper(ITestOutputHelper output, VectorStoreRedisContainerFixture redisFixture) : BaseTest(output), IClassFixture
-{
- ///
- /// A record definition for the glossary entries that defines the storage schema of the record.
- ///
- private static readonly VectorStoreRecordDefinition s_glossaryDefinition = new()
- {
- Properties = new List
- {
- new VectorStoreRecordKeyProperty("Key", typeof(string)),
- new VectorStoreRecordDataProperty("Term", typeof(string)),
- new VectorStoreRecordDataProperty("Definition", typeof(string)),
- new VectorStoreRecordVectorProperty("DefinitionEmbedding", typeof(ReadOnlyMemory)) { Dimensions = 1536, DistanceFunction = DistanceFunction.DotProductSimilarity }
- }
- };
-
- [Fact]
- public async Task ExampleAsync()
- {
- // Create an embedding generation service.
- var textEmbeddingGenerationService = new AzureOpenAITextEmbeddingGenerationService(
- TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
- TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
- new AzureCliCredential());
-
- // Initiate the docker container and construct the vector store using the custom factory for creating collections.
- await redisFixture.ManualInitializeAsync();
- ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost:6379");
- var vectorStore = new CustomRedisVectorStore(redis.GetDatabase());
-
- // Get and create collection if it doesn't exist, using the record definition containing the storage model.
- var collection = vectorStore.GetCollection("skglossary", s_glossaryDefinition);
- await collection.CreateCollectionIfNotExistsAsync();
-
- // Create glossary entries and generate embeddings for them.
- var glossaryEntries = CreateGlossaryEntries().ToList();
- var tasks = glossaryEntries.Select(entry => Task.Run(async () =>
- {
- entry.Vectors["DefinitionEmbedding"] = await textEmbeddingGenerationService.GenerateEmbeddingAsync((string)entry.Data["Definition"]);
- }));
- await Task.WhenAll(tasks);
-
- // Upsert the glossary entries into the collection and return their keys.
- var upsertedKeysTasks = glossaryEntries.Select(x => collection.UpsertAsync(x));
- var upsertedKeys = await Task.WhenAll(upsertedKeysTasks);
-
- // Retrieve one of the upserted records from the collection.
- var upsertedRecord = await collection.GetAsync(upsertedKeys.First(), new() { IncludeVectors = true });
-
- // Write upserted keys and one of the upserted records to the console.
- Console.WriteLine($"Upserted keys: {string.Join(", ", upsertedKeys)}");
- Console.WriteLine($"Upserted record: {JsonSerializer.Serialize(upsertedRecord)}");
- }
-
- ///
- /// A custom mapper that maps between the data model and the storage model.
- ///
- private sealed class Mapper : IVectorStoreRecordMapper
- {
- public (string Key, JsonNode Node) MapFromDataToStorageModel(GenericDataModel dataModel)
- {
- var jsonObject = new JsonObject();
-
- jsonObject.Add("Term", dataModel.Data["Term"].ToString());
- jsonObject.Add("Definition", dataModel.Data["Definition"].ToString());
-
- var vector = (ReadOnlyMemory)dataModel.Vectors["DefinitionEmbedding"];
- var jsonArray = new JsonArray(vector.ToArray().Select(x => JsonValue.Create(x)).ToArray());
- jsonObject.Add("DefinitionEmbedding", jsonArray);
-
- return (dataModel.Key, jsonObject);
- }
-
- public GenericDataModel MapFromStorageToDataModel((string Key, JsonNode Node) storageModel, StorageToDataModelMapperOptions options)
- {
- var dataModel = new GenericDataModel
- {
- Key = storageModel.Key,
- Data = new Dictionary
- {
- { "Term", (string)storageModel.Node["Term"]! },
- { "Definition", (string)storageModel.Node["Definition"]! }
- },
- Vectors = new Dictionary
- {
- { "DefinitionEmbedding", new ReadOnlyMemory(storageModel.Node["DefinitionEmbedding"]!.AsArray().Select(x => (float)x!).ToArray()) }
- }
- };
-
- return dataModel;
- }
- }
-
- private sealed class CustomRedisVectorStore(IDatabase database, RedisVectorStoreOptions? options = default)
- : RedisVectorStore(database, options)
- {
- private readonly IDatabase _database = database;
-
- public override IVectorStoreRecordCollection GetCollection(string name, VectorStoreRecordDefinition? vectorStoreRecordDefinition = null)
- {
- // If the record definition is the glossary definition and the record type is the generic data model, inject the custom mapper into the collection options.
- if (vectorStoreRecordDefinition == s_glossaryDefinition && typeof(TRecord) == typeof(GenericDataModel))
- {
- var customCollection = new RedisJsonVectorStoreRecordCollection(_database, name, new() { VectorStoreRecordDefinition = vectorStoreRecordDefinition, JsonNodeCustomMapper = new Mapper() }) as IVectorStoreRecordCollection;
- return customCollection!;
- }
-
- // Otherwise, just create a standard collection with the default mapper.
- var collection = new RedisJsonVectorStoreRecordCollection(_database, name, new() { VectorStoreRecordDefinition = vectorStoreRecordDefinition }) as IVectorStoreRecordCollection;
- return collection!;
- }
- }
-
- ///
- /// Sample generic data model class that can store any data.
- ///
- private sealed class GenericDataModel
- {
- public string Key { get; set; }
-
- public Dictionary Data { get; set; }
-
- public Dictionary Vectors { get; set; }
- }
-
- ///
- /// Create some sample glossary entries using the generic data model.
- ///
- /// A list of sample glossary entries.
- private static IEnumerable CreateGlossaryEntries()
- {
- yield return new GenericDataModel
- {
- Key = "1",
- Data = new()
- {
- { "Term", "API" },
- { "Definition", "Application Programming Interface. A set of rules and specifications that allow software components to communicate and exchange data." }
- },
- Vectors = new()
- };
-
- yield return new GenericDataModel
- {
- Key = "2",
- Data = new()
- {
- { "Term", "Connectors" },
- { "Definition", "Connectors allow you to integrate with various services provide AI capabilities, including LLM, AudioToText, TextToAudio, Embedding generation, etc." }
- },
- Vectors = new()
- };
-
- yield return new GenericDataModel
- {
- Key = "3",
- Data = new()
- {
- { "Term", "RAG" },
- { "Definition", "Retrieval Augmented Generation - a term that refers to the process of retrieving additional data to provide as context to an LLM to use when generating a response (completion) to a user’s question (prompt)." }
- },
- Vectors = new()
- };
- }
-}
diff --git a/dotnet/samples/Concepts/Memory/VectorStore_GenericDataModel_Interop.cs b/dotnet/samples/Concepts/Memory/VectorStore_DynamicDataModel_Interop.cs
similarity index 71%
rename from dotnet/samples/Concepts/Memory/VectorStore_GenericDataModel_Interop.cs
rename to dotnet/samples/Concepts/Memory/VectorStore_DynamicDataModel_Interop.cs
index 50c99dfcd03c..d7bb667284f4 100644
--- a/dotnet/samples/Concepts/Memory/VectorStore_GenericDataModel_Interop.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStore_DynamicDataModel_Interop.cs
@@ -12,15 +12,15 @@
namespace Memory;
///
-/// Semantic Kernel provides a generic data model for vector stores that can be used with any
+/// Semantic Kernel support dynamic data modeling for vector stores that can be used with any
/// schema. The schema still has to be provided in the form of a record definition, but no
-/// custom data model is required.
+/// custom .NET data model is required; a simple dictionary can be used.
///
/// The sample shows how to
-/// 1. Upsert data using the generic data model and retrieve it from the vector store using a custom data model.
-/// 2. Upsert data using a custom data model and retrieve it from the vector store using the generic data model.
+/// 1. Upsert data using dynamic data modeling and retrieve it from the vector store using a custom data model.
+/// 2. Upsert data using a custom data model and retrieve it from the vector store using the dynamic data modeling.
///
-public class VectorStore_GenericDataModel_Interop(ITestOutputHelper output, VectorStoreQdrantContainerFixture qdrantFixture) : BaseTest(output), IClassFixture
+public class VectorStore_DynamicDataModel_Interop(ITestOutputHelper output, VectorStoreQdrantContainerFixture qdrantFixture) : BaseTest(output), IClassFixture
{
private static readonly JsonSerializerOptions s_indentedSerializerOptions = new() { WriteIndented = true };
@@ -31,12 +31,12 @@ public class VectorStore_GenericDataModel_Interop(ITestOutputHelper output, Vect
new VectorStoreRecordKeyProperty("Key", typeof(ulong)),
new VectorStoreRecordDataProperty("Term", typeof(string)),
new VectorStoreRecordDataProperty("Definition", typeof(string)),
- new VectorStoreRecordVectorProperty("DefinitionEmbedding", typeof(ReadOnlyMemory)) { Dimensions = 1536 }
+ new VectorStoreRecordVectorProperty("DefinitionEmbedding", typeof(ReadOnlyMemory), 1536)
}
};
[Fact]
- public async Task UpsertWithGenericRetrieveWithCustomAsync()
+ public async Task UpsertWithDynamicRetrieveWithCustomAsync()
{
// Create an embedding generation service.
var textEmbeddingGenerationService = new AzureOpenAITextEmbeddingGenerationService(
@@ -48,27 +48,27 @@ public async Task UpsertWithGenericRetrieveWithCustomAsync()
await qdrantFixture.ManualInitializeAsync();
var vectorStore = new QdrantVectorStore(new QdrantClient("localhost"));
- // Get and create collection if it doesn't exist using the generic data model and record definition that defines the schema.
- var genericDataModelCollection = vectorStore.GetCollection>("skglossary", s_vectorStoreRecordDefinition);
- await genericDataModelCollection.CreateCollectionIfNotExistsAsync();
+ // Get and create collection if it doesn't exist using the dynamic data model and record definition that defines the schema.
+ var dynamicDataModelCollection = vectorStore.GetCollection