Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
ee0a5d2
port ActivityPrompt class
Zerryth Jun 10, 2019
448dbd5
input_hint defaults to 'acceptingInput' only if none set
Zerryth Jun 10, 2019
b63ef4f
added attachment prompt class
Zerryth Jun 11, 2019
deaab86
Added OAuthPromptSettings. Began Choice & OAuth Prompts
Zerryth Jun 11, 2019
bd3eebb
Merge branch 'master' into Zerryth/DialogPrompts
Zerryth Jun 12, 2019
ad61b55
removed properties in OAuthPromptSettings
Zerryth Jun 12, 2019
d967519
Merge branch 'Zerryth/DialogPrompts' of https://github.com/microsoft/…
Zerryth Jun 12, 2019
10f5502
corrected on_prompt calls in ActivityPrompt
Zerryth Jun 21, 2019
7d75008
attachment prompt hint typing
Zerryth Jun 21, 2019
ebf7c19
Merge branch 'master' into Zerryth/DialogPrompts
Zerryth Jun 21, 2019
6be6d5f
added ChoiceFactory docs
Zerryth Jun 21, 2019
03f9e88
Merge branch 'Zerryth/DialogPrompts' of https://github.com/microsoft/…
Zerryth Jun 21, 2019
17d1486
Merge branch 'master' into Zerryth/DialogPrompts
Zerryth Jun 21, 2019
036911c
trying to fix build error--using List from typing for validator type …
Zerryth Jun 21, 2019
f22c71d
Merge branch 'Zerryth/DialogPrompts' of https://github.com/microsoft/…
Zerryth Jun 21, 2019
dc4a360
removed references to OAuthPrompt, as Axel will implement it
Zerryth Jun 21, 2019
75429d1
ported FoundChoice class
Zerryth Jun 21, 2019
7c943e2
ported ModelResult
Zerryth Jun 21, 2019
f9d6b6f
added ModelResult to init file
Zerryth Jun 21, 2019
60a8b35
ported Token class
Zerryth Jun 21, 2019
6fbe48e
added FindValuesOptions class & began building Choice recognizer
Zerryth Jun 23, 2019
394f68e
ported FindChoicesOptions class & removed accidental trailing commas …
Zerryth Jun 24, 2019
d45e24a
ported SortedValue class, added FindChoicesOptions docs, fixed ModelR…
Zerryth Jun 24, 2019
5418abe
removed Oauth prompt test file -- started Find class
Zerryth Jun 24, 2019
3743a0f
removed oauth prompt settings to not conflict w/Axel's PR; building o…
Zerryth Jun 25, 2019
60861f2
removed references to auth prompt settings in init file
Zerryth Jun 25, 2019
ddb1eed
finished choice tokenizer
Zerryth Jun 25, 2019
7dfbb8b
Merge branch 'master' into Zerryth/DialogPrompts
Zerryth Jun 25, 2019
4479ba4
added match_value and index_of_token methods to Find
Zerryth Jun 25, 2019
ba55d44
Merge branch 'Zerryth/DialogPrompts' of https://github.com/microsoft/…
Zerryth Jun 25, 2019
6f8d6f2
Initial tests for activity prompt. Fixes on prompt, prompt_validator_…
axelsrz Jun 26, 2019
5c37f06
finished Find utility class
Zerryth Jun 26, 2019
238db33
began ChoiceRecognizers class -- pending Recognizers-Numbers publishi…
Zerryth Jun 26, 2019
dc2db65
removed note to self
Zerryth Jun 26, 2019
ab31ca2
Added missing tests for activity prompt
axelsrz Jun 26, 2019
6c8ae77
removed provisional test on test_activity_prompt
axelsrz Jun 26, 2019
76ba270
Merge pull request #228 from microsoft/axsuarez/DialogPrompts
Zerryth Jun 26, 2019
ad05e3f
attachment_prompt tests
axelsrz Jun 26, 2019
39bcb74
attachment_prompt tests (#230)
axelsrz Jun 26, 2019
1084d33
Merge branch 'Zerryth/DialogPrompts' of https://github.com/Microsoft/…
axelsrz Jun 26, 2019
8b14b7b
tests for tokenizer
axelsrz Jun 27, 2019
80eecad
PR fixes
axelsrz Jun 27, 2019
20d73e3
Merge pull request #231 from microsoft/axsuarez/DialogPrompts
axelsrz Jun 27, 2019
90fe6b8
Merge branch 'master' of https://github.com/microsoft/botbuilder-pyth…
Zerryth Jun 27, 2019
1974dc2
pushing incomplete choice recognizers & choice prompts
Zerryth Jun 27, 2019
06cd153
tests for find menthods
axelsrz Jun 28, 2019
3a05676
Merge pull request #233 from microsoft/axsuarez/DialogPrompts
Zerryth Jun 28, 2019
4db9e1b
completed ChoiceRecognizers w/o tests
Zerryth Jul 1, 2019
36f8b3c
completed ChoicePrompt class w/o test; added ChoiceRecognizers to init
Zerryth Jul 2, 2019
0c54263
completed unit tests for ChoiceRecognizers
Zerryth Jul 2, 2019
614b016
removed prompt circular dependency
Zerryth Jul 2, 2019
a0be5b9
finished ChoicePrompt tests; stripped whitespace from TestAdapter
Zerryth Jul 3, 2019
72693f3
Solved merge conflicts
axelsrz Jul 3, 2019
69414ee
Solved merge conflicts with master
axelsrz Jul 3, 2019
685bc9c
resolved merge conflicts
Zerryth Jul 3, 2019
ff45057
resolved prompt_validator_context conflicts
Zerryth Jul 3, 2019
923eb38
changed to 'is not'/'is' vs '!='/'='
Zerryth Jul 3, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ async def receive_activity(self, activity):
if value is not None and key != 'additional_properties':
setattr(request, key, value)

request.type = ActivityTypes.message
request.type = request.type or ActivityTypes.message
if not request.id:
self._next_id += 1
request.id = str(self._next_id)
Expand All @@ -143,6 +143,9 @@ async def receive_activity(self, activity):
context = TurnContext(self, request)
return await self.run_pipeline(context, self.logic)

def get_next_activity(self) -> Activity:
return self.activity_buffer.pop(0)

async def send(self, user_says) -> object:
"""
Sends something to the bot. This returns a new `TestFlow` instance which can be used to add
Expand Down Expand Up @@ -300,7 +303,7 @@ def default_inspector(reply, description=None):
validate_activity(reply, expected)
else:
assert reply.type == 'message', description + f" type == {reply.type}"
assert reply.text == expected, description + f" text == {reply.text}"
assert reply.text.strip() == expected.strip(), description + f" text == {reply.text}"

if description is None:
description = ''
Expand Down
3 changes: 2 additions & 1 deletion libraries/botbuilder-core/botbuilder/core/turn_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ async def send_activity(self, *activity_or_text: Union[Activity, str]) -> Resour
Activity(text=a, type='message') if isinstance(a, str) else a, reference)
for a in activity_or_text]
for activity in output:
activity.input_hint = 'acceptingInput'
if not activity.input_hint:
activity.input_hint = 'acceptingInput'

async def callback(context: 'TurnContext', output):
responses = await context.adapter.send_activities(context, output)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,30 @@
from .choice import Choice
from .choice_factory_options import ChoiceFactoryOptions
from .choice_factory import ChoiceFactory
from .choice_recognizers import ChoiceRecognizers
from .find import Find
from .find_choices_options import FindChoicesOptions, FindValuesOptions
from .found_choice import FoundChoice
from .found_value import FoundValue
from .list_style import ListStyle
from .model_result import ModelResult
from .sorted_value import SortedValue
from .token import Token
from .tokenizer import Tokenizer

__all__ = ["Channel", "Choice", "ChoiceFactory", "ChoiceFactoryOptions", "ListStyle"]
__all__ = [
"Channel",
"Choice",
"ChoiceFactory",
"ChoiceFactoryOptions",
"ChoiceRecognizers",
"Find",
"FindChoicesOptions",
"FindValuesOptions",
"FoundChoice",
"ListStyle",
"ModelResult",
"SortedValue",
"Token",
"Tokenizer"
]
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

class Channel(object):
"""
Methods for determining channel specific functionality.
Methods for determining channel-specific functionality.
"""

@staticmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@


class ChoiceFactory:
"""
Assists with formatting a message activity that contains a list of choices.
"""
@staticmethod
def for_channel(
channel_id: str,
Expand All @@ -18,6 +21,20 @@ def for_channel(
speak: str = None,
options: ChoiceFactoryOptions = None,
) -> Activity:
"""
Creates a message activity that includes a list of choices formatted based on the capabilities of a given channel.

Parameters:
----------

channel_id: A channel ID.

choices: List of choices to render.

text: (Optional) Text of the message to send.

speak (Optional) SSML. Text to be spoken by your bot on a speech-enabled channel.
"""
if channel_id is None:
channel_id = ""

Expand Down Expand Up @@ -65,6 +82,20 @@ def inline(
speak: str = None,
options: ChoiceFactoryOptions = None,
) -> Activity:
"""
Creates a message activity that includes a list of choices formatted as an inline list.

Parameters:
----------

choices: The list of choices to render.

text: (Optional) The text of the message to send.

speak: (Optional) SSML. Text to be spoken by your bot on a speech-enabled channel.

options: (Optional) The formatting options to use to tweak rendering of list.
"""
if choices is None:
choices = []

Expand Down Expand Up @@ -113,6 +144,20 @@ def list_style(
speak: str = None,
options: ChoiceFactoryOptions = None,
):
"""
Creates a message activity that includes a list of choices formatted as a numbered or bulleted list.

Parameters:
----------

choices: The list of choices to render.

text: (Optional) The text of the message to send.

speak: (Optional) SSML. Text to be spoken by your bot on a speech-enabled channel.

options: (Optional) The formatting options to use to tweak rendering of list.
"""
if choices is None:
choices = []
if options is None:
Expand Down Expand Up @@ -153,6 +198,9 @@ def list_style(
def suggested_action(
choices: List[Choice], text: str = None, speak: str = None
) -> Activity:
"""
Creates a message activity that includes a list of choices that have been added as suggested actions.
"""
# Return activity with choices as suggested actions
return MessageFactory.suggested_actions(
ChoiceFactory._extract_actions(choices),
Expand All @@ -165,6 +213,9 @@ def suggested_action(
def hero_card(
choices: List[Choice], text: str = None, speak: str = None
) -> Activity:
"""
Creates a message activity that includes a lsit of coices that have been added as `HeroCard`'s
"""
attachment = CardFactory.hero_card(
HeroCard(text=text, buttons=ChoiceFactory._extract_actions(choices))
)
Expand All @@ -176,6 +227,9 @@ def hero_card(

@staticmethod
def _to_choices(choices: List[str]) -> List[Choice]:
"""
Takes a list of strings and returns them as [`Choice`].
"""
if choices is None:
return []
else:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from recognizers_number import NumberModel, NumberRecognizer, OrdinalModel
from recognizers_text import Culture
from typing import List, Union


from .choice import Choice
from .find import Find
from .find_choices_options import FindChoicesOptions
from .found_choice import FoundChoice
from .model_result import ModelResult

class ChoiceRecognizers:
""" Contains methods for matching user input against a list of choices. """

@staticmethod
def recognize_choices(
utterance: str,
choices: List[Union[str, Choice]],
options: FindChoicesOptions = None
) -> List[ModelResult]:
"""
Matches user input against a list of choices.

This is layered above the `Find.find_choices()` function, and adds logic to let the user specify
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]`.)
The user's utterance is recognized in the following order:

- By name using `find_choices()`
- By 1's based ordinal position.
- By 1's based index position.

Parameters:
-----------

utterance: The input.

choices: The list of choices.

options: (Optional) Options to control the recognition strategy.

Returns:
--------
A list of found choices, sorted by most relevant first.
"""
if utterance is None:
utterance = ''

# Normalize list of choices
choices_list = [Choice(value=choice) if isinstance(choice, str) else choice for choice in choices]

# Try finding choices by text search first
# - We only want to use a single strategy for returning results to avoid issues where utterances
# like the "the third one" or "the red one" or "the first division book" would miss-recognize as
# a numerical index or ordinal as well.
locale = options.locale if (options and options.locale) else Culture.English
matched = Find.find_choices(utterance, choices_list, options)
if len(matched) == 0:
# Next try finding by ordinal
matches = ChoiceRecognizers._recognize_ordinal(utterance, locale)

if len(matches) > 0:
for match in matches:
ChoiceRecognizers._match_choice_by_index(choices_list, matched, match)
else:
# Finally try by numerical index
matches = ChoiceRecognizers._recognize_number(utterance, locale)

for match in matches:
ChoiceRecognizers._match_choice_by_index(choices_list, matched, match)

# Sort any found matches by their position within the utterance.
# - The results from find_choices() are already properly sorted so we just need this
# for ordinal & numerical lookups.
matched = sorted(
matched,
key=lambda model_result: model_result.start
)

return matched


@staticmethod
def _recognize_ordinal(utterance: str, culture: str) -> List[ModelResult]:
model: OrdinalModel = NumberRecognizer(culture).get_ordinal_model(culture)

return list(map(ChoiceRecognizers._found_choice_constructor, model.parse(utterance)))

@staticmethod
def _match_choice_by_index(
choices: List[Choice],
matched: List[ModelResult],
match: ModelResult
):
try:
index: int = int(match.resolution.value) - 1
if (index >= 0 and index < len(choices)):
choice = choices[index]

matched.append(ModelResult(
start=match.start,
end=match.end,
type_name='choice',
text=match.text,
resolution=FoundChoice(
value=choice.value,
index=index,
score=1.0
)
))
except:
# noop here, as in dotnet/node repos
pass

@staticmethod
def _recognize_number(utterance: str, culture: str) -> List[ModelResult]:
model: NumberModel = NumberRecognizer(culture).get_number_model(culture)

return list(map(ChoiceRecognizers._found_choice_constructor, model.parse(utterance)))

@staticmethod
def _found_choice_constructor(value_model: ModelResult) -> ModelResult:
return ModelResult(
start=value_model.start,
end=value_model.end,
type_name='choice',
text=value_model.text,
resolution=FoundChoice(
value=value_model.resolution['value'],
index=0,
score=1.0,
)
)


Loading