Skip to content

Commit

Permalink
Merge pull request #19 from aaronba/syncbranch
Browse files Browse the repository at this point in the history
Disabling User Setting Toggles for Deployment Config; Default Feedback to true for all users
  • Loading branch information
mattahearn authored May 22, 2024
2 parents 6951a71 + 71f9ec6 commit e9e3884
Show file tree
Hide file tree
Showing 22 changed files with 489 additions and 96 deletions.
7 changes: 6 additions & 1 deletion shared/ServiceConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,12 @@ private void ConfigureTextGenerator(IKernelMemoryBuilder builder)
{
case string x when x.Equals("AzureOpenAI", StringComparison.OrdinalIgnoreCase):
case string y when y.Equals("AzureOpenAIText", StringComparison.OrdinalIgnoreCase):
builder.Services.AddAzureOpenAITextGeneration(this.GetServiceConfig<AzureOpenAIConfig>("AzureOpenAIText"));
// Initialize singletons for each type of AI Deployment Model Name so users can choose to switch between them at runtime
AzureOpenAIConfig config = this.GetServiceConfig<AzureOpenAIConfig>("AzureOpenAIText");
// config.Deployment = "gpt-35-turbo";
builder.Services.AddAzureOpenAITextGeneration(config);
// config.Deployment = "gpt-4";
// builder.Services.AddAzureOpenAITextGeneration(config);
break;

case string x when x.Equals("OpenAI", StringComparison.OrdinalIgnoreCase):
Expand Down
12 changes: 12 additions & 0 deletions webapi/.config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "8.0.3",
"commands": [
"dotnet-ef"
]
}
}
}
15 changes: 10 additions & 5 deletions webapi/Controllers/ChatController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
using Microsoft.SemanticKernel.Plugins.MsGraph.Connectors;
using Microsoft.SemanticKernel.Plugins.MsGraph.Connectors.Client;
using Microsoft.SemanticKernel.Plugins.OpenApi;
using Microsoft.SemanticKernel.Services;
using Microsoft.Extensions.Azure;

namespace CopilotChat.WebApi.Controllers;

Expand Down Expand Up @@ -76,7 +78,7 @@ public ChatController(
/// <param name="kernel">Semantic kernel obtained through dependency injection.</param>
/// <param name="params">Request body parameters i.e. Azure OpenAI Deployment Name.</param>
/// <returns>Results containing the response from the model.</returns>
[Route("chats/updatekernelservices")]
[Route("chats/updatekernelservice")]
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> UpdateKernelService(
Expand All @@ -85,7 +87,7 @@ public async Task<IActionResult> UpdateKernelService(
{
this._logger.LogDebug("Request received to update semantic kernel service.");

// SemanticKernelExtensions.ReplaceKernelServices();
// SemanticKernelExtensions.ReplaceKernelServices(kp.deployment);

return this.Ok(kernel);
}
Expand Down Expand Up @@ -115,14 +117,15 @@ public async Task<IActionResult> ChatAsync(
[FromServices] ChatParticipantRepository chatParticipantRepository,
[FromServices] IAuthInfo authInfo,
[FromBody] Ask ask,
[FromRoute] Guid chatId)
[FromRoute] Guid chatId,
[FromQuery] string deploymentName)
{
this._logger.LogDebug("Chat message received.");

string chatIdString = chatId.ToString();

// Put ask's variables in the context we will use.
var contextVariables = GetContextVariables(ask, authInfo, chatIdString);
var contextVariables = GetContextVariables(ask, authInfo, chatIdString, deploymentName);

// Verify that the chat exists and that the user has access to it.
ChatSession? chat = null;
Expand Down Expand Up @@ -380,12 +383,13 @@ await kernel.ImportPluginFromOpenAIAsync(
return;
}

private static KernelArguments GetContextVariables(Ask ask, IAuthInfo authInfo, string chatId)
private static KernelArguments GetContextVariables(Ask ask, IAuthInfo authInfo, string chatId, string deploymentName)
{
const string UserIdKey = "userId";
const string UserNameKey = "userName";
const string ChatIdKey = "chatId";
const string MessageKey = "message";
const string DeploymentName = "deploymentName";

var contextVariables = new KernelArguments();
foreach (var variable in ask.Variables)
Expand All @@ -397,6 +401,7 @@ private static KernelArguments GetContextVariables(Ask ask, IAuthInfo authInfo,
contextVariables[UserNameKey] = authInfo.Name;
contextVariables[ChatIdKey] = chatId;
contextVariables[MessageKey] = ask.Input;
contextVariables[DeploymentName] = deploymentName;

return contextVariables;
}
Expand Down
9 changes: 9 additions & 0 deletions webapi/Controllers/UserSettingsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ public async Task<IActionResult> GetUserSettingsAsync([FromRoute] string userId)
this._logger.LogDebug("User settings record found for: {0}", userId);
foreach (var setting in settings)
{
if (setting.DeploymentGPT35 != true && setting.DeploymentGPT4 != true)
{
setting.DeploymentGPT35 = true; // Default value
}

UserSettings us = new(setting.UserId, setting.DarkMode, setting.Planners, setting.Personas, setting.SimplifiedChatExperience,
setting.AzureContentSafety, setting.AzureAISearch, setting.ExportChatSessions, setting.LiveChatSessionSharing,
setting.FeedbackFromUser, setting.DeploymentGPT35, setting.DeploymentGPT4);
Expand Down Expand Up @@ -124,6 +129,10 @@ public async Task<IActionResult> UpdateUserSettingsAsync(
setting!.FeedbackFromUser = msgParameters.feedbackFromUser;
setting!.DeploymentGPT35 = msgParameters.deploymentGPT35;
setting!.DeploymentGPT4 = msgParameters.deploymentGPT4;
if (setting.DeploymentGPT35 != true && setting.DeploymentGPT4 != true)
{
setting.DeploymentGPT35 = true; // Default value
}
await this._userSettingsRepository.UpsertAsync(setting);

return this.Ok(setting);
Expand Down
82 changes: 69 additions & 13 deletions webapi/Extensions/SemanticKernelExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
using CopilotChat.WebApi.Auth;
using CopilotChat.WebApi.Hubs;
using CopilotChat.WebApi.Models.Response;
using CopilotChat.WebApi.Models.Storage;
using CopilotChat.WebApi.Options;
using CopilotChat.WebApi.Plugins.Chat;
using CopilotChat.WebApi.Services;
Expand Down Expand Up @@ -47,19 +50,37 @@ public enum SemanticProvider
/// </summary>
public delegate Task KernelSetupHook(IServiceProvider sp, Kernel kernel);

public delegate Task<UserSettings?> UserSettingsDelegate(IServiceProvider sp);

/// <summary>
/// Add Semantic Kernel services
/// </summary>
public static WebApplicationBuilder AddSemanticKernelServices(this WebApplicationBuilder builder)
{
builder.InitializeKernelProvider();
builder.InitializeKernelProvider(null);

// Semantic Kernel
builder.Services.AddScoped<Kernel>(
sp =>
(sp) =>
{
var provider = sp.GetRequiredService<SemanticKernelProvider>();
var kernel = provider.GetCompletionKernel();

string? deploymentName = null;
UserSettings? settings = sp.GetService<UserSettingsDelegate>()?.Invoke(sp).Result;
if (settings != null)
{
if ((bool)settings.DeploymentGPT35)
{
deploymentName = "gpt-35-turbo";
}
else if ((bool)settings.DeploymentGPT4)
{
deploymentName = "gpt-4";
}
}

deploymentName = null; // For now, keep null until switcher tested fully
var kernel = provider.GetCompletionKernel(deploymentName);

sp.GetRequiredService<RegisterFunctionsWithKernel>()(sp, kernel);

Expand All @@ -78,6 +99,9 @@ public static WebApplicationBuilder AddSemanticKernelServices(this WebApplicatio
// Uncomment the following line and pass in a custom hook for any complimentary setup of the kernel.
builder.Services.AddKernelSetupHook(RegisterPluginsAsync);

// Service to retrieve user settings
builder.Services.AddScoped<UserSettingsDelegate>(sp => RetrieveUserSettings);

return builder;
}

Expand Down Expand Up @@ -124,18 +148,26 @@ public static Kernel RegisterChatPlugin(this Kernel kernel, IServiceProvider sp)
return kernel;
}

public static WebApplicationBuilder ReplaceKernelServices(this WebApplicationBuilder builder, string deploymentName)
public static void ReplaceKernelServices(this IServiceCollection services, string deploymentName)
{
var config = services.BuildServiceProvider().GetService<IConfiguration>()?.GetSection("AzureOpenAIText").Get(typeof(IConfiguration));
if (config == null)
{
return;
}

//IConfiguration config = services.BuildServiceProvider().GetService<IConfiguration>(); //?.GetSection("AzureOpenAIText").Get<PluginConfig>();

// Swap out the kernel provider service with this one to use a different Azure OpenAI Deployment Name updated by the user
ServiceDescriptor descriptorProvider = ServiceDescriptor.Singleton(typeof(SemanticKernelProvider),
sp => new SemanticKernelProvider(sp, builder.Configuration, sp.GetRequiredService<IHttpClientFactory>(), deploymentName));
builder.Services.Replace(descriptorProvider);
sp => new SemanticKernelProvider(sp, (IConfiguration)config, sp.GetRequiredService<IHttpClientFactory>(), deploymentName));
services.Replace(descriptorProvider);

// Swap out the kernel service
ServiceDescriptor descriptorKernel = ServiceDescriptor.Scoped(typeof(Kernel), sp =>
{
var provider = sp.GetRequiredService<SemanticKernelProvider>();
var kernel = provider.GetCompletionKernel();
var kernel = provider.GetCompletionKernel(deploymentName);

sp.GetRequiredService<RegisterFunctionsWithKernel>()(sp, kernel);

Expand All @@ -144,17 +176,41 @@ public static WebApplicationBuilder ReplaceKernelServices(this WebApplicationBui
return kernel;
});

builder.Services.Replace(descriptorKernel);
services.Replace(descriptorKernel);
}

return builder;
private static void InitializeKernelProvider(this WebApplicationBuilder builder, string? deploymentName)
{
builder.Services.AddSingleton(sp => new SemanticKernelProvider(sp, builder.Configuration, sp.GetRequiredService<IHttpClientFactory>(), deploymentName));
//builder.Services.AddTransient(sp => new SemanticKernelProvider(sp, builder.Configuration, sp.GetRequiredService<IHttpClientFactory>(), deploymentName));
//builder.Services.AddKeyedSingleton<SemanticKernelProvider>("gpt-35-turbo", (sp, ConfigurationManager) => new SemanticKernelProvider(sp, builder.Configuration, sp.GetRequiredService<IHttpClientFactory>(), "gpt-35-turbo"));
//builder.Services.AddKeyedSingleton<SemanticKernelProvider>("gpt-4", (sp, ConfigurationManager) => new SemanticKernelProvider(sp, builder.Configuration, sp.GetRequiredService<IHttpClientFactory>(), "gpt-4"));
}

private static void InitializeKernelProvider(this WebApplicationBuilder builder)
private static async Task<UserSettings?> RetrieveUserSettings(IServiceProvider sp)
{
builder.Services.AddSingleton(sp => new SemanticKernelProvider(sp, builder.Configuration, sp.GetRequiredService<IHttpClientFactory>(), null));
string userID = "";

try
{
var auth = sp.GetService<IAuthInfo>();

userID = auth.UserId;
}
catch (Exception)
{
userID = "c05c61eb-65e4-4223-915a-fe72b0c9ece1"; // Default user
}

UserSettingsRepository userSettingsRepo = sp.GetService<UserSettingsRepository>();
IEnumerable<UserSettings> settings = await userSettingsRepo.FindSettingsByUserIdAsync(userID);

foreach (var setting in settings)
{
return setting;
}

// SemanticKernelProvider skp => new(skp, builder.Configuration, skp.GetRequiredService<IHttpClientFactory>(), "gpt-35-turbro");
// builder.Services.AddKeyedSingleton<SemanticKernelProvider>("gpt-35-turbro", skp);
return null;
}

/// <summary>
Expand Down
21 changes: 21 additions & 0 deletions webapi/Models/Request/AppConfigParameters.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Text.Json.Serialization;

namespace CopilotChat.WebApi.Models.Request;

/// <summary>
/// Parameters for updating App Config.
/// </summary>
public class AppConfigParameters
{
/// <summary>
/// Force configuration reload
/// </summary>
[JsonPropertyName("force")]
public bool force { get; set; }

/// <summary>
/// AI Deployment Model Name
/// </summary>
[JsonPropertyName("deploymentName")]
public string? deploymentName { get; set; }
}
16 changes: 16 additions & 0 deletions webapi/Options/AzureOpenAITextOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace CopilotChat.WebApi.Options;

/// <summary>
/// Configuration options for Azure OpenAI Text service.
/// </summary>
public sealed class AzureOpenAITextOptions
{
public const string PropertyName = "AzureOpenAIText";

public string Auth { get; set; } = string.Empty;
public string APIKey { get; set; } = string.Empty;
public string Endpoint { get; set; } = string.Empty;
public string Deployment { get; set; } = string.Empty;
public string APIType { get; set; } = string.Empty;
public int MaxRetries { get; set; } = 0;
}
12 changes: 8 additions & 4 deletions webapi/Plugins/Chat/ChatPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Services;
using CopilotChatMessage = CopilotChat.WebApi.Models.Storage.CopilotChatMessage;

namespace CopilotChat.WebApi.Plugins.Chat;
Expand Down Expand Up @@ -196,6 +197,7 @@ public async Task<KernelArguments> ChatAsync(
[Description("Unique and persistent identifier for the user")] string userId,
[Description("Name of the user")] string userName,
[Description("Unique and persistent identifier for the chat")] string chatId,
[Description("AI Deployment Model Name retrieves correct Semantic Kernel Service Provider i.e. gpt35turbo or gpt4")] string deploymentName,
[Description("Type of the message")] string messageType,
KernelArguments context,
CancellationToken cancellationToken = default)
Expand All @@ -211,7 +213,7 @@ public async Task<KernelArguments> ChatAsync(
KernelArguments chatContext = new(context);
chatContext["knowledgeCutoff"] = this._promptOptions.KnowledgeCutoffDate;

CopilotChatMessage chatMessage = await this.GetChatResponseAsync(chatId, userId, chatContext, newUserMessage, cancellationToken);
CopilotChatMessage chatMessage = await this.GetChatResponseAsync(chatId, userId, deploymentName, chatContext, newUserMessage, cancellationToken);
context["input"] = chatMessage.Content;

if (chatMessage.TokenUsage != null)
Expand All @@ -235,7 +237,7 @@ public async Task<KernelArguments> ChatAsync(
/// <param name="userMessage">ChatMessage object representing new user message.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The created chat message containing the model-generated response.</returns>
private async Task<CopilotChatMessage> GetChatResponseAsync(string chatId, string userId, KernelArguments chatContext, CopilotChatMessage userMessage, CancellationToken cancellationToken)
private async Task<CopilotChatMessage> GetChatResponseAsync(string chatId, string userId, string deploymentName, KernelArguments chatContext, CopilotChatMessage userMessage, CancellationToken cancellationToken)
{
// Render system instruction components and create the meta-prompt template
var systemInstructions = await AsyncUtils.SafeInvokeAsync(
Expand Down Expand Up @@ -287,7 +289,7 @@ private async Task<CopilotChatMessage> GetChatResponseAsync(string chatId, strin
// Stream the response to the client
var promptView = new BotResponsePrompt(systemInstructions, audience, userIntent, memoryText, allowedChatHistory, metaPrompt);

return await this.HandleBotResponseAsync(chatId, userId, chatContext, promptView, citationMap.Values.AsEnumerable(), cancellationToken);
return await this.HandleBotResponseAsync(chatId, userId, deploymentName, chatContext, promptView, citationMap.Values.AsEnumerable(), cancellationToken);
}

/// <summary>
Expand Down Expand Up @@ -319,6 +321,7 @@ private async Task<string> RenderSystemInstructions(string chatId, KernelArgumen
private async Task<CopilotChatMessage> HandleBotResponseAsync(
string chatId,
string userId,
string deploymentName,
KernelArguments chatContext,
BotResponsePrompt promptView,
IEnumerable<CitationSource>? citations,
Expand All @@ -327,7 +330,7 @@ private async Task<CopilotChatMessage> HandleBotResponseAsync(
// Get bot response and stream to client
await this.UpdateBotResponseStatusOnClientAsync(chatId, "Generating bot response", cancellationToken);
CopilotChatMessage chatMessage = await AsyncUtils.SafeInvokeAsync(
() => this.StreamResponseToClientAsync(chatId, userId, promptView, cancellationToken, citations), nameof(StreamResponseToClientAsync));
() => this.StreamResponseToClientAsync(chatId, userId, deploymentName, promptView, cancellationToken, citations), nameof(StreamResponseToClientAsync));

// Save the message into chat history
await this.UpdateBotResponseStatusOnClientAsync(chatId, "Saving message to chat history", cancellationToken);
Expand Down Expand Up @@ -634,6 +637,7 @@ private Dictionary<string, int> GetTokenUsages(KernelArguments kernelArguments,
private async Task<CopilotChatMessage> StreamResponseToClientAsync(
string chatId,
string userId,
string deploymentName,
BotResponsePrompt prompt,
CancellationToken cancellationToken,
IEnumerable<CitationSource>? citations = null)
Expand Down
Loading

0 comments on commit e9e3884

Please sign in to comment.