適用対象: SDK v4
この記事では、スキル コンシューマー内で "スキル ダイアログ" を使用する方法について説明します。
スキル ダイアログでは、親ボットからスキル ボットにアクティビティがポストされ、スキルの応答がユーザーに返されます。
このコンシューマーによってアクセスされるスキル ボットでは、メッセージ アクティビティとイベント アクティビティの両方を処理できます。
スキル マニフェストのサンプルおよびスキルの実装に関する情報については、スキル内でダイアログを使用する方法を参照してください。
ダイアログ外でスキル ボットを使用する方法については、スキル コンシューマーを実装する方法を参照してください。
Important
Bot Framework SDK と Bot Framework Emulator は GitHub にアーカイブされています。 プロジェクトが更新または保守されなくなりました。 Bot Framework SDK のサポート チケットは、2025 年 12 月 31 日の時点で提供されなくなります。
AI サービス、オーケストレーション、知識を選択してエージェントを構築するには、Microsoft 365 Agents SDK の使用を検討してください。 Agents SDK には、C#、JavaScript、または Python の言語サポートがあります。 Agents SDK の詳細については、 aka.ms/agents を参照してください。
Bot Framework SDK を使用して構築された既存のボットがある場合は、ボットを Agents SDK に更新できます。
Bot Framework SDK から Agents SDK への移行ガイダンスで、主要な変更と更新を確認します。
Microsoft Teams内で動作するように設計されたコラボレーション エージェントを構築する場合は、Teams SDK の使用を検討してください。 Teams 環境で実行されるエージェントに対して、Teams 固有の API、アダプティブ カードのサポート、組み込みの AI オーケストレーションが提供されます。 詳細については、 Teams SDK (Teams AI ライブラリ) を参照してください。
SaaS ベースのエージェント プラットフォームをお探しの場合は、 Microsoft Copilot Studio を検討してください。
前提条件
このサンプルについて
skills skillDialog サンプルには、次の 2 つのボットのプロジェクトが含まれています。
-
ダイアログ ルート ボットは、スキル ダイアログクラスを使用してスキルを利用します。
- "ダイアログ スキル ボット"。ダイアログを使用して、スキル コンシューマーからのアクティビティを処理します。
この記事では、ルート ボットで "スキル ダイアログ" クラスを使用してスキルを管理する方法、メッセージ アクティビティとイベント アクティビティを送信する方法、およびスキルをキャンセルする方法について説明します。
スキル コンシューマーの作成に関するその他の側面については、スキル コンシューマーを実装する方法を参照してください。
ダイアログ スキル ボットについては、スキル内でダイアログを使用する方法を参照してください。
リソース
デプロイ済みのボットの場合、ボット間認証では、参加している各ボットが有効な ID を保持している必要があります。
ただし、ID 情報なしで Bot Framework Emulator を使用して、ローカルでスキルとスキル コンシューマーをテストできます。
アプリケーション構成
- 必要に応じて、ルート ボットの ID 情報を構成ファイルに追加します。
- スキルがスキル コンシューマーに応答するスキル ホスト エンドポイント (サービスまたはコールバックURL) を追加します。
- スキル コンシューマーが使用するスキルごとにエントリを追加します。 各エントリには次のものが含まれます。
- スキルコンシューマーが各スキルを識別するために使用するID。
- 必要に応じて、スキル ボットのアプリ ID またはクライアント ID。
- スキルのメッセージング エンドポイント。
メモ
スキルまたはスキル コンシューマーのいずれかが ID を指定している場合は、両方を指定する必要があります。
DialogRootBot\appsettings.js作動中
必要に応じて、ルート ボットの ID 情報を追加し、エコー スキル ボット用のアプリ ID またはクライアント ID を BotFrameworkSkills 配列に追加します。
{
"MicrosoftAppType": "",
"MicrosoftAppId": "",
"MicrosoftAppPassword": "",
"MicrosoftAppTenantId": "",
"SkillHostEndpoint": "http://localhost:3978/api/skills/",
"BotFrameworkSkills": [
{
"Id": "DialogSkillBot",
"AppId": "",
"SkillEndpoint": "http://localhost:39783/api/messages"
}
]
}
dialogRootBot/.env
必要に応じて、ルート ボットの ID 情報を追加し、エコー スキル ボット用のアプリ ID またはクライアント ID を追加します。
MicrosoftAppType=
MicrosoftAppId=
MicrosoftAppPassword=
MicrosoftAppTenantId=
SkillHostEndpoint=http://localhost:3978/api/skills/
SkillId=DialogSkillBot
SkillAppId=
SkillEndpoint=http://localhost:39783/api/messages
DialogRootBot\application.properties
必要に応じて、ルート ボットのアプリ ID とパスワードを追加し、エコー スキル ボット用のアプリ ID を BotFrameworkSkills 配列に追加します。
MicrosoftAppId=
MicrosoftAppPassword=
server.port=3978
SkillhostEndpoint=http://localhost:3978/api/skills/
#replicate these three entries, incrementing the index value [0] for each successive Skill that is added.
BotFrameworkSkills[0].Id=DialogSkillBot
BotFrameworkSkills[0].AppId=
BotFrameworkSkills[0].SkillEndpoint=http://localhost:39783/api/messages
dialog-root-bot/config.py
必要に応じて、ルート ボットのアプリ ID とパスワードを追加し、エコー スキル ボット用のアプリ ID を追加します。
APP_ID = os.environ.get("MicrosoftAppId", "")
APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "")
APP_TYPE = os.environ.get("MicrosoftAppType", "MultiTenant")
APP_TENANTID = os.environ.get("MicrosoftAppTenantId", "")
SKILL_HOST_ENDPOINT = "http://localhost:3978/api/skills"
SKILLS = [
{
"id": "DialogSkillBot",
"app_id": "",
"skill_endpoint": "http://localhost:39783/api/messages",
},
]
ダイアログ ロジック
ボットのメイン ダイアログには、このボットで使用する各スキルの "スキル ダイアログ" が含まれています。 スキル ダイアログでは、スキルに関連するさまざまなオブジェクト ("スキル クライアント" オブジェクトや "スキル会話 ID ファクトリ" オブジェクトなど) を通じてスキルが自動的に管理されます。
メイン ダイアログでは、ユーザー入力に基づいて (スキル ダイアログを通じて) スキルをキャンセルする方法も示します。
このボットで使用されるスキルでは、いくつかの異なる機能がサポートされています。 フライトを予約したり、都市の天気を取得したりできます。 また、これらのいずれかのコンテキストの外部でメッセージを受信し、LUIS 認識エンジンが構成されている場合、ユーザーの意図の解釈が試行されます。
スキル マニフェスト (C#、JavaScript、Java、Python) は、スキルが実行できるアクション、その入出力パラメーター、およびスキルのエンドポイントを記述したものです。
注目すべき点として、このスキルでは "BookFlight" イベントや "GetWeather" イベントを処理できます。 また、メッセージを処理することもできます。
メイン ダイアログには、次のことを行うコードが含まれています。
メイン ダイアログは、"コンポーネント ダイアログ" クラスから継承されます。 コンポーネント ダイアログの詳細については、ダイアログの複雑さを管理する方法を参照してください。
メイン ダイアログを初期化する
メイン ダイアログには、ダイアログ (スキルの外部にある会話フローの管理用) とスキル ダイアログ (スキルの管理用) が含まれています。
ウォーターフォールには、次の手順が含まれています。詳細については、次のいくつかのセクションで説明します。
- 使用するスキルを選択するようユーザーに要求します ルートボットは1つのスキルを消費します。
- そのスキルに使用するアクションを選択するよう、ユーザーに要求します (スキル ボットでは 3 つのアクションが定義されます。)
- 選択したスキルを開始します。初期アクティビティは、選択したアクションに基づきます。
- スキルが完了したら、結果を表示します (ある場合)。 その後、ウォーターフォールを再起動します。
DialogRootBot\Dialogs\MainDialog.cs
MainDialog クラスは、ComponentDialog から派生したものです。
会話の状態に加えて、ダイアログにはルート ボットの ID と、スキル会話 ID ファクトリ、スキル HTTP クライアント、およびスキル構成オブジェクトへの参照が必要です。
ダイアログ コンストラクターでは、その入力パラメーターが確認され、スキル ダイアログが追加され、スキルの外部にある会話フローを管理するためにプロンプトとウォーターフォール ダイアログが追加され、アクティブなスキル (ある場合) を追跡するためにプロパティ アクセサーが作成されます。
構成ファイルから AddSkillDialogs オブジェクトに読み込まれる際に、コンストラクターによりヘルパー メソッド SkillDialog が呼び出されて、構成ファイルに含まれる各スキルに対して SkillsConfiguration が作成されます。
// Helper method that creates and adds SkillDialog instances for the configured skills.
private void AddSkillDialogs(ConversationState conversationState, SkillConversationIdFactoryBase conversationIdFactory, SkillsConfiguration skillsConfig, string botId)
{
foreach (var skillInfo in _skillsConfig.Skills.Values)
{
// Create the dialog options.
var skillDialogOptions = new SkillDialogOptions
{
BotId = botId,
ConversationIdFactory = conversationIdFactory,
SkillClient = _auth.CreateBotFrameworkClient(),
SkillHostEndpoint = skillsConfig.SkillHostEndpoint,
ConversationState = conversationState,
Skill = skillInfo
};
// Add a SkillDialog for the selected skill.
AddDialog(new SkillDialog(skillDialogOptions, skillInfo.Id));
}
}
dialogRootBot/dialogs/mainDialog.js
MainDialog クラスは、ComponentDialog から派生したものです。
会話の状態に加えて、ダイアログにはルート ボットの ID と、スキル会話 ID ファクトリ、スキル HTTP クライアント、およびスキル構成オブジェクトへの参照が必要です。 このコードでは、ユーザー環境からボットの ID が取得されます。
ダイアログ コンストラクターでは、その入力パラメーターが確認され、スキル ダイアログが追加され、スキルの外部にある会話フローを管理するためにプロンプトとウォーターフォール ダイアログが追加され、アクティブなスキル (ある場合) を追跡するためにプロパティ アクセサーが作成されます。
構成ファイルから addSkillDialogs オブジェクトに読み込まれる際に、コンストラクターによりヘルパー メソッド SkillDialog が呼び出されて、構成ファイルに含まれる各スキルに対して SkillsConfiguration が作成されます。
* Helper method that creates and adds SkillDialog instances for the configured skills.
*/
async addSkillDialogs(conversationState, conversationIdFactory, skillClient, skillsConfig, botId) {
Object.keys(skillsConfig.skills).forEach((skillId) => {
const skillInfo = skillsConfig.skills[skillId];
const skillOptions = {
botId: process.env.MicrosoftAppId ?? '',
conversationIdFactory,
conversationState,
skill: skillInfo,
skillHostEndpoint: process.env.SkillHostEndpoint ?? '',
skillClient
};
// Add a SkillDialog for the selected skill.
this.addDialog(new SkillDialog(skillOptions, skillInfo.id));
DialogRootBot\dialogs\MainDialog.java
MainDialog クラスは、ComponentDialog から派生したものです。
会話の状態に加えて、ダイアログにはルート ボットのアプリ ID と、スキル会話 ID ファクトリ、スキル HTTP クライアント、およびスキル構成オブジェクトへの参照が必要です。
ダイアログ コンストラクターでは、その入力パラメーターが確認され、スキル ダイアログが追加され、スキルの外部にある会話フローを管理するためにプロンプトとウォーターフォール ダイアログが追加され、アクティブなスキル (ある場合) を追跡するためにプロパティ アクセサーが作成されます。
構成ファイルから addSkillDialogs オブジェクトに読み込まれる際に、コンストラクターによりヘルパー メソッド SkillDialog が呼び出されて、構成ファイルに含まれる各スキルに対して SkillsConfiguration が作成されます。
private void addSkillDialogs(
ConversationState conversationState,
SkillConversationIdFactoryBase conversationIdFactory,
SkillHttpClient skillClient,
SkillsConfiguration skillsConfig,
String botId
) {
for (BotFrameworkSkill skillInfo : _skillsConfig.getSkills().values()) {
// Create the dialog options.
SkillDialogOptions skillDialogOptions = new SkillDialogOptions();
skillDialogOptions.setBotId(botId);
skillDialogOptions.setConversationIdFactory(conversationIdFactory);
skillDialogOptions.setSkillClient(skillClient);
skillDialogOptions.setSkillHostEndpoint(skillsConfig.getSkillHostEndpoint());
skillDialogOptions.setConversationState(conversationState);
skillDialogOptions.setSkill(skillInfo);
// Add a SkillDialog for the selected skill.
addDialog(new SkillDialog(skillDialogOptions, skillInfo.getId()));
}
}
dialog-root-bot/dialogs/main_dialog.py
MainDialog クラスは、ComponentDialog から派生したものです。
会話の状態に加えて、ダイアログにはルート ボットのアプリ ID と、スキル会話 ID ファクトリ、スキル HTTP クライアント、およびスキル構成オブジェクトへの参照が必要です。
ダイアログ コンストラクターでは、その入力パラメーターが確認され、スキル ダイアログが追加され、スキルの外部にある会話フローを管理するためにプロンプトとウォーターフォール ダイアログが追加され、アクティブなスキル (ある場合) を追跡するためにプロパティ アクセサーが作成されます。
構成ファイルから AddSkillDialogs オブジェクトに読み込まれる際に、コンストラクターによりヘルパー メソッド SkillDialog が呼び出されて、構成ファイルに含まれる各スキルに対して SkillConfiguration が作成されます。
def _add_skill_dialogs(
self,
conversation_state: ConversationState,
conversation_id_factory: ConversationIdFactoryBase,
skill_client: SkillHttpClient,
skills_config: SkillConfiguration,
bot_id: str,
):
"""
Helper method that creates and adds SkillDialog instances for the configured skills.
"""
for _, skill_info in skills_config.SKILLS.items():
# Create the dialog options.
skill_dialog_options = SkillDialogOptions(
bot_id=bot_id,
conversation_id_factory=conversation_id_factory,
skill_client=skill_client,
skill_host_endpoint=skills_config.SKILL_HOST_ENDPOINT,
conversation_state=conversation_state,
skill=skill_info,
)
# Add a SkillDialog for the selected skill.
self.add_dialog(SkillDialog(skill_dialog_options, skill_info.id))
スキルを選択する
最初の手順では、メイン ダイアログで、呼び出すスキルを入力するようユーザーに要求し、"SkillPrompt" プロンプトを使用して回答を取得します (このボットで定義されるスキルは 1 つだけです)。
DialogRootBot\Dialogs\MainDialog.cs
// Render a prompt to select the skill to call.
private async Task<DialogTurnResult> SelectSkillStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// Create the PromptOptions from the skill configuration which contain the list of configured skills.
var messageText = stepContext.Options?.ToString() ?? "What skill would you like to call?";
var repromptMessageText = "That was not a valid choice, please select a valid skill.";
var options = new PromptOptions
{
Prompt = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput),
RetryPrompt = MessageFactory.Text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput),
Choices = _skillsConfig.Skills.Select(skill => new Choice(skill.Value.Id)).ToList()
};
// Prompt the user to select a skill.
return await stepContext.PromptAsync("SkillPrompt", options, cancellationToken);
}
dialogRootBot/dialogs/mainDialog.js
}
/**
* Render a prompt to select the skill to call.
*/
async selectSkillStep(stepContext) {
// Create the PromptOptions from the skill configuration which contains the list of configured skills.
const messageText = stepContext.options && stepContext.options.text ? stepContext.options.text : 'What skill would you like to call?';
const repromptMessageText = 'That was not a valid choice, please select a valid skill.';
const options = {
prompt: MessageFactory.text(messageText, messageText, InputHints.ExpectingInput),
retryPrompt: MessageFactory.text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput),
choices: Object.keys(this.skillsConfig.skills)
};
// Prompt the user to select a skill.
DialogRootBot\Dialogs\MainDialog.java
public CompletableFuture<DialogTurnResult> selectSkillStep(WaterfallStepContext stepContext) {
String messageText = "What skill would you like to call?";
// Create the PromptOptions from the skill configuration which contain the list
// of configured skills.
if (stepContext.getOptions() != null) {
messageText = stepContext.getOptions().toString();
}
String repromptMessageText = "That was not a valid choice, please select a valid skill.";
PromptOptions options = new PromptOptions();
options.setPrompt(MessageFactory.text(messageText, messageText, InputHints.EXPECTING_INPUT));
options
.setRetryPrompt(MessageFactory.text(repromptMessageText, repromptMessageText, InputHints.EXPECTING_INPUT));
List<Choice> choicesList = new ArrayList<Choice>();
for (BotFrameworkSkill skill : _skillsConfig.getSkills().values()) {
choicesList.add(new Choice(skill.getId()));
}
options.setChoices(choicesList);
// Prompt the user to select a skill.
return stepContext.prompt("SkillPrompt", options);
}
dialog-root-bot/dialogs/main_dialog.py
async def _select_skill_action_step(
self, step_context: WaterfallStepContext
) -> DialogTurnResult:
"""
Render a prompt to select the action for the skill.
"""
# Get the skill info based on the selected skill.
selected_skill_id = step_context.result.value
selected_skill = self._skills_config.SKILLS.get(selected_skill_id)
# Remember the skill selected by the user.
step_context.values[self._selected_skill_key] = selected_skill
# Create the PromptOptions with the actions supported by the selected skill.
message_text = (
f"Select an action # to send to **{selected_skill.id}** or just type in a message "
f"and it will be forwarded to the skill"
)
options = PromptOptions(
prompt=MessageFactory.text(
message_text, message_text, InputHints.expecting_input
),
choices=self._get_skill_actions(selected_skill),
)
# Prompt the user to select a skill action.
return await step_context.prompt("SkillActionPrompt", options)
スキル アクションを選択する
次の手順では、メイン ダイアログで次の操作を行います。
- ユーザーが選択したスキルに関する情報を保存します。
- 使用するスキル アクションをユーザーに要求し、"SkillActionPrompt" 選択プロンプトを使用して回答を取得します。
- ヘルパー メソッドを使用して、選択元となるアクションの一覧を取得します。
- このプロンプトに関連付けられているプロンプト検証コントロールでは、ユーザーの入力がいずれかの選択肢に一致しない場合、既定でスキルにメッセージが送信されます。
このボットに含まれる選択肢は、このスキルに対して定義されているアクションをテストするのに役立ちます。 通常は、スキルのマニフェストからオプションを読み取り、その一覧に基づいてユーザーにオプションを表示します。
DialogRootBot\Dialogs\MainDialog.cs
// Render a prompt to select the action for the skill.
private async Task<DialogTurnResult> SelectSkillActionStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// Get the skill info based on the selected skill.
var selectedSkillId = ((FoundChoice)stepContext.Result).Value;
var selectedSkill = _skillsConfig.Skills.FirstOrDefault(s => s.Value.Id == selectedSkillId).Value;
// Remember the skill selected by the user.
stepContext.Values[_selectedSkillKey] = selectedSkill;
// Create the PromptOptions with the actions supported by the selected skill.
var messageText = $"Select an action # to send to **{selectedSkill.Id}** or just type in a message and it will be forwarded to the skill";
var options = new PromptOptions
{
Prompt = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput),
Choices = GetSkillActions(selectedSkill)
};
// Prompt the user to select a skill action.
return await stepContext.PromptAsync("SkillActionPrompt", options, cancellationToken);
}
// Helper method to create Choice elements for the actions supported by the skill.
private IList<Choice> GetSkillActions(BotFrameworkSkill skill)
{
// Note: the bot would probably render this by reading the skill manifest.
// We are just using hardcoded skill actions here for simplicity.
var choices = new List<Choice>();
switch (skill.Id)
{
case "DialogSkillBot":
choices.Add(new Choice(SkillActionBookFlight));
choices.Add(new Choice(SkillActionBookFlightWithInputParameters));
choices.Add(new Choice(SkillActionGetWeather));
break;
}
return choices;
}
// This validator defaults to Message if the user doesn't select an existing option.
private Task<bool> SkillActionPromptValidator(PromptValidatorContext<FoundChoice> promptContext, CancellationToken cancellationToken)
{
if (!promptContext.Recognized.Succeeded)
{
// Assume the user wants to send a message if an item in the list is not selected.
promptContext.Recognized.Value = new FoundChoice { Value = SkillActionMessage };
}
return Task.FromResult(true);
}
dialogRootBot/dialogs/mainDialog.js
}
/**
* Render a prompt to select the action for the skill.
*/
async selectSkillActionStep(stepContext) {
// Get the skill info based on the selected skill.
const selectedSkillId = stepContext.result.value;
const selectedSkill = this.skillsConfig.skills[selectedSkillId];
// Remember the skill selected by the user.
stepContext.values[this.selectedSkillKey] = selectedSkill;
// Create the PromptOptions with the actions supported by the selected skill.
const messageText = `Select an action # to send to **${ selectedSkill.id }** or just type in a message and it will be forwarded to the skill`;
const options = {
prompt: MessageFactory.text(messageText, messageText, InputHints.ExpectingInput),
choices: this.getSkillActions(selectedSkill)
};
// Prompt the user to select a skill action.
});
}
/**
* Helper method to create Choice elements for the actions supported by the skill.
*/
getSkillActions(skill) {
// Note: The bot would probably render this by reading the skill manifest.
// We are just using hardcoded skill actions here for simplicity.
const choices = [];
switch (skill.id) {
case 'DialogSkillBot':
choices.push({ value: SKILL_ACTION_BOOK_FLIGHT });
choices.push({ value: SKILL_ACTION_BOOK_FLIGHT_WITH_INPUT_PARAMETERS });
choices.push({ value: SKILL_ACTION_GET_WEATHER });
break;
}
activity.properties = turnContext.activity.properties;
return activity;
}
/**
* This validator defaults to Message if the user doesn't select an existing option.
*/
async skillActionPromptValidator(promptContext) {
if (!promptContext.recognized.succeeded) {
promptContext.recognized.value = { value: SKILL_ACTION_MESSAGE };
DialogRootBot\Dialogs\MainDialog.java
public CompletableFuture<DialogTurnResult> selectSkillActionStep(WaterfallStepContext stepContext) {
// Get the skill info super. on the selected skill.
String selectedSkillId = ((FoundChoice) stepContext.getResult()).getValue();
BotFrameworkSkill selectedSkill = _skillsConfig.getSkills()
.values()
.stream()
.filter(x -> x.getId().equals(selectedSkillId))
.findFirst()
.get();
// Remember the skill selected by the user.
stepContext.getValues().put(_selectedSkillKey, selectedSkill);
// Create the PromptOptions with the actions supported by the selected skill.
String messageText = String.format(
"Select an action # to send to **%n** or just type in a " + "message and it will be forwarded to the skill",
selectedSkill.getId()
);
PromptOptions options = new PromptOptions();
options.setPrompt(MessageFactory.text(messageText, messageText, InputHints.EXPECTING_INPUT));
options.setChoices(getSkillActions(selectedSkill));
// Prompt the user to select a skill action.
return stepContext.prompt("SkillActionPrompt", options);
}
private List<Choice> getSkillActions(BotFrameworkSkill skill) {
// Note: the bot would probably render this by reading the skill manifest.
// We are just using hardcoded skill actions here for simplicity.
List<Choice> choices = new ArrayList<Choice>();
switch (skill.getId()) {
case "DialogSkillBot":
choices.add(new Choice(SkillActionBookFlight));
choices.add(new Choice(SkillActionBookFlightWithInputParameters));
choices.add(new Choice(SkillActionGetWeather));
break;
}
return choices;
}
addDialog(new ChoicePrompt("SkillActionPrompt", (promptContext) -> {
if (!promptContext.getRecognized().getSucceeded()) {
// Assume the user wants to send a message if an item in the list is not
// selected.
FoundChoice foundChoice = new FoundChoice();
foundChoice.setValue(SkillActionMessage);
promptContext.getRecognized().setValue(foundChoice);
}
return CompletableFuture.completedFuture(true);
}, ""));
dialog-root-bot/dialogs/main_dialog.py
async def _select_skill_action_step(
self, step_context: WaterfallStepContext
) -> DialogTurnResult:
"""
Render a prompt to select the action for the skill.
"""
# Get the skill info based on the selected skill.
selected_skill_id = step_context.result.value
selected_skill = self._skills_config.SKILLS.get(selected_skill_id)
# Remember the skill selected by the user.
step_context.values[self._selected_skill_key] = selected_skill
# Create the PromptOptions with the actions supported by the selected skill.
message_text = (
f"Select an action # to send to **{selected_skill.id}** or just type in a message "
f"and it will be forwarded to the skill"
)
options = PromptOptions(
prompt=MessageFactory.text(
message_text, message_text, InputHints.expecting_input
),
choices=self._get_skill_actions(selected_skill),
)
# Prompt the user to select a skill action.
return await step_context.prompt("SkillActionPrompt", options)
def _get_skill_actions(self, skill: BotFrameworkSkill) -> List[Choice]:
"""
Helper method to create Choice elements for the actions supported by the skill.
"""
# Note: the bot would probably render this by reading the skill manifest.
# We are just using hardcoded skill actions here for simplicity.
choices = []
if skill.id == "DialogSkillBot":
choices.append(Choice(self._skill_action_book_flight))
choices.append(Choice(self._skill_action_book_flight_with_input_parameters))
choices.append(Choice(self._skill_action_get_weather))
return choices
async def _skill_action_prompt_validator(
self, prompt_context: PromptValidatorContext
) -> bool:
"""
This validator defaults to Message if the user doesn't select an existing option.
"""
if not prompt_context.recognized.succeeded:
# Assume the user wants to send a message if an item in the list is not selected.
prompt_context.recognized.value = FoundChoice(
self._skill_action_message, None, None
)
return True
スキルを開始する
次の手順では、メイン ダイアログで次の操作を行います。
- ユーザーが選択したスキルとスキル アクティビティに関する情報を取得します。
- ヘルパー メソッドを使用して、最初にスキルに送信するアクティビティを作成します。
- スキル ダイアログを開始するためのダイアログ オプションを作成します。 これには、送信する最初のアクティビティが含まれます。
- スキルを呼び出す前に状態を保存します (これは、スキルの応答がスキル コンシューマーの別のインスタンスに由来する可能性があるため、必要です)。
- スキル ダイアログを開始し、呼び出すスキル ID と、それを呼び出すオプションを渡します。
DialogRootBot\Dialogs\MainDialog.cs
// Starts the SkillDialog based on the user's selections.
private async Task<DialogTurnResult> CallSkillActionStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var selectedSkill = (BotFrameworkSkill)stepContext.Values[_selectedSkillKey];
Activity skillActivity;
switch (selectedSkill.Id)
{
case "DialogSkillBot":
skillActivity = CreateDialogSkillBotActivity(((FoundChoice)stepContext.Result).Value, stepContext.Context);
break;
// We can add other case statements here if we support more than one skill.
default:
throw new Exception($"Unknown target skill id: {selectedSkill.Id}.");
}
// Create the BeginSkillDialogOptions and assign the activity to send.
var skillDialogArgs = new BeginSkillDialogOptions { Activity = skillActivity };
// Save active skill in state.
await _activeSkillProperty.SetAsync(stepContext.Context, selectedSkill, cancellationToken);
// Start the skillDialog instance with the arguments.
return await stepContext.BeginDialogAsync(selectedSkill.Id, skillDialogArgs, cancellationToken);
}
dialogRootBot/dialogs/mainDialog.js
}
/**
* Starts the SkillDialog based on the user's selections.
*/
async callSkillActionStep(stepContext) {
const selectedSkill = stepContext.values[this.selectedSkillKey];
let skillActivity;
switch (selectedSkill.id) {
case 'DialogSkillBot':
skillActivity = this.createDialogSkillBotActivity(stepContext.result.value, stepContext.context);
break;
// We can add other case statements here if we support more than one skill.
default:
throw new Error(`Unknown target skill id: ${ selectedSkill.id }`);
}
// Create the BeginSkillDialogOptions and assign the activity to send.
const skillDialogArgs = { activity: skillActivity };
// Save active skill in state.
await this.activeSkillProperty.set(stepContext.context, selectedSkill);
// Start the skillDialog instance with the arguments.
DialogRootBot\Dialogs\MainDialog.java
public CompletableFuture<DialogTurnResult> callSkillActionStep(WaterfallStepContext stepContext) {
BotFrameworkSkill selectedSkill = (BotFrameworkSkill) stepContext.getValues().get(_selectedSkillKey);
Activity skillActivity;
switch (selectedSkill.getId()) {
case "DialogSkillBot":
skillActivity = createDialogSkillBotActivity(
((FoundChoice) stepContext.getResult()).getValue(),
stepContext.getContext()
);
break;
// We can add other case statements here if we support more than one skill.
default:
throw new RuntimeException(String.format("Unknown target skill id: %s.", selectedSkill.getId()));
}
// Create the BeginSkillDialogOptions and assign the activity to send.
BeginSkillDialogOptions skillDialogArgs = new BeginSkillDialogOptions();
skillDialogArgs.setActivity(skillActivity);
// Save active skill in state.
activeSkillProperty.set(stepContext.getContext(), selectedSkill);
// Start the skillDialog instance with the arguments.
return stepContext.beginDialog(selectedSkill.getId(), skillDialogArgs);
}
dialog-root-bot/dialogs/main_dialog.py
async def _call_skill_action_step(
self, step_context: WaterfallStepContext
) -> DialogTurnResult:
"""
Starts the SkillDialog based on the user's selections.
"""
selected_skill: BotFrameworkSkill = step_context.values[
self._selected_skill_key
]
if selected_skill.id == "DialogSkillBot":
skill_activity = self._create_dialog_skill_bot_activity(
step_context.result.value, step_context.context
)
else:
raise Exception(f"Unknown target skill id: {selected_skill.id}.")
# Create the BeginSkillDialogOptions and assign the activity to send.
skill_dialog_args = BeginSkillDialogOptions(skill_activity)
# Save active skill in state.
await self._active_skill_property.set(step_context.context, selected_skill)
# Start the skillDialog instance with the arguments.
return await step_context.begin_dialog(selected_skill.id, skill_dialog_args)
スキル結果を要約する
最後の手順では、メイン ダイアログで次の操作を行います。
- スキルによって値が返された場合は、結果をユーザーに表示します。
- ダイアログの状態からアクティブなスキルをクリアします。
- 会話の状態からアクティブなスキル プロパティを削除します。
- 自動的に再起動します (メイン ダイアログ)。
DialogRootBot\Dialogs\MainDialog.cs
// The SkillDialog has ended, render the results (if any) and restart MainDialog.
private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var activeSkill = await _activeSkillProperty.GetAsync(stepContext.Context, () => null, cancellationToken);
// Check if the skill returned any results and display them.
if (stepContext.Result != null)
{
var message = $"Skill \"{activeSkill.Id}\" invocation complete.";
message += $" Result: {JsonConvert.SerializeObject(stepContext.Result)}";
await stepContext.Context.SendActivityAsync(MessageFactory.Text(message, message, inputHint: InputHints.IgnoringInput), cancellationToken: cancellationToken);
}
// Clear the skill selected by the user.
stepContext.Values[_selectedSkillKey] = null;
// Clear active skill in state.
await _activeSkillProperty.DeleteAsync(stepContext.Context, cancellationToken);
// Restart the main dialog with a different message the second time around.
return await stepContext.ReplaceDialogAsync(InitialDialogId, $"Done with \"{activeSkill.Id}\". \n\n What skill would you like to call?", cancellationToken);
}
dialogRootBot/dialogs/mainDialog.js
}
/**
* The SkillDialog has ended, render the results (if any) and restart MainDialog.
*/
async finalStep(stepContext) {
const activeSkill = await this.activeSkillProperty.get(stepContext.context, () => null);
// Check if the skill returned any results and display them.
if (stepContext.result != null) {
let message = `Skill "${ activeSkill.id }" invocation complete.`;
message += `\nResult: ${ JSON.stringify(stepContext.result, null, 2) }`;
await stepContext.context.sendActivity(message, message, InputHints.IgnoringInput);
}
// Clear the skill selected by the user.
stepContext.values[this.selectedSkillKey] = null;
// Clear active skill in state.
await this.activeSkillProperty.delete(stepContext.context);
// Restart the main dialog with a different message the second time around.
DialogRootBot\Dialogs\MainDialog.java
public CompletableFuture<DialogTurnResult> finalStep(WaterfallStepContext stepContext) {
return activeSkillProperty.get(stepContext.getContext(), () -> null).thenCompose(activeSkill -> {
if (stepContext.getResult() != null) {
String jsonResult = "";
try {
jsonResult =
new JacksonAdapter().serialize(stepContext.getResult()).replace("{", "").replace("}", "");
} catch (IOException e) {
e.printStackTrace();
}
String message =
String.format("Skill \"%s\" invocation complete. Result: %s", activeSkill.getId(), jsonResult);
stepContext.getContext().sendActivity(MessageFactory.text(message, message, InputHints.IGNORING_INPUT));
}
// Clear the skill selected by the user.
stepContext.getValues().put(_selectedSkillKey, null);
// Clear active skill in state.
activeSkillProperty.delete(stepContext.getContext());
// Restart the main dialog with a different message the second time around.
return stepContext.replaceDialog(
getInitialDialogId(),
String.format("Done with \"%s\". \n\n What skill would you like to call?", activeSkill.getId())
);
});
// Check if the skill returned any results and display them.
}
dialog-root-bot/dialogs/main_dialog.py
async def _final_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
"""
The SkillDialog has ended, render the results (if any) and restart MainDialog.
"""
active_skill = await self._active_skill_property.get(step_context.context)
if step_context.result:
message = f"Skill {active_skill.id} invocation complete."
message += f" Result: {step_context.result}"
await step_context.context.send_activity(
MessageFactory.text(message, input_hint=InputHints.ignoring_input)
)
# Clear the skill selected by the user.
step_context.values[self._selected_skill_key] = None
# Clear active skill in state.
await self._active_skill_property.delete(step_context.context)
# Restart the main dialog with a different message the second time around
return await step_context.replace_dialog(
self.initial_dialog_id,
f'Done with "{active_skill.id}". \n\n What skill would you like to call?',
)
スキルのキャンセルをユーザーに許可する
メイン ダイアログでは、on continue dialog メソッドの既定の動作がオーバーライドされて、ユーザーは現在のスキル (ある場合) をキャンセルできます。 メソッド内:
- アクティブなスキルがあり、ユーザーが "abort" メッセージを送信した場合は、すべてのダイアログをキャンセルし、メイン ダイアログをキューに入れて最初から再開します。
- 次に、on continue dialog メソッドの基本実装を呼び出して、現在のターンの処理を続行します。
DialogRootBot\Dialogs\MainDialog.cs
protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
{
// This is an example on how to cancel a SkillDialog that is currently in progress from the parent bot.
var activeSkill = await _activeSkillProperty.GetAsync(innerDc.Context, () => null, cancellationToken);
var activity = innerDc.Context.Activity;
if (activeSkill != null && activity.Type == ActivityTypes.Message && activity.Text.Equals("abort", StringComparison.OrdinalIgnoreCase))
{
// Cancel all dialogs when the user says abort.
// The SkillDialog automatically sends an EndOfConversation message to the skill to let the
// skill know that it needs to end its current dialogs, too.
await innerDc.CancelAllDialogsAsync(cancellationToken);
return await innerDc.ReplaceDialogAsync(InitialDialogId, "Canceled! \n\n What skill would you like to call?", cancellationToken);
}
return await base.OnContinueDialogAsync(innerDc, cancellationToken);
}
dialogRootBot/dialogs/mainDialog.js
}
async onContinueDialog(innerDc) {
const activeSkill = await this.activeSkillProperty.get(innerDc.context, () => null);
const activity = innerDc.context.activity;
if (activeSkill != null && activity.type === ActivityTypes.Message && activity.text.toLowerCase() === 'abort') {
// Cancel all dialogs when the user says abort.
// The SkillDialog automatically sends an EndOfConversation message to the skill to let the
// skill know that it needs to end its current dialogs, too.
await innerDc.cancelAllDialogs();
return await innerDc.replaceDialog(this.initialDialogId, { text: 'Canceled! \n\n What skill would you like to call?' });
}
DialogRootBot\Dialogs\MainDialog.java
// Cancel all dialogs when the user says abort.
// The SkillDialog automatically sends an EndOfConversation message to the skill
// to let the
// skill know that it needs to end its current dialogs, too.
return innerDc.cancelAllDialogs()
.thenCompose(
result -> innerDc
.replaceDialog(getInitialDialogId(), "Canceled! \n\n What skill would you like to call?")
);
dialog-root-bot/dialogs/main_dialog.py
async def on_continue_dialog(self, inner_dc: DialogContext) -> DialogTurnResult:
# This is an example on how to cancel a SkillDialog that is currently in progress from the parent bot.
active_skill = await self._active_skill_property.get(inner_dc.context)
activity = inner_dc.context.activity
if (
active_skill
and activity.type == ActivityTypes.message
and "abort" in activity.text
):
# Cancel all dialogs when the user says abort.
# The SkillDialog automatically sends an EndOfConversation message to the skill to let the
# skill know that it needs to end its current dialogs, too.
await inner_dc.cancel_all_dialogs()
return await inner_dc.replace_dialog(self.initial_dialog_id)
return await super().on_continue_dialog(inner_dc)
アクティビティ ハンドラーのロジック
各ターンのスキル ロジックはメイン ダイアログで処理されるため、アクティビティ ハンドラーは他のダイアログ サンプルの場合と同じように見えます。
DialogRootBot\Bots\RootBot.cs
public class RootBot<T> : ActivityHandler
where T : Dialog
private readonly ConversationState _conversationState;
private readonly Dialog _mainDialog;
public RootBot(ConversationState conversationState, T mainDialog)
{
_conversationState = conversationState;
_mainDialog = mainDialog;
}
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
{
if (turnContext.Activity.Type != ActivityTypes.ConversationUpdate)
{
// Run the Dialog with the Activity.
await _mainDialog.RunAsync(turnContext, _conversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);
}
else
{
// Let the base class handle the activity.
await base.OnTurnAsync(turnContext, cancellationToken);
}
// Save any state changes that might have occurred during the turn.
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
}
dialogRootBot/bots/rootBot.js
const WelcomeCard = require('../cards/welcomeCard.json');
/**
*
* @param {ConversationState} conversationState
* @param {MainDialog} dialog
*/
constructor(conversationState, dialog) {
super();
if (!conversationState) throw new Error('[RootBot]: Missing parameter. conversationState is required');
if (!dialog) throw new Error('[RootBot]: Missing parameter. dialog is required');
this.conversationState = conversationState;
this.dialog = dialog;
this.onTurn(async (turnContext, next) => {
if (turnContext.activity.type !== ActivityTypes.ConversationUpdate) {
// Run the Dialog with the activity.
// By calling next() you ensure that the next BotHandler is run.
await next();
});
}
/**
* Override the ActivityHandler.run() method to save state changes after the bot logic completes.
*/
async run(context) {
DialogRootBot\Bots\RootBot.java
public class RootBot<T extends Dialog> extends ActivityHandler {
public RootBot(ConversationState conversationState, T mainDialog) {
this.conversationState = conversationState;
this.mainDialog = mainDialog;
}
@Override
public CompletableFuture<Void> onTurn(TurnContext turnContext) {
return handleTurn(turnContext).thenCompose(result -> conversationState.saveChanges(turnContext, false));
}
private CompletableFuture<Void> handleTurn(TurnContext turnContext) {
if (!turnContext.getActivity().getType().equals(ActivityTypes.CONVERSATION_UPDATE)) {
// Run the Dialog with the Activity.
return Dialog.run(mainDialog, turnContext, conversationState.createProperty("DialogState"));
} else {
// Let the super.class handle the activity.
return super.onTurn(turnContext);
}
}
dialog-root-bot/bots/root_bot.py
class RootBot(ActivityHandler):
def __init__(
self, conversation_state: ConversationState, main_dialog: Dialog,
):
self._conversation_state = conversation_state
self._main_dialog = main_dialog
async def on_turn(self, turn_context: TurnContext):
if turn_context.activity.type != ActivityTypes.conversation_update:
# Run the Dialog with the Activity.
await DialogExtensions.run_dialog(
self._main_dialog,
turn_context,
self._conversation_state.create_property("DialogState"),
)
else:
# Let the base class handle the activity.
await super().on_turn(turn_context)
# Save any state changes that might have occurred during the turn.
await self._conversation_state.save_changes(turn_context)
サービス登録
スキル ダイアログを使用するために必要なサービスは、一般的に、スキル コンシューマーに必要なサービスと同じです。
必要なサービスについては、スキル コンシューマーを実装する方法を参照してください。
ルート ボットのテスト
スキル コンシューマーは通常のボットと同様にエミュレーターでテストできますが、スキルとスキル コンシューマーの両方のボットを同時に実行する必要があります。 スキルを構成する方法については、スキル内でダイアログを使用する方法を参照してください。
最新の Bot Framework Emulator をダウンロードしてインストールします。
- ダイアログ スキル ボットとダイアログ ルート ボットを自分のコンピュータでローカルに実行します。 手順が必要な場合は、サンプルの
README で C#、JavaScript、Java、Python の箇所を参照してください。
- エミュレーターを使用してボットをテストします。
- 最初に会話に参加すると、ボットによってウェルカム メッセージが表示され、呼び出したいスキルをたずねられます。 このサンプルのスキル ボットには、スキルは 1 つしかありません。
-
[DialogSkillBot] を選択します。
- 次に、スキルのアクションを選択するようにボットから求められます。 [BookFlight] を選択します。
- プロンプトに答えます。
- スキルが完了すると、ルートボットが、予約の詳細を表示し、再び呼び出したいスキルを尋ねます。
- もう一度 [DialogSkillBot] を選択し、[BookFlight] を選択します。
- 最初のプロンプトに答え、「abort」と入力してスキルを中断します。
- ルート ボットでスキルがキャンセルされ、呼び出すスキルが要求されます。
デバッグの詳細
スキルとスキル コンシューマーの間のトラフィックは認証されるため、このようなボットをデバッグする際には追加の手順があります。
それ以外の場合は、その他のボットをデバッグするのと同様に、スキル コンシューマーまたはスキルをデバッグできます。 詳細については、ボットのデバッグに関する記事、およびBot Framework Emulator を使用したデバッグに関する記事を参照してください。
一般的なスキル コンシューマーの実装方法については、スキル コンシューマーを実装する方法を参照してください。