8000 Axsuarez/skill dialog (#757) · microsoft/botbuilder-python@d282ea3 · GitHub
[go: up one dir, main page]

Skip to content

Commit d282ea3

Browse files
Axsuarez/skill dialog (#757)
* initial changes for skill dialog * Skill dialog * Initial echo skill with dialog * pylint: Initial echo skill with dialog * made integration package for aiohttp, dialog root bot for testing * pylint: made integration package for aiohttp, dialog root bot for testing * pylint: made integration package for aiohttp, dialog root bot for testing * pylint: made integration package for aiohttp, dialog root bot for testing * Initial dialog skill bot * Changes to skill bot * Updates for dialog interruption and buffered response. Pending to move some classes to botbuilder.integration.aiohttp * Relates to in post_activity in BotFrameworkHttpClient * fix on BeginSkillDialogOptions * Moved SkillHttpClient to correct library with corresponding tests. Fix SkillDialog. * black: Moved SkillHttpClient to correct library with corresponding tests. Fix SkillDialog. * relative import fix * Removed unused import * Modified TurnContext.send_trace_activity to default args. * Removed argument checks that didn't exist in C#. Fixed bug in SkillDialog.begin_dialog * Added initial SkillDialog unit test. * Added remainder of SkillDialog unit tests * Updates on dialog-root-bot * Updated buffered_replies to expect_replies * Using HTTPStatus defines. * Skill OAuth only change card action for emulator * black * skill root bot updated * skill root bot updated * Removed old import in dialog root bot * Dialog-to-dialog work * Ummm... the actual dialog-to-dialog work * Corrected dialog-skill-bot AcitivyRouterDialog to actually have a WaterfallDialog step. * dialog-to-dialog test bot changes: dialog-echo-skill-bot, corrected missing async on ComponentDialog * dialog-to-dialog: Handling messages with values (serialization and whatnot) * Memory storage does not validate e_tag integrity anymore, following the same behavior as C# * pylint: Memory storage does not validate e_tag integrity anymore, following the same behavior as C# * pylint: Memory storage does not validate e_tag integrity anymore, following the same behavior as C# * Removing samples from product code PR Co-authored-by: tracyboehrer <tracyboehrer@users.noreply.github.com>
1 parent faf4eed commit d282ea3

39 files changed

+1114
-67
lines changed

ci-pr-pipeline.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ jobs:
5353
pip install -e ./libraries/botbuilder-testing
5454
pip install -e ./libraries/botbuilder-integration-applicationinsights-aiohttp
5555
pip install -e ./libraries/botbuilder-adapters-slack
56+
pip install -e ./libraries/botbuilder-integration-aiohttp
5657
pip install -r ./libraries/botframework-connector/tests/requirements.txt
5758
pip install -r ./libraries/botbuilder-core/tests/requirements.txt
5859
pip install coveralls

libraries/botbuilder-core/botbuilder/core/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
from .extended_user_token_provider import ExtendedUserTokenProvider
2323
from .intent_score import IntentScore
2424
from .invoke_response import InvokeResponse
25-
from .bot_framework_http_client import BotFrameworkHttpClient
2625
from .memory_storage import MemoryStorage
2726
from .memory_transcript_store import MemoryTranscriptStore
2827
from .message_factory import MessageFactory
@@ -63,7 +62,6 @@
6362
"ExtendedUserTokenProvider",
6463
"IntentScore",
6564
"InvokeResponse",
66-
"BotFrameworkHttpClient",
6765
"MemoryStorage",
6866
"MemoryTranscriptStore",
6967
"MessageFactory",

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,9 @@ async def receive_activity(self, activity):
241241
return await self.run_pipeline(context, self.logic)
242242

243243
def get_next_activity(self) -> Activity:
244-
return self.activity_buffer.pop(0)
244+
if len(self.activity_buffer) > 0:
245+
return self.activity_buffer.pop(0)
246+
return None
245247

246248
async def send(self, user_says) -> object:
247249
"""

libraries/botbuilder-core/botbuilder/core/bot_adapter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
class BotAdapter(ABC):
1616
BOT_IDENTITY_KEY = "BotIdentity"
17-
BOT_OAUTH_SCOPE_KEY = "OAuthScope"
17+
BOT_OAUTH_SCOPE_KEY = "botbuilder.core.BotAdapter.OAuthScope"
1818
BOT_CONNECTOR_CLIENT_KEY = "ConnectorClient"
1919
BOT_CALLBACK_HANDLER_KEY = "BotCallbackHandler"
2020

libraries/botbuilder-core/botbuilder/core/memory_storage.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,14 @@ async def write(self, changes: Dict[str, StoreItem]):
7373
"Etag conflict.\nOriginal: %s\r\nCurrent: %s"
7474
% (new_value_etag, old_state_etag)
7575
)
76-
if isinstance(new_state, dict):
77-
new_state["e_tag"] = str(self._e_tag)
78-
else:
79-
new_state.e_tag = str(self._e_tag)
76+
77+
# If the original object didn't have an e_tag, don't set one (C# behavior)
78+
if old_state_etag:
79+
if isinstance(new_state, dict):
80+
new_state["e_tag"] = str(self._e_tag)
81+
else:
82+
new_state.e_tag = str(self._e_tag)
83+
8084
self._e_tag += 1
8185
self.memory[key] = deepcopy(new_state)
8286

libraries/botbuilder-core/botbuilder/core/skills/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
# --------------------------------------------------------------------------
77

88
from .bot_framework_skill import BotFrameworkSkill
9+
from .bot_framework_client import BotFrameworkClient
910
from .conversation_id_factory import ConversationIdFactoryBase
1011
from .skill_handler import SkillHandler
1112
from .skill_conversation_id_factory_options import SkillConversationIdFactoryOptions
1213
from .skill_conversation_reference import SkillConversationReference
1314

1415
__all__ = [
1516
"BotFrameworkSkill",
17+
"BotFrameworkClient",
1618
"ConversationIdFactoryBase",
1719
"SkillConversationIdFactoryOptions",
1820
"SkillConversationReference",
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from abc import ABC
5+
6+
from botbuilder.schema import Activity
7+
from botbuilder.core import InvokeResponse
8+
9+
10+
class BotFrameworkClient(ABC):
11+
def post_activity(
12+
self,
13+
from_bot_id: str,
14+
to_bot_id: str,
15+
to_url: str,
16+
service_url: str,
17+
conversation_id: str,
18+
activity: Activity,
19+
) -> InvokeResponse:
20+
raise NotImplementedError()

libraries/botbuilder-core/botbuilder/core/skills/skill_conversation_id_factory_options.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,6 @@ def __init__(
1313
activity: Activity,
1414
bot_framework_skill: BotFrameworkSkill,
1515
):
16-
if from_bot_oauth_scope is None:
17-
raise TypeError(
18-
"SkillConversationIdFactoryOptions(): from_bot_oauth_scope cannot be None."
19-
)
20-
21-
if from_bot_id is None:
22-
raise TypeError(
23-
"SkillConversationIdFactoryOptions(): from_bot_id cannot be None."
24-
)
25-
26-
if activity is None:
27-
raise TypeError(
28-
"SkillConversationIdFactoryOptions(): activity cannot be None."
29-
)
30-
31-
if bot_framework_skill is None:
32-
raise TypeError(
33-
"SkillConversationIdFactoryOptions(): bot_framework_skill cannot be None."
34-
)
35-
3616
self.from_bot_oauth_scope = from_bot_oauth_scope
3717
self.from_bot_id = from_bot_id
3818
self.activity = activity

libraries/botbuilder-core/botbuilder/core/skills/skill_conversation_reference.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,5 @@ class SkillConversationReference:
99
"""
1010

1111
def __init__(self, conversation_reference: ConversationReference, oauth_scope: str):
12-
if conversation_reference is None:
13-
raise TypeError(
14-
"SkillConversationReference(): conversation_reference cannot be None."
15-
)
16-
17-
if oauth_scope is None:
18-
raise TypeError("SkillConversationReference(): oauth_scope cannot be None.")
19-
2012
self.conversation_reference = conversation_reference
2113
self.oauth_scope = oauth_scope

libraries/botbuilder-core/botbuilder/core/turn_context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ async def next_handler():
295295
return await logic
296296

297297
async def send_trace_activity(
298-
self, name: str, value: object, value_type: str, label: str
298+
self, name: str, value: object = None, value_type: str = None, label: str = None
299299
) -> ResourceResponse:
300300
trace_activity = Activity(
301301
type=ActivityTypes.trace,

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from .about import __version__
99
from .component_dialog import ComponentDialog
1010
from .dialog_context import DialogContext
11+
from .dialog_events import DialogEvents
1112
from .dialog_instance import DialogInstance
1213
from .dialog_reason import DialogReason
1314
from .dialog_set import DialogSet
@@ -17,12 +18,15 @@
1718
from .dialog import Dialog
1819
from .waterfall_dialog import WaterfallDialog
1920
from .waterfall_step_context import WaterfallStepContext
21+
from .dialog_extensions import DialogExtensions
2022
from .prompts import *
2123
from .choices import *
24+
from .skills import *
2225

2326
__all__ = [
2427
"ComponentDialog",
2528
"DialogContext",
29+
"DialogEvents",
2630
"DialogInstance",
2731
"DialogReason",
2832
"DialogSet",
@@ -43,5 +47,6 @@
4347
"Prompt",
4448
"PromptOptions",
4549
"TextPrompt",
50+
"DialogExtensions",
4651
"__version__",
4752
]

libraries/botbuilder-dialogs/botbuilder/dialogs/component_dialog.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,15 +189,15 @@ def add_dialog(self, dialog: Dialog) -> object:
189189
self.initial_dialog_id = dialog.id
190190
return self
191191

192-
def find_dialog(self, dialog_id: str) -> Dialog:
192+
async def find_dialog(self, dialog_id: str) -> Dialog:
193193
"""
194194
Finds a dialog by ID.
195195
196196
:param dialog_id: The dialog to add.
197197
:return: The dialog; or None if there is not a match for the ID.
198198
:rtype: :class:`botbuilder.dialogs.Dialog`
199199
"""
200-
return self._dialogs.find(dialog_id)
200+
return await self._dialogs.find(dialog_id)
201201

202202
async def on_begin_dialog(
203203
self, inner_dc: DialogContext, options: object
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from enum import Enum
5+
6+
7+
class DialogEvents(str, Enum):
8+
9+
begin_dialog = "beginDialog"
10+
reprompt_dialog = "repromptDialog"
11+
cancel_dialog = "cancelDialog"
12+
activity_received = "activityReceived"
13+
error = "error"
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from botbuilder.core import BotAdapter, StatePropertyAccessor, TurnContext
5+
from botbuilder.dialogs import (
6+
Dialog,
7+
DialogEvents,
8+
DialogSet,
9+
DialogTurnStatus,
10+
)
11+
from botbuilder.schema import Activity, ActivityTypes
12+
from botframework.connector.auth import ClaimsIdentity, SkillValidation
13+
14+
15+
class DialogExtensions:
16+
@staticmethod
17+
async def run_dialog(
18+
dialog: Dialog, turn_context: TurnContext, accessor: StatePropertyAccessor
19+
):
20+
dialog_set = DialogSet(accessor)
21+
dialog_set.add(dialog)
22+
23+
dialog_context = await dialog_set.create_context(turn_context)
24+
25+
claims = turn_context.turn_state.get(BotAdapter.BOT_IDENTITY_KEY)
26+
if isinstance(claims, ClaimsIdentity) and SkillValidation.is_skill_claim(
27+
claims.claims
28+
):
29+
# The bot is running as a skill.
30+
if (
31+
turn_context.activity.type == ActivityTypes.end_of_conversation
32+
and dialog_context.stack
33+
):
34+
await dialog_context.cancel_all_dialogs()
35+
else:
36+
# Process a reprompt event sent from the parent.
37+
if (
38+
turn_context.activity.type == ActivityTypes.event
39+
and turn_context.activity.name == DialogEvents.reprompt_dialog
40+
and dialog_context.stack
41+
):
42+
await dialog_context.reprompt_dialog()
43+
return
44+
45+
# Run the Dialog with the new message Activity and capture the results
46+
# so we can send end of conversation if needed.
47+
result = await dialog_context.continue_dialog()
48+
if result.status == DialogTurnStatus.Empty:
49+
start_message_text = f"Starting {dialog.id}"
50+
await turn_context.send_trace_activity(
51+
f"Extension {Dialog.__name__}.run_dialog",
52+
label=start_message_text,
53+
)
54+
result = await dialog_context.begin_dialog(dialog.id)
55+
56+
# Send end of conversation if it is completed or cancelled.
57+
if (
58+
result.status == DialogTurnStatus.Complete
59+
or result.status == DialogTurnStatus.Cancelled
60+
):
61+
end_message_text = f"Dialog {dialog.id} has **completed**. Sending EndOfConversation."
62+
await turn_context.send_trace_activity(
63+
f"Extension {Dialog.__name__}.run_dialog",
64+
label=end_message_text,
65+
value=result.result,
66+
)
67+
68+
activity = Activity(
69+
type=ActivityTypes.end_of_conversation, value=result.result
70+
)
71+
await turn_context.send_activity(activity)
72+
73+
else:
74+
# The bot is running as a standard bot.
75+
results = await dialog_context.continue_dialog()
76+
if results.status == DialogTurnStatus.Empty:
77+
await dialog_context.begin_dialog(dialog.id)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# coding=utf-8
2+
# --------------------------------------------------------------------------
3+
# Copyright (c) Microsoft Corporation. All rights reserved.
4+
# Licensed under the MIT License. See License.txt in the project root for
5+
# license information.
6+
# --------------------------------------------------------------------------
7+
8+
from .begin_skill_dialog_options import BeginSkillDialogOptions
9+
from .skill_dialog_options import SkillDialogOptions
10+
from .skill_dialog import SkillDialog
11+
12+
13+
__all__ = [
14+
"BeginSkillDialogOptions",
15+
"SkillDialogOptions",
16+
"SkillDialog",
17+
97AE ]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from botbuilder.schema import Activity
5+
6+
7+
class BeginSkillDialogOptions:
8+
def __init__(self, activity: Activity): # pylint: disable=unused-argument
9+
self.activity = activity
10+
11+
@staticmethod
12+
def from_object(obj: object) -> "BeginSkillDialogOptions":
13+
if isinstance(obj, dict) and "activity" in obj:
14+
return BeginSkillDialogOptions(obj["activity"])
15+
if hasattr(obj, "activity"):
16+
return BeginSkillDialogOptions(obj.activity)
17+
18+
return None

0 commit comments

Comments
 (0)
0