Skip to content

Commit d5ce281

Browse files
author
Russ Williams
committed
Added "agent closed conversation logic"
1 parent 31bb0a0 commit d5ce281

File tree

4 files changed

+116
-7
lines changed

4 files changed

+116
-7
lines changed

ACSAgentHub/Utils/ACSHelper.cs

+13-2
Original file line numberDiff line numberDiff line change
@@ -444,8 +444,19 @@ public static async Task SendMessageToBot(IConfiguration config, ACSAgentHubSDK.
444444
}
445445
else
446446
{
447-
// The bot has no record of this conversation, this should not happen
448-
throw new Exception("Cannot find conversation");
447+
// The conversation could not be found. This will happen on "echo" messages that bots send to
448+
// agent hub since those messages work their way to the ACS ChatThread which then generates
449+
// a "message received" event that the Event Grip is listening for. Normally the "message
450+
// received" will be the result of an agent sending a message that works it way to the
451+
// ACS Chat Thread and those messages are caught by Event Grid and then brokered over to the
452+
// bot. But, when its the bot that sends a message to the agent, it has to go in the same
453+
// ACS Chat Thread and that creates that "boomerang" or "echo" effect where the Event Grid
454+
// send the message back to the bot that originally send it since it can't tell the difference
455+
// between agent and bot messages.
456+
//
457+
// Since it is possible to see an "echoed" message from a closed conversation, we are not
458+
// going to throw an exception since it's a situation that can occur and it's a performance
459+
// killer so we'll just ignore this situation
449460
}
450461
}
451462

ACSConnector/ACSAdapter.cs

+51-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.Bot.Schema;
88
using Microsoft.Extensions.Configuration;
99
using Microsoft.Extensions.Logging;
10+
using Newtonsoft.Json.Linq;
1011
using System;
1112
using System.Collections.Generic;
1213
using System.Text;
@@ -30,9 +31,18 @@ namespace ACSConnector
3031
/// </remarks>
3132
public class ACSAdapter : BotFrameworkHttpAdapter, IACSAdapter
3233
{
33-
public ACSAdapter(IConfiguration configuration, ILogger<BotFrameworkHttpAdapter> logger, HandoffMiddleware handoffMiddleware, TranscriptLoggingMiddleware loggingMiddleware)
34+
private readonly ConversationState _conversationState;
35+
36+
public ACSAdapter(
37+
IConfiguration configuration,
38+
ILogger<BotFrameworkHttpAdapter> logger,
39+
HandoffMiddleware handoffMiddleware,
40+
ConversationState conversationState,
41+
TranscriptLoggingMiddleware loggingMiddleware)
3442
: base(configuration, logger)
3543
{
44+
_conversationState = conversationState ?? throw new ArgumentNullException(nameof(conversationState));
45+
3646
Use(handoffMiddleware);
3747
Use(loggingMiddleware);
3848

@@ -72,12 +82,50 @@ public async Task ProcessActivityAsync(Activity activity, string msAppId, Conver
7282
await ContinueConversationAsync(
7383
msAppId,
7484
conversationRef,
75-
(ITurnContext proactiveContext, CancellationToken ct) =>
85+
async (ITurnContext proactiveContext, CancellationToken ct) =>
7686
{
7787
using (var contextWithActivity = new TurnContext(this, activity))
7888
{
7989
contextWithActivity.TurnState.Add(proactiveContext.TurnState.Get<IConnectorClient>());
80-
return base.RunPipelineAsync(contextWithActivity, callback, cancellationToken);
90+
await base.RunPipelineAsync(contextWithActivity, callback, cancellationToken);
91+
92+
if (contextWithActivity.Activity.Name == "handoff.status")
93+
{
94+
Activity replyActivity;
95+
var state = (contextWithActivity.Activity.Value as JObject)?.Value<string>("state");
96+
if (state == "typing")
97+
{
98+
replyActivity = new Activity
99+
{
100+
Type = ActivityTypes.Typing,
101+
Text = "agent is typing",
102+
};
103+
}
104+
else if (state == "accepted")
105+
{
106+
replyActivity = MessageFactory.Text("An agent has accepted the conversation and will respond shortly.");
107+
await _conversationState.SaveChangesAsync(contextWithActivity);
108+
}
109+
else if (state == "closed")
110+
{
111+
replyActivity = MessageFactory.Text("The agent has ended the conversation and you're now reconnected with the digital assistant");
112+
113+
// Route the conversation based on whether it's been escalated
114+
var conversationStateAccessors = _conversationState.CreateProperty<EscalationRecord>(nameof(EscalationRecord));
115+
var escalationRecord = await conversationStateAccessors.GetAsync(contextWithActivity, () => new EscalationRecord()).ConfigureAwait(false);
116+
117+
// End the escalation
118+
escalationRecord.EndEscalation();
119+
await _conversationState.SaveChangesAsync(contextWithActivity).ConfigureAwait(false);
120+
}
121+
else
122+
{
123+
replyActivity = MessageFactory.Text($"Conversation status changed to '{state}'");
124+
}
125+
126+
await contextWithActivity.SendActivityAsync(replyActivity);
127+
}
128+
81129
}
82130
},
83131
cancellationToken).ConfigureAwait(false);

ACSConnector/Controllers/ACSController.cs

+44-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.Bot.Schema;
99
using Microsoft.Extensions.Configuration;
1010
using Newtonsoft.Json;
11+
using Newtonsoft.Json.Linq;
1112
using System;
1213
using System.Collections.Generic;
1314
using System.IO;
@@ -30,12 +31,15 @@ public class ACSController : ControllerBase
3031
// This is the Id of the ACS service user for the agent user which we'll will use when accessing ACS services
3132
private static string _acsAgentUserId;
3233
AgentHubHttpClient _diInjectedHttpClient;
34+
private readonly ConversationState _conversationState;
3335

3436
public ACSController(IConfiguration configuration,
3537
BotAdapter adapter,
3638
IBot bot,
39+
ConversationState conversationState,
3740
AgentHubHttpClient httpClient)
3841
{
42+
_conversationState = conversationState ?? throw new ArgumentNullException(nameof(conversationState));
3943
_adapter = adapter;
4044
_bot = bot;
4145
_botAppId = configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value;
@@ -184,12 +188,50 @@ private async Task ProcessActivityAsync(BotAdapter adapter, Activity activity, s
184188
await adapter.ContinueConversationAsync(
185189
msAppId,
186190
conversationRef,
187-
(ITurnContext proactiveContext, CancellationToken ct) =>
191+
async (ITurnContext proactiveContext, CancellationToken ct) =>
188192
{
189193
using (var contextWithActivity = new TurnContext(adapter, activity))
190194
{
191195
contextWithActivity.TurnState.Add(proactiveContext.TurnState.Get<IConnectorClient>());
192-
return callback(proactiveContext, cancellationToken);
196+
await callback(proactiveContext, cancellationToken);
197+
198+
if (contextWithActivity.Activity.Name == "handoff.status")
199+
{
200+
Activity replyActivity;
201+
var state = (contextWithActivity.Activity.Value as JObject)?.Value<string>("state");
202+
if (state == "typing")
203+
{
204+
replyActivity = new Activity
205+
{
206+
Type = ActivityTypes.Typing,
207+
Text = "agent is typing",
208+
};
209+
}
210+
else if (state == "accepted")
211+
{
212+
replyActivity = MessageFactory.Text("An agent has accepted the conversation and will respond shortly.");
213+
await _conversationState.SaveChangesAsync(contextWithActivity);
214+
}
215+
else if (state == "closed")
216+
{
217+
replyActivity = MessageFactory.Text("The agent has ended the conversation and you're now reconnected with the digital assistant");
218+
219+
// Route the conversation based on whether it's been escalated
220+
var conversationStateAccessors = _conversationState.CreateProperty<EscalationRecord>(nameof(EscalationRecord));
221+
var escalationRecord = await conversationStateAccessors.GetAsync(contextWithActivity, () => new EscalationRecord()).ConfigureAwait(false);
222+
223+
// End the escalation
224+
escalationRecord.EndEscalation();
225+
await _conversationState.SaveChangesAsync(contextWithActivity).ConfigureAwait(false);
226+
}
227+
else
228+
{
229+
replyActivity = MessageFactory.Text($"Conversation status changed to '{state}'");
230+
}
231+
232+
await contextWithActivity.SendActivityAsync(replyActivity);
233+
}
234+
193235
}
194236
},
195237
cancellationToken).ConfigureAwait(false);

VATemplateExample/VATemplateExample/Adapters/DefaultAdapter.cs

+8
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,14 @@ await ContinueConversationAsync(
198198
else if (state == "closed")
199199
{
200200
replyActivity = MessageFactory.Text("The agent has ended the conversation and you're now reconnected with the digital assistant");
201+
202+
// Route the conversation based on whether it's been escalated
203+
var conversationStateAccessors = _conversationState.CreateProperty<EscalationRecord>(nameof(EscalationRecord));
204+
var escalationRecord = await conversationStateAccessors.GetAsync(contextWithActivity, () => new EscalationRecord()).ConfigureAwait(false);
205+
206+
// End the escalation
207+
escalationRecord.EndEscalation();
208+
await _conversationState.SaveChangesAsync(contextWithActivity).ConfigureAwait(false);
201209
}
202210
else
203211
{

0 commit comments

Comments
 (0)