diff --git a/samples/apps/copilot-chat-app/webapi/CopilotChat/Controllers/ChatController.cs b/samples/apps/copilot-chat-app/webapi/CopilotChat/Controllers/ChatController.cs
index d163d08c03..f13b4aaff4 100644
--- a/samples/apps/copilot-chat-app/webapi/CopilotChat/Controllers/ChatController.cs
+++ b/samples/apps/copilot-chat-app/webapi/CopilotChat/Controllers/ChatController.cs
@@ -22,6 +22,7 @@ using Microsoft.SemanticKernel.Skills.MsGraph;
using Microsoft.SemanticKernel.Skills.MsGraph.Connectors;
using Microsoft.SemanticKernel.Skills.MsGraph.Connectors.Client;
using Microsoft.SemanticKernel.Skills.OpenAPI.Authentication;
+using Microsoft.SemanticKernel.Skills.OpenAPI.Extensions;
using SemanticKernel.Service.CopilotChat.Hubs;
using SemanticKernel.Service.CopilotChat.Models;
using SemanticKernel.Service.CopilotChat.Skills.ChatSkills;
@@ -162,7 +163,10 @@ public class ChatController : ControllerBase, IDisposable
using HttpClient importHttpClient = new(retryHandler, false);
importHttpClient.DefaultRequestHeaders.Add("User-Agent", "Microsoft.CopilotChat");
await planner.Kernel.ImportChatGptPluginSkillFromUrlAsync("KlarnaShoppingSkill", new Uri("https://www.klarna.com/.well-known/ai-plugin.json"),
- importHttpClient);
+ new OpenApiSkillExecutionParameters
+ {
+ HttpClient = importHttpClient,
+ });
}
// GitHub
@@ -173,7 +177,10 @@ public class ChatController : ControllerBase, IDisposable
await planner.Kernel.ImportOpenApiSkillFromFileAsync(
skillName: "GitHubSkill",
filePath: Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "CopilotChat", "Skills", "OpenApiSkills/GitHubSkill/openapi.json"),
- authCallback: authenticationProvider.AuthenticateRequestAsync);
+ new OpenApiSkillExecutionParameters
+ {
+ AuthCallback = authenticationProvider.AuthenticateRequestAsync,
+ });
}
// Jira
@@ -186,8 +193,11 @@ public class ChatController : ControllerBase, IDisposable
await planner.Kernel.ImportOpenApiSkillFromFileAsync(
skillName: "JiraSkill",
filePath: Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "CopilotChat", "Skills", "OpenApiSkills/JiraSkill/openapi.json"),
- authCallback: authenticationProvider.AuthenticateRequestAsync,
- serverUrlOverride: hasServerUrlOverride ? new Uri(serverUrlOverride!) : null);
+ new OpenApiSkillExecutionParameters
+ {
+ AuthCallback = authenticationProvider.AuthenticateRequestAsync,
+ ServerUrlOverride = hasServerUrlOverride ? new Uri(serverUrlOverride!) : null,
+ });
}
// Microsoft Graph
diff --git a/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/ChatSkill.cs b/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/ChatSkill.cs
index 8cede79bb7..2cf2fd0c16 100644
--- a/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/ChatSkill.cs
+++ b/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/ChatSkill.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text.Json;
@@ -93,10 +94,9 @@ public class ChatSkill
/// Extract user intent from the conversation history.
///
/// The SKContext.
- [SKFunction("Extract user intent")]
- [SKFunctionName("ExtractUserIntent")]
- [SKFunctionContextParameter(Name = "chatId", Description = "Chat ID to extract history from")]
- [SKFunctionContextParameter(Name = "audience", Description = "The audience the chat bot is interacting with.")]
+ [SKFunction, Description("Extract user intent")]
+ [SKParameter("chatId", "Chat ID to extract history from")]
+ [SKParameter("audience", "The audience the chat bot is interacting with.")]
public async Task ExtractUserIntentAsync(SKContext context)
{
var tokenLimit = this._promptOptions.CompletionTokenLimit;
@@ -140,9 +140,8 @@ public class ChatSkill
/// Extract the list of participants from the conversation history.
/// Note that only those who have spoken will be included.
///
- [SKFunction("Extract audience list")]
- [SKFunctionName("ExtractAudience")]
- [SKFunctionContextParameter(Name = "chatId", Description = "Chat ID to extract history from")]
+ [SKFunction, Description("Extract audience list")]
+ [SKParameter("chatId", "Chat ID to extract history from")]
public async Task ExtractAudienceAsync(SKContext context)
{
var tokenLimit = this._promptOptions.CompletionTokenLimit;
@@ -184,15 +183,11 @@ public class ChatSkill
/// Extract chat history.
///
/// Contains the 'tokenLimit' controlling the length of the prompt.
- [SKFunction("Extract chat history")]
- [SKFunctionName("ExtractChatHistory")]
- [SKFunctionContextParameter(Name = "chatId", Description = "Chat ID to extract history from")]
- [SKFunctionContextParameter(Name = "tokenLimit", Description = "Maximum number of tokens")]
- public async Task ExtractChatHistoryAsync(SKContext context)
+ [SKFunction, Description("Extract chat history")]
+ public async Task ExtractChatHistoryAsync(
+ [Description("Chat ID to extract history from")] string chatId,
+ [Description("Maximum number of tokens")] int tokenLimit)
{
- var chatId = context["chatId"];
- var tokenLimit = int.Parse(context["tokenLimit"], new NumberFormatInfo());
-
var messages = await this._chatMessageRepository.FindByChatIdAsync(chatId);
var sortedMessages = messages.OrderByDescending(m => m.Timestamp);
@@ -242,36 +237,19 @@ public class ChatSkill
/// messages to memory, and fill in the necessary context variables for completing the
/// prompt that will be rendered by the template engine.
///
- ///
- /// Contains the 'tokenLimit' and the 'contextTokenLimit' controlling the length of the prompt.
- [SKFunction("Get chat response")]
- [SKFunctionName("Chat")]
- [SKFunctionInput(Description = "The new message")]
- [SKFunctionContextParameter(Name = "userId", Description = "Unique and persistent identifier for the user")]
- [SKFunctionContextParameter(Name = "userName", Description = "Name of the user")]
- [SKFunctionContextParameter(Name = "chatId", Description = "Unique and persistent identifier for the chat")]
- [SKFunctionContextParameter(Name = "proposedPlan", Description = "Previously proposed plan that is approved")]
- [SKFunctionContextParameter(Name = "messageType", Description = "Type of the message")]
- [SKFunctionContextParameter(Name = "responseMessageId", Description = "ID of the response message for planner")]
- public async Task ChatAsync(string message, SKContext context)
+ [SKFunction, Description("Get chat response")]
+ public async Task ChatAsync(
+ [Description("The new message")] string message,
+ [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("Type of the message")] string messageType,
+ [Description("Previously proposed plan that is approved"), DefaultValue(null), SKName("proposedPlan")] string? planJson,
+ [Description("ID of the response message for planner"), DefaultValue(null), SKName("responseMessageId")] string? messageId,
+ SKContext context)
{
- // TODO: check if user has access to the chat
- var userId = context["userId"];
- var userName = context["userName"];
- var chatId = context["chatId"];
- var messageType = context["messageType"];
-
// Save this new message to memory such that subsequent chat responses can use it
- try
- {
- await this.SaveNewMessageAsync(message, userId, userName, chatId, messageType);
- }
- catch (Exception ex) when (!ex.IsCriticalException())
- {
- context.Log.LogError("Unable to save new message: {0}", ex.Message);
- context.Fail($"Unable to save new message: {ex.Message}", ex);
- return context;
- }
+ await this.SaveNewMessageAsync(message, userId, userName, chatId, messageType);
// Clone the context to avoid modifying the original context variables.
var chatContext = Utilities.CopyContextWithVariablesClone(context);
@@ -280,16 +258,15 @@ public class ChatSkill
// Check if plan exists in ask's context variables.
// If plan was returned at this point, that means it was approved or cancelled.
// Update the response previously saved in chat history with state
- if (context.Variables.TryGetValue("proposedPlan", out string? planJson)
- && !string.IsNullOrWhiteSpace(planJson)
- && context.Variables.TryGetValue("responseMessageId", out string? messageId))
+ if (!string.IsNullOrWhiteSpace(planJson) &&
+ !string.IsNullOrEmpty(messageId))
{
await this.UpdateResponseAsync(planJson, messageId);
}
var response = chatContext.Variables.ContainsKey("userCancelledPlan")
? "I am sorry the plan did not meet your goals."
- : await this.GetChatResponseAsync(chatContext);
+ : await this.GetChatResponseAsync(chatId, chatContext);
if (chatContext.ErrorOccurred)
{
@@ -299,24 +276,14 @@ public class ChatSkill
// Retrieve the prompt used to generate the response
// and return it to the caller via the context variables.
- var prompt = chatContext.Variables.ContainsKey("prompt")
- ? chatContext.Variables["prompt"]
- : string.Empty;
+ chatContext.Variables.TryGetValue("prompt", out string? prompt);
+ prompt ??= string.Empty;
context.Variables.Set("prompt", prompt);
// Save this response to memory such that subsequent chat responses can use it
- try
- {
- ChatMessage botMessage = await this.SaveNewResponseAsync(response, prompt, chatId);
- context.Variables.Set("messageId", botMessage.Id);
- context.Variables.Set("messageType", ((int)botMessage.Type).ToString(CultureInfo.InvariantCulture));
- }
- catch (Exception ex) when (!ex.IsCriticalException())
- {
- context.Log.LogError("Unable to save new response: {0}", ex.Message);
- context.Fail($"Unable to save new response: {ex.Message}");
- return context;
- }
+ ChatMessage botMessage = await this.SaveNewResponseAsync(response, prompt, chatId);
+ context.Variables.Set("messageId", botMessage.Id);
+ context.Variables.Set("messageType", ((int)botMessage.Type).ToString(CultureInfo.InvariantCulture));
// Extract semantic chat memory
await SemanticChatMemoryExtractor.ExtractSemanticChatMemoryAsync(
@@ -336,7 +303,7 @@ public class ChatSkill
///
/// The SKContext.
/// A response from the model.
- private async Task GetChatResponseAsync(SKContext chatContext)
+ private async Task GetChatResponseAsync(string chatId, SKContext chatContext)
{
// 0. Get the audience
var audience = await this.GetAudienceAsync(chatContext);
@@ -371,7 +338,7 @@ public class ChatSkill
// 4. Query relevant semantic memories
var chatMemoriesTokenLimit = (int)(remainingToken * this._promptOptions.MemoriesResponseContextWeight);
- var chatMemories = await this.QueryChatMemoriesAsync(chatContext, userIntent, chatMemoriesTokenLimit);
+ var chatMemories = await this._semanticChatMemorySkill.QueryMemoriesAsync(userIntent, chatId, chatMemoriesTokenLimit, chatContext.Memory);
if (chatContext.ErrorOccurred)
{
return string.Empty;
@@ -379,7 +346,7 @@ public class ChatSkill
// 5. Query relevant document memories
var documentContextTokenLimit = (int)(remainingToken * this._promptOptions.DocumentContextWeight);
- var documentMemories = await this.QueryDocumentsAsync(chatContext, userIntent, documentContextTokenLimit);
+ var documentMemories = await this._documentMemorySkill.QueryDocumentsAsync(userIntent, chatId, documentContextTokenLimit, chatContext.Memory);
if (chatContext.ErrorOccurred)
{
return string.Empty;
@@ -391,7 +358,7 @@ public class ChatSkill
var chatContextTextTokenCount = remainingToken - Utilities.TokenCount(chatContextText);
if (chatContextTextTokenCount > 0)
{
- var chatHistory = await this.GetChatHistoryAsync(chatContext, chatContextTextTokenCount);
+ var chatHistory = await this.ExtractChatHistoryAsync(chatId, chatContextTextTokenCount);
if (chatContext.ErrorOccurred)
{
return string.Empty;
@@ -490,62 +457,13 @@ public class ChatSkill
return userIntent;
}
- ///
- /// Helper function create the correct context variables to
- /// extract chat history messages from the conversation history.
- ///
- private Task GetChatHistoryAsync(SKContext context, int tokenLimit)
- {
- var contextVariables = new ContextVariables();
- contextVariables.Set("chatId", context["chatId"]);
- contextVariables.Set("tokenLimit", tokenLimit.ToString(new NumberFormatInfo()));
-
- var chatHistoryContext = new SKContext(
- contextVariables,
- context.Memory,
- context.Skills,
- context.Log,
- context.CancellationToken
- );
-
- var chatHistory = this.ExtractChatHistoryAsync(chatHistoryContext);
-
- // Propagate the error
- if (chatHistoryContext.ErrorOccurred)
- {
- context.Fail(chatHistoryContext.LastErrorDescription);
- }
-
- return chatHistory;
- }
-
///
/// Helper function create the correct context variables to
/// query chat memories from the chat memory store.
///
private Task QueryChatMemoriesAsync(SKContext context, string userIntent, int tokenLimit)
{
- var contextVariables = new ContextVariables();
- contextVariables.Set("chatId", context["chatId"]);
- contextVariables.Set("tokenLimit", tokenLimit.ToString(new NumberFormatInfo()));
-
- var chatMemoriesContext = new SKContext(
- contextVariables,
- context.Memory,
- context.Skills,
- context.Log,
- context.CancellationToken
- );
-
- var chatMemories = this._semanticChatMemorySkill.QueryMemoriesAsync(userIntent, chatMemoriesContext);
-
- // Propagate the error
- if (chatMemoriesContext.ErrorOccurred)
- {
- context.Fail(chatMemoriesContext.LastErrorDescription);
- }
-
- return chatMemories;
+ return this._semanticChatMemorySkill.QueryMemoriesAsync(userIntent, context["chatId"], tokenLimit, context.Memory);
}
///
@@ -554,27 +472,7 @@ public class ChatSkill
///
private Task QueryDocumentsAsync(SKContext context, string userIntent, int tokenLimit)
{
- var contextVariables = new ContextVariables();
- contextVariables.Set("chatId", context["chatId"]);
- contextVariables.Set("tokenLimit", tokenLimit.ToString(new NumberFormatInfo()));
-
- var documentMemoriesContext = new SKContext(
- contextVariables,
- context.Memory,
- context.Skills,
- context.Log,
- context.CancellationToken
- );
-
- var documentMemories = this._documentMemorySkill.QueryDocumentsAsync(userIntent, documentMemoriesContext);
-
- // Propagate the error
- if (documentMemoriesContext.ErrorOccurred)
- {
- context.Fail(documentMemoriesContext.LastErrorDescription);
- }
-
- return documentMemories;
+ return this._documentMemorySkill.QueryDocumentsAsync(userIntent, context["chatId"], tokenLimit, context.Memory);
}
///
diff --git a/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/DocumentMemorySkill.cs b/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/DocumentMemorySkill.cs
index 01475eedbc..ac9f879380 100644
--- a/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/DocumentMemorySkill.cs
+++ b/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/DocumentMemorySkill.cs
@@ -1,12 +1,11 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
-using System.Globalization;
+using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Microsoft.SemanticKernel.Memory;
-using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.SkillDefinition;
using SemanticKernel.Service.CopilotChat.Options;
@@ -43,15 +42,13 @@ public class DocumentMemorySkill
///
/// Query to match.
/// The SkContext.
- [SKFunction("Query documents in the memory given a user message")]
- [SKFunctionName("QueryDocuments")]
- [SKFunctionInput(Description = "Query to match.")]
- [SKFunctionContextParameter(Name = "chatId", Description = "ID of the chat that owns the documents")]
- [SKFunctionContextParameter(Name = "tokenLimit", Description = "Maximum number of tokens")]
- public async Task QueryDocumentsAsync(string query, SKContext context)
+ [SKFunction, Description("Query documents in the memory given a user message")]
+ public async Task QueryDocumentsAsync(
+ [Description("Query to match.")] string query,
+ [Description("ID of the chat that owns the documents")] string chatId,
+ [Description("Maximum number of tokens")] int tokenLimit,
+ ISemanticTextMemory textMemory)
{
- string chatId = context.Variables["chatId"];
- int tokenLimit = int.Parse(context.Variables["tokenLimit"], new NumberFormatInfo());
var remainingToken = tokenLimit;
// Search for relevant document snippets.
@@ -64,7 +61,7 @@ public class DocumentMemorySkill
List relevantMemories = new();
foreach (var documentCollection in documentCollections)
{
- var results = context.Memory.SearchAsync(
+ var results = textMemory.SearchAsync(
documentCollection,
query,
limit: 100,
diff --git a/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/ExternalInformationSkill.cs b/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/ExternalInformationSkill.cs
index 37a9e6f3f9..9fea94f622 100644
--- a/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/ExternalInformationSkill.cs
+++ b/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/ExternalInformationSkill.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text.Json;
@@ -64,12 +65,12 @@ public class ExternalInformationSkill
///
/// Extract relevant additional knowledge using a planner.
///
- [SKFunction("Acquire external information")]
- [SKFunctionName("AcquireExternalInformation")]
- [SKFunctionInput(Description = "The intent to whether external information is needed")]
- [SKFunctionContextParameter(Name = "tokenLimit", Description = "Maximum number of tokens")]
- [SKFunctionContextParameter(Name = "proposedPlan", Description = "Previously proposed plan that is approved")]
- public async Task AcquireExternalInformationAsync(string userIntent, SKContext context)
+ [SKFunction, Description("Acquire external information")]
+ [SKParameter("tokenLimit", "Maximum number of tokens")]
+ [SKParameter("proposedPlan", "Previously proposed plan that is approved")]
+ public async Task AcquireExternalInformationAsync(
+ [Description("The intent to whether external information is needed")] string userIntent,
+ SKContext context)
{
FunctionsView functions = this._planner.Kernel.Skills.GetFunctionsView(true, true);
if (functions.NativeFunctions.IsEmpty && functions.SemanticFunctions.IsEmpty)
diff --git a/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/SemanticChatMemorySkill.cs b/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/SemanticChatMemorySkill.cs
index 6358a0cb18..b9d7efaeb1 100644
--- a/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/SemanticChatMemorySkill.cs
+++ b/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/SemanticChatMemorySkill.cs
@@ -1,12 +1,11 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
-using System.Globalization;
+using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Microsoft.SemanticKernel.Memory;
-using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.SkillDefinition;
using SemanticKernel.Service.CopilotChat.Options;
@@ -37,22 +36,20 @@ public class SemanticChatMemorySkill
/// Query to match.
/// The SKContext
/// A string containing the relevant memories.
- [SKFunction("Query chat memories")]
- [SKFunctionName("QueryMemories")]
- [SKFunctionInput(Description = "Query to match.")]
- [SKFunctionContextParameter(Name = "chatId", Description = "Chat ID to query history from")]
- [SKFunctionContextParameter(Name = "tokenLimit", Description = "Maximum number of tokens")]
- public async Task QueryMemoriesAsync(string query, SKContext context)
+ [SKFunction, Description("Query chat memories")]
+ public async Task QueryMemoriesAsync(
+ [Description("Query to match.")] string query,
+ [Description("Chat ID to query history from")] string chatId,
+ [Description("Maximum number of tokens")] int tokenLimit,
+ ISemanticTextMemory textMemory)
{
- var chatId = context["chatId"];
- var tokenLimit = int.Parse(context["tokenLimit"], new NumberFormatInfo());
var remainingToken = tokenLimit;
// Search for relevant memories.
List relevantMemories = new();
foreach (var memoryName in this._promptOptions.MemoryMap.Keys)
{
- var results = context.Memory.SearchAsync(
+ var results = textMemory.SearchAsync(
SemanticChatMemoryExtractor.MemoryCollectionName(chatId, memoryName),
query,
limit: 100,
diff --git a/samples/apps/copilot-chat-app/webapi/CopilotChatWebApi.csproj b/samples/apps/copilot-chat-app/webapi/CopilotChatWebApi.csproj
index dea8e5e98e..95f4dd6f79 100644
--- a/samples/apps/copilot-chat-app/webapi/CopilotChatWebApi.csproj
+++ b/samples/apps/copilot-chat-app/webapi/CopilotChatWebApi.csproj
@@ -11,13 +11,13 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs b/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs
index 703ba93743..5432bccfde 100644
--- a/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs
+++ b/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs
@@ -12,8 +12,8 @@ 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.CoreSkills;
using Microsoft.SemanticKernel.Memory;
+using Microsoft.SemanticKernel.Skills.Core;
using Microsoft.SemanticKernel.TemplateEngine;
using SemanticKernel.Service.CopilotChat.Extensions;
using SemanticKernel.Service.CopilotChat.Storage;