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,563 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ..dialog_context import DialogContext
|
|
10
|
+
|
|
11
|
+
import builtins
|
|
12
|
+
|
|
13
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
14
|
+
from inspect import isawaitable
|
|
15
|
+
from traceback import print_tb
|
|
16
|
+
from typing import TypeVar, cast
|
|
17
|
+
|
|
18
|
+
from .scopes.memory_scope import MemoryScope
|
|
19
|
+
from .component_memory_scopes_base import ComponentMemoryScopesBase
|
|
20
|
+
from .component_path_resolvers_base import ComponentPathResolversBase
|
|
21
|
+
from .dialog_path import DialogPath
|
|
22
|
+
from .dialog_state_manager_configuration import DialogStateManagerConfiguration
|
|
23
|
+
|
|
24
|
+
# Declare type variable
|
|
25
|
+
T = TypeVar("T") # pylint: disable=invalid-name
|
|
26
|
+
|
|
27
|
+
BUILTIN_TYPES = list(filter(lambda x: not x.startswith("_"), dir(builtins)))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# <summary>
|
|
31
|
+
# The DialogStateManager manages memory scopes and pathresolvers
|
|
32
|
+
# MemoryScopes are named root level objects, which can exist either in the dialogcontext or off of turn state
|
|
33
|
+
# PathResolvers allow for shortcut behavior for mapping things like $foo -> dialog.foo.
|
|
34
|
+
# </summary>
|
|
35
|
+
class DialogStateManager:
|
|
36
|
+
SEPARATORS = [",", "["]
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
dialog_context: "DialogContext",
|
|
41
|
+
configuration: DialogStateManagerConfiguration | None = None,
|
|
42
|
+
):
|
|
43
|
+
"""
|
|
44
|
+
Initializes a new instance of the DialogStateManager class.
|
|
45
|
+
:param dialog_context: The dialog context for the current turn of the conversation.
|
|
46
|
+
:param configuration: Configuration for the dialog state manager. Default is None.
|
|
47
|
+
"""
|
|
48
|
+
# pylint: disable=import-outside-toplevel
|
|
49
|
+
# These modules are imported at static level to avoid circular dependency problems
|
|
50
|
+
from microsoft_agents.hosting.dialogs import (
|
|
51
|
+
DialogsComponentRegistration,
|
|
52
|
+
ObjectPath,
|
|
53
|
+
)
|
|
54
|
+
from microsoft_agents.hosting.dialogs._component_registration import (
|
|
55
|
+
ComponentRegistration,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
self._object_path_cls = ObjectPath
|
|
59
|
+
self._dialog_component_registration_cls = DialogsComponentRegistration
|
|
60
|
+
|
|
61
|
+
# Information for tracking when path was last modified.
|
|
62
|
+
self.path_tracker = "dialog._tracker.paths"
|
|
63
|
+
|
|
64
|
+
self._dialog_context = dialog_context
|
|
65
|
+
self._version: int = 0
|
|
66
|
+
|
|
67
|
+
if not dialog_context:
|
|
68
|
+
raise TypeError(f"Expecting: DialogContext, but received None")
|
|
69
|
+
|
|
70
|
+
from typing import cast as _cast # pylint: disable=import-outside-toplevel
|
|
71
|
+
|
|
72
|
+
self._configuration: DialogStateManagerConfiguration | None = (
|
|
73
|
+
configuration
|
|
74
|
+
or _cast(
|
|
75
|
+
DialogStateManagerConfiguration | None,
|
|
76
|
+
dialog_context.context.turn_state.get(
|
|
77
|
+
DialogStateManagerConfiguration.__name__, None
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
if not self._configuration:
|
|
82
|
+
self._configuration = DialogStateManagerConfiguration()
|
|
83
|
+
|
|
84
|
+
ComponentRegistration.add(self._dialog_component_registration_cls())
|
|
85
|
+
|
|
86
|
+
# get all of the component memory scopes
|
|
87
|
+
memory_component: ComponentMemoryScopesBase
|
|
88
|
+
for memory_component in filter(
|
|
89
|
+
lambda comp: isinstance(comp, ComponentMemoryScopesBase),
|
|
90
|
+
ComponentRegistration.get_components(),
|
|
91
|
+
):
|
|
92
|
+
for memory_scope in memory_component.get_memory_scopes():
|
|
93
|
+
self._configuration.memory_scopes.append(memory_scope)
|
|
94
|
+
|
|
95
|
+
# get all of the component path resolvers
|
|
96
|
+
path_component: ComponentPathResolversBase
|
|
97
|
+
for path_component in filter(
|
|
98
|
+
lambda comp: isinstance(comp, ComponentPathResolversBase),
|
|
99
|
+
ComponentRegistration.get_components(),
|
|
100
|
+
):
|
|
101
|
+
for path_resolver in path_component.get_path_resolvers():
|
|
102
|
+
self._configuration.path_resolvers.append(path_resolver)
|
|
103
|
+
|
|
104
|
+
# cache for any other new dialog_state_manager instances in this turn.
|
|
105
|
+
dialog_context.context.turn_state[self._configuration.__class__.__name__] = (
|
|
106
|
+
self._configuration
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def __len__(self) -> int:
|
|
110
|
+
"""
|
|
111
|
+
Gets the number of memory scopes in the dialog state manager.
|
|
112
|
+
"""
|
|
113
|
+
return len(self.configuration.memory_scopes)
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def configuration(self) -> DialogStateManagerConfiguration:
|
|
117
|
+
"""
|
|
118
|
+
Gets or sets the configured path resolvers and memory scopes for the dialog state manager.
|
|
119
|
+
"""
|
|
120
|
+
assert self._configuration is not None
|
|
121
|
+
return self._configuration
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def keys(self) -> Iterable[str]:
|
|
125
|
+
"""
|
|
126
|
+
Gets a Iterable containing the keys of the memory scopes
|
|
127
|
+
"""
|
|
128
|
+
return [memory_scope.name for memory_scope in self.configuration.memory_scopes]
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def values(self) -> Iterable[object]:
|
|
132
|
+
"""
|
|
133
|
+
Gets a Iterable containing the values of the memory scopes.
|
|
134
|
+
"""
|
|
135
|
+
return [
|
|
136
|
+
memory_scope.get_memory(self._dialog_context)
|
|
137
|
+
for memory_scope in self.configuration.memory_scopes
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def is_read_only(self) -> bool:
|
|
142
|
+
"""
|
|
143
|
+
Gets a value indicating whether the dialog state manager is read-only.
|
|
144
|
+
"""
|
|
145
|
+
return True
|
|
146
|
+
|
|
147
|
+
def __getitem__(self, key):
|
|
148
|
+
"""
|
|
149
|
+
:param key:
|
|
150
|
+
:return The value stored at key's position:
|
|
151
|
+
"""
|
|
152
|
+
return self.get_value(object, key, default_value=lambda: None)
|
|
153
|
+
|
|
154
|
+
def __setitem__(self, key, value):
|
|
155
|
+
if self._index_of_any(key, self.SEPARATORS) == -1:
|
|
156
|
+
# Root is handled by SetMemory rather than SetValue
|
|
157
|
+
scope = self.get_memory_scope(key)
|
|
158
|
+
if not scope:
|
|
159
|
+
raise IndexError(self._get_bad_scope_message(key))
|
|
160
|
+
scope.set_memory(self._dialog_context, value)
|
|
161
|
+
else:
|
|
162
|
+
self.set_value(key, value)
|
|
163
|
+
|
|
164
|
+
def _get_bad_scope_message(self, path: str) -> str:
|
|
165
|
+
return (
|
|
166
|
+
f"'{path}' does not match memory scopes:["
|
|
167
|
+
f"{', '.join((memory_scope.name for memory_scope in self.configuration.memory_scopes))}]"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
@staticmethod
|
|
171
|
+
def _index_of_any(string: str, elements_to_search_for) -> int:
|
|
172
|
+
for element in elements_to_search_for:
|
|
173
|
+
index = string.find(element)
|
|
174
|
+
if index != -1:
|
|
175
|
+
return index
|
|
176
|
+
|
|
177
|
+
return -1
|
|
178
|
+
|
|
179
|
+
def get_memory_scope(self, name: str) -> MemoryScope:
|
|
180
|
+
"""
|
|
181
|
+
Get MemoryScope by name.
|
|
182
|
+
"""
|
|
183
|
+
if not name:
|
|
184
|
+
raise TypeError(f"Expecting: {str.__name__}, but received None")
|
|
185
|
+
|
|
186
|
+
memory_scope = next(
|
|
187
|
+
(
|
|
188
|
+
memory_scope
|
|
189
|
+
for memory_scope in self.configuration.memory_scopes
|
|
190
|
+
if memory_scope.name.lower() == name.lower()
|
|
191
|
+
),
|
|
192
|
+
None,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if not memory_scope:
|
|
196
|
+
raise IndexError(self._get_bad_scope_message(name))
|
|
197
|
+
|
|
198
|
+
return memory_scope
|
|
199
|
+
|
|
200
|
+
def version(self) -> str:
|
|
201
|
+
"""
|
|
202
|
+
Version help caller to identify the updates and decide cache or not.
|
|
203
|
+
"""
|
|
204
|
+
return str(self._version)
|
|
205
|
+
|
|
206
|
+
def resolve_memory_scope(self, path: str) -> tuple[MemoryScope, str]:
|
|
207
|
+
"""
|
|
208
|
+
Will find the MemoryScope for and return the remaining path.
|
|
209
|
+
"""
|
|
210
|
+
scope = path
|
|
211
|
+
sep_index = -1
|
|
212
|
+
dot = path.find(".")
|
|
213
|
+
open_square_bracket = path.find("[")
|
|
214
|
+
|
|
215
|
+
if dot > 0 and open_square_bracket > 0:
|
|
216
|
+
sep_index = min(dot, open_square_bracket)
|
|
217
|
+
|
|
218
|
+
elif dot > 0:
|
|
219
|
+
sep_index = dot
|
|
220
|
+
|
|
221
|
+
elif open_square_bracket > 0:
|
|
222
|
+
sep_index = open_square_bracket
|
|
223
|
+
|
|
224
|
+
if sep_index > 0:
|
|
225
|
+
scope = path[0:sep_index]
|
|
226
|
+
memory_scope = self.get_memory_scope(scope)
|
|
227
|
+
if memory_scope:
|
|
228
|
+
remaining_path = path[sep_index + 1 :]
|
|
229
|
+
return memory_scope, remaining_path
|
|
230
|
+
|
|
231
|
+
memory_scope = self.get_memory_scope(scope)
|
|
232
|
+
if not scope:
|
|
233
|
+
raise IndexError(self._get_bad_scope_message(scope))
|
|
234
|
+
return memory_scope, ""
|
|
235
|
+
|
|
236
|
+
def transform_path(self, path: str) -> str:
|
|
237
|
+
"""
|
|
238
|
+
Transform the path using the registered PathTransformers.
|
|
239
|
+
"""
|
|
240
|
+
for path_resolver in self.configuration.path_resolvers:
|
|
241
|
+
path = path_resolver.transform_path(path)
|
|
242
|
+
|
|
243
|
+
return path
|
|
244
|
+
|
|
245
|
+
@staticmethod
|
|
246
|
+
def _is_primitive(type_to_check: type) -> bool:
|
|
247
|
+
return type_to_check.__name__ in BUILTIN_TYPES
|
|
248
|
+
|
|
249
|
+
def try_get_value(
|
|
250
|
+
self, path: str, class_type: type = object
|
|
251
|
+
) -> tuple[bool, object]:
|
|
252
|
+
"""
|
|
253
|
+
Get the value from memory using path expression (NOTE: This always returns clone of value).
|
|
254
|
+
"""
|
|
255
|
+
if not path:
|
|
256
|
+
raise TypeError(f"Expecting: {str.__name__}, but received None")
|
|
257
|
+
return_value = (
|
|
258
|
+
class_type() if DialogStateManager._is_primitive(class_type) else None
|
|
259
|
+
)
|
|
260
|
+
path = self.transform_path(path)
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
memory_scope, remaining_path = self.resolve_memory_scope(path)
|
|
264
|
+
except Exception as error:
|
|
265
|
+
print_tb(error.__traceback__)
|
|
266
|
+
return False, return_value
|
|
267
|
+
|
|
268
|
+
if not memory_scope:
|
|
269
|
+
return False, return_value
|
|
270
|
+
|
|
271
|
+
if not remaining_path:
|
|
272
|
+
memory = memory_scope.get_memory(self._dialog_context)
|
|
273
|
+
if not memory:
|
|
274
|
+
return False, return_value
|
|
275
|
+
|
|
276
|
+
return True, memory
|
|
277
|
+
|
|
278
|
+
# TODO: HACK to support .First() retrieval on turn.recognized.entities.foo, replace with Expressions once
|
|
279
|
+
# expressions ship
|
|
280
|
+
first = ".FIRST()"
|
|
281
|
+
try:
|
|
282
|
+
i_first = path.upper().rindex(first)
|
|
283
|
+
except ValueError:
|
|
284
|
+
i_first = -1
|
|
285
|
+
if i_first >= 0:
|
|
286
|
+
remaining_path = path[i_first + len(first) :]
|
|
287
|
+
path = path[0:i_first]
|
|
288
|
+
success, first_value = self._try_get_first_nested_value(path, self)
|
|
289
|
+
if success:
|
|
290
|
+
if not remaining_path:
|
|
291
|
+
return True, first_value
|
|
292
|
+
|
|
293
|
+
path_value = self._object_path_cls.try_get_path_value(
|
|
294
|
+
first_value, remaining_path
|
|
295
|
+
)
|
|
296
|
+
return bool(path_value), path_value
|
|
297
|
+
|
|
298
|
+
return False, return_value
|
|
299
|
+
|
|
300
|
+
path_value = self._object_path_cls.try_get_path_value(self, path)
|
|
301
|
+
return bool(path_value), path_value
|
|
302
|
+
|
|
303
|
+
def get_value(
|
|
304
|
+
self,
|
|
305
|
+
class_type: type,
|
|
306
|
+
path_expression: str,
|
|
307
|
+
default_value: Callable[[], T] | None = None,
|
|
308
|
+
) -> T | None:
|
|
309
|
+
"""
|
|
310
|
+
Get the value from memory using path expression (NOTE: This always returns clone of value).
|
|
311
|
+
"""
|
|
312
|
+
if not path_expression:
|
|
313
|
+
raise TypeError(f"Expecting: {str.__name__}, but received None")
|
|
314
|
+
|
|
315
|
+
success, value = self.try_get_value(path_expression, class_type)
|
|
316
|
+
if success:
|
|
317
|
+
return cast(T, value)
|
|
318
|
+
|
|
319
|
+
return default_value() if default_value else None
|
|
320
|
+
|
|
321
|
+
def get_int_value(self, path_expression: str, default_value: int = 0) -> int:
|
|
322
|
+
"""
|
|
323
|
+
Get an int value from memory using a path expression.
|
|
324
|
+
"""
|
|
325
|
+
if not path_expression:
|
|
326
|
+
raise TypeError(f"Expecting: {str.__name__}, but received None")
|
|
327
|
+
success, value = self.try_get_value(path_expression, int)
|
|
328
|
+
if success:
|
|
329
|
+
return cast(int, value)
|
|
330
|
+
|
|
331
|
+
return default_value
|
|
332
|
+
|
|
333
|
+
def get_bool_value(self, path_expression: str, default_value: bool = False) -> bool:
|
|
334
|
+
"""
|
|
335
|
+
Get a bool value from memory using a path expression.
|
|
336
|
+
"""
|
|
337
|
+
if not path_expression:
|
|
338
|
+
raise TypeError(f"Expecting: {str.__name__}, but received None")
|
|
339
|
+
success, value = self.try_get_value(path_expression, bool)
|
|
340
|
+
if success:
|
|
341
|
+
return cast(bool, value)
|
|
342
|
+
|
|
343
|
+
return default_value
|
|
344
|
+
|
|
345
|
+
def get_string_value(self, path_expression: str, default_value: str = "") -> str:
|
|
346
|
+
"""
|
|
347
|
+
Get a string value from memory using a path expression.
|
|
348
|
+
"""
|
|
349
|
+
if not path_expression:
|
|
350
|
+
raise TypeError(f"Expecting: {str.__name__}, but received None")
|
|
351
|
+
success, value = self.try_get_value(path_expression, str)
|
|
352
|
+
if success:
|
|
353
|
+
return cast(str, value)
|
|
354
|
+
|
|
355
|
+
return default_value
|
|
356
|
+
|
|
357
|
+
def set_value(self, path: str, value: object):
|
|
358
|
+
"""
|
|
359
|
+
Set memory to value.
|
|
360
|
+
"""
|
|
361
|
+
if isawaitable(value):
|
|
362
|
+
raise Exception(f"{path} = You can't pass an awaitable to set_value")
|
|
363
|
+
|
|
364
|
+
if not path:
|
|
365
|
+
raise TypeError(f"Expecting: {str.__name__}, but received None")
|
|
366
|
+
|
|
367
|
+
path = self.transform_path(path)
|
|
368
|
+
if self._track_change(path, value):
|
|
369
|
+
self._object_path_cls.set_path_value(self, path, value)
|
|
370
|
+
|
|
371
|
+
# Every set will increase version
|
|
372
|
+
self._version += 1
|
|
373
|
+
|
|
374
|
+
def remove_value(self, path: str):
|
|
375
|
+
"""
|
|
376
|
+
Remove memory at the given path.
|
|
377
|
+
"""
|
|
378
|
+
if not path:
|
|
379
|
+
raise TypeError(f"Expecting: {str.__name__}, but received None")
|
|
380
|
+
|
|
381
|
+
path = self.transform_path(path)
|
|
382
|
+
if self._track_change(path, None):
|
|
383
|
+
self._object_path_cls.remove_path_value(self, path)
|
|
384
|
+
|
|
385
|
+
def get_memory_snapshot(self) -> dict[str, object]:
|
|
386
|
+
"""
|
|
387
|
+
Gets all memoryscopes suitable for logging.
|
|
388
|
+
"""
|
|
389
|
+
result = {}
|
|
390
|
+
|
|
391
|
+
for scope in [
|
|
392
|
+
ms for ms in self.configuration.memory_scopes if ms.include_in_snapshot
|
|
393
|
+
]:
|
|
394
|
+
memory = scope.get_memory(self._dialog_context)
|
|
395
|
+
if memory:
|
|
396
|
+
result[scope.name] = memory
|
|
397
|
+
|
|
398
|
+
return result
|
|
399
|
+
|
|
400
|
+
async def load_all_scopes(self):
|
|
401
|
+
"""
|
|
402
|
+
Load all of the scopes.
|
|
403
|
+
"""
|
|
404
|
+
for scope in self.configuration.memory_scopes:
|
|
405
|
+
await scope.load(self._dialog_context)
|
|
406
|
+
|
|
407
|
+
async def save_all_changes(self):
|
|
408
|
+
"""
|
|
409
|
+
Save all changes for all scopes.
|
|
410
|
+
"""
|
|
411
|
+
for scope in self.configuration.memory_scopes:
|
|
412
|
+
await scope.save_changes(self._dialog_context)
|
|
413
|
+
|
|
414
|
+
async def delete_scopes_memory_async(self, name: str):
|
|
415
|
+
"""
|
|
416
|
+
Delete the memory for a scope.
|
|
417
|
+
"""
|
|
418
|
+
name = name.upper()
|
|
419
|
+
scope_list = [
|
|
420
|
+
ms for ms in self.configuration.memory_scopes if ms.name.upper() == name
|
|
421
|
+
]
|
|
422
|
+
if len(scope_list) > 1:
|
|
423
|
+
raise RuntimeError(f"More than 1 scopes found with the name '{name}'")
|
|
424
|
+
scope = scope_list[0] if scope_list else None
|
|
425
|
+
if scope:
|
|
426
|
+
await scope.delete(self._dialog_context)
|
|
427
|
+
|
|
428
|
+
def add(self, key: str, value: object):
|
|
429
|
+
raise RuntimeError("Not supported")
|
|
430
|
+
|
|
431
|
+
def contains_key(self, key: str) -> bool:
|
|
432
|
+
scopes_with_key = [
|
|
433
|
+
ms
|
|
434
|
+
for ms in self.configuration.memory_scopes
|
|
435
|
+
if ms.name.upper() == key.upper()
|
|
436
|
+
]
|
|
437
|
+
return bool(scopes_with_key)
|
|
438
|
+
|
|
439
|
+
def remove(self, key: str):
|
|
440
|
+
raise RuntimeError("Not supported")
|
|
441
|
+
|
|
442
|
+
def clear(self, key: str):
|
|
443
|
+
raise RuntimeError("Not supported")
|
|
444
|
+
|
|
445
|
+
def contains(self, item: tuple[str, object]) -> bool:
|
|
446
|
+
raise RuntimeError("Not supported")
|
|
447
|
+
|
|
448
|
+
def __contains__(self, item: tuple[str, object]) -> bool:
|
|
449
|
+
raise RuntimeError("Not supported")
|
|
450
|
+
|
|
451
|
+
def copy_to(self, array: list[tuple[str, object]], array_index: int):
|
|
452
|
+
for memory_scope in self.configuration.memory_scopes:
|
|
453
|
+
array[array_index] = (
|
|
454
|
+
memory_scope.name,
|
|
455
|
+
memory_scope.get_memory(self._dialog_context),
|
|
456
|
+
)
|
|
457
|
+
array_index += 1
|
|
458
|
+
|
|
459
|
+
def remove_item(self, item: tuple[str, object]) -> bool:
|
|
460
|
+
raise RuntimeError("Not supported")
|
|
461
|
+
|
|
462
|
+
def get_enumerator(self) -> Iterator[tuple[str, object]]:
|
|
463
|
+
for memory_scope in self.configuration.memory_scopes:
|
|
464
|
+
yield (memory_scope.name, memory_scope.get_memory(self._dialog_context))
|
|
465
|
+
|
|
466
|
+
def track_paths(self, paths: Iterable[str]) -> list[str]:
|
|
467
|
+
"""
|
|
468
|
+
Track when specific paths are changed.
|
|
469
|
+
"""
|
|
470
|
+
all_paths = []
|
|
471
|
+
for path in paths:
|
|
472
|
+
t_path = self.transform_path(path)
|
|
473
|
+
|
|
474
|
+
# Track any path that resolves to a constant path
|
|
475
|
+
segments = self._object_path_cls.try_resolve_path(self, t_path)
|
|
476
|
+
if segments:
|
|
477
|
+
n_path = "_".join(segments)
|
|
478
|
+
self.set_value(self.path_tracker + "." + n_path, 0)
|
|
479
|
+
all_paths.append(n_path)
|
|
480
|
+
|
|
481
|
+
return all_paths
|
|
482
|
+
|
|
483
|
+
def any_path_changed(self, counter: int, paths: Iterable[str]) -> bool:
|
|
484
|
+
"""
|
|
485
|
+
Check to see if any path has changed since watermark.
|
|
486
|
+
"""
|
|
487
|
+
found = False
|
|
488
|
+
if paths:
|
|
489
|
+
for path in paths:
|
|
490
|
+
if self.get_int_value(self.path_tracker + "." + path) > counter:
|
|
491
|
+
found = True
|
|
492
|
+
break
|
|
493
|
+
|
|
494
|
+
return found
|
|
495
|
+
|
|
496
|
+
def __iter__(self):
|
|
497
|
+
for memory_scope in self.configuration.memory_scopes:
|
|
498
|
+
yield (memory_scope.name, memory_scope.get_memory(self._dialog_context))
|
|
499
|
+
|
|
500
|
+
@staticmethod
|
|
501
|
+
def _try_get_first_nested_value(
|
|
502
|
+
remaining_path: str, memory: object
|
|
503
|
+
) -> tuple[bool, object]:
|
|
504
|
+
# pylint: disable=import-outside-toplevel
|
|
505
|
+
from microsoft_agents.hosting.dialogs import ObjectPath
|
|
506
|
+
|
|
507
|
+
array = ObjectPath.try_get_path_value(memory, remaining_path)
|
|
508
|
+
if array and isinstance(array, list):
|
|
509
|
+
if isinstance(array[0], list):
|
|
510
|
+
first = array[0]
|
|
511
|
+
if first:
|
|
512
|
+
second = first[0]
|
|
513
|
+
return True, second
|
|
514
|
+
|
|
515
|
+
return False, None
|
|
516
|
+
|
|
517
|
+
return True, array[0]
|
|
518
|
+
|
|
519
|
+
return False, None
|
|
520
|
+
|
|
521
|
+
def _track_change(self, path: str, value: object) -> bool:
|
|
522
|
+
has_path = False
|
|
523
|
+
segments = self._object_path_cls.try_resolve_path(self, path)
|
|
524
|
+
if segments:
|
|
525
|
+
root = segments[1] if len(segments) > 1 else ""
|
|
526
|
+
|
|
527
|
+
# Skip _* as first scope, i.e. _adaptive, _tracker, ...
|
|
528
|
+
if not root.startswith("_"):
|
|
529
|
+
# Convert to a simple path with _ between segments
|
|
530
|
+
path_name = "_".join(segments)
|
|
531
|
+
tracked_path = f"{self.path_tracker}.{path_name}"
|
|
532
|
+
counter = None
|
|
533
|
+
|
|
534
|
+
def update():
|
|
535
|
+
nonlocal counter
|
|
536
|
+
last_changed = self.try_get_value(tracked_path, int)
|
|
537
|
+
if last_changed:
|
|
538
|
+
if counter is not None:
|
|
539
|
+
counter = self.get_value(int, DialogPath.EVENT_COUNTER)
|
|
540
|
+
|
|
541
|
+
self.set_value(tracked_path, counter)
|
|
542
|
+
|
|
543
|
+
update()
|
|
544
|
+
if not self._is_primitive(type(value)):
|
|
545
|
+
# For an object we need to see if any children path are being tracked
|
|
546
|
+
def check_children(property: str, instance: object):
|
|
547
|
+
nonlocal tracked_path
|
|
548
|
+
# Add new child segment
|
|
549
|
+
tracked_path += "_" + property.lower()
|
|
550
|
+
update()
|
|
551
|
+
if not self._is_primitive(type(instance)):
|
|
552
|
+
self._object_path_cls.for_each_property(
|
|
553
|
+
property, check_children
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
# Remove added child segment
|
|
557
|
+
tracked_path = tracked_path[: tracked_path.rfind("_")]
|
|
558
|
+
|
|
559
|
+
self._object_path_cls.for_each_property(value, check_children)
|
|
560
|
+
|
|
561
|
+
has_path = True
|
|
562
|
+
|
|
563
|
+
return has_path
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
|
|
3
|
+
from .scopes.memory_scope import MemoryScope
|
|
4
|
+
from .path_resolver_base import PathResolverBase
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class DialogStateManagerConfiguration:
|
|
9
|
+
|
|
10
|
+
path_resolvers: list[PathResolverBase] = field(default_factory=list)
|
|
11
|
+
memory_scopes: list[MemoryScope] = field(default_factory=list)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from .alias_path_resolver import AliasPathResolver
|
|
6
|
+
from .at_at_path_resolver import AtAtPathResolver
|
|
7
|
+
from .at_path_resolver import AtPathResolver
|
|
8
|
+
from .dollar_path_resolver import DollarPathResolver
|
|
9
|
+
from .hash_path_resolver import HashPathResolver
|
|
10
|
+
from .percent_path_resolver import PercentPathResolver
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"AliasPathResolver",
|
|
14
|
+
"AtAtPathResolver",
|
|
15
|
+
"AtPathResolver",
|
|
16
|
+
"DollarPathResolver",
|
|
17
|
+
"HashPathResolver",
|
|
18
|
+
"PercentPathResolver",
|
|
19
|
+
]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from ..path_resolver_base import PathResolverBase
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AliasPathResolver(PathResolverBase):
|
|
8
|
+
def __init__(self, alias: str, prefix: str, postfix: str = ""):
|
|
9
|
+
"""
|
|
10
|
+
Initializes a new instance of the <see cref="AliasPathResolver"/> class.
|
|
11
|
+
<param name="alias">Alias name.</param>
|
|
12
|
+
<param name="prefix">Prefix name.</param>
|
|
13
|
+
<param name="postfix">Postfix name.</param>
|
|
14
|
+
"""
|
|
15
|
+
if alias is None:
|
|
16
|
+
raise TypeError(f"Expecting: alias, but received None")
|
|
17
|
+
if prefix is None:
|
|
18
|
+
raise TypeError(f"Expecting: prefix, but received None")
|
|
19
|
+
|
|
20
|
+
# Gets the alias name.
|
|
21
|
+
self.alias = alias.strip()
|
|
22
|
+
self._prefix = prefix.strip()
|
|
23
|
+
self._postfix = postfix.strip()
|
|
24
|
+
|
|
25
|
+
def transform_path(self, path: str):
|
|
26
|
+
"""
|
|
27
|
+
Transforms the path.
|
|
28
|
+
<param name="path">Path to inspect.</param>
|
|
29
|
+
<returns>Transformed path.</returns>
|
|
30
|
+
"""
|
|
31
|
+
if not path:
|
|
32
|
+
raise TypeError(f"Expecting: path, but received None")
|
|
33
|
+
|
|
34
|
+
path = path.strip()
|
|
35
|
+
if (
|
|
36
|
+
path.startswith(self.alias)
|
|
37
|
+
and len(path) > len(self.alias)
|
|
38
|
+
and AliasPathResolver._is_path_char(path[len(self.alias)])
|
|
39
|
+
):
|
|
40
|
+
# here we only deals with trailing alias, alias in middle be handled in further breakdown
|
|
41
|
+
# $xxx -> path.xxx
|
|
42
|
+
return f"{self._prefix}{path[len(self.alias):]}{self._postfix}".rstrip(".")
|
|
43
|
+
|
|
44
|
+
return path
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def _is_path_char(char: str) -> bool:
|
|
48
|
+
"""
|
|
49
|
+
Verifies if a character is valid for a path.
|
|
50
|
+
<param name="ch">Character to verify.</param>
|
|
51
|
+
<returns><c>true</c> if the character is valid for a path otherwise, <c>false</c>.</returns>
|
|
52
|
+
"""
|
|
53
|
+
return len(char) == 1 and (char.isalpha() or char == "_")
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from .alias_path_resolver import AliasPathResolver
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AtAtPathResolver(AliasPathResolver):
|
|
8
|
+
def __init__(self):
|
|
9
|
+
super().__init__(alias="@@", prefix="turn.recognized.entities.")
|