Skip to content

Commit

Permalink
CopilotChat: Add Azure Cog Search for Semantic Memory w/ IOptions fix (
Browse files Browse the repository at this point in the history
…#866)

### Motivation and Context
Previous change introduced bug with configuration.

### Description
Changes ISemanticTextMemory from Singleton to Scoped.
  • Loading branch information
adrianwyatt authored May 8, 2023
1 parent dac05fc commit 67a4854
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 42 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft. All rights reserved.

using System.ComponentModel.DataAnnotations;

namespace SemanticKernel.Service.Config;

/// <summary>
/// Configuration settings for connecting to Azure Cognitive Search.
/// </summary>
public class AzureCognitiveSearchOptions
{
/// <summary>
/// Gets or sets the endpoint protocol and host (e.g. https://contoso.search.windows.net).
/// </summary>
[Required, Url]
public string Endpoint { get; set; } = string.Empty;

/// <summary>
/// Key to access Azure Cognitive Search.
/// </summary>
[Required, NotEmptyOrWhitespace]
public string Key { get; set; } = string.Empty;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ public enum MemoriesStoreType
/// <summary>
/// Qdrant based persistent memories store.
/// </summary>
Qdrant
Qdrant,

/// <summary>
/// Azure Cognitive Search persistent memories store.
/// </summary>
AzureCognitiveSearch
}

/// <summary>
Expand All @@ -35,4 +40,10 @@ public enum MemoriesStoreType
/// </summary>
[RequiredOnPropertyValue(nameof(Type), MemoriesStoreType.Qdrant)]
public QdrantOptions? Qdrant { get; set; }

/// <summary>
/// Gets or sets the configuration for the Azure Cognitive Search memories store.
/// </summary>
[RequiredOnPropertyValue(nameof(Type), MemoriesStoreType.AzureCognitiveSearch)]
public AzureCognitiveSearchOptions? AzureCognitiveSearch { get; set; }
}
1 change: 1 addition & 0 deletions samples/apps/copilot-chat-app/webapi/CopilotChatApi.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

<ItemGroup>
<ProjectReference Include="..\..\..\..\dotnet\src\Connectors\Connectors.AI.OpenAI\Connectors.AI.OpenAI.csproj" />
<ProjectReference Include="..\..\..\..\dotnet\src\Connectors\Connectors.Memory.AzureCognitiveSearch\Connectors.Memory.AzureCognitiveSearch.csproj" />
<ProjectReference Include="..\..\..\..\dotnet\src\Skills\Skills.MsGraph\Skills.MsGraph.csproj" />
<ProjectReference Include="..\..\..\..\dotnet\src\Skills\Skills.OpenAPI\Skills.OpenAPI.csproj" />
<ProjectReference Include="..\..\..\..\dotnet\src\Connectors\Connectors.Memory.Qdrant\Connectors.Memory.Qdrant.csproj" />
Expand Down
70 changes: 34 additions & 36 deletions samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -22,7 +23,7 @@ internal static class SemanticKernelExtensions
/// </summary>
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<PromptsConfig>(sp =>
{
string promptsConfigPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "prompts.json");
Expand All @@ -34,38 +35,7 @@ internal static IServiceCollection AddSemanticKernelServices(this IServiceCollec
services.AddSingleton<PromptSettings>();

// Add the semantic memory with backing memory store.
services.AddSingleton<IMemoryStore>(serviceProvider =>
{
MemoriesStoreOptions config = serviceProvider.GetRequiredService<IOptions<MemoriesStoreOptions>>().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<ILogger<QdrantMemoryStore>>());
default:
throw new InvalidOperationException($"Invalid 'MemoriesStore' type '{config.Type}'.");
}
});

services.AddScoped<ISemanticTextMemory>(serviceProvider
=> new SemanticTextMemory(
serviceProvider.GetRequiredService<IMemoryStore>(),
serviceProvider.GetRequiredService<IOptionsSnapshot<AIServiceOptions>>().Get(AIServiceOptions.EmbeddingPropertyName)
.ToTextEmbeddingsService(logger: serviceProvider.GetRequiredService<ILogger<AIServiceOptions>>())));
services.AddScoped<ISemanticTextMemory>(CreateSemanticTextMemory);

// Add the planner.
services.AddScoped<CopilotChatPlanner>(sp =>
Expand Down Expand Up @@ -96,10 +66,38 @@ internal static IServiceCollection AddSemanticKernelServices(this IServiceCollec
return services;
}

/// <summary>
/// Create the semantic memory with backing memory store.
/// </summary>
private static ISemanticTextMemory CreateSemanticTextMemory(this IServiceProvider serviceProvider)
{
MemoriesStoreOptions config = serviceProvider.GetRequiredService<IOptions<MemoriesStoreOptions>>().Value;
switch (config.Type)
{
case MemoriesStoreOptions.MemoriesStoreType.Volatile:
return new SemanticTextMemory(
new VolatileMemoryStore(),
serviceProvider.GetRequiredService<IOptionsSnapshot<AIServiceOptions>>().Get(AIServiceOptions.EmbeddingPropertyName)
.ToTextEmbeddingsService(logger: serviceProvider.GetRequiredService<ILogger<AIServiceOptions>>()));

case MemoriesStoreOptions.MemoriesStoreType.Qdrant:
return new SemanticTextMemory(
new QdrantMemoryStore(config.Qdrant!.Host, config.Qdrant.Port, config.Qdrant.VectorSize, serviceProvider.GetRequiredService<ILogger<QdrantMemoryStore>>()),
serviceProvider.GetRequiredService<IOptionsSnapshot<AIServiceOptions>>().Get(AIServiceOptions.EmbeddingPropertyName)
.ToTextEmbeddingsService(logger: serviceProvider.GetRequiredService<ILogger<AIServiceOptions>>()));

case MemoriesStoreOptions.MemoriesStoreType.AzureCognitiveSearch:
return new AzureCognitiveSearchMemory(config.AzureCognitiveSearch!.Endpoint, config.AzureCognitiveSearch.Key);

default:
throw new InvalidOperationException($"Invalid 'MemoriesStore' type '{config.Type}'.");
}
}

/// <summary>
/// Add the completion backend to the kernel config
/// </summary>
internal static KernelConfig AddCompletionBackend(this KernelConfig kernelConfig, AIServiceOptions aiServiceOptions)
private static KernelConfig AddCompletionBackend(this KernelConfig kernelConfig, AIServiceOptions aiServiceOptions)
{
switch (aiServiceOptions.AIService)
{
Expand All @@ -126,7 +124,7 @@ internal static KernelConfig AddCompletionBackend(this KernelConfig kernelConfig
/// <summary>
/// Add the embedding backend to the kernel config
/// </summary>
internal static KernelConfig AddEmbeddingBackend(this KernelConfig kernelConfig, AIServiceOptions aiServiceOptions)
private static KernelConfig AddEmbeddingBackend(this KernelConfig kernelConfig, AIServiceOptions aiServiceOptions)
{
switch (aiServiceOptions.AIService)
{
Expand Down Expand Up @@ -158,7 +156,7 @@ internal static KernelConfig AddEmbeddingBackend(this KernelConfig kernelConfig,
/// <param name="serviceConfig">The service configuration</param>
/// <param name="httpClient">Custom <see cref="HttpClient"/> for HTTP requests.</param>
/// <param name="logger">Application logger</param>
internal static IEmbeddingGeneration<string, float> ToTextEmbeddingsService(this AIServiceOptions serviceConfig,
private static IEmbeddingGeneration<string, float> ToTextEmbeddingsService(this AIServiceOptions serviceConfig,
HttpClient? httpClient = null,
ILogger? logger = null)
{
Expand Down
17 changes: 12 additions & 5 deletions samples/apps/copilot-chat-app/webapi/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
}
},

Expand Down

0 comments on commit 67a4854

Please sign in to comment.