Skip to content

Commit

Permalink
Alavil/support center changes (microsoft#97)
Browse files Browse the repository at this point in the history
* Fix in post provision hook.

* Removed "azd env get-values" command execution.

* Some fixes to agents logic.

* Added SupportCenterAgentExtensions.

* Pushed new screenshot.

---------

Co-authored-by: Alessandro Avila <[email protected]>
  • Loading branch information
alessandro-avila and alessandro-avila authored Jul 15, 2024
1 parent 62b1d9f commit c6ba273
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 72 deletions.
Binary file modified samples/support-center/docs/media/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ internal static KernelSettings LoadSettings()
}

Console.WriteLine($"Semantic kernel settings '{DefaultConfigFile}' not found, attempting to load configuration from user secrets.");
Console.WriteLine("Current process path: " + Directory.GetCurrentDirectory());

return FromUserSecrets();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using SupportCenter.Events;
using SupportCenter.Extensions;
using SupportCenter.Options;
using SupportCenter.SignalRHub;

namespace SupportCenter.Agents;
[ImplicitStreamSubscription(Consts.OrleansNamespace)]
Expand All @@ -26,10 +27,6 @@ public Conversation([PersistentState("state", "messages")] IPersistentState<Agen

public async override Task HandleEvent(Event item)
{
string? messageId = item.Data.GetValueOrDefault<string>("id");
string? userId = item.Data.GetValueOrDefault<string>("userId");
string? userMessage = item.Data.GetValueOrDefault<string>("userMessage");

switch (item.Type)
{
case nameof(EventType.UserConnected):
Expand All @@ -41,11 +38,16 @@ public async override Task HandleEvent(Event item)
}
break;
case nameof(EventType.ConversationRequested):
_logger.LogInformation("[{Agent}]:{EventType}:{EventData}", nameof(Conversation), nameof(EventType.ConversationRequested), userMessage);
var context = new KernelArguments { ["input"] = AppendChatHistory(userMessage) };
string? userId = item.Data.GetValueOrDefault<string>("userId");
string? message = item.Data.GetValueOrDefault<string>("message");

string? conversationId = SignalRConnectionsDB.GetConversationId(userId);
string id = $"{userId}/{conversationId}";
_logger.LogInformation("[{Agent}]:[{EventType}]:[{EventData}]", nameof(Conversation), nameof(EventType.ConversationRequested), message);
var context = new KernelArguments { ["input"] = AppendChatHistory(message) };
string answer = await CallFunction(ConversationPrompts.Answer, context);

await SendAnswerEvent(messageId, userId, answer);
await SendAnswerEvent(id, userId, answer);
break;

default:
Expand All @@ -55,9 +57,9 @@ public async override Task HandleEvent(Event item)

private async Task SendAnswerEvent(string id, string userId, string message)
{
await PublishEvent(Consts.OrleansNamespace, this.GetPrimaryKeyString(), new Event
await PublishEvent(Consts.OrleansNamespace, id, new Event
{
Type = nameof(EventType.QnARetrieved),
Type = nameof(EventType.ConversationRetrieved),
Data = new Dictionary<string, string> {
{ nameof(id), id },
{ nameof(userId), userId },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using SupportCenter.Events;
using SupportCenter.Extensions;
using SupportCenter.Options;
using SupportCenter.SignalRHub;

namespace SupportCenter.Agents;

Expand Down Expand Up @@ -37,34 +36,34 @@ public CustomerInfo(

public async override Task HandleEvent(Event item)
{
string? userId = item.Data.GetValueOrDefault<string>("userId");
string? userMessage = item.Data.GetValueOrDefault<string>("userMessage");
string? conversationId = SignalRConnectionsDB.GetConversationId(userId);
string id = $"{userId}/{conversationId}";

switch (item.Type)
{
case nameof(EventType.UserNewConversation):
// The user started a new conversation.
_state.State.History.Clear();
break;
case nameof(EventType.CustomerInfoRequested):
_logger.LogInformation("[{Agent}]:{EventType}:{EventData}", nameof(CustomerInfo), item.Type, item.Data);
var ssc = item.GetAgentData();
string? userId = ssc.UserId;
string? message = ssc.UserMessage;
string? id = ssc.Id;

_logger.LogInformation("[{Agent}]:[{EventType}]:[{EventData}]", nameof(CustomerInfo), item.Type, item.Data);
await PublishEvent(Namespace, id, new Event
{
Type = nameof(EventType.CustomerInfoNotification),
Data = new Dictionary<string, string>
{
{ nameof(userId), userId },
{ "message", "I'm working on the user's request..." }
{ nameof(message), "I'm working on the user's request..." }
}
});

// Get the customer info via the planners.
var prompt = CustomerInfoPrompts.GetCustomerInfo
.Replace("{{$userId}}", userId)
.Replace("{{$userMessage}}", userMessage)
.Replace("{{$history}}", AppendChatHistory(userMessage));
.Replace("{{$userMessage}}", message)
.Replace("{{$history}}", AppendChatHistory(message));

#pragma warning disable SKEXP0060 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
// FunctionCallingStepwisePlanner
Expand All @@ -73,13 +72,15 @@ public async override Task HandleEvent(Event item)
MaxIterations = 10,
});
var result = await planner.ExecuteAsync(_kernel, prompt);
_logger.LogInformation("[{Agent}]:[{EventType}]:[{EventData}]", nameof(CustomerInfo), item.Type, result.FinalAnswer);

await PublishEvent(Namespace, id, new Event
{
Type = nameof(EventType.CustomerInfoRetrieved),
Data = new Dictionary<string, string>
{
{ nameof(userId), userId },
{ "message", result.FinalAnswer }
{ nameof(message), result.FinalAnswer }
}
});

Expand Down
51 changes: 27 additions & 24 deletions samples/support-center/src/backend/Agents/Dispatcher/Dispatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@
using SupportCenter.Events;
using SupportCenter.Extensions;
using SupportCenter.Options;
using SupportCenter.SignalRHub;
using System.Reflection;

namespace SupportCenter.Agents;

[ImplicitStreamSubscription(Consts.OrleansNamespace)]
[DispatcherChoice("QnA", "The customer is asking a question.", EventType.QnARequested)]
[DispatcherChoice("QnA", "The customer is asking a question related to internal Contoso knowledge base.", EventType.QnARequested)]
[DispatcherChoice("Discount", "The customer is asking for a discount about a product or service.", EventType.DiscountRequested)]
[DispatcherChoice("Invoice", "The customer is asking for an invoice.", EventType.InvoiceRequested)]
[DispatcherChoice("CustomerInfo", "The customer is asking for reading or updating his or her personal data.", EventType.CustomerInfoRequested)]
Expand All @@ -35,63 +34,67 @@ public Dispatcher(

public async override Task HandleEvent(Event item)
{
_logger.LogInformation("[{Agent}]:{EventType}:{EventData}", nameof(Dispatcher), item.Type, item.Data);
_logger.LogInformation("[{Agent}]:[{EventType}]:[{EventData}]", nameof(Dispatcher), item.Type, item.Data);

string? userId = item.Data.GetValueOrDefault<string>("userId");
string? userMessage = item.Data.GetValueOrDefault<string>("userMessage");
if (userId == null || userMessage == null)
var ssc = item.GetAgentData();
string? userId = ssc.UserId;
string? message = ssc.UserMessage;
string? id = ssc.Id;
string? intent;

_logger.LogInformation($"userId: {userId}, message: {message}");
if (userId == null || message == null)
{
_logger.LogWarning("[{Agent}]:{EventType}:{EventData}. Input is missing.", nameof(Dispatcher), item.Type, item.Data);
_logger.LogWarning("[{Agent}]:[{EventType}]:[{EventData}]. Input is missing.", nameof(Dispatcher), item.Type, item.Data);
return;
}

string? conversationId = SignalRConnectionsDB.GetConversationId(userId);
string id = $"{userId}/{conversationId}";
string? intent;


switch (item.Type)
{
case nameof(EventType.UserConnected):
// The user reconnected, let's send the last message if we have one.
string? lastMessage = _state.State.History.LastOrDefault()?.Message;
if (lastMessage == null)
{
_logger.LogInformation("[{Agent}]:{EventType}:{EventData}. Last message is missing.", nameof(Dispatcher), item.Type, item.Data);
_logger.LogInformation("[{Agent}]:[{EventType}]:[{EventData}]. Last message is missing.", nameof(Dispatcher), item.Type, item.Data);
return;
}
intent = (await ExtractIntentAsync(lastMessage))?.Trim(' ', '\"', '.') ?? string.Empty;
await SendDispatcherEvent(id, userId, intent, lastMessage);
break;

case nameof(EventType.UserNewConversation):
// The user started a new conversation.
_state.State.History.Clear();
break;

case nameof(EventType.UserChatInput):
intent = (await ExtractIntentAsync(userMessage))?.Trim(' ', '\"', '.') ?? string.Empty;
intent = (await ExtractIntentAsync(message))?.Trim(' ', '\"', '.') ?? string.Empty;
await PublishEvent(Namespace, id, new Event
{
Type = nameof(EventType.DispatcherNotification),
Data = new Dictionary<string, string>
{
{ nameof(userId), userId },
{ "message", $"The user request has been dispatched to the '{intent}' agent." }
{ nameof(message), $"The user request has been dispatched to the '{intent}' agent." }
}
});

await SendDispatcherEvent(id, userId, intent, userMessage);
await SendDispatcherEvent(id, userId, intent, message);
break;

case nameof(EventType.QnARetrieved):
case nameof(EventType.DiscountRetrieved):
case nameof(EventType.InvoiceRetrieved):
case nameof(EventType.CustomerInfoRetrieved):
var message = item.Data.GetValueOrDefault<string>("message");
if (message == null)
case nameof(EventType.ConversationRetrieved):
var answer = item.Data.GetValueOrDefault<string>("message");
if (answer == null)
{
_logger.LogWarning("[{Agent}]:{EventType}:{EventData}. Message is missing.", nameof(Dispatcher), item.Type, item.Data);
_logger.LogWarning("[{Agent}]:[{EventType}]:[{EventData}]. Answer from agent is missing.", nameof(Dispatcher), item.Type, item.Data);
return;
}
_logger.LogInformation("[{Agent}]:{EventType}:{EventData}", nameof(Dispatcher), item.Type, message);
AddToHistory(message, ChatUserType.Agent);
_logger.LogInformation("[{Agent}]:[{EventType}]:[{EventData}]", nameof(Dispatcher), item.Type, answer);
AddToHistory(answer, ChatUserType.Agent);
break;
default:
break;
Expand All @@ -116,7 +119,7 @@ private string GetAndSerializeChoices()
return string.Join("\n", choices.Select(c => $"- {c.Name}: {c.Description}")); ;
}

private async Task SendDispatcherEvent(string id, string userId, string intent, string userMessage)
private async Task SendDispatcherEvent(string id, string userId, string intent, string message)
{
var type = this.GetType()
.GetCustomAttributes<DispatcherChoice>()
Expand All @@ -129,7 +132,7 @@ private async Task SendDispatcherEvent(string id, string userId, string intent,
Data = new Dictionary<string, string>
{
{ nameof(userId), userId },
{ nameof(userMessage), userMessage }
{ nameof(message), message }
}
});
}
Expand Down
34 changes: 23 additions & 11 deletions samples/support-center/src/backend/Agents/Invoice/Invoice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.SemanticKernel.Memory;
using Orleans.Runtime;
using SupportCenter.Events;
using SupportCenter.Extensions;
using SupportCenter.Options;

namespace SupportCenter.Agents;
Expand All @@ -26,35 +27,46 @@ public Invoice([PersistentState("state", "messages")] IPersistentState<AgentStat

public async override Task HandleEvent(Event item)
{
var ssc = item.GetAgentData();
string? userId = ssc.UserId;
string? message = ssc.UserMessage;
string? id = ssc.Id;

_logger.LogInformation($"userId: {userId}, message: {message}");
if (userId == null || message == null)
{
_logger.LogWarning("[{Agent}]:[{EventType}]:[{EventData}]. Input is missing.", nameof(Dispatcher), item.Type, item.Data);
return;
}

switch (item.Type)
{
case nameof(EventType.InvoiceRequested):
{
var userId = item.Data["userId"];
var userMessage = item.Data["userMessage"];

await SendAnswerEvent($"Please wait while I look up the details for invoice...", userId);
_logger.LogInformation("[{Agent}]:{EventType}:{EventData}", nameof(Invoice), nameof(EventType.InvoiceRequested), userMessage);
await SendAnswerEvent(id, userId, $"Please wait while I look up the details for invoice...");
_logger.LogInformation("[{Agent}]:[{EventType}]:[{EventData}]", nameof(Invoice), nameof(EventType.InvoiceRequested), message);

var querycontext = new KernelArguments { ["input"] = AppendChatHistory(userMessage) };
var querycontext = new KernelArguments { ["input"] = AppendChatHistory(message) };
var instruction = "Consider the following knowledge:!invoices!";
var enhancedContext = await AddKnowledge(instruction, "invoices", querycontext);
string answer = await CallFunction(InvoicePrompts.InvoiceRequest, enhancedContext);
await SendAnswerEvent(answer, userId);
await SendAnswerEvent(id, userId, answer);
break;
}
default:
break;
}
}
private async Task SendAnswerEvent(string message, string userId)

private async Task SendAnswerEvent(string id, string userId, string message)
{
await PublishEvent(Consts.OrleansNamespace, this.GetPrimaryKeyString(), new Event
await PublishEvent(Namespace, id, new Event
{
Type = nameof(EventType.InvoiceRetrieved),
Data = new Dictionary<string, string> {
Data = new Dictionary<string, string>
{
{ nameof(userId), userId },
{ nameof(message), message }
{ nameof(message), message }
}
});
}
Expand Down
27 changes: 17 additions & 10 deletions samples/support-center/src/backend/Agents/QnA/QnA.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ public QnA([PersistentState("state", "messages")] IPersistentState<AgentState<Qn

public async override Task HandleEvent(Event item)
{
string? messageId = item.Data.GetValueOrDefault<string>("id");
string? userId = item.Data.GetValueOrDefault<string>("userId");
string? userMessage = item.Data.GetValueOrDefault<string>("userMessage");

switch (item.Type)
{
case nameof(EventType.UserConnected):
Expand All @@ -41,15 +37,27 @@ public async override Task HandleEvent(Event item)
}
break;
case nameof(EventType.QnARequested):
_logger.LogInformation("[{Agent}]:{EventType}:{EventData}", nameof(QnA), nameof(EventType.QnARequested), userMessage);
await SendAnswerEvent(messageId, userId, $"Please wait while I look in the documents for answers to your question...");
var ssc = item.GetAgentData();
string? userId = ssc.UserId;
string? message = ssc.UserMessage;
string? id = ssc.Id;

_logger.LogInformation($"userId: {userId}, message: {message}");
if (userId == null || message == null)
{
_logger.LogWarning("[{Agent}]:[{EventType}]:[{EventData}]. Input is missing.", nameof(Dispatcher), item.Type, item.Data);
return;
}

_logger.LogInformation("[{Agent}]:[{EventType}]:[{EventData}]", nameof(QnA), nameof(EventType.QnARequested), message);
await SendAnswerEvent(id, userId, $"Please wait while I look in the documents for answers to your question...");

var context = new KernelArguments { ["input"] = AppendChatHistory(userMessage) };
var context = new KernelArguments { ["input"] = AppendChatHistory(message) };
var instruction = "Consider the following knowledge:!vfcon106047!";
var enhancedContext = await AddKnowledge(instruction, "vfcon106047", context);
string answer = await CallFunction(QnAPrompts.Answer, enhancedContext);

await SendAnswerEvent(messageId, userId, answer);
await SendAnswerEvent(id, userId, answer);
break;

default:
Expand All @@ -59,11 +67,10 @@ public async override Task HandleEvent(Event item)

private async Task SendAnswerEvent(string id, string userId, string message)
{
await PublishEvent(Consts.OrleansNamespace, this.GetPrimaryKeyString(), new Event
await PublishEvent(Consts.OrleansNamespace, id, new Event
{
Type = nameof(EventType.QnARetrieved),
Data = new Dictionary<string, string> {
{ nameof(id), id },
{ nameof(userId), userId },
{ nameof(message), message }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public async override Task HandleEvent(Event item)

if (agentType == AgentType.Unknown)
{
_logger.LogWarning("[{Agent}]:{EventType}:{EventData}. This event is not supported.", nameof(SignalR), item.Type, item.Data);
_logger.LogWarning("[{Agent}]:[{EventType}]:[{EventData}]. This event is not supported.", nameof(SignalR), item.Type, item.Data);
message = "Sorry, I don't know how to handle this request. Try to rephrase it.";
}

Expand Down
Loading

0 comments on commit c6ba273

Please sign in to comment.