Skip to content
This repository was archived by the owner on Jan 5, 2026. It is now read-only.

Commit 69414ee

Browse files
committed
Solved merge conflicts with master
2 parents 72693f3 + a0be5b9 commit 69414ee

22 files changed

+1790
-200
lines changed

libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ async def receive_activity(self, activity):
134134
if value is not None and key != 'additional_properties':
135135
setattr(request, key, value)
136136

137-
request.type = ActivityTypes.message
137+
request.type = request.type or ActivityTypes.message
138138
if not request.id:
139139
self._next_id += 1
140140
request.id = str(self._next_id)
@@ -143,6 +143,9 @@ async def receive_activity(self, activity):
143143
context = TurnContext(self, request)
144144
return await self.run_pipeline(context, self.logic)
145145

146+
def get_next_activity(self) -> Activity:
147+
return self.activity_buffer.pop(0)
148+
146149
async def send(self, user_says) -> object:
147150
"""
148151
Sends something to the bot. This returns a new `TestFlow` instance which can be used to add
@@ -300,7 +303,7 @@ def default_inspector(reply, description=None):
300303
validate_activity(reply, expected)
301304
else:
302305
assert reply.type == 'message', description + f" type == {reply.type}"
303-
assert reply.text == expected, description + f" text == {reply.text}"
306+
assert reply.text.strip() == expected.strip(), description + f" text == {reply.text}"
304307

305308
if description is None:
306309
description = ''

libraries/botbuilder-dialogs/botbuilder/dialogs/choices/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,30 @@
99
from .choice import Choice
1010
from .choice_factory_options import ChoiceFactoryOptions
1111
from .choice_factory import ChoiceFactory
12+
from .choice_recognizers import ChoiceRecognizers
13+
from .find import Find
1214
from .find_choices_options import FindChoicesOptions, FindValuesOptions
1315
from .found_choice import FoundChoice
16+
from .found_value import FoundValue
1417
from .list_style import ListStyle
1518
from .model_result import ModelResult
1619
from .sorted_value import SortedValue
1720
from .token import Token
21+
from .tokenizer import Tokenizer
1822

1923
__all__ = [
2024
"Channel",
2125
"Choice",
2226
"ChoiceFactory",
2327
"ChoiceFactoryOptions",
28+
"ChoiceRecognizers",
29+
"Find",
2430
"FindChoicesOptions",
2531
"FindValuesOptions",
2632
"FoundChoice",
2733
"ListStyle",
2834
"ModelResult",
2935
"SortedValue",
30-
"Token"
36+
"Token",
37+
"Tokenizer"
3138
]
Lines changed: 129 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,137 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
33

4+
from recognizers_number import NumberModel, NumberRecognizer, OrdinalModel
5+
from recognizers_text import Culture
6+
from typing import List, Union
7+
8+
9+
from .choice import Choice
10+
from .find import Find
11+
from .find_choices_options import FindChoicesOptions
12+
from .found_choice import FoundChoice
13+
from .model_result import ModelResult
14+
415
class ChoiceRecognizers:
516
""" Contains methods for matching user input against a list of choices. """
617

7-
# Note to self: C# implementation has 2 RecognizeChoices overloads, different in their list parameter
8-
# 1. list of strings - that gets converted into a list of Choice's
9-
# 2. list of choices
10-
# Looks like in TS the implement also allows for either string[] or Choice[]
18+
@staticmethod
19+
def recognize_choices(
20+
utterance: str,
21+
choices: List[Union[str, Choice]],
22+
options: FindChoicesOptions = None
23+
) -> List[ModelResult]:
24+
"""
25+
Matches user input against a list of choices.
26+
27+
This is layered above the `Find.find_choices()` function, and adds logic to let the user specify
28+
their choice by index (they can say "one" to pick `choice[0]`) or ordinal position (they can say "the second one" to pick `choice[1]`.)
29+
The user's utterance is recognized in the following order:
30+
31+
- By name using `find_choices()`
32+
- By 1's based ordinal position.
33+
- By 1's based index position.
34+
35+
Parameters:
36+
-----------
37+
38+
utterance: The input.
39+
40+
choices: The list of choices.
41+
42+
options: (Optional) Options to control the recognition strategy.
43+
44+
Returns:
45+
--------
46+
A list of found choices, sorted by most relevant first.
47+
"""
48+
if utterance == None:
49+
utterance = ''
50+
51+
# Normalize list of choices
52+
choices_list = [Choice(value=choice) if isinstance(choice, str) else choice for choice in choices]
1153

12-
# C# none of the functions seem to be nested inside another function
13-
# TS has only 1 recognizer funtion, recognizeChoices()
14-
# nested within recognizeChoices() is matchChoiceByIndex()
54+
# Try finding choices by text search first
55+
# - We only want to use a single strategy for returning results to avoid issues where utterances
56+
# like the "the third one" or "the red one" or "the first division book" would miss-recognize as
57+
# a numerical index or ordinal as well.
58+
locale = options.locale if (options and options.locale) else Culture.English
59+
matched = Find.find_choices(utterance, choices_list, options)
60+
if len(matched) == 0:
61+
# Next try finding by ordinal
62+
matches = ChoiceRecognizers._recognize_ordinal(utterance, locale)
63+
64+
if len(matches) > 0:
65+
for match in matches:
66+
ChoiceRecognizers._match_choice_by_index(choices_list, matched, match)
67+
else:
68+
# Finally try by numerical index
69+
matches = ChoiceRecognizers._recognize_number(utterance, locale)
70+
71+
for match in matches:
72+
ChoiceRecognizers._match_choice_by_index(choices_list, matched, match)
73+
74+
# Sort any found matches by their position within the utterance.
75+
# - The results from find_choices() are already properly sorted so we just need this
76+
# for ordinal & numerical lookups.
77+
matched = sorted(
78+
matched,
79+
key=lambda model_result: model_result.start
80+
)
81+
82+
return matched
83+
84+
85+
@staticmethod
86+
def _recognize_ordinal(utterance: str, culture: str) -> List[ModelResult]:
87+
model: OrdinalModel = NumberRecognizer(culture).get_ordinal_model(culture)
88+
89+
return list(map(ChoiceRecognizers._found_choice_constructor, model.parse(utterance)))
90+
91+
@staticmethod
92+
def _match_choice_by_index(
93+
choices: List[Choice],
94+
matched: List[ModelResult],
95+
match: ModelResult
96+
):
97+
try:
98+
index: int = int(match.resolution.value) - 1
99+
if (index >= 0 and index < len(choices)):
100+
choice = choices[index]
101+
102+
matched.append(ModelResult(
103+
start=match.start,
104+
end=match.end,
105+
type_name='choice',
106+
text=match.text,
107+
resolution=FoundChoice(
108+
value=choice.value,
109+
index=index,
110+
score=1.0
111+
)
112+
))
113+
except:
114+
# noop here, as in dotnet/node repos
115+
pass
116+
117+
@staticmethod
118+
def _recognize_number(utterance: str, culture: str) -> List[ModelResult]:
119+
model: NumberModel = NumberRecognizer(culture).get_number_model(culture)
120+
121+
return list(map(ChoiceRecognizers._found_choice_constructor, model.parse(utterance)))
15122

123+
@staticmethod
124+
def _found_choice_constructor(value_model: ModelResult) -> ModelResult:
125+
return ModelResult(
126+
start=value_model.start,
127+
end=value_model.end,
128+
type_name='choice',
129+
text=value_model.text,
130+
resolution=FoundChoice(
131+
value=value_model.resolution['value'],
132+
index=0,
133+
score=1.0,
134+
)
135+
)
136+
137+

0 commit comments

Comments
 (0)