Skip to content

Commit

Permalink
Remove unneeded planner code and simplifu chat plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
glahaye committed Feb 27, 2024
1 parent 607bcf1 commit 31a3e55
Show file tree
Hide file tree
Showing 11 changed files with 21 additions and 258 deletions.
104 changes: 17 additions & 87 deletions webapi/Controllers/ChatController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ public class ChatController : ControllerBase, IDisposable

private const string ChatPluginName = nameof(ChatPlugin);
private const string ChatFunctionName = "Chat";
private const string ProcessPlanFunctionName = "ProcessPlan";
private const string GeneratingResponseClientCall = "ReceiveBotResponseStatus";

public ChatController(
Expand All @@ -74,8 +73,6 @@ public ChatController(
/// </summary>
/// <param name="kernel">Semantic kernel obtained through dependency injection.</param>
/// <param name="messageRelayHubContext">Message Hub that performs the real time relay service.</param>
/// <param name="planner">Planner to use to create function sequences.</param>
/// <param name="askConverter">Converter to use for converting Asks.</param>
/// <param name="chatSessionRepository">Repository of chat sessions.</param>
/// <param name="chatParticipantRepository">Repository of chat participants.</param>
/// <param name="authInfo">Auth info for the current request.</param>
Expand All @@ -100,78 +97,19 @@ public async Task<IActionResult> ChatAsync(
{
this._logger.LogDebug("Chat message received.");

return await this.HandleRequest(ChatFunctionName, kernel, messageRelayHubContext, chatSessionRepository, chatParticipantRepository, authInfo, ask, chatId.ToString());
}
string chatIdString = chatId.ToString();

/// <summary>
/// Invokes the chat function to process and/or execute plan.
/// </summary>
/// <param name="kernel">Semantic kernel obtained through dependency injection.</param>
/// <param name="messageRelayHubContext">Message Hub that performs the real time relay service.</param>
/// <param name="planner">Planner to use to create function sequences.</param>
/// <param name="askConverter">Converter to use for converting Asks.</param>
/// <param name="chatSessionRepository">Repository of chat sessions.</param>
/// <param name="chatParticipantRepository">Repository of chat participants.</param>
/// <param name="authInfo">Auth info for the current request.</param>
/// <param name="ask">Prompt along with its parameters.</param>
/// <param name="chatId">Chat ID.</param>
/// <returns>Results containing the response from the model.</returns>
[Route("chats/{chatId:guid}/plan")]
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status504GatewayTimeout)]
public async Task<IActionResult> ProcessPlanAsync(
[FromServices] Kernel kernel,
[FromServices] IHubContext<MessageRelayHub> messageRelayHubContext,
[FromServices] ChatSessionRepository chatSessionRepository,
[FromServices] ChatParticipantRepository chatParticipantRepository,
[FromServices] IAuthInfo authInfo,
[FromBody] ExecutePlanParameters ask,
[FromRoute] Guid chatId)
{
this._logger.LogDebug("plan request received.");

return await this.HandleRequest(ProcessPlanFunctionName, kernel, messageRelayHubContext, chatSessionRepository, chatParticipantRepository, authInfo, ask, chatId.ToString());
}

/// <summary>
/// Invokes given function of ChatPlugin.
/// </summary>
/// <param name="functionName">Name of the ChatPlugin function to invoke.</param>
/// <param name="kernel">Semantic kernel obtained through dependency injection.</param>
/// <param name="messageRelayHubContext">Message Hub that performs the real time relay service.</param>
/// <param name="planner">Planner to use to create function sequences.</param>
/// <param name="askConverter">Converter to use for converting Asks.</param>
/// <param name="chatSessionRepository">Repository of chat sessions.</param>
/// <param name="chatParticipantRepository">Repository of chat participants.</param>
/// <param name="authInfo">Auth info for the current request.</param>
/// <param name="ask">Prompt along with its parameters.</param>
/// <param name="chatId"Chat ID.</>
/// <returns>Results containing the response from the model.</returns>
private async Task<IActionResult> HandleRequest(
string functionName,
Kernel kernel,
IHubContext<MessageRelayHub> messageRelayHubContext,
ChatSessionRepository chatSessionRepository,
ChatParticipantRepository chatParticipantRepository,
IAuthInfo authInfo,
Ask ask,
string chatId)
{
// Put ask's variables in the context we will use.
var contextVariables = GetContextVariables(ask, authInfo, chatId);
var contextVariables = GetContextVariables(ask, authInfo, chatIdString);

// Verify that the chat exists and that the user has access to it.
ChatSession? chat = null;
if (!(await chatSessionRepository.TryFindByIdAsync(chatId, callback: c => chat = c)))
if (!(await chatSessionRepository.TryFindByIdAsync(chatIdString, callback: c => chat = c)))
{
return this.NotFound("Failed to find chat session for the chatId specified in variables.");
}

if (!(await chatParticipantRepository.IsUserInChatAsync(authInfo.UserId, chatId)))
if (!(await chatParticipantRepository.IsUserInChatAsync(authInfo.UserId, chatIdString)))
{
return this.Forbid("User does not have access to the chatId specified in variables.");
}
Expand All @@ -184,16 +122,7 @@ private async Task<IActionResult> HandleRequest(
await this.RegisterHostedFunctionsAsync(kernel, chat!.EnabledPlugins);

// Get the function to invoke
KernelFunction? function = null;
try
{
function = kernel.Plugins.GetFunction(ChatPluginName, functionName);
}
catch (KernelException ex)
{
this._logger.LogError("Failed to find {PluginName}/{FunctionName} on server: {Exception}", ChatPluginName, functionName, ex);
return this.NotFound($"Failed to find {ChatPluginName}/{functionName} on server");
}
KernelFunction? chatFunction = kernel.Plugins.GetFunction(ChatPluginName, ChatFunctionName);

// Run the function.
FunctionResult? result = null;
Expand All @@ -204,20 +133,21 @@ private async Task<IActionResult> HandleRequest(
? new CancellationTokenSource(TimeSpan.FromSeconds((double)this._serviceOptions.TimeoutLimitInS))
: null;

result = await kernel.InvokeAsync(function!, contextVariables, cts?.Token ?? default);
this._telemetryService.TrackPluginFunction(ChatPluginName, functionName, true);
result = await kernel.InvokeAsync(chatFunction!, contextVariables, cts?.Token ?? default);
this._telemetryService.TrackPluginFunction(ChatPluginName, ChatFunctionName, true);
}
catch (Exception ex)
{
if (ex is OperationCanceledException || ex.InnerException is OperationCanceledException)
{
// Log the timeout and return a 504 response
this._logger.LogError("The {FunctionName} operation timed out.", functionName);
return this.StatusCode(StatusCodes.Status504GatewayTimeout, $"The chat {functionName} timed out.");
this._logger.LogError("The {FunctionName} operation timed out.", ChatFunctionName);
return this.StatusCode(StatusCodes.Status504GatewayTimeout, $"The chat {ChatFunctionName} timed out.");
}

this._telemetryService.TrackPluginFunction(ChatPluginName, functionName, false);
throw ex;
this._telemetryService.TrackPluginFunction(ChatPluginName, ChatFunctionName, false);

throw;
}

AskResult chatAskResult = new()
Expand All @@ -227,7 +157,7 @@ private async Task<IActionResult> HandleRequest(
};

// Broadcast AskResult to all users
await messageRelayHubContext.Clients.Group(chatId).SendAsync(GeneratingResponseClientCall, chatId, null);
await messageRelayHubContext.Clients.Group(chatIdString).SendAsync(GeneratingResponseClientCall, chatIdString, null);

return this.Ok(chatAskResult);
}
Expand Down Expand Up @@ -317,7 +247,7 @@ await kernel.ImportPluginFromOpenApiAsync(
{
if (authHeaders.TryGetValue(plugin.AuthHeaderTag.ToUpperInvariant(), out string? PluginAuthValue))
{
// Register the ChatGPT plugin with the planner's kernel.
// Register the ChatGPT plugin with the kernel.
this._logger.LogInformation("Enabling {0} plugin.", plugin.NameForHuman);

// TODO: [Issue #44] Support other forms of auth. Currently, we only support user PAT or no auth.
Expand All @@ -334,7 +264,7 @@ await kernel.ImportPluginFromOpenAIAsync(
PluginUtils.GetPluginManifestUri(plugin.ManifestDomain),
new OpenAIFunctionExecutionParameters
{
HttpClient = this._httpClientFactory.CreateClient("Plugin"),
HttpClient = this._httpClientFactory.CreateClient(),
IgnoreNonCompliantErrors = true,
AuthCallback = requiresAuth ? authCallback : null
});
Expand Down Expand Up @@ -383,13 +313,13 @@ Task authCallback(HttpRequestMessage request, string _, OpenAIAuthenticationConf
return Task.CompletedTask;
}

// Register the ChatGPT plugin with the planner's kernel.
// Register the ChatGPT plugin with the kernel.
await kernel.ImportPluginFromOpenAIAsync(
PluginUtils.SanitizePluginName(plugin.Name),
PluginUtils.GetPluginManifestUri(plugin.ManifestDomain),
new OpenAIFunctionExecutionParameters
{
HttpClient = this._httpClientFactory.CreateClient("Plugin"),
HttpClient = this._httpClientFactory.CreateClient(),
IgnoreNonCompliantErrors = true,
AuthCallback = authCallback
});
Expand Down
2 changes: 1 addition & 1 deletion webapi/Controllers/PluginController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public async Task<IActionResult> GetPluginManifest([FromQuery] Uri manifestDomai
// Need to set the user agent to avoid 403s from some sites.
request.Headers.Add("User-Agent", "Semantic-Kernel");

using HttpClient client = this._httpClientFactory.CreateClient("Plugin");
using HttpClient client = this._httpClientFactory.CreateClient();
var response = await client.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
Expand Down
1 change: 0 additions & 1 deletion webapi/CopilotChatWebApi.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
<PackageReference Include="Microsoft.SemanticKernel.Abstractions" Version="1.4.0" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.AzureAISearch" Version="1.4.0-alpha" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Qdrant" Version="1.4.0-alpha" />
<PackageReference Include="Microsoft.SemanticKernel.Planners.Handlebars" Version="1.4.0-preview" />
<PackageReference Include="Microsoft.SemanticKernel.Plugins.Core" Version="1.4.0-alpha" />
<PackageReference Include="Microsoft.SemanticKernel.Plugins.Memory" Version="1.4.0-alpha" />
<PackageReference Include="Microsoft.SemanticKernel.Plugins.MsGraph" Version="1.4.0-alpha" />
Expand Down
10 changes: 0 additions & 10 deletions webapi/Models/Request/ExecutePlanParameters.cs

This file was deleted.

80 changes: 0 additions & 80 deletions webapi/Models/Response/ProposedPlan.cs

This file was deleted.

31 changes: 2 additions & 29 deletions webapi/Models/Storage/CopilotChatMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public CopilotChatMessage(
/// <param name="tokenUsage">Total token usage of response completion</param>
public static CopilotChatMessage CreateBotResponseMessage(string chatId, string content, string prompt, IEnumerable<CitationSource>? citations, IDictionary<string, int>? tokenUsage = null)
{
return new CopilotChatMessage("Bot", "Bot", chatId, content, prompt, citations, AuthorRoles.Bot, IsPlan(content) ? ChatMessageType.Plan : ChatMessageType.Message, tokenUsage);
return new CopilotChatMessage("Bot", "Bot", chatId, content, prompt, citations, AuthorRoles.Bot, ChatMessageType.Message, tokenUsage);
}

/// <summary>
Expand All @@ -185,28 +185,13 @@ public string ToFormattedString()
var messagePrefix = $"[{this.Timestamp.ToString("G", CultureInfo.CurrentCulture)}]";
switch (this.Type)
{
case ChatMessageType.Plan:
var planMessageContent = "proposed a plan.";
if (this.Content.Contains("proposedPlan\":", StringComparison.InvariantCultureIgnoreCase))
{
// Try to extract user intent from the plan proposal.
string pattern = ".*User Intent:User intent: (.*)(?=\"})";
Match match = Regex.Match(this.Content, pattern);
if (match.Success)
{
string userIntent = match.Groups[1].Value.Trim();
planMessageContent = $"proposed a plan to fulfill user intent: {userIntent}";
}
}

return $"{messagePrefix} {this.UserName} {planMessageContent}";

case ChatMessageType.Document:
var documentMessage = DocumentMessageContent.FromString(this.Content);
var documentMessageContent = (documentMessage != null) ? documentMessage.ToFormattedString() : "documents";

return $"{messagePrefix} {this.UserName} uploaded: {documentMessageContent}";

case ChatMessageType.Plan: // Fall through
case ChatMessageType.Message:
return $"{messagePrefix} {this.UserName} said: {this.Content}";

Expand Down Expand Up @@ -234,16 +219,4 @@ public override string ToString()
{
return JsonSerializer.Deserialize<CopilotChatMessage>(json, SerializerSettings);
}

/// <summary>
/// Check if the response is a Plan.
/// This is a copy of the `isPlan` function on the frontend.
/// </summary>
/// <param name="response">The response from the bot.</param>
/// <returns>True if the response represents Plan, false otherwise.</returns>
private static bool IsPlan(string response)
{
var planPrefix = "proposedPlan\":";
return response.Contains(planPrefix, StringComparison.InvariantCulture);
}
}
21 changes: 0 additions & 21 deletions webapi/Options/PromptsOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,6 @@ public class PromptsOptions
/// </summary>
internal double MemoriesResponseContextWeight { get; } = 0.6;

/// <summary>
/// Weight of information returned from planner (i.e., responses from OpenAPI functions).
/// Contextual prompt excludes all the system commands and user intent.
/// </summary>
internal double ExternalInformationContextWeight { get; } = 0.3;

/// <summary>
/// Upper bound of the relevancy score of a kernel memory to be included in the final prompt.
/// The actual relevancy score is determined by the memory balance.
Expand All @@ -61,21 +55,6 @@ public class PromptsOptions
[Required, NotEmptyOrWhitespace] public string SystemDescription { get; set; } = string.Empty;
[Required, NotEmptyOrWhitespace] public string SystemResponse { get; set; } = string.Empty;

/// <summary>
/// Context bot message for meta prompt when using external information acquired from a plan.
/// </summary>
[Required, NotEmptyOrWhitespace] public string ProposedPlanBotMessage { get; set; } = string.Empty;

/// <summary>
/// Supplement to help guide model in using data.
/// </summary>
[Required, NotEmptyOrWhitespace] public string PlanResultsDescription { get; set; } = string.Empty;

/// <summary>
/// Supplement to help guide model in using a response from StepwisePlanner.
/// </summary>
[Required, NotEmptyOrWhitespace] public string StepwisePlannerSupplement { get; set; } = string.Empty;

internal string[] SystemAudiencePromptComponents => new string[]
{
this.SystemAudience,
Expand Down
Loading

0 comments on commit 31a3e55

Please sign in to comment.