microsoft-agents-hosting-dialogs 0.10.0.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- microsoft_agents/hosting/dialogs/__init__.py +76 -0
- microsoft_agents/hosting/dialogs/_component_registration.py +30 -0
- microsoft_agents/hosting/dialogs/_telemetry_client.py +78 -0
- microsoft_agents/hosting/dialogs/choices/__init__.py +38 -0
- microsoft_agents/hosting/dialogs/choices/channel.py +121 -0
- microsoft_agents/hosting/dialogs/choices/choice_factory.py +262 -0
- microsoft_agents/hosting/dialogs/choices/choice_recognizer.py +148 -0
- microsoft_agents/hosting/dialogs/choices/find.py +242 -0
- microsoft_agents/hosting/dialogs/choices/models/__init__.py +23 -0
- microsoft_agents/hosting/dialogs/choices/models/choice.py +14 -0
- microsoft_agents/hosting/dialogs/choices/models/choice_factory_options.py +13 -0
- microsoft_agents/hosting/dialogs/choices/models/find_choices_options.py +28 -0
- microsoft_agents/hosting/dialogs/choices/models/find_values_options.py +31 -0
- microsoft_agents/hosting/dialogs/choices/models/found_choice.py +22 -0
- microsoft_agents/hosting/dialogs/choices/models/found_value.py +20 -0
- microsoft_agents/hosting/dialogs/choices/models/list_style.py +15 -0
- microsoft_agents/hosting/dialogs/choices/models/model_result.py +16 -0
- microsoft_agents/hosting/dialogs/choices/models/sorted_value.py +16 -0
- microsoft_agents/hosting/dialogs/choices/models/token.py +20 -0
- microsoft_agents/hosting/dialogs/choices/tokenizer.py +92 -0
- microsoft_agents/hosting/dialogs/component_dialog.py +284 -0
- microsoft_agents/hosting/dialogs/dialog.py +198 -0
- microsoft_agents/hosting/dialogs/dialog_component_registration.py +52 -0
- microsoft_agents/hosting/dialogs/dialog_container.py +31 -0
- microsoft_agents/hosting/dialogs/dialog_context.py +426 -0
- microsoft_agents/hosting/dialogs/dialog_extensions.py +201 -0
- microsoft_agents/hosting/dialogs/dialog_manager.py +189 -0
- microsoft_agents/hosting/dialogs/dialog_manager_result.py +17 -0
- microsoft_agents/hosting/dialogs/dialog_set.py +174 -0
- microsoft_agents/hosting/dialogs/dialog_state.py +20 -0
- microsoft_agents/hosting/dialogs/memory/__init__.py +24 -0
- microsoft_agents/hosting/dialogs/memory/component_memory_scopes_base.py +14 -0
- microsoft_agents/hosting/dialogs/memory/component_path_resolvers_base.py +15 -0
- microsoft_agents/hosting/dialogs/memory/dialog_path.py +33 -0
- microsoft_agents/hosting/dialogs/memory/dialog_state_manager.py +563 -0
- microsoft_agents/hosting/dialogs/memory/dialog_state_manager_configuration.py +11 -0
- microsoft_agents/hosting/dialogs/memory/path_resolver_base.py +8 -0
- microsoft_agents/hosting/dialogs/memory/path_resolvers/__init__.py +19 -0
- microsoft_agents/hosting/dialogs/memory/path_resolvers/alias_path_resolver.py +53 -0
- microsoft_agents/hosting/dialogs/memory/path_resolvers/at_at_path_resolver.py +9 -0
- microsoft_agents/hosting/dialogs/memory/path_resolvers/at_path_resolver.py +44 -0
- microsoft_agents/hosting/dialogs/memory/path_resolvers/dollar_path_resolver.py +9 -0
- microsoft_agents/hosting/dialogs/memory/path_resolvers/hash_path_resolver.py +9 -0
- microsoft_agents/hosting/dialogs/memory/path_resolvers/percent_path_resolver.py +9 -0
- microsoft_agents/hosting/dialogs/memory/scope_path.py +38 -0
- microsoft_agents/hosting/dialogs/memory/scopes/__init__.py +31 -0
- microsoft_agents/hosting/dialogs/memory/scopes/bot_state_memory_scope.py +66 -0
- microsoft_agents/hosting/dialogs/memory/scopes/class_memory_scope.py +64 -0
- microsoft_agents/hosting/dialogs/memory/scopes/conversation_memory_scope.py +12 -0
- microsoft_agents/hosting/dialogs/memory/scopes/dialog_class_memory_scope.py +52 -0
- microsoft_agents/hosting/dialogs/memory/scopes/dialog_context_memory_scope.py +68 -0
- microsoft_agents/hosting/dialogs/memory/scopes/dialog_memory_scope.py +75 -0
- microsoft_agents/hosting/dialogs/memory/scopes/memory_scope.py +91 -0
- microsoft_agents/hosting/dialogs/memory/scopes/settings_memory_scope.py +38 -0
- microsoft_agents/hosting/dialogs/memory/scopes/this_memory_scope.py +36 -0
- microsoft_agents/hosting/dialogs/memory/scopes/turn_memory_scope.py +86 -0
- microsoft_agents/hosting/dialogs/memory/scopes/user_memory_scope.py +12 -0
- microsoft_agents/hosting/dialogs/models/__init__.py +15 -0
- microsoft_agents/hosting/dialogs/models/dialog_event.py +13 -0
- microsoft_agents/hosting/dialogs/models/dialog_events.py +12 -0
- microsoft_agents/hosting/dialogs/models/dialog_instance.py +28 -0
- microsoft_agents/hosting/dialogs/models/dialog_reason.py +34 -0
- microsoft_agents/hosting/dialogs/models/dialog_turn_result.py +17 -0
- microsoft_agents/hosting/dialogs/models/dialog_turn_status.py +26 -0
- microsoft_agents/hosting/dialogs/object_path.py +315 -0
- microsoft_agents/hosting/dialogs/persisted_state.py +22 -0
- microsoft_agents/hosting/dialogs/persisted_state_keys.py +8 -0
- microsoft_agents/hosting/dialogs/prompts/__init__.py +41 -0
- microsoft_agents/hosting/dialogs/prompts/activity_prompt.py +203 -0
- microsoft_agents/hosting/dialogs/prompts/attachment_prompt.py +87 -0
- microsoft_agents/hosting/dialogs/prompts/choice_prompt.py +156 -0
- microsoft_agents/hosting/dialogs/prompts/confirm_prompt.py +161 -0
- microsoft_agents/hosting/dialogs/prompts/datetime_prompt.py +90 -0
- microsoft_agents/hosting/dialogs/prompts/datetime_resolution.py +16 -0
- microsoft_agents/hosting/dialogs/prompts/number_prompt.py +81 -0
- microsoft_agents/hosting/dialogs/prompts/oauth_prompt.py +569 -0
- microsoft_agents/hosting/dialogs/prompts/oauth_prompt_settings.py +43 -0
- microsoft_agents/hosting/dialogs/prompts/prompt.py +224 -0
- microsoft_agents/hosting/dialogs/prompts/prompt_culture_models.py +222 -0
- microsoft_agents/hosting/dialogs/prompts/prompt_options.py +42 -0
- microsoft_agents/hosting/dialogs/prompts/prompt_recognizer_result.py +11 -0
- microsoft_agents/hosting/dialogs/prompts/prompt_validator.py +0 -0
- microsoft_agents/hosting/dialogs/prompts/prompt_validator_context.py +44 -0
- microsoft_agents/hosting/dialogs/prompts/text_prompt.py +82 -0
- microsoft_agents/hosting/dialogs/waterfall_dialog.py +266 -0
- microsoft_agents/hosting/dialogs/waterfall_step_context.py +109 -0
- microsoft_agents_hosting_dialogs-0.10.0.dev2.dist-info/METADATA +87 -0
- microsoft_agents_hosting_dialogs-0.10.0.dev2.dist-info/RECORD +91 -0
- microsoft_agents_hosting_dialogs-0.10.0.dev2.dist-info/WHEEL +5 -0
- microsoft_agents_hosting_dialogs-0.10.0.dev2.dist-info/licenses/LICENSE +21 -0
- microsoft_agents_hosting_dialogs-0.10.0.dev2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from typing import Any, Callable, cast
|
|
5
|
+
|
|
6
|
+
from microsoft_agents.hosting.core import TurnContext
|
|
7
|
+
from microsoft_agents.activity import ActivityTypes, InputHints
|
|
8
|
+
|
|
9
|
+
from ..dialog import Dialog
|
|
10
|
+
from ..dialog_context import DialogContext
|
|
11
|
+
from ..models.dialog_instance import DialogInstance
|
|
12
|
+
from ..models.dialog_reason import DialogReason
|
|
13
|
+
from ..models.dialog_turn_result import DialogTurnResult
|
|
14
|
+
from .prompt import Prompt
|
|
15
|
+
from .prompt_options import PromptOptions
|
|
16
|
+
from .prompt_recognizer_result import PromptRecognizerResult
|
|
17
|
+
from .prompt_validator_context import PromptValidatorContext
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ActivityPrompt(Dialog):
|
|
21
|
+
"""
|
|
22
|
+
Waits for an activity to be received.
|
|
23
|
+
|
|
24
|
+
This prompt requires a validator be passed in and is useful when waiting for non-message
|
|
25
|
+
activities like an event to be received. The validator can ignore received events until the
|
|
26
|
+
expected activity is received.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
persisted_options = "options"
|
|
30
|
+
persisted_state = "state"
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self, dialog_id: str, validator: Callable[[PromptValidatorContext], Any]
|
|
34
|
+
):
|
|
35
|
+
Dialog.__init__(self, dialog_id)
|
|
36
|
+
|
|
37
|
+
if validator is None:
|
|
38
|
+
raise TypeError("validator was expected but received None")
|
|
39
|
+
self._validator = validator
|
|
40
|
+
|
|
41
|
+
async def begin_dialog(
|
|
42
|
+
self, dialog_context: DialogContext, options: object = None
|
|
43
|
+
) -> DialogTurnResult:
|
|
44
|
+
"""Starts the prompt by sending the initial prompt activity.
|
|
45
|
+
|
|
46
|
+
Initialises persisted state with an attempt count of 0, then calls
|
|
47
|
+
:meth:`on_prompt`.
|
|
48
|
+
|
|
49
|
+
:param dialog_context: The dialog context for the current turn.
|
|
50
|
+
:param options: Must be a :class:`PromptOptions` instance.
|
|
51
|
+
:raises TypeError: If ``options`` is not a :class:`PromptOptions`.
|
|
52
|
+
:return: :attr:`Dialog.end_of_turn` to wait for the user's response.
|
|
53
|
+
"""
|
|
54
|
+
if not dialog_context:
|
|
55
|
+
raise TypeError("ActivityPrompt.begin_dialog(): dc cannot be None.")
|
|
56
|
+
if not isinstance(options, PromptOptions):
|
|
57
|
+
raise TypeError(
|
|
58
|
+
"ActivityPrompt.begin_dialog(): Prompt options are required for ActivityPrompts."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Ensure prompts have input hint set
|
|
62
|
+
if options.prompt is not None and not options.prompt.input_hint:
|
|
63
|
+
options.prompt.input_hint = InputHints.expecting_input
|
|
64
|
+
|
|
65
|
+
if options.retry_prompt is not None and not options.retry_prompt.input_hint:
|
|
66
|
+
options.retry_prompt.input_hint = InputHints.expecting_input
|
|
67
|
+
|
|
68
|
+
# Initialize prompt state
|
|
69
|
+
assert dialog_context.active_dialog is not None
|
|
70
|
+
state: dict[str, object] = dialog_context.active_dialog.state
|
|
71
|
+
state[self.persisted_options] = options
|
|
72
|
+
state[self.persisted_state] = {Prompt.ATTEMPT_COUNT_KEY: 0}
|
|
73
|
+
|
|
74
|
+
# Send initial prompt
|
|
75
|
+
await self.on_prompt(
|
|
76
|
+
dialog_context.context,
|
|
77
|
+
cast(dict[str, object], state[self.persisted_state]),
|
|
78
|
+
cast(PromptOptions, state[self.persisted_options]),
|
|
79
|
+
False,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return Dialog.end_of_turn
|
|
83
|
+
|
|
84
|
+
async def continue_dialog(self, dialog_context: DialogContext) -> DialogTurnResult:
|
|
85
|
+
"""Processes the next incoming activity through the prompt.
|
|
86
|
+
|
|
87
|
+
Increments the persisted attempt count **before** calling the validator,
|
|
88
|
+
so :attr:`PromptValidatorContext.attempt_count` is at least 1 on the
|
|
89
|
+
first validation call.
|
|
90
|
+
|
|
91
|
+
.. note::
|
|
92
|
+
This differs from the base :class:`Prompt` class, where
|
|
93
|
+
``attempt_count`` is never stored in state and is always 0 when the
|
|
94
|
+
validator runs. Code that validates both ``ActivityPrompt`` and
|
|
95
|
+
``Prompt`` subclasses should rely on
|
|
96
|
+
:attr:`PromptOptions.number_of_attempts` for consistent counting.
|
|
97
|
+
|
|
98
|
+
:param dialog_context: The dialog context for the current turn.
|
|
99
|
+
:return: :attr:`Dialog.end_of_turn` while waiting for valid input, or
|
|
100
|
+
a Complete result once the validator accepts the activity.
|
|
101
|
+
"""
|
|
102
|
+
if not dialog_context:
|
|
103
|
+
raise TypeError(
|
|
104
|
+
"ActivityPrompt.continue_dialog(): DialogContext cannot be None."
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Perform base recognition
|
|
108
|
+
instance = dialog_context.active_dialog
|
|
109
|
+
assert instance is not None
|
|
110
|
+
state: dict[str, object] = cast(
|
|
111
|
+
dict[str, object], instance.state[self.persisted_state]
|
|
112
|
+
)
|
|
113
|
+
prompt_options: PromptOptions = cast(
|
|
114
|
+
PromptOptions, instance.state[self.persisted_options]
|
|
115
|
+
)
|
|
116
|
+
recognized: PromptRecognizerResult = await self.on_recognize(
|
|
117
|
+
dialog_context.context, state, prompt_options
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Increment attempt count
|
|
121
|
+
state[Prompt.ATTEMPT_COUNT_KEY] = (
|
|
122
|
+
int(cast(int, state.get(Prompt.ATTEMPT_COUNT_KEY, 0))) + 1
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Validate the return value
|
|
126
|
+
is_valid = False
|
|
127
|
+
if self._validator is not None:
|
|
128
|
+
prompt_context = PromptValidatorContext(
|
|
129
|
+
dialog_context.context, recognized, state, prompt_options
|
|
130
|
+
)
|
|
131
|
+
is_valid = await self._validator(prompt_context)
|
|
132
|
+
|
|
133
|
+
prompt_options.number_of_attempts += 1
|
|
134
|
+
elif recognized.succeeded:
|
|
135
|
+
is_valid = True
|
|
136
|
+
|
|
137
|
+
# Return recognized value or re-prompt
|
|
138
|
+
if is_valid:
|
|
139
|
+
return await dialog_context.end_dialog(recognized.value)
|
|
140
|
+
|
|
141
|
+
if (
|
|
142
|
+
dialog_context.context.activity.type == ActivityTypes.message
|
|
143
|
+
and not dialog_context.context.responded
|
|
144
|
+
):
|
|
145
|
+
await self.on_prompt(dialog_context.context, state, prompt_options, True)
|
|
146
|
+
|
|
147
|
+
return Dialog.end_of_turn
|
|
148
|
+
|
|
149
|
+
async def resume_dialog( # pylint: disable=unused-argument
|
|
150
|
+
self, dialog_context: DialogContext, reason: DialogReason, result: object = None
|
|
151
|
+
):
|
|
152
|
+
assert dialog_context.active_dialog is not None
|
|
153
|
+
await self.reprompt_dialog(dialog_context.context, dialog_context.active_dialog)
|
|
154
|
+
|
|
155
|
+
return Dialog.end_of_turn
|
|
156
|
+
|
|
157
|
+
async def reprompt_dialog(self, context: TurnContext, instance: DialogInstance):
|
|
158
|
+
state: dict[str, object] = instance.state[self.persisted_state]
|
|
159
|
+
options: PromptOptions = instance.state[self.persisted_options]
|
|
160
|
+
await self.on_prompt(context, state, options, False)
|
|
161
|
+
|
|
162
|
+
async def on_prompt(
|
|
163
|
+
self,
|
|
164
|
+
context: TurnContext,
|
|
165
|
+
state: dict[str, object], # pylint: disable=unused-argument
|
|
166
|
+
options: PromptOptions,
|
|
167
|
+
is_retry: bool = False,
|
|
168
|
+
):
|
|
169
|
+
"""Sends the initial or retry prompt activity to the user.
|
|
170
|
+
|
|
171
|
+
Always sets ``input_hint`` to ``expecting_input`` before sending.
|
|
172
|
+
|
|
173
|
+
:param context: The context for the current turn.
|
|
174
|
+
:param state: Persisted prompt state (unused by default implementation).
|
|
175
|
+
:param options: Prompt options containing the prompt and optional retry prompt.
|
|
176
|
+
:param is_retry: ``True`` when re-prompting after failed validation.
|
|
177
|
+
"""
|
|
178
|
+
if is_retry and options.retry_prompt:
|
|
179
|
+
options.retry_prompt.input_hint = InputHints.expecting_input
|
|
180
|
+
await context.send_activity(options.retry_prompt)
|
|
181
|
+
elif options.prompt:
|
|
182
|
+
options.prompt.input_hint = InputHints.expecting_input
|
|
183
|
+
await context.send_activity(options.prompt)
|
|
184
|
+
|
|
185
|
+
async def on_recognize( # pylint: disable=unused-argument
|
|
186
|
+
self, context: TurnContext, state: dict[str, object], options: PromptOptions
|
|
187
|
+
) -> PromptRecognizerResult:
|
|
188
|
+
"""Default recognizer: always succeeds and returns the raw incoming activity.
|
|
189
|
+
|
|
190
|
+
Override this in a subclass to restrict which activities are considered
|
|
191
|
+
valid (e.g. accept only events with a specific name).
|
|
192
|
+
|
|
193
|
+
:param context: The context for the current turn.
|
|
194
|
+
:param state: Persisted prompt state.
|
|
195
|
+
:param options: Prompt options.
|
|
196
|
+
:return: A result with ``succeeded=True`` and ``value`` set to the
|
|
197
|
+
current :class:`Activity`.
|
|
198
|
+
"""
|
|
199
|
+
result = PromptRecognizerResult()
|
|
200
|
+
result.succeeded = True
|
|
201
|
+
result.value = context.activity
|
|
202
|
+
|
|
203
|
+
return result
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from typing import Callable
|
|
5
|
+
|
|
6
|
+
from microsoft_agents.hosting.core import TurnContext
|
|
7
|
+
from microsoft_agents.activity import ActivityTypes
|
|
8
|
+
|
|
9
|
+
from .prompt import Prompt, PromptValidatorContext
|
|
10
|
+
from .prompt_options import PromptOptions
|
|
11
|
+
from .prompt_recognizer_result import PromptRecognizerResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AttachmentPrompt(Prompt):
|
|
15
|
+
"""
|
|
16
|
+
Prompts a user to upload attachments like images.
|
|
17
|
+
|
|
18
|
+
By default the prompt will return to the calling dialog an `[Attachment]`
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
dialog_id: str,
|
|
24
|
+
validator: Callable[[PromptValidatorContext], bool] | None = None,
|
|
25
|
+
):
|
|
26
|
+
super().__init__(dialog_id, validator)
|
|
27
|
+
|
|
28
|
+
async def on_prompt(
|
|
29
|
+
self,
|
|
30
|
+
turn_context: TurnContext,
|
|
31
|
+
state: dict[str, object],
|
|
32
|
+
options: PromptOptions,
|
|
33
|
+
is_retry: bool,
|
|
34
|
+
):
|
|
35
|
+
"""Sends the initial or retry prompt activity to the user.
|
|
36
|
+
|
|
37
|
+
:param turn_context: The context for the current turn.
|
|
38
|
+
:param state: Persisted prompt state (unused by AttachmentPrompt).
|
|
39
|
+
:param options: Prompt options containing the prompt and optional retry prompt.
|
|
40
|
+
:param is_retry: ``True`` when re-prompting after failed validation.
|
|
41
|
+
"""
|
|
42
|
+
if not turn_context:
|
|
43
|
+
raise TypeError("AttachmentPrompt.on_prompt(): TurnContext cannot be None.")
|
|
44
|
+
|
|
45
|
+
if not isinstance(options, PromptOptions):
|
|
46
|
+
raise TypeError(
|
|
47
|
+
"AttachmentPrompt.on_prompt(): PromptOptions are required for Attachment Prompt dialogs."
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if is_retry and options.retry_prompt:
|
|
51
|
+
await turn_context.send_activity(options.retry_prompt)
|
|
52
|
+
elif options.prompt:
|
|
53
|
+
await turn_context.send_activity(options.prompt)
|
|
54
|
+
|
|
55
|
+
async def on_recognize(
|
|
56
|
+
self,
|
|
57
|
+
turn_context: TurnContext,
|
|
58
|
+
state: dict[str, object],
|
|
59
|
+
options: PromptOptions,
|
|
60
|
+
) -> PromptRecognizerResult:
|
|
61
|
+
"""Attempts to recognise attachments from the incoming activity.
|
|
62
|
+
|
|
63
|
+
Succeeds only when the activity is a message **and** ``activity.attachments``
|
|
64
|
+
is a non-empty list. The full attachment list is returned as the result.
|
|
65
|
+
|
|
66
|
+
.. note::
|
|
67
|
+
A message activity with no attachments (e.g. plain text) will fail
|
|
68
|
+
recognition and trigger the retry prompt.
|
|
69
|
+
|
|
70
|
+
:param turn_context: The context for the current turn.
|
|
71
|
+
:param state: Persisted prompt state (unused by AttachmentPrompt).
|
|
72
|
+
:param options: Prompt options (unused by AttachmentPrompt).
|
|
73
|
+
:return: Recognition result with ``succeeded=True`` and the list of
|
|
74
|
+
:class:`Attachment` objects, or ``succeeded=False`` if none were found.
|
|
75
|
+
"""
|
|
76
|
+
if not turn_context:
|
|
77
|
+
raise TypeError("AttachmentPrompt.on_recognize(): context cannot be None.")
|
|
78
|
+
|
|
79
|
+
result = PromptRecognizerResult()
|
|
80
|
+
|
|
81
|
+
if turn_context.activity.type == ActivityTypes.message:
|
|
82
|
+
message = turn_context.activity
|
|
83
|
+
if isinstance(message.attachments, list) and message.attachments:
|
|
84
|
+
result.succeeded = True
|
|
85
|
+
result.value = message.attachments
|
|
86
|
+
|
|
87
|
+
return result
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from typing import Callable
|
|
5
|
+
|
|
6
|
+
from microsoft_agents.hosting.core import TurnContext
|
|
7
|
+
from microsoft_agents.activity import Activity, ActivityTypes
|
|
8
|
+
|
|
9
|
+
from ..choices import (
|
|
10
|
+
Choice,
|
|
11
|
+
ChoiceFactoryOptions,
|
|
12
|
+
ChoiceRecognizers,
|
|
13
|
+
FindChoicesOptions,
|
|
14
|
+
ListStyle,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from .prompt import Prompt
|
|
18
|
+
from .prompt_culture_models import PromptCultureModels
|
|
19
|
+
from .prompt_options import PromptOptions
|
|
20
|
+
from .prompt_validator_context import PromptValidatorContext
|
|
21
|
+
from .prompt_recognizer_result import PromptRecognizerResult
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ChoicePrompt(Prompt):
|
|
25
|
+
"""
|
|
26
|
+
Prompts a user to select from a list of choices.
|
|
27
|
+
|
|
28
|
+
By default the prompt will return to the calling dialog a `FoundChoice` object containing the choice that
|
|
29
|
+
was selected.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
_default_choice_options: dict[str, ChoiceFactoryOptions] = {
|
|
33
|
+
c.locale: ChoiceFactoryOptions(
|
|
34
|
+
inline_separator=c.separator,
|
|
35
|
+
inline_or=c.inline_or,
|
|
36
|
+
inline_or_more=c.inline_or_more,
|
|
37
|
+
include_numbers=True,
|
|
38
|
+
)
|
|
39
|
+
for c in PromptCultureModels.get_supported_cultures()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
dialog_id: str,
|
|
45
|
+
validator: Callable[[PromptValidatorContext], bool] | None = None,
|
|
46
|
+
default_locale: str | None = None,
|
|
47
|
+
choice_defaults: dict[str, ChoiceFactoryOptions] | None = None,
|
|
48
|
+
):
|
|
49
|
+
super().__init__(dialog_id, validator)
|
|
50
|
+
|
|
51
|
+
self.style = ListStyle.auto
|
|
52
|
+
self.default_locale = default_locale
|
|
53
|
+
self.choice_options: ChoiceFactoryOptions | None = None
|
|
54
|
+
self.recognizer_options: FindChoicesOptions | None = None
|
|
55
|
+
|
|
56
|
+
if choice_defaults is not None:
|
|
57
|
+
self._default_choice_options = choice_defaults
|
|
58
|
+
|
|
59
|
+
async def on_prompt(
|
|
60
|
+
self,
|
|
61
|
+
turn_context: TurnContext,
|
|
62
|
+
state: dict[str, object],
|
|
63
|
+
options: PromptOptions,
|
|
64
|
+
is_retry: bool,
|
|
65
|
+
):
|
|
66
|
+
if not turn_context:
|
|
67
|
+
raise TypeError("ChoicePrompt.on_prompt(): turn_context cannot be None.")
|
|
68
|
+
|
|
69
|
+
if not options:
|
|
70
|
+
raise TypeError("ChoicePrompt.on_prompt(): options cannot be None.")
|
|
71
|
+
|
|
72
|
+
# Determine culture
|
|
73
|
+
culture = self._determine_culture(turn_context.activity)
|
|
74
|
+
|
|
75
|
+
# Format prompt to send
|
|
76
|
+
choices: list[Choice] = options.choices if options.choices else []
|
|
77
|
+
channel_id: str = turn_context.activity.channel_id or ""
|
|
78
|
+
choice_options: ChoiceFactoryOptions = (
|
|
79
|
+
self.choice_options
|
|
80
|
+
if self.choice_options
|
|
81
|
+
else self._default_choice_options[culture]
|
|
82
|
+
)
|
|
83
|
+
choice_style = (
|
|
84
|
+
0 if options.style == 0 else options.style if options.style else self.style
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if is_retry and options.retry_prompt is not None:
|
|
88
|
+
prompt = self.append_choices(
|
|
89
|
+
options.retry_prompt, channel_id, choices, choice_style, choice_options
|
|
90
|
+
)
|
|
91
|
+
else:
|
|
92
|
+
prompt = self.append_choices(
|
|
93
|
+
options.prompt, channel_id, choices, choice_style, choice_options
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Send prompt
|
|
97
|
+
await turn_context.send_activity(prompt)
|
|
98
|
+
|
|
99
|
+
async def on_recognize(
|
|
100
|
+
self,
|
|
101
|
+
turn_context: TurnContext,
|
|
102
|
+
state: dict[str, object],
|
|
103
|
+
options: PromptOptions,
|
|
104
|
+
) -> PromptRecognizerResult:
|
|
105
|
+
if not turn_context:
|
|
106
|
+
raise TypeError("ChoicePrompt.on_recognize(): turn_context cannot be None.")
|
|
107
|
+
|
|
108
|
+
choices: list[Choice] = options.choices if (options and options.choices) else []
|
|
109
|
+
result: PromptRecognizerResult = PromptRecognizerResult()
|
|
110
|
+
|
|
111
|
+
if turn_context.activity.type == ActivityTypes.message:
|
|
112
|
+
activity: Activity = turn_context.activity
|
|
113
|
+
utterance: str = activity.text
|
|
114
|
+
if not utterance:
|
|
115
|
+
return result
|
|
116
|
+
opt: FindChoicesOptions = (
|
|
117
|
+
self.recognizer_options
|
|
118
|
+
if self.recognizer_options
|
|
119
|
+
else FindChoicesOptions()
|
|
120
|
+
)
|
|
121
|
+
opt.locale = self._determine_culture(turn_context.activity, opt)
|
|
122
|
+
results = ChoiceRecognizers.recognize_choices(utterance, choices, opt)
|
|
123
|
+
|
|
124
|
+
if results is not None and results:
|
|
125
|
+
result.succeeded = True
|
|
126
|
+
result.value = results[0].resolution
|
|
127
|
+
|
|
128
|
+
return result
|
|
129
|
+
|
|
130
|
+
def _determine_culture(
|
|
131
|
+
self, activity: Activity, opt: FindChoicesOptions | None = None
|
|
132
|
+
) -> str:
|
|
133
|
+
"""Resolves the culture/locale string to use for choice formatting and recognition.
|
|
134
|
+
|
|
135
|
+
Resolution order:
|
|
136
|
+
1. ``activity.locale`` (mapped to the nearest supported language)
|
|
137
|
+
2. ``self.default_locale`` set at construction
|
|
138
|
+
3. ``opt.locale`` from the recognizer options (if provided)
|
|
139
|
+
4. English (``en-us``) as the final fallback
|
|
140
|
+
|
|
141
|
+
If the resolved locale is not in ``_default_choice_options``, English is used.
|
|
142
|
+
|
|
143
|
+
:param activity: The incoming activity (provides ``locale``).
|
|
144
|
+
:param opt: Optional recogniser options (provides ``locale`` fallback).
|
|
145
|
+
:return: A locale string present in :attr:`_default_choice_options`.
|
|
146
|
+
"""
|
|
147
|
+
culture = (
|
|
148
|
+
PromptCultureModels.map_to_nearest_language(activity.locale)
|
|
149
|
+
or self.default_locale
|
|
150
|
+
or (opt.locale if opt else None)
|
|
151
|
+
or PromptCultureModels.English.locale
|
|
152
|
+
)
|
|
153
|
+
if not culture or not self._default_choice_options.get(culture):
|
|
154
|
+
culture = PromptCultureModels.English.locale
|
|
155
|
+
|
|
156
|
+
return culture
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from typing import Any, Callable
|
|
5
|
+
|
|
6
|
+
from recognizers_choice import recognize_boolean
|
|
7
|
+
|
|
8
|
+
from microsoft_agents.hosting.core import TurnContext
|
|
9
|
+
from microsoft_agents.activity import ActivityTypes, Activity
|
|
10
|
+
|
|
11
|
+
from ..choices import (
|
|
12
|
+
Choice,
|
|
13
|
+
ChoiceFactoryOptions,
|
|
14
|
+
ChoiceRecognizers,
|
|
15
|
+
ListStyle,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from .prompt import Prompt
|
|
19
|
+
from .prompt_culture_models import PromptCultureModels
|
|
20
|
+
from .prompt_options import PromptOptions
|
|
21
|
+
from .prompt_recognizer_result import PromptRecognizerResult
|
|
22
|
+
from .prompt_validator_context import PromptValidatorContext
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ConfirmPrompt(Prompt):
|
|
26
|
+
"""Prompts a user to confirm something with a yes/no response.
|
|
27
|
+
|
|
28
|
+
The prompt first attempts to recognise a boolean value using the
|
|
29
|
+
``recognizers_choice`` package (e.g. "yes", "no", "true", "false", locale
|
|
30
|
+
equivalents). If that fails and the prompt was configured to include
|
|
31
|
+
numbered choices (the default), it falls back to matching choice indices
|
|
32
|
+
("1" → yes, "2" → no).
|
|
33
|
+
|
|
34
|
+
Returns ``True`` for confirmation, ``False`` for denial.
|
|
35
|
+
|
|
36
|
+
Culture/locale detection uses :class:`PromptCultureModels`. Override
|
|
37
|
+
``default_locale`` or ``choice_defaults`` at construction time to customise
|
|
38
|
+
the displayed choices and the locale used for recognition.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
_default_choice_options: dict[str, tuple[Choice, Choice, ChoiceFactoryOptions]] = {
|
|
42
|
+
c.locale: (
|
|
43
|
+
Choice(c.yes_in_language),
|
|
44
|
+
Choice(c.no_in_language),
|
|
45
|
+
ChoiceFactoryOptions(c.separator, c.inline_or, c.inline_or_more, True),
|
|
46
|
+
)
|
|
47
|
+
for c in PromptCultureModels.get_supported_cultures()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
dialog_id: str,
|
|
53
|
+
validator: Callable[[PromptValidatorContext], Any] | None = None,
|
|
54
|
+
default_locale: str | None = None,
|
|
55
|
+
choice_defaults: (
|
|
56
|
+
dict[str, tuple[Choice, Choice, ChoiceFactoryOptions]] | None
|
|
57
|
+
) = None,
|
|
58
|
+
):
|
|
59
|
+
super().__init__(dialog_id, validator)
|
|
60
|
+
if dialog_id is None:
|
|
61
|
+
raise TypeError("ConfirmPrompt(): dialog_id cannot be None.")
|
|
62
|
+
self.style = ListStyle.auto
|
|
63
|
+
self.default_locale = default_locale
|
|
64
|
+
self.choice_options = None
|
|
65
|
+
self.confirm_choices = None
|
|
66
|
+
|
|
67
|
+
if choice_defaults is not None:
|
|
68
|
+
self._default_choice_options = choice_defaults
|
|
69
|
+
|
|
70
|
+
async def on_prompt(
|
|
71
|
+
self,
|
|
72
|
+
turn_context: TurnContext,
|
|
73
|
+
state: dict[str, object],
|
|
74
|
+
options: PromptOptions,
|
|
75
|
+
is_retry: bool,
|
|
76
|
+
):
|
|
77
|
+
if not turn_context:
|
|
78
|
+
raise TypeError("ConfirmPrompt.on_prompt(): turn_context cannot be None.")
|
|
79
|
+
if not options:
|
|
80
|
+
raise TypeError("ConfirmPrompt.on_prompt(): options cannot be None.")
|
|
81
|
+
|
|
82
|
+
# Format prompt to send
|
|
83
|
+
channel_id = turn_context.activity.channel_id or ""
|
|
84
|
+
culture = self._determine_culture(turn_context.activity)
|
|
85
|
+
defaults = self._default_choice_options[culture]
|
|
86
|
+
choice_opts = (
|
|
87
|
+
self.choice_options if self.choice_options is not None else defaults[2]
|
|
88
|
+
)
|
|
89
|
+
confirms = (
|
|
90
|
+
self.confirm_choices
|
|
91
|
+
if self.confirm_choices is not None
|
|
92
|
+
else (defaults[0], defaults[1])
|
|
93
|
+
)
|
|
94
|
+
choices = [confirms[0], confirms[1]]
|
|
95
|
+
if is_retry and options.retry_prompt is not None:
|
|
96
|
+
prompt = self.append_choices(
|
|
97
|
+
options.retry_prompt, channel_id, choices, self.style, choice_opts
|
|
98
|
+
)
|
|
99
|
+
else:
|
|
100
|
+
prompt = self.append_choices(
|
|
101
|
+
options.prompt, channel_id, choices, self.style, choice_opts
|
|
102
|
+
)
|
|
103
|
+
await turn_context.send_activity(prompt)
|
|
104
|
+
|
|
105
|
+
async def on_recognize(
|
|
106
|
+
self,
|
|
107
|
+
turn_context: TurnContext,
|
|
108
|
+
state: dict[str, object],
|
|
109
|
+
options: PromptOptions,
|
|
110
|
+
) -> PromptRecognizerResult:
|
|
111
|
+
if not turn_context:
|
|
112
|
+
raise TypeError("ConfirmPrompt.on_prompt(): turn_context cannot be None.")
|
|
113
|
+
|
|
114
|
+
result = PromptRecognizerResult()
|
|
115
|
+
if turn_context.activity.type == ActivityTypes.message:
|
|
116
|
+
# Recognize utterance
|
|
117
|
+
utterance = turn_context.activity.text
|
|
118
|
+
if not utterance:
|
|
119
|
+
return result
|
|
120
|
+
culture = self._determine_culture(turn_context.activity)
|
|
121
|
+
results = recognize_boolean(utterance, culture)
|
|
122
|
+
if results:
|
|
123
|
+
first = results[0]
|
|
124
|
+
if "value" in first.resolution:
|
|
125
|
+
result.succeeded = True
|
|
126
|
+
result.value = first.resolution["value"]
|
|
127
|
+
else:
|
|
128
|
+
# First check whether the prompt was sent to the user with numbers
|
|
129
|
+
defaults = self._default_choice_options[culture]
|
|
130
|
+
opts = (
|
|
131
|
+
self.choice_options
|
|
132
|
+
if self.choice_options is not None
|
|
133
|
+
else defaults[2]
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if opts.include_numbers is None or opts.include_numbers:
|
|
137
|
+
confirm_choices = (
|
|
138
|
+
self.confirm_choices
|
|
139
|
+
if self.confirm_choices is not None
|
|
140
|
+
else (defaults[0], defaults[1])
|
|
141
|
+
)
|
|
142
|
+
choices = [confirm_choices[0], confirm_choices[1]]
|
|
143
|
+
second_attempt_results = ChoiceRecognizers.recognize_choices(
|
|
144
|
+
utterance, choices
|
|
145
|
+
)
|
|
146
|
+
if second_attempt_results:
|
|
147
|
+
result.succeeded = True
|
|
148
|
+
result.value = second_attempt_results[0].resolution.index == 0
|
|
149
|
+
|
|
150
|
+
return result
|
|
151
|
+
|
|
152
|
+
def _determine_culture(self, activity: Activity) -> str:
|
|
153
|
+
culture = (
|
|
154
|
+
PromptCultureModels.map_to_nearest_language(activity.locale)
|
|
155
|
+
or self.default_locale
|
|
156
|
+
or PromptCultureModels.English.locale
|
|
157
|
+
)
|
|
158
|
+
if not culture or not self._default_choice_options.get(culture):
|
|
159
|
+
culture = PromptCultureModels.English.locale
|
|
160
|
+
|
|
161
|
+
return culture
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from typing import Any, Callable, cast
|
|
5
|
+
|
|
6
|
+
from recognizers_date_time import recognize_datetime
|
|
7
|
+
from recognizers_text import Culture
|
|
8
|
+
|
|
9
|
+
from microsoft_agents.hosting.core import TurnContext
|
|
10
|
+
from microsoft_agents.activity import ActivityTypes
|
|
11
|
+
|
|
12
|
+
from .datetime_resolution import DateTimeResolution
|
|
13
|
+
from .prompt import Prompt
|
|
14
|
+
from .prompt_options import PromptOptions
|
|
15
|
+
from .prompt_recognizer_result import PromptRecognizerResult
|
|
16
|
+
from .prompt_validator_context import PromptValidatorContext
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DateTimePrompt(Prompt):
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
dialog_id: str,
|
|
23
|
+
validator: Callable[[PromptValidatorContext], Any] | None = None,
|
|
24
|
+
default_locale: str | None = None,
|
|
25
|
+
):
|
|
26
|
+
super(DateTimePrompt, self).__init__(dialog_id, validator)
|
|
27
|
+
self.default_locale = default_locale
|
|
28
|
+
|
|
29
|
+
async def on_prompt(
|
|
30
|
+
self,
|
|
31
|
+
turn_context: TurnContext,
|
|
32
|
+
state: dict[str, object],
|
|
33
|
+
options: PromptOptions,
|
|
34
|
+
is_retry: bool,
|
|
35
|
+
):
|
|
36
|
+
if not turn_context:
|
|
37
|
+
raise TypeError("DateTimePrompt.on_prompt(): turn_context cannot be None.")
|
|
38
|
+
if not options:
|
|
39
|
+
raise TypeError("DateTimePrompt.on_prompt(): options cannot be None.")
|
|
40
|
+
|
|
41
|
+
if is_retry and options.retry_prompt is not None:
|
|
42
|
+
await turn_context.send_activity(options.retry_prompt)
|
|
43
|
+
else:
|
|
44
|
+
if options.prompt is not None:
|
|
45
|
+
await turn_context.send_activity(options.prompt)
|
|
46
|
+
|
|
47
|
+
async def on_recognize(
|
|
48
|
+
self,
|
|
49
|
+
turn_context: TurnContext,
|
|
50
|
+
state: dict[str, object],
|
|
51
|
+
options: PromptOptions,
|
|
52
|
+
) -> PromptRecognizerResult:
|
|
53
|
+
if not turn_context:
|
|
54
|
+
raise TypeError(
|
|
55
|
+
"DateTimePrompt.on_recognize(): turn_context cannot be None."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
result = PromptRecognizerResult()
|
|
59
|
+
if turn_context.activity.type == ActivityTypes.message:
|
|
60
|
+
# Recognize utterance
|
|
61
|
+
utterance = turn_context.activity.text
|
|
62
|
+
if not utterance:
|
|
63
|
+
return result
|
|
64
|
+
culture = (
|
|
65
|
+
turn_context.activity.locale or self.default_locale or Culture.English
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
results = recognize_datetime(utterance, culture)
|
|
69
|
+
if results:
|
|
70
|
+
result.succeeded = True
|
|
71
|
+
result.value = []
|
|
72
|
+
values = cast(list, results[0].resolution["values"])
|
|
73
|
+
for value in values:
|
|
74
|
+
cast(list, result.value).append(self.read_resolution(value))
|
|
75
|
+
|
|
76
|
+
return result
|
|
77
|
+
|
|
78
|
+
def read_resolution(self, resolution: dict[str, str]) -> DateTimeResolution:
|
|
79
|
+
result = DateTimeResolution()
|
|
80
|
+
|
|
81
|
+
if "timex" in resolution:
|
|
82
|
+
result.timex = resolution["timex"]
|
|
83
|
+
if "value" in resolution:
|
|
84
|
+
result.value = resolution["value"]
|
|
85
|
+
if "start" in resolution:
|
|
86
|
+
result.start = resolution["start"]
|
|
87
|
+
if "end" in resolution:
|
|
88
|
+
result.end = resolution["end"]
|
|
89
|
+
|
|
90
|
+
return result
|