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,148 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from collections.abc import Iterable
|
|
5
|
+
|
|
6
|
+
from recognizers_number import NumberModel, NumberRecognizer, OrdinalModel
|
|
7
|
+
from recognizers_text import Culture
|
|
8
|
+
|
|
9
|
+
from typing import cast
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from .models.choice import Choice
|
|
13
|
+
from .find import Find
|
|
14
|
+
from .models.find_choices_options import FindChoicesOptions
|
|
15
|
+
from .models.found_choice import FoundChoice
|
|
16
|
+
from .models.model_result import ModelResult
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ChoiceRecognizers:
|
|
20
|
+
"""Contains methods for matching user input against a list of choices."""
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def recognize_choices(
|
|
24
|
+
utterance: str,
|
|
25
|
+
choices: Iterable[str | Choice],
|
|
26
|
+
options: FindChoicesOptions | None = None,
|
|
27
|
+
) -> list[ModelResult]:
|
|
28
|
+
"""
|
|
29
|
+
Matches user input against a list of choices.
|
|
30
|
+
|
|
31
|
+
This is layered above the `Find.find_choices()` function, and adds logic to let the user specify
|
|
32
|
+
their choice by index (they can say "one" to pick `choice[0]`) or ordinal position
|
|
33
|
+
(they can say "the second one" to pick `choice[1]`.)
|
|
34
|
+
The user's utterance is recognized in the following order:
|
|
35
|
+
|
|
36
|
+
- By name using `find_choices()`
|
|
37
|
+
- By 1's based ordinal position.
|
|
38
|
+
- By 1's based index position.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
-----------
|
|
42
|
+
|
|
43
|
+
utterance: The input.
|
|
44
|
+
|
|
45
|
+
choices: The list of choices.
|
|
46
|
+
|
|
47
|
+
options: (Optional) Options to control the recognition strategy.
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
--------
|
|
51
|
+
A list of found choices, sorted by most relevant first.
|
|
52
|
+
"""
|
|
53
|
+
if utterance is None:
|
|
54
|
+
utterance = ""
|
|
55
|
+
|
|
56
|
+
# Normalize list of choices
|
|
57
|
+
choices_list = [
|
|
58
|
+
Choice(value=choice) if isinstance(choice, str) else choice
|
|
59
|
+
for choice in choices
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
# Try finding choices by text search first
|
|
63
|
+
# - We only want to use a single strategy for returning results to avoid issues where utterances
|
|
64
|
+
# like the "the third one" or "the red one" or "the first division book" would miss-recognize as
|
|
65
|
+
# a numerical index or ordinal as well.
|
|
66
|
+
locale = options.locale if (options and options.locale) else Culture.English
|
|
67
|
+
matched = Find.find_choices(utterance, choices_list, options)
|
|
68
|
+
if not matched:
|
|
69
|
+
matches = []
|
|
70
|
+
|
|
71
|
+
if not options or options.recognize_ordinals:
|
|
72
|
+
# Next try finding by ordinal
|
|
73
|
+
matches = ChoiceRecognizers._recognize_ordinal(utterance, locale)
|
|
74
|
+
for match in matches:
|
|
75
|
+
ChoiceRecognizers._match_choice_by_index(
|
|
76
|
+
choices_list, matched, match
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if not matches and (not options or options.recognize_numbers):
|
|
80
|
+
# Then try by numerical index
|
|
81
|
+
matches = ChoiceRecognizers._recognize_number(utterance, locale)
|
|
82
|
+
for match in matches:
|
|
83
|
+
ChoiceRecognizers._match_choice_by_index(
|
|
84
|
+
choices_list, matched, match
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Sort any found matches by their position within the utterance.
|
|
88
|
+
# - The results from find_choices() are already properly sorted so we just need this
|
|
89
|
+
# for ordinal & numerical lookups.
|
|
90
|
+
matched = sorted(matched, key=lambda model_result: model_result.start)
|
|
91
|
+
|
|
92
|
+
return matched
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def _recognize_ordinal(utterance: str, culture: str) -> list[ModelResult]:
|
|
96
|
+
model: OrdinalModel = cast(
|
|
97
|
+
OrdinalModel, NumberRecognizer(culture).get_ordinal_model(culture)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
return list(
|
|
101
|
+
map(ChoiceRecognizers._found_choice_constructor, model.parse(utterance)) # type: ignore[arg-type]
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
@staticmethod
|
|
105
|
+
def _match_choice_by_index(
|
|
106
|
+
choices: list[Choice], matched: list[ModelResult], match: ModelResult
|
|
107
|
+
):
|
|
108
|
+
try:
|
|
109
|
+
index: int = int(match.resolution.value) - 1
|
|
110
|
+
if 0 <= index < len(choices):
|
|
111
|
+
choice = choices[index]
|
|
112
|
+
|
|
113
|
+
matched.append(
|
|
114
|
+
ModelResult(
|
|
115
|
+
start=match.start,
|
|
116
|
+
end=match.end,
|
|
117
|
+
type_name="choice",
|
|
118
|
+
text=match.text,
|
|
119
|
+
resolution=FoundChoice(
|
|
120
|
+
value=choice.value, index=index, score=1.0
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
except:
|
|
125
|
+
# noop here, as in dotnet/node repos
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
def _recognize_number(utterance: str, culture: str) -> list[ModelResult]:
|
|
130
|
+
model: NumberModel = cast(
|
|
131
|
+
NumberModel, NumberRecognizer(culture).get_number_model(culture)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return list(
|
|
135
|
+
map(ChoiceRecognizers._found_choice_constructor, model.parse(utterance)) # type: ignore[arg-type]
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
@staticmethod
|
|
139
|
+
def _found_choice_constructor(value_model: ModelResult) -> ModelResult:
|
|
140
|
+
return ModelResult(
|
|
141
|
+
start=value_model.start,
|
|
142
|
+
end=value_model.end,
|
|
143
|
+
type_name="choice",
|
|
144
|
+
text=value_model.text,
|
|
145
|
+
resolution=FoundChoice(
|
|
146
|
+
value=value_model.resolution["value"], index=0, score=1.0
|
|
147
|
+
),
|
|
148
|
+
)
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from typing import Callable
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
|
|
7
|
+
from .models.choice import Choice
|
|
8
|
+
from .models.find_choices_options import FindChoicesOptions, FindValuesOptions
|
|
9
|
+
from .models.found_choice import FoundChoice
|
|
10
|
+
from .models.found_value import FoundValue
|
|
11
|
+
from .models.model_result import ModelResult
|
|
12
|
+
from .models.sorted_value import SortedValue
|
|
13
|
+
from .models.token import Token
|
|
14
|
+
from .tokenizer import Tokenizer
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Find:
|
|
18
|
+
"""Contains methods for matching user input against a list of choices"""
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def find_choices(
|
|
22
|
+
utterance: str,
|
|
23
|
+
choices: Iterable[str | Choice],
|
|
24
|
+
options: FindChoicesOptions | None = None,
|
|
25
|
+
) -> list[ModelResult]:
|
|
26
|
+
"""Matches user input against a list of choices"""
|
|
27
|
+
|
|
28
|
+
if not choices:
|
|
29
|
+
raise TypeError("Find: choices cannot be None.")
|
|
30
|
+
|
|
31
|
+
opt = options or FindChoicesOptions()
|
|
32
|
+
|
|
33
|
+
# Normalize list of choices
|
|
34
|
+
choices_list = [
|
|
35
|
+
Choice(value=choice) if isinstance(choice, str) else choice
|
|
36
|
+
for choice in choices
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
# Build up full list of synonyms to search over.
|
|
40
|
+
# - Each entry in the list contains the index of the choice it belongs to which will later be
|
|
41
|
+
# used to map the search results back to their choice.
|
|
42
|
+
synonyms: list[SortedValue] = []
|
|
43
|
+
|
|
44
|
+
for index, choice in enumerate(choices_list):
|
|
45
|
+
if not opt.no_value:
|
|
46
|
+
synonyms.append(SortedValue(value=choice.value, index=index))
|
|
47
|
+
|
|
48
|
+
if choice.action and choice.action.title and not opt.no_action:
|
|
49
|
+
synonyms.append(SortedValue(value=choice.action.title, index=index))
|
|
50
|
+
|
|
51
|
+
if choice.synonyms is not None:
|
|
52
|
+
for synonym in choice.synonyms:
|
|
53
|
+
synonyms.append(SortedValue(value=synonym, index=index))
|
|
54
|
+
|
|
55
|
+
def found_choice_constructor(value_model: ModelResult) -> ModelResult:
|
|
56
|
+
choice = choices_list[value_model.resolution.index]
|
|
57
|
+
|
|
58
|
+
return ModelResult(
|
|
59
|
+
start=value_model.start,
|
|
60
|
+
end=value_model.end,
|
|
61
|
+
type_name="choice",
|
|
62
|
+
text=value_model.text,
|
|
63
|
+
resolution=FoundChoice(
|
|
64
|
+
value=choice.value,
|
|
65
|
+
index=value_model.resolution.index,
|
|
66
|
+
score=value_model.resolution.score,
|
|
67
|
+
synonym=value_model.resolution.value,
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Find synonyms in utterance and map back to their choices_list
|
|
72
|
+
return list(
|
|
73
|
+
map(found_choice_constructor, Find.find_values(utterance, synonyms, opt))
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def find_values(
|
|
78
|
+
utterance: str,
|
|
79
|
+
values: list[SortedValue],
|
|
80
|
+
options: FindValuesOptions | None = None,
|
|
81
|
+
) -> list[ModelResult]:
|
|
82
|
+
# Sort values in descending order by length, so that the longest value is searchd over first.
|
|
83
|
+
sorted_values = sorted(
|
|
84
|
+
values, key=lambda sorted_val: len(sorted_val.value), reverse=True
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Search for each value within the utterance.
|
|
88
|
+
matches: list[ModelResult] = []
|
|
89
|
+
opt = options if options else FindValuesOptions()
|
|
90
|
+
tokenizer: Callable[[str, str | None], list[Token]] = (
|
|
91
|
+
opt.tokenizer if opt.tokenizer else Tokenizer.default_tokenizer
|
|
92
|
+
)
|
|
93
|
+
tokens = tokenizer(utterance, opt.locale)
|
|
94
|
+
max_distance = (
|
|
95
|
+
opt.max_token_distance if opt.max_token_distance is not None else 2
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
for entry in sorted_values:
|
|
99
|
+
# Find all matches for a value
|
|
100
|
+
# - To match "last one" in "the last time I chose the last one" we need
|
|
101
|
+
# to re-search the string starting from the end of the previous match.
|
|
102
|
+
# - The start & end position returned for the match are token positions.
|
|
103
|
+
start_pos = 0
|
|
104
|
+
searched_tokens = tokenizer(entry.value.strip(), opt.locale)
|
|
105
|
+
|
|
106
|
+
while start_pos < len(tokens):
|
|
107
|
+
match: ModelResult | None = Find._match_value(
|
|
108
|
+
tokens,
|
|
109
|
+
max_distance,
|
|
110
|
+
opt,
|
|
111
|
+
entry.index,
|
|
112
|
+
entry.value,
|
|
113
|
+
searched_tokens,
|
|
114
|
+
start_pos,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if match is not None:
|
|
118
|
+
start_pos = match.end + 1
|
|
119
|
+
matches.append(match)
|
|
120
|
+
else:
|
|
121
|
+
break
|
|
122
|
+
|
|
123
|
+
# Sort matches by score descending
|
|
124
|
+
sorted_matches = sorted(
|
|
125
|
+
matches,
|
|
126
|
+
key=lambda model_result: model_result.resolution.score,
|
|
127
|
+
reverse=True,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Filter out duplicate matching indexes and overlapping characters
|
|
131
|
+
# - The start & end positions are token positions and need to be translated to
|
|
132
|
+
# character positions before returning. We also need to populate the "text"
|
|
133
|
+
# field as well.
|
|
134
|
+
results: list[ModelResult] = []
|
|
135
|
+
found_indexes = set()
|
|
136
|
+
used_tokens = set()
|
|
137
|
+
|
|
138
|
+
for match in sorted_matches:
|
|
139
|
+
# Apply filters.
|
|
140
|
+
add = match.resolution.index not in found_indexes
|
|
141
|
+
|
|
142
|
+
for i in range(match.start, match.end + 1):
|
|
143
|
+
if i in used_tokens:
|
|
144
|
+
add = False
|
|
145
|
+
break
|
|
146
|
+
|
|
147
|
+
# Add to results
|
|
148
|
+
if add:
|
|
149
|
+
# Update filter info
|
|
150
|
+
found_indexes.add(match.resolution.index)
|
|
151
|
+
|
|
152
|
+
for i in range(match.start, match.end + 1):
|
|
153
|
+
used_tokens.add(i)
|
|
154
|
+
|
|
155
|
+
# Translate start & end and populate text field
|
|
156
|
+
match.start = tokens[match.start].start
|
|
157
|
+
match.end = tokens[match.end].end
|
|
158
|
+
match.text = utterance[match.start : match.end + 1]
|
|
159
|
+
results.append(match)
|
|
160
|
+
|
|
161
|
+
# Return the results sorted by position in the utterance
|
|
162
|
+
return sorted(results, key=lambda model_result: model_result.start)
|
|
163
|
+
|
|
164
|
+
@staticmethod
|
|
165
|
+
def _match_value(
|
|
166
|
+
source_tokens: list[Token],
|
|
167
|
+
max_distance: int,
|
|
168
|
+
options: FindValuesOptions,
|
|
169
|
+
index: int,
|
|
170
|
+
value: str,
|
|
171
|
+
searched_tokens: list[Token],
|
|
172
|
+
start_pos: int,
|
|
173
|
+
) -> ModelResult | None:
|
|
174
|
+
# Match value to utterance and calculate total deviation.
|
|
175
|
+
# - The tokens are matched in order so "second last" will match in
|
|
176
|
+
# "the second from last one" but not in "the last from the second one".
|
|
177
|
+
# - The total deviation is a count of the number of tokens skipped in the
|
|
178
|
+
# match so for the example above the number of tokens matched would be
|
|
179
|
+
# 2 and the total deviation would be 1.
|
|
180
|
+
matched = 0
|
|
181
|
+
total_deviation = 0
|
|
182
|
+
start = -1
|
|
183
|
+
end = -1
|
|
184
|
+
|
|
185
|
+
for token in searched_tokens:
|
|
186
|
+
# Find the position of the token in the utterance.
|
|
187
|
+
pos = Find._index_of_token(source_tokens, token, start_pos)
|
|
188
|
+
if pos >= 0:
|
|
189
|
+
# Calculate the distance between the current token's position and the previous token's distance.
|
|
190
|
+
distance = pos - start_pos if matched > 0 else 0
|
|
191
|
+
if distance <= max_distance:
|
|
192
|
+
# Update count of tokens matched and move start pointer to search for next token
|
|
193
|
+
# after the current token
|
|
194
|
+
matched += 1
|
|
195
|
+
total_deviation += distance
|
|
196
|
+
start_pos = pos + 1
|
|
197
|
+
|
|
198
|
+
# Update start & end position that will track the span of the utterance that's matched.
|
|
199
|
+
if start < 0:
|
|
200
|
+
start = pos
|
|
201
|
+
|
|
202
|
+
end = pos
|
|
203
|
+
|
|
204
|
+
# Calculate score and format result
|
|
205
|
+
# - The start & end positions and the results text field will be corrected by the caller.
|
|
206
|
+
result: ModelResult | None = None
|
|
207
|
+
|
|
208
|
+
if matched > 0 and (
|
|
209
|
+
matched == len(searched_tokens) or options.allow_partial_matches
|
|
210
|
+
):
|
|
211
|
+
# Percentage of tokens matched. If matching "second last" in
|
|
212
|
+
# "the second form last one" the completeness would be 1.0 since
|
|
213
|
+
# all tokens were found.
|
|
214
|
+
completeness = matched / len(searched_tokens)
|
|
215
|
+
|
|
216
|
+
# Accuracy of the match. The accuracy is reduced by additional tokens
|
|
217
|
+
# occuring in the value that weren't in the utterance. So an utterance
|
|
218
|
+
# of "second last" matched against a value of "second from last" would
|
|
219
|
+
# result in an accuracy of 0.5.
|
|
220
|
+
accuracy = float(matched) / (matched + total_deviation)
|
|
221
|
+
|
|
222
|
+
# The final score is simply the compeleteness multiplied by the accuracy.
|
|
223
|
+
score = completeness * accuracy
|
|
224
|
+
|
|
225
|
+
# Format result
|
|
226
|
+
result = ModelResult(
|
|
227
|
+
text="",
|
|
228
|
+
start=start,
|
|
229
|
+
end=end,
|
|
230
|
+
type_name="value",
|
|
231
|
+
resolution=FoundValue(value=value, index=index, score=score),
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
return result
|
|
235
|
+
|
|
236
|
+
@staticmethod
|
|
237
|
+
def _index_of_token(tokens: list[Token], token: Token, start_pos: int) -> int:
|
|
238
|
+
for i in range(start_pos, len(tokens)):
|
|
239
|
+
if tokens[i].normalized == token.normalized:
|
|
240
|
+
return i
|
|
241
|
+
|
|
242
|
+
return -1
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from .choice_factory_options import ChoiceFactoryOptions
|
|
2
|
+
from .choice import Choice
|
|
3
|
+
from .find_choices_options import FindChoicesOptions
|
|
4
|
+
from .find_values_options import FindValuesOptions
|
|
5
|
+
from .found_choice import FoundChoice
|
|
6
|
+
from .found_value import FoundValue
|
|
7
|
+
from .list_style import ListStyle
|
|
8
|
+
from .model_result import ModelResult
|
|
9
|
+
from .sorted_value import SortedValue
|
|
10
|
+
from .token import Token
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"ChoiceFactoryOptions",
|
|
14
|
+
"Choice",
|
|
15
|
+
"FindChoicesOptions",
|
|
16
|
+
"FindValuesOptions",
|
|
17
|
+
"FoundChoice",
|
|
18
|
+
"FoundValue",
|
|
19
|
+
"ListStyle",
|
|
20
|
+
"ModelResult",
|
|
21
|
+
"SortedValue",
|
|
22
|
+
"Token",
|
|
23
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
|
|
6
|
+
from microsoft_agents.activity import CardAction
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Choice:
|
|
11
|
+
|
|
12
|
+
value: str = ""
|
|
13
|
+
action: CardAction | None = None
|
|
14
|
+
synonyms: list[str] = field(default_factory=list)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class ChoiceFactoryOptions:
|
|
9
|
+
|
|
10
|
+
inline_separator: str | None = None
|
|
11
|
+
inline_or: str | None = None
|
|
12
|
+
inline_or_more: str | None = None
|
|
13
|
+
include_numbers: bool = True
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from .find_values_options import FindValuesOptions
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class FindChoicesOptions(FindValuesOptions):
|
|
11
|
+
"""Contains options to control how input is matched against a list of choices
|
|
12
|
+
|
|
13
|
+
no_value: If `True`, the choices `value` field will NOT be search over. Defaults to `False`.
|
|
14
|
+
|
|
15
|
+
no_action: If `True`, the choices `action.title` field will NOT be searched over.
|
|
16
|
+
Defaults to `False`.
|
|
17
|
+
|
|
18
|
+
recognize_numbers: Indicates whether the recognizer should check for Numbers using the
|
|
19
|
+
NumberRecognizer's NumberModel.
|
|
20
|
+
|
|
21
|
+
recognize_ordinals: Indicates whether the recognizer should check for Ordinal Numbers using
|
|
22
|
+
the NumberRecognizer's OrdinalModel.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
no_value: bool = False
|
|
26
|
+
no_action: bool = False
|
|
27
|
+
recognize_numbers: bool = True
|
|
28
|
+
recognize_ordinals: bool = True
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from collections.abc import Iterable
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Callable
|
|
7
|
+
|
|
8
|
+
from .token import Token
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class FindValuesOptions:
|
|
13
|
+
"""Contains search options, used to control how choices are recognized in a user's utterance.
|
|
14
|
+
|
|
15
|
+
allow_partial_matches: (Optional) If `True`, then only some of the tokens in a value need to exist to be considered
|
|
16
|
+
a match. The default value is `False`.
|
|
17
|
+
|
|
18
|
+
locale: (Optional) locale/culture code of the utterance. Default is `en-US`.
|
|
19
|
+
|
|
20
|
+
max_token_distance: (Optional) maximum tokens allowed between two matched tokens in the utterance. So with
|
|
21
|
+
a max distance of 2 the value "second last" would match the utterance "second from the last"
|
|
22
|
+
but it wouldn't match "Wait a second. That's not the last one is it?".
|
|
23
|
+
The default value is "2".
|
|
24
|
+
|
|
25
|
+
tokenizer: (Optional) Tokenizer to use when parsing the utterance and values being recognized.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
allow_partial_matches: bool = False
|
|
29
|
+
locale: str = "en-US"
|
|
30
|
+
max_token_distance: int = 2
|
|
31
|
+
tokenizer: Callable[[str, str | None], list[Token]] | None = None
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class FoundChoice:
|
|
9
|
+
"""Represents a result from matching user input against a list of choices.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
value: The value of the choice that was matched.
|
|
13
|
+
index: The index of the choice that was matched.
|
|
14
|
+
score: The accuracy with which the synonym matched the specified portion of the utterance.
|
|
15
|
+
A value of 1.0 would indicate a perfect match.
|
|
16
|
+
synonym: The synonym that was matched in case of a synonym match.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
value: str
|
|
20
|
+
index: int
|
|
21
|
+
score: float
|
|
22
|
+
synonym: str | None = None
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class FoundValue:
|
|
9
|
+
"""Represents a result from matching user input against a list of choices
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
value: The value that was matched.
|
|
13
|
+
index: The index of the value that was matched.
|
|
14
|
+
score: The accuracy with which the synonym matched the specified portion of the utterance.
|
|
15
|
+
A value of 1.0 would indicate a perfect match.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
value: str
|
|
19
|
+
index: int
|
|
20
|
+
score: float
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ListStyle(int, Enum):
|
|
8
|
+
"""Defines the style of list to present choices to the user."""
|
|
9
|
+
|
|
10
|
+
none = 0
|
|
11
|
+
auto = 1
|
|
12
|
+
in_line = 2
|
|
13
|
+
list_style = 3
|
|
14
|
+
suggested_action = 4
|
|
15
|
+
hero_card = 5
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class ModelResult:
|
|
10
|
+
"""Contains recognition result information."""
|
|
11
|
+
|
|
12
|
+
text: str
|
|
13
|
+
start: int
|
|
14
|
+
end: int
|
|
15
|
+
type_name: str
|
|
16
|
+
resolution: Any
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class SortedValue:
|
|
9
|
+
"""A value that can be sorted and still refer to its original position with a source array.
|
|
10
|
+
|
|
11
|
+
value: the value that will be sorted.
|
|
12
|
+
index: the value's original position within its unsorted array.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
value: str
|
|
16
|
+
index: int
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class Token:
|
|
9
|
+
"""Represents an individual token, such as a word in an input string.
|
|
10
|
+
|
|
11
|
+
start: The index of the first character of the token within the outer input string.
|
|
12
|
+
end: The index of the last character of the token within the outer input string.
|
|
13
|
+
text: The original text of the token.
|
|
14
|
+
normalized: A normalized version of the token. This can include things like lower casing or stemming.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
start: int
|
|
18
|
+
end: int
|
|
19
|
+
text: str
|
|
20
|
+
normalized: str | None
|