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+
415class 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