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

Updating SK to dotnet-0.24.230918.1-preview #369

Merged
merged 5 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 5 additions & 10 deletions webapi/Controllers/ChatController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Graph;
using Microsoft.IdentityModel.Tokens;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Diagnostics;
using Microsoft.SemanticKernel.Orchestration;
Expand Down Expand Up @@ -147,23 +146,19 @@ public ChatController(ILogger<ChatController> logger, ITelemetryService telemetr
: null;

result = await kernel.RunAsync(function!, contextVariables, cts?.Token ?? default);
this._telemetryService.TrackSkillFunction(ChatSkillName, ChatFunctionName, true);
}
finally
catch (Exception ex)
{
this._telemetryService.TrackSkillFunction(ChatSkillName, ChatFunctionName, (!result?.ErrorOccurred) ?? false);
}

if (result.ErrorOccurred)
{
if (result.LastException is OperationCanceledException || result.LastException?.InnerException is OperationCanceledException)
if (ex is OperationCanceledException || ex.InnerException is OperationCanceledException)
{
// Log the timeout and return a 504 response
this._logger.LogError("The chat operation timed out.");
return this.StatusCode(StatusCodes.Status504GatewayTimeout, "The chat operation timed out.");
}

var errorMessage = result.LastException!.Message.IsNullOrEmpty() ? result.LastException!.InnerException?.Message : result.LastException!.Message;
return this.BadRequest(errorMessage);
this._telemetryService.TrackSkillFunction(ChatSkillName, ChatFunctionName, false);
throw ex;
}

AskResult chatSkillAskResult = new()
Expand Down
17 changes: 9 additions & 8 deletions webapi/CopilotChatWebApi.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,19 @@
<ItemGroup>
<PackageReference Include="Azure.AI.FormRecognizer" Version="4.1.0" />
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.35.4" />
<PackageReference Include="Microsoft.SemanticKernel" Version="0.23.230906.2-preview" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.AI.OpenAI" Version="0.23.230906.2-preview" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Memory.AzureCognitiveSearch" Version="0.23.230906.2-preview" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Memory.Chroma" Version="0.23.230906.2-preview" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Memory.Qdrant" Version="0.23.230906.2-preview" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Memory.Postgres" Version="0.23.230906.2-preview" />
<PackageReference Include="Microsoft.SemanticKernel.Skills.MsGraph" Version="0.23.230906.2-preview" />
<PackageReference Include="Microsoft.SemanticKernel.Skills.OpenAPI" Version="0.23.230906.2-preview" />
<PackageReference Include="Microsoft.SemanticKernel" Version="0.24.230918.1-preview" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.AI.OpenAI" Version="0.24.230918.1-preview" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Memory.AzureCognitiveSearch" Version="0.24.230918.1-preview" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Memory.Chroma" Version="0.24.230918.1-preview" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Memory.Qdrant" Version="0.24.230918.1-preview" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Memory.Postgres" Version="0.24.230918.1-preview" />
<PackageReference Include="Microsoft.SemanticKernel.Skills.MsGraph" Version="0.24.230918.1-preview" />
<PackageReference Include="Microsoft.SemanticKernel.Skills.OpenAPI" Version="0.24.230918.1-preview" />
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.2.2" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.21.0" />
<PackageReference Include="Microsoft.Identity.Web" Version="2.13.4" />
<PackageReference Include="PdfPig" Version="0.1.8" />
<PackageReference Include="SharpToken" Version="1.2.12" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>

Expand Down
20 changes: 13 additions & 7 deletions webapi/Extensions/SemanticKernelExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
using Microsoft.SemanticKernel.Connectors.Memory.Qdrant;
using Microsoft.SemanticKernel.Diagnostics;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.Skills.Core;
using Npgsql;
using Pgvector.Npgsql;
Expand Down Expand Up @@ -117,15 +116,22 @@ public static IKernel RegisterChatSkill(this IKernel kernel, IServiceProvider sp
}

/// <summary>
/// Propagate exception from within semantic function
/// Invokes an asynchronous callback function and tags any exception that occurs with function name.
/// </summary>
public static void ThrowIfFailed(this SKContext context)
/// <typeparam name="T">The type of the result returned by the callback function.</typeparam>
/// <param name="callback">The asynchronous callback function to invoke.</param>
/// <param name="functionName">The name of the function that calls this method, for logging purposes.</param>
/// <returns>A task that represents the asynchronous operation and contains the result of the callback function.</returns>
public static async Task<T> SafeInvokeAsync<T>(Func<Task<T>> callback, string functionName)
{
if (context.ErrorOccurred)
try
{
var logger = context.LoggerFactory.CreateLogger(nameof(SKContext));
logger.LogError(context.LastException, "{0}", context.LastException?.Message);
throw context.LastException!;
teresaqhoang marked this conversation as resolved.
Show resolved Hide resolved
// Invoke the callback and await the result
return await callback();
}
catch (Exception ex)
{
throw new SKException($"{functionName} failed.", ex);
}
}

Expand Down
5 changes: 2 additions & 3 deletions webapi/Models/Response/BotResponsePrompt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ public class BotResponsePrompt
public ChatCompletionContextMessages MetaPromptTemplate { get; set; } = new();

public BotResponsePrompt(
string systemDescription,
string systemResponse,
string systemInstructions,
string audience,
string userIntent,
string chatMemories,
Expand All @@ -65,7 +64,7 @@ public class BotResponsePrompt
ChatCompletionContextMessages metaPromptTemplate
)
{
this.SystemPersona = string.Join("\n", systemDescription, systemResponse);
this.SystemPersona = systemInstructions;
this.Audience = audience;
this.UserIntent = userIntent;
this.PastMemories = string.Join("\n", chatMemories, documentMemories).Trim();
Expand Down
43 changes: 16 additions & 27 deletions webapi/Skills/ChatSkills/ChatSkill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,6 @@ public async Task<string> ExtractUserIntentAsync(SKContext context, Cancellation
// Get token usage from ChatCompletion result and add to context
TokenUtilities.GetFunctionTokenUsage(result, context, this._logger, "SystemIntentExtraction");

result.ThrowIfFailed();

return $"User intent: {result}";
}

Expand Down Expand Up @@ -209,8 +207,6 @@ public async Task<string> ExtractAudienceAsync(SKContext context, CancellationTo
// Get token usage from ChatCompletion result and add to context
TokenUtilities.GetFunctionTokenUsage(result, context, this._logger, "SystemAudienceExtraction");

result.ThrowIfFailed();

return $"List of participants: {result}";
}

Expand Down Expand Up @@ -400,16 +396,21 @@ private async Task<ChatMessage> GetChatResponseAsync(string chatId, string userI
var chatCompletion = this._kernel.GetService<IChatCompletion>();
var promptTemplate = chatCompletion.CreateNewChat(systemInstructions);

// Get the audience
await this.UpdateBotResponseStatusOnClientAsync(chatId, "Extracting audience", cancellationToken);
var audience = await this.GetAudienceAsync(chatContext, cancellationToken);
chatContext.ThrowIfFailed();
promptTemplate.AddSystemMessage(audience);
// Bypass audience extraction if Auth is disabled
var audience = string.Empty;
if (!PassThroughAuthenticationHandler.IsDefaultUser(userId))
{
// Get the audience
await this.UpdateBotResponseStatusOnClientAsync(chatId, "Extracting audience", cancellationToken);
audience = await SemanticKernelExtensions.SafeInvokeAsync(
() => this.GetAudienceAsync(chatContext, cancellationToken), nameof(GetAudienceAsync));
promptTemplate.AddSystemMessage(audience);
}

// Extract user intent from the conversation history.
await this.UpdateBotResponseStatusOnClientAsync(chatId, "Extracting user intent", cancellationToken);
var userIntent = await this.GetUserIntentAsync(chatContext, cancellationToken);
chatContext.ThrowIfFailed();
var userIntent = await SemanticKernelExtensions.SafeInvokeAsync(
() => this.GetUserIntentAsync(chatContext, cancellationToken), nameof(GetUserIntentAsync));
promptTemplate.AddSystemMessage(userIntent);

// Calculate the remaining token budget.
Expand All @@ -419,8 +420,8 @@ private async Task<ChatMessage> GetChatResponseAsync(string chatId, string userI
// Acquire external information from planner
await this.UpdateBotResponseStatusOnClientAsync(chatId, "Acquiring external information from planner", cancellationToken);
var externalInformationTokenLimit = (int)(remainingTokenBudget * this._promptOptions.ExternalInformationContextWeight);
var planResult = await this.AcquireExternalInformationAsync(chatContext, userIntent, externalInformationTokenLimit, cancellationToken);
chatContext.ThrowIfFailed();
var planResult = await SemanticKernelExtensions.SafeInvokeAsync(
() => this.AcquireExternalInformationAsync(chatContext, userIntent, externalInformationTokenLimit, cancellationToken), nameof(AcquireExternalInformationAsync));

// Extract additional details about planner execution in chat context
// TODO: [Issue #150, sk#2106] Accommodate different planner contexts once core team finishes work to return prompt and token usage.
Expand Down Expand Up @@ -480,7 +481,7 @@ private async Task<ChatMessage> GetChatResponseAsync(string chatId, string userI
promptTemplate.AddSystemMessage(planResult);
}

var promptView = new BotResponsePrompt(this._promptOptions.SystemDescription, this._promptOptions.SystemResponse, audience, userIntent, chatMemories, documentMemories, plannerDetails, chatHistory, promptTemplate);
var promptView = new BotResponsePrompt(systemInstructions, audience, userIntent, chatMemories, documentMemories, plannerDetails, chatHistory, promptTemplate);
chatContext.Variables.Set(TokenUtilities.GetFunctionKey(this._logger, "SystemMetaPrompt")!, TokenUtilities.GetContextMessagesTokenCount(promptTemplate).ToString(CultureInfo.CurrentCulture));

// Stream the response to the client
Expand Down Expand Up @@ -517,7 +518,6 @@ private async Task<ChatMessage> GetChatResponseAsync(string chatId, string userI
private async Task<string> GetAudienceAsync(SKContext context, CancellationToken cancellationToken)
{
SKContext audienceContext = context.Clone();

var audience = await this.ExtractAudienceAsync(audienceContext, cancellationToken);

// Copy token usage into original chat context
Expand All @@ -527,9 +527,6 @@ private async Task<string> GetAudienceAsync(SKContext context, CancellationToken
context.Variables.Set(functionKey, tokenUsage);
}

// Propagate the error
audienceContext.ThrowIfFailed();

return audience;
}

Expand All @@ -553,8 +550,6 @@ private async Task<string> GetUserIntentAsync(SKContext context, CancellationTok
{
context.Variables.Set(functionKey!, tokenUsage);
}

intentContext.ThrowIfFailed();
}

return userIntent;
Expand Down Expand Up @@ -598,13 +593,7 @@ private async Task<string> AcquireExternalInformationAsync(SKContext context, st
{
SKContext planContext = context.Clone();
planContext.Variables.Set("tokenLimit", tokenLimit.ToString(new NumberFormatInfo()));

var plan = await this._externalInformationSkill.AcquireExternalInformationAsync(userIntent, planContext, cancellationToken);

// Propagate the error
planContext.ThrowIfFailed();

return plan;
return await this._externalInformationSkill.AcquireExternalInformationAsync(userIntent, planContext, cancellationToken);
}

/// <summary>
Expand Down
38 changes: 25 additions & 13 deletions webapi/Skills/TokenUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel.AI.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.AI.OpenAI.AzureSdk;
using Microsoft.SemanticKernel.Connectors.AI.OpenAI.Tokenizers;
using Microsoft.SemanticKernel.Orchestration;
using ChatCompletionContextMessages = Microsoft.SemanticKernel.AI.ChatCompletion.ChatHistory;

Expand Down Expand Up @@ -69,27 +68,40 @@ public static class TokenUtilities
/// <returns> true if token usage is found in result context; otherwise, false.</returns>
internal static void GetFunctionTokenUsage(SKContext result, SKContext chatContext, ILogger logger, string? functionName = null)
{
var functionKey = GetFunctionKey(logger, functionName);
if (functionKey == null)
try
{
return;
}
var functionKey = GetFunctionKey(logger, functionName);
if (functionKey == null)
{
return;
}

if (result.ModelResults == null || result.ModelResults.Count == 0)
{
logger.LogError("Unable to determine token usage for {0}", functionKey);
return;
}

if (result.ModelResults == null || result.ModelResults.Count == 0)
var tokenUsage = result.ModelResults.First().GetResult<ChatModelResult>().Usage.TotalTokens;
chatContext.Variables.Set(functionKey!, tokenUsage.ToString(CultureInfo.InvariantCulture));
}
catch (Exception e)
{
logger.LogError("Unable to determine token usage for {0}", functionKey);
return;
logger.LogError(e, "Unable to determine token usage for {0}", functionName);
throw e;
}

var tokenUsage = result.ModelResults.First().GetResult<ChatModelResult>().Usage.TotalTokens;
chatContext.Variables.Set(functionKey!, tokenUsage.ToString(CultureInfo.InvariantCulture));
}

/// <summary>
/// Calculate the number of tokens in a string.
/// Calculate the number of tokens in a string using custom SharpToken token counter implementation with cl100k_base encoding.
/// </summary>
/// <param name="text">The string to calculate the number of tokens in.</param>
internal static int TokenCount(string text) => GPT3Tokenizer.Encode(text).Count;
internal static int TokenCount(string text)
{
var tokenizer = SharpToken.GptEncoding.GetEncoding("cl100k_base");
var tokens = tokenizer.Encode(text);
return tokens.Count;
}

/// <summary>
/// Rough token costing of ChatHistory's message object.
Expand Down
12 changes: 7 additions & 5 deletions webapp/src/libs/services/BaseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,17 @@ export class BaseService {
});

if (!response.ok) {
const responseText = await response.text();

if (response.status === 504) {
throw Object.assign(new Error('The request timed out. Please try sending your message again.'));
}

const errorMessage = `${response.status}: ${response.statusText}${
responseText ? ` => ${responseText}` : ''
}`;
const responseText = await response.text();
const responseDetails = responseText.split('--->');
TaoChenOSU marked this conversation as resolved.
Show resolved Hide resolved
const errorDetails =
responseDetails.length > 1
? `${responseDetails[0].trim()} ---> ${responseDetails[1].trim()}`
: responseDetails[0];
const errorMessage = `${response.status}: ${response.statusText}${errorDetails}`;

throw Object.assign(new Error(errorMessage));
}
Expand Down
Loading