cohit's picture
Upload folder using huggingface_hub
0827183 verified
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
from datetime import datetime, timedelta
from threading import Lock
from warnings import warn
from botbuilder.core import (
BotAdapter,
BotStateSet,
ConversationState,
UserState,
TurnContext,
)
from botbuilder.core.skills import SkillConversationReference, SkillHandler
from botbuilder.dialogs.memory import DialogStateManagerConfiguration
from botbuilder.schema import Activity, ActivityTypes, EndOfConversationCodes
from botframework.connector.auth import (
AuthenticationConstants,
ClaimsIdentity,
GovernmentConstants,
SkillValidation,
)
from .dialog import Dialog
from .dialog_context import DialogContext
from .dialog_events import DialogEvents
from .dialog_extensions import DialogExtensions
from .dialog_set import DialogSet
from .dialog_state import DialogState
from .dialog_manager_result import DialogManagerResult
from .dialog_turn_status import DialogTurnStatus
from .dialog_turn_result import DialogTurnResult
class DialogManager:
"""
Class which runs the dialog system.
"""
def __init__(self, root_dialog: Dialog = None, dialog_state_property: str = None):
"""
Initializes a instance of the <see cref="DialogManager"/> class.
:param root_dialog: Root dialog to use.
:param dialog_state_property: alternate name for the dialog_state property. (Default is "DialogState").
"""
self.last_access = "_lastAccess"
self._root_dialog_id = ""
self._dialog_state_property = dialog_state_property or "DialogState"
self._lock = Lock()
# Gets or sets root dialog to use to start conversation.
self.root_dialog = root_dialog
# Gets or sets the ConversationState.
self.conversation_state: ConversationState = None
# Gets or sets the UserState.
self.user_state: UserState = None
# Gets InitialTurnState collection to copy into the TurnState on every turn.
self.initial_turn_state = {}
# Gets or sets global dialogs that you want to have be callable.
self.dialogs = DialogSet()
# Gets or sets the DialogStateManagerConfiguration.
self.state_configuration: DialogStateManagerConfiguration = None
# Gets or sets (optional) number of milliseconds to expire the bot's state after.
self.expire_after: int = None
async def on_turn(self, context: TurnContext) -> DialogManagerResult:
"""
Runs dialog system in the context of an ITurnContext.
:param context: turn context.
:return:
"""
# pylint: disable=too-many-statements
# Lazy initialize RootDialog so it can refer to assets like LG function templates
if not self._root_dialog_id:
with self._lock:
if not self._root_dialog_id:
self._root_dialog_id = self.root_dialog.id
# self.dialogs = self.root_dialog.telemetry_client
self.dialogs.add(self.root_dialog)
bot_state_set = BotStateSet([])
# Preload TurnState with DM TurnState.
for key, val in self.initial_turn_state.items():
context.turn_state[key] = val
# register DialogManager with TurnState.
context.turn_state[DialogManager.__name__] = self
conversation_state_name = ConversationState.__name__
if self.conversation_state is None:
if conversation_state_name not in context.turn_state:
raise Exception(
f"Unable to get an instance of {conversation_state_name} from turn_context."
)
self.conversation_state: ConversationState = context.turn_state[
conversation_state_name
]
else:
context.turn_state[conversation_state_name] = self.conversation_state
bot_state_set.add(self.conversation_state)
user_state_name = UserState.__name__
if self.user_state is None:
self.user_state = context.turn_state.get(user_state_name, None)
else:
context.turn_state[user_state_name] = self.user_state
if self.user_state is not None:
self.user_state: UserState = self.user_state
bot_state_set.add(self.user_state)
# create property accessors
# DateTime(last_access)
last_access_property = self.conversation_state.create_property(self.last_access)
last_access: datetime = await last_access_property.get(context, datetime.now)
# Check for expired conversation
if self.expire_after is not None and (
datetime.now() - last_access
) >= timedelta(milliseconds=float(self.expire_after)):
# Clear conversation state
await self.conversation_state.clear_state(context)
last_access = datetime.now()
await last_access_property.set(context, last_access)
# get dialog stack
dialogs_property = self.conversation_state.create_property(
self._dialog_state_property
)
dialog_state: DialogState = await dialogs_property.get(context, DialogState)
# Create DialogContext
dialog_context = DialogContext(self.dialogs, context, dialog_state)
# Call the common dialog "continue/begin" execution pattern shared with the classic RunAsync extension method
turn_result = (
await DialogExtensions._internal_run( # pylint: disable=protected-access
context, self._root_dialog_id, dialog_context
)
)
# save BotState changes
await bot_state_set.save_all_changes(dialog_context.context, False)
return DialogManagerResult(turn_result=turn_result)
@staticmethod
async def send_state_snapshot_trace(
dialog_context: DialogContext,
trace_label: str = None, # pylint: disable=unused-argument
):
"""
Helper to send a trace activity with a memory snapshot of the active dialog DC.
:param dialog_context:
:param trace_label:
:return:
"""
warn(
"This method will be deprecated as no longer is necesary",
PendingDeprecationWarning,
)
await DialogExtensions._send_state_snapshot_trace( # pylint: disable=protected-access
dialog_context
)
@staticmethod
def is_from_parent_to_skill(turn_context: TurnContext) -> bool:
if turn_context.turn_state.get(
SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY, None
):
return False
claims_identity: ClaimsIdentity = turn_context.turn_state.get(
BotAdapter.BOT_IDENTITY_KEY, None
)
return isinstance(
claims_identity, ClaimsIdentity
) and SkillValidation.is_skill_claim(claims_identity.claims)
# Recursively walk up the DC stack to find the active DC.
@staticmethod
def get_active_dialog_context(dialog_context: DialogContext) -> DialogContext:
"""
Recursively walk up the DC stack to find the active DC.
:param dialog_context:
:return:
"""
warn(
"This method will be deprecated as no longer is necesary",
PendingDeprecationWarning,
)
return DialogExtensions._get_active_dialog_context( # pylint: disable=protected-access
dialog_context
)
@staticmethod
def should_send_end_of_conversation_to_parent(
context: TurnContext, turn_result: DialogTurnResult
) -> bool:
"""
Helper to determine if we should send an EndOfConversation to the parent or not.
:param context:
:param turn_result:
:return:
"""
if not (
turn_result.status == DialogTurnStatus.Complete
or turn_result.status == DialogTurnStatus.Cancelled
):
# The dialog is still going, don't return EoC.
return False
claims_identity: ClaimsIdentity = context.turn_state.get(
BotAdapter.BOT_IDENTITY_KEY, None
)
if isinstance(
claims_identity, ClaimsIdentity
) and SkillValidation.is_skill_claim(claims_identity.claims):
# EoC Activities returned by skills are bounced back to the bot by SkillHandler.
# In those cases we will have a SkillConversationReference instance in state.
skill_conversation_reference: SkillConversationReference = (
context.turn_state.get(SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY)
)
if skill_conversation_reference:
# If the skill_conversation_reference.OAuthScope is for one of the supported channels, we are at the
# root and we should not send an EoC.
return skill_conversation_reference.oauth_scope not in (
AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE,
GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE,
)
return True
return False
async def handle_skill_on_turn(
self, dialog_context: DialogContext
) -> DialogTurnResult:
warn(
"This method will be deprecated as no longer is necesary",
PendingDeprecationWarning,
)
# the bot is running as a skill.
turn_context = dialog_context.context
# Process remote cancellation
if (
turn_context.activity.type == ActivityTypes.end_of_conversation
and dialog_context.active_dialog is not None
and self.is_from_parent_to_skill(turn_context)
):
# Handle remote cancellation request from parent.
active_dialog_context = self.get_active_dialog_context(dialog_context)
# Send cancellation message to the top dialog in the stack to ensure all the parents are canceled in the
# right order.
return await active_dialog_context.cancel_all_dialogs(True)
# Handle reprompt
# Process a reprompt event sent from the parent.
if (
turn_context.activity.type == ActivityTypes.event
and turn_context.activity.name == DialogEvents.reprompt_dialog
):
if not dialog_context.active_dialog:
return DialogTurnResult(DialogTurnStatus.Empty)
await dialog_context.reprompt_dialog()
return DialogTurnResult(DialogTurnStatus.Waiting)
# Continue execution
# - This will apply any queued up interruptions and execute the current/next step(s).
turn_result = await dialog_context.continue_dialog()
if turn_result.status == DialogTurnStatus.Empty:
# restart root dialog
turn_result = await dialog_context.begin_dialog(self._root_dialog_id)
await DialogManager.send_state_snapshot_trace(dialog_context, "Skill State")
if self.should_send_end_of_conversation_to_parent(turn_context, turn_result):
# Send End of conversation at the end.
activity = Activity(
type=ActivityTypes.end_of_conversation,
value=turn_result.result,
locale=turn_context.activity.locale,
code=(
EndOfConversationCodes.completed_successfully
if turn_result.status == DialogTurnStatus.Complete
else EndOfConversationCodes.user_cancelled
),
)
await turn_context.send_activity(activity)
return turn_result
async def handle_bot_on_turn(
self, dialog_context: DialogContext
) -> DialogTurnResult:
warn(
"This method will be deprecated as no longer is necesary",
PendingDeprecationWarning,
)
# the bot is running as a root bot.
if dialog_context.active_dialog is None:
# start root dialog
turn_result = await dialog_context.begin_dialog(self._root_dialog_id)
else:
# Continue execution
# - This will apply any queued up interruptions and execute the current/next step(s).
turn_result = await dialog_context.continue_dialog()
if turn_result.status == DialogTurnStatus.Empty:
# restart root dialog
turn_result = await dialog_context.begin_dialog(self._root_dialog_id)
await self.send_state_snapshot_trace(dialog_context, "Bot State")
return turn_result