From 67a4854e935e4cf3b4bde6c2e06be408d927321b Mon Sep 17 00:00:00 2001
From: Adrian Bonar <56417140+adrianwyatt@users.noreply.github.com>
Date: Mon, 8 May 2023 16:45:04 -0700
Subject: [PATCH] CopilotChat: Add Azure Cog Search for Semantic Memory w/
IOptions fix (#866)
### Motivation and Context
Previous change introduced bug with configuration.
### Description
Changes ISemanticTextMemory from Singleton to Scoped.
---
.../Config/AzureCognitiveSearchOptions.cs | 23 ++++++
.../webapi/Config/MemoriesStoreOptions.cs | 13 +++-
.../webapi/CopilotChatApi.csproj | 1 +
.../webapi/SemanticKernelExtensions.cs | 70 +++++++++----------
.../copilot-chat-app/webapi/appsettings.json | 17 +++--
5 files changed, 82 insertions(+), 42 deletions(-)
create mode 100644 samples/apps/copilot-chat-app/webapi/Config/AzureCognitiveSearchOptions.cs
diff --git a/samples/apps/copilot-chat-app/webapi/Config/AzureCognitiveSearchOptions.cs b/samples/apps/copilot-chat-app/webapi/Config/AzureCognitiveSearchOptions.cs
new file mode 100644
index 000000000000..07cb960ddff5
--- /dev/null
+++ b/samples/apps/copilot-chat-app/webapi/Config/AzureCognitiveSearchOptions.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.ComponentModel.DataAnnotations;
+
+namespace SemanticKernel.Service.Config;
+
+///
+/// Configuration settings for connecting to Azure Cognitive Search.
+///
+public class AzureCognitiveSearchOptions
+{
+ ///
+ /// Gets or sets the endpoint protocol and host (e.g. https://contoso.search.windows.net).
+ ///
+ [Required, Url]
+ public string Endpoint { get; set; } = string.Empty;
+
+ ///
+ /// Key to access Azure Cognitive Search.
+ ///
+ [Required, NotEmptyOrWhitespace]
+ public string Key { get; set; } = string.Empty;
+}
diff --git a/samples/apps/copilot-chat-app/webapi/Config/MemoriesStoreOptions.cs b/samples/apps/copilot-chat-app/webapi/Config/MemoriesStoreOptions.cs
index c60f070ef16f..633799146a95 100644
--- a/samples/apps/copilot-chat-app/webapi/Config/MemoriesStoreOptions.cs
+++ b/samples/apps/copilot-chat-app/webapi/Config/MemoriesStoreOptions.cs
@@ -22,7 +22,12 @@ public enum MemoriesStoreType
///
/// Qdrant based persistent memories store.
///
- Qdrant
+ Qdrant,
+
+ ///
+ /// Azure Cognitive Search persistent memories store.
+ ///
+ AzureCognitiveSearch
}
///
@@ -35,4 +40,10 @@ public enum MemoriesStoreType
///
[RequiredOnPropertyValue(nameof(Type), MemoriesStoreType.Qdrant)]
public QdrantOptions? Qdrant { get; set; }
+
+ ///
+ /// Gets or sets the configuration for the Azure Cognitive Search memories store.
+ ///
+ [RequiredOnPropertyValue(nameof(Type), MemoriesStoreType.AzureCognitiveSearch)]
+ public AzureCognitiveSearchOptions? AzureCognitiveSearch { get; set; }
}
diff --git a/samples/apps/copilot-chat-app/webapi/CopilotChatApi.csproj b/samples/apps/copilot-chat-app/webapi/CopilotChatApi.csproj
index 3f2f94e366fd..eabb1cf0c9bc 100644
--- a/samples/apps/copilot-chat-app/webapi/CopilotChatApi.csproj
+++ b/samples/apps/copilot-chat-app/webapi/CopilotChatApi.csproj
@@ -29,6 +29,7 @@
+
diff --git a/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs b/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs
index e24833208dc0..95c7c53356c9 100644
--- a/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs
+++ b/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs
@@ -6,6 +6,7 @@
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.AI.Embeddings;
using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding;
+using Microsoft.SemanticKernel.Connectors.Memory.AzureCognitiveSearch;
using Microsoft.SemanticKernel.Connectors.Memory.Qdrant;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.SkillDefinition;
@@ -22,7 +23,7 @@ internal static class SemanticKernelExtensions
///
internal static IServiceCollection AddSemanticKernelServices(this IServiceCollection services)
{
- // The chat skill's prompts are stored in a separate file.
+ // Load the chat skill's prompts from the prompt configuration file.
services.AddSingleton(sp =>
{
string promptsConfigPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "prompts.json");
@@ -34,38 +35,7 @@ internal static IServiceCollection AddSemanticKernelServices(this IServiceCollec
services.AddSingleton();
// Add the semantic memory with backing memory store.
- services.AddSingleton(serviceProvider =>
- {
- MemoriesStoreOptions config = serviceProvider.GetRequiredService>().Value;
-
- switch (config.Type)
- {
- case MemoriesStoreOptions.MemoriesStoreType.Volatile:
- return new VolatileMemoryStore();
-
- case MemoriesStoreOptions.MemoriesStoreType.Qdrant:
- if (config.Qdrant is null)
- {
- throw new InvalidOperationException(
- $"MemoriesStore:Qdrant is required when MemoriesStore:Type is '{MemoriesStoreOptions.MemoriesStoreType.Qdrant}'");
- }
-
- return new QdrantMemoryStore(
- host: config.Qdrant.Host,
- port: config.Qdrant.Port,
- vectorSize: config.Qdrant.VectorSize,
- logger: serviceProvider.GetRequiredService>());
-
- default:
- throw new InvalidOperationException($"Invalid 'MemoriesStore' type '{config.Type}'.");
- }
- });
-
- services.AddScoped(serviceProvider
- => new SemanticTextMemory(
- serviceProvider.GetRequiredService(),
- serviceProvider.GetRequiredService>().Get(AIServiceOptions.EmbeddingPropertyName)
- .ToTextEmbeddingsService(logger: serviceProvider.GetRequiredService>())));
+ services.AddScoped(CreateSemanticTextMemory);
// Add the planner.
services.AddScoped(sp =>
@@ -96,10 +66,38 @@ internal static IServiceCollection AddSemanticKernelServices(this IServiceCollec
return services;
}
+ ///
+ /// Create the semantic memory with backing memory store.
+ ///
+ private static ISemanticTextMemory CreateSemanticTextMemory(this IServiceProvider serviceProvider)
+ {
+ MemoriesStoreOptions config = serviceProvider.GetRequiredService>().Value;
+ switch (config.Type)
+ {
+ case MemoriesStoreOptions.MemoriesStoreType.Volatile:
+ return new SemanticTextMemory(
+ new VolatileMemoryStore(),
+ serviceProvider.GetRequiredService>().Get(AIServiceOptions.EmbeddingPropertyName)
+ .ToTextEmbeddingsService(logger: serviceProvider.GetRequiredService>()));
+
+ case MemoriesStoreOptions.MemoriesStoreType.Qdrant:
+ return new SemanticTextMemory(
+ new QdrantMemoryStore(config.Qdrant!.Host, config.Qdrant.Port, config.Qdrant.VectorSize, serviceProvider.GetRequiredService>()),
+ serviceProvider.GetRequiredService>().Get(AIServiceOptions.EmbeddingPropertyName)
+ .ToTextEmbeddingsService(logger: serviceProvider.GetRequiredService>()));
+
+ case MemoriesStoreOptions.MemoriesStoreType.AzureCognitiveSearch:
+ return new AzureCognitiveSearchMemory(config.AzureCognitiveSearch!.Endpoint, config.AzureCognitiveSearch.Key);
+
+ default:
+ throw new InvalidOperationException($"Invalid 'MemoriesStore' type '{config.Type}'.");
+ }
+ }
+
///
/// Add the completion backend to the kernel config
///
- internal static KernelConfig AddCompletionBackend(this KernelConfig kernelConfig, AIServiceOptions aiServiceOptions)
+ private static KernelConfig AddCompletionBackend(this KernelConfig kernelConfig, AIServiceOptions aiServiceOptions)
{
switch (aiServiceOptions.AIService)
{
@@ -126,7 +124,7 @@ internal static KernelConfig AddCompletionBackend(this KernelConfig kernelConfig
///
/// Add the embedding backend to the kernel config
///
- internal static KernelConfig AddEmbeddingBackend(this KernelConfig kernelConfig, AIServiceOptions aiServiceOptions)
+ private static KernelConfig AddEmbeddingBackend(this KernelConfig kernelConfig, AIServiceOptions aiServiceOptions)
{
switch (aiServiceOptions.AIService)
{
@@ -158,7 +156,7 @@ internal static KernelConfig AddEmbeddingBackend(this KernelConfig kernelConfig,
/// The service configuration
/// Custom for HTTP requests.
/// Application logger
- internal static IEmbeddingGeneration ToTextEmbeddingsService(this AIServiceOptions serviceConfig,
+ private static IEmbeddingGeneration ToTextEmbeddingsService(this AIServiceOptions serviceConfig,
HttpClient? httpClient = null,
ILogger? logger = null)
{
diff --git a/samples/apps/copilot-chat-app/webapi/appsettings.json b/samples/apps/copilot-chat-app/webapi/appsettings.json
index db2c30bc001a..273f92c1c0c2 100644
--- a/samples/apps/copilot-chat-app/webapi/appsettings.json
+++ b/samples/apps/copilot-chat-app/webapi/appsettings.json
@@ -44,6 +44,7 @@
//
// Embeddings are used for semantically encoding memories.
+ // This section is ignored when MemoriesStore:Type is set to Azure Cognitive Search.
// https://platform.openai.com/docs/guides/embeddings
// To use Azure OpenAI as the AI embedding service:
// - Set "AIService" to "AzureOpenAI"
@@ -109,15 +110,21 @@
//
// Memories stores are used for storing new memories and retrieving semantically similar memories.
- // - Supported Types are "volatile" or "qdrant".
- // - When using Qdrant, see ./README.md for local deployment instructions.
+ // - Supported Types are "volatile", "qdrant", or "azurecognitivesearch".
+ // - When using Qdrant or Azure Cognitive Search, see ./README.md for deployment instructions.
+ // - The "Semantic Search" feature must be enabled on Azure Cognitive Search.
+ // - The Embedding configuration above will not be used when Azure Cognitive Search is selected.
//
"MemoriesStore": {
"Type": "volatile",
"Qdrant": {
- "Host": "http://localhost", // Endpoint of the Qdrant server
- "Port": "6333", // Port of the Qdrant server
- "VectorSize": 1536 // Size of the vectors used by the Qdrant server
+ "Host": "http://localhost",
+ "Port": "6333",
+ "VectorSize": 1536
+ },
+ "AzureCognitiveSearch": {
+ "Endpoint": ""
+ // "Key": "" // dotnet user-secrets set "MemoriesStore:AzureCognitiveSearch:Key" "MY_ACS_KEY"
}
},