在 Cosmos DB 中管理状态,而不是在内存中管理 Bot 到 Human 的切换场景
Manage state in Cosmos DB instead of in-memory for Bot to Human handover scenario
我正在开发一个具有人工切换功能的机器人(人-2-人聊天),其中机器人负责整个通信。用户可以开始与bot的交流,如果对bot的回答不满意,可以向Human寻求进一步的帮助。
Bot 能够使用第三方系统将用户连接到实时代理。 Bot 将消息从对话转发到该系统的 API 端点以及回调 url。该第三方系统使用回调机制将代理写入的消息传递到指定的url.
我已经创建了一个 API 控制器端点并作为回调 url 传递给这个系统。当代理发送消息时,系统会在此端点上发出通知。它是简单的 Web API 控制器,与 Bot Framework 没有直接联系。
尽管我在 Cosmos DB 中维护了机器人的对话和用户状态,并且它具有某些包含聊天连接状态的属性,例如(ChatConnected、ChatClosed 等)。现在为了将这些消息通知传递给机器人,我维护了两个并发字典,一个用于对话参考,第二个用于 TurnContext。
对话参考有助于使用 ContinueConversationAsync 将代理消息从 bot 传递给用户。
TurnContext 有助于在会话关闭等时管理和更新这些属性的状态。并且还可以使用它在 activity 的特定时间段后发送消息,因为上一回合有 activity时间戳。
现在这两个都在内存中,这意味着它们会随着新聊天会话的创建和更多消息的交换而被添加和删除。我现在想将其从内存中移出到共享缓存或低延迟 cosmos。这样我也可以在需要时使用自动扩展新的机器人服务实例的可能性。我目前正在使用应用服务。但是由于这种耦合,新实例无法访问内存中的数据,因此无法提供服务。我不认为为 Bot Scenarios 启用 AffinityCookie 实际上有效。
我能够序列化 ConversationReference 对象(通过 NewtonSoft),但是序列化 TurnContext 会抛出 JSON 由于对象内部循环而导致的序列化异常。我试图用 SerilizationSettings 来减轻这种情况以忽略循环,但它甚至在调试期间在 VS 中不起作用抛出 VS 堆栈溢出异常。
那么我怎样才能移动此代码以独立于实例上的单例 ConcurrentDictionary-
private readonly ConcurrentDictionary<string, ITurnContext> TurnContextReferences;
private void AddTurnContext(ITurnContext turnContext, string sessionId)
{
if (turnContext != null && !string.IsNullOrWhiteSpace(sessionId))
{
//Add the Session Id and TurnContext to dictionary
TurnContextReferences.AddOrUpdate(sessionId, turnContext, (key, newValue) => turnContext);
}
}
//Using above method inside a function
//Trim the incoming message
var userMessage = messageActivity.Text.Trim();
if (!string.IsNullOrWhiteSpace(userMessage))
{
//send the incoming message from client to Agent
await TPSystem.SendMessageAsync(messageActivity.Conversation.Id, conversationData.SessionId, messageActivity.Text.Trim());
}
//Add to Turn context Dictionary
AddTurnContext(stepContext.Context, conversationData.SessionId);
//Inside API Controller
//Get the TurnContext from the Dictionary
TurnContextReferences.TryGetValue(sessionStateChangedEventData.SessionId, out ITurnContext turnContext);
if (turnContext != null)
{
var conversationData = await BotStateAccessors.ConversationStateAccessor.GetAsync(turnContext, () => new ConversationStateDataModel());
if (!conversationData.LiveAgentChatClosed)
{
conversationData.LiveAgentChatClosed = true;
await BotStateAccessors.ConversationStateAccessor.SetAsync(turnContext, conversationData);
await BotConversationState.SaveChangesAsync(turnContext);
}
}
任何想通的想法都将不胜感激。
对话引用包含活动中信息的子集,activity 只是回合上下文中的一个 属性,因此对话引用包含回合上下文中信息的子集.保存对话参考和回合上下文是多余的,因为如果你保存回合上下文,那么你已经拥有对话参考中的所有信息。
也就是说,尝试保存回合上下文是一个非常糟糕的主意。如果您需要对话参考中没有的一些信息,则只需保存该特定信息即可。例如,您可以创建自己的 class,其中包含对话引用和表示该对话中最后一条消息的时间的时间戳。
public class ConversationInfo
{
[JsonProperty(PropertyName = "conversationReference")]
public ConversationReference ConversationReference { get; set; }
[JsonProperty(PropertyName = "timestamp")]
public DateTimeOffset Timestamp { get; set; }
}
我正在开发一个具有人工切换功能的机器人(人-2-人聊天),其中机器人负责整个通信。用户可以开始与bot的交流,如果对bot的回答不满意,可以向Human寻求进一步的帮助。
Bot 能够使用第三方系统将用户连接到实时代理。 Bot 将消息从对话转发到该系统的 API 端点以及回调 url。该第三方系统使用回调机制将代理写入的消息传递到指定的url.
我已经创建了一个 API 控制器端点并作为回调 url 传递给这个系统。当代理发送消息时,系统会在此端点上发出通知。它是简单的 Web API 控制器,与 Bot Framework 没有直接联系。
尽管我在 Cosmos DB 中维护了机器人的对话和用户状态,并且它具有某些包含聊天连接状态的属性,例如(ChatConnected、ChatClosed 等)。现在为了将这些消息通知传递给机器人,我维护了两个并发字典,一个用于对话参考,第二个用于 TurnContext。
对话参考有助于使用 ContinueConversationAsync 将代理消息从 bot 传递给用户。
TurnContext 有助于在会话关闭等时管理和更新这些属性的状态。并且还可以使用它在 activity 的特定时间段后发送消息,因为上一回合有 activity时间戳。
现在这两个都在内存中,这意味着它们会随着新聊天会话的创建和更多消息的交换而被添加和删除。我现在想将其从内存中移出到共享缓存或低延迟 cosmos。这样我也可以在需要时使用自动扩展新的机器人服务实例的可能性。我目前正在使用应用服务。但是由于这种耦合,新实例无法访问内存中的数据,因此无法提供服务。我不认为为 Bot Scenarios 启用 AffinityCookie 实际上有效。
我能够序列化 ConversationReference 对象(通过 NewtonSoft),但是序列化 TurnContext 会抛出 JSON 由于对象内部循环而导致的序列化异常。我试图用 SerilizationSettings 来减轻这种情况以忽略循环,但它甚至在调试期间在 VS 中不起作用抛出 VS 堆栈溢出异常。
那么我怎样才能移动此代码以独立于实例上的单例 ConcurrentDictionary-
private readonly ConcurrentDictionary<string, ITurnContext> TurnContextReferences;
private void AddTurnContext(ITurnContext turnContext, string sessionId)
{
if (turnContext != null && !string.IsNullOrWhiteSpace(sessionId))
{
//Add the Session Id and TurnContext to dictionary
TurnContextReferences.AddOrUpdate(sessionId, turnContext, (key, newValue) => turnContext);
}
}
//Using above method inside a function
//Trim the incoming message
var userMessage = messageActivity.Text.Trim();
if (!string.IsNullOrWhiteSpace(userMessage))
{
//send the incoming message from client to Agent
await TPSystem.SendMessageAsync(messageActivity.Conversation.Id, conversationData.SessionId, messageActivity.Text.Trim());
}
//Add to Turn context Dictionary
AddTurnContext(stepContext.Context, conversationData.SessionId);
//Inside API Controller
//Get the TurnContext from the Dictionary
TurnContextReferences.TryGetValue(sessionStateChangedEventData.SessionId, out ITurnContext turnContext);
if (turnContext != null)
{
var conversationData = await BotStateAccessors.ConversationStateAccessor.GetAsync(turnContext, () => new ConversationStateDataModel());
if (!conversationData.LiveAgentChatClosed)
{
conversationData.LiveAgentChatClosed = true;
await BotStateAccessors.ConversationStateAccessor.SetAsync(turnContext, conversationData);
await BotConversationState.SaveChangesAsync(turnContext);
}
}
任何想通的想法都将不胜感激。
对话引用包含活动中信息的子集,activity 只是回合上下文中的一个 属性,因此对话引用包含回合上下文中信息的子集.保存对话参考和回合上下文是多余的,因为如果你保存回合上下文,那么你已经拥有对话参考中的所有信息。
也就是说,尝试保存回合上下文是一个非常糟糕的主意。如果您需要对话参考中没有的一些信息,则只需保存该特定信息即可。例如,您可以创建自己的 class,其中包含对话引用和表示该对话中最后一条消息的时间的时间戳。
public class ConversationInfo
{
[JsonProperty(PropertyName = "conversationReference")]
public ConversationReference ConversationReference { get; set; }
[JsonProperty(PropertyName = "timestamp")]
public DateTimeOffset Timestamp { get; set; }
}