Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various changes to make Feedback enabled for all users, disable "toggle" switches for now on Deployment #20

Merged
merged 9 commits into from
May 24, 2024
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"
]
}
}
}
17 changes: 11 additions & 6 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.");
this._logger.LogInformation("Chat request received from {0}", authInfo.UserId);

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
2 changes: 1 addition & 1 deletion webapi/Controllers/ChatHistoryController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public async Task<IActionResult> CreateChatSessionAsync(
// Add the user to the chat session
await this._participantRepository.CreateAsync(new ChatParticipant(this._authInfo.UserId, newChat.Id));

this._logger.LogDebug("Created chat session with id {0}.", newChat.Id);
this._logger.LogInformation("Created chat session with id {0}.", newChat.Id);

return this.CreatedAtRoute(GetChatRoute, new { chatId = newChat.Id }, new CreateChatResponse(newChat, chatMessage));
}
Expand Down
1 change: 1 addition & 0 deletions webapi/Controllers/ChatParticipantController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public async Task<IActionResult> JoinChatAsync(
// Note that the client who initiated the request may not have joined the group.
await messageRelayHubContext.Clients.Group(chatId.ToString()).SendAsync(UserJoinedClientCall, chatId, userId);

this._logger.LogInformation("User {0} has joined conversation {1}", userId, chatId);
return this.Ok(chatParticipant);
}

Expand Down
11 changes: 10 additions & 1 deletion webapi/Controllers/UserSettingsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,14 @@ public async Task<IActionResult> GetUserSettingsAsync([FromRoute] string userId)
return this.Ok(newUserSettings); // Only 1 record per user id
}

this._logger.LogDebug("User settings record found for: {0}", userId);
this._logger.LogInformation("User logged in and retrieved settings record 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
Loading