適用対象: 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 への移行ガイダンスで、主要な変更と更新を確認します。
SaaS ベースのエージェント プラットフォームをお探しの場合は、 Microsoft Copilot Studio を検討してください。
前提条件
このサンプルについて
skills skillDialog サンプルには、次の 2 つのボットのプロジェクトが含まれています。
-
ダイアログ ルート ボットは、スキル ダイアログクラスを使用してスキルを利用します。
- "ダイアログ スキル ボット"。ダイアログを使用して、スキル コンシューマーからのアクティビティを処理します。 このスキルは、コア ボット サンプルを適応させたものです (コア ボットの詳細については、「ボットに自然言語の理解を追加する」を参照してください)。
この記事では、スキル ボット内でダイアログを使用して複数のアクションを管理する方法について説明します。
スキル コンシューマー ボットの詳細については、ダイアログを使用してスキルを利用する方法を参照してください。
リソース
デプロイ済みのボットの場合、ボット間認証では、参加している各ボットが有効な ID を保持している必要があります。
ただし、ID 情報なしで Bot Framework Emulator を使用して、ローカルでスキルとスキル コンシューマーをテストできます。
ユーザー向けボットでスキルを使用できるようにするには、スキルを Azure に登録します。 詳細については、Azure AI Bot Service にボットを登録する方法に関する記事を参照してください。
必要に応じて、スキル ボットではフライト予約 LUIS モデルを使用できます。 このモデルを使用するには、CognitiveModels/FlightBooking.json ファイルを使用して、LUIS モデルを作成し、トレーニングし、発行します。
アプリケーション構成
必要に応じて、スキルの ID 情報をスキルの構成ファイルに追加します。
(スキルまたはスキル コンシューマーのいずれかで ID が指定されている場合は、両方で指定が必要です)。
LUIS モデルを使用する場合は、LUIS アプリ ID、API キー、および API ホスト名を追加します。
DialogSkillBot\appsettings.json
{
"MicrosoftAppType": "",
"MicrosoftAppId": "",
"MicrosoftAppPassword": "",
"MicrosoftAppTenantId": "",
"ConnectionName": "",
"LuisAppId": "",
"LuisAPIKey": "",
"LuisAPIHostName": "",
// This is a comma separate list with the App IDs that will have access to the skill.
// This setting is used in AllowedCallersClaimsValidator.
// Examples:
// [ "*" ] allows all callers.
// [ "AppId1", "AppId2" ] only allows access to parent bots with "AppId1" and "AppId2".
"AllowedCallers": [ "*" ]
}
dialogSkillBot/.env
MicrosoftAppType=
MicrosoftAppId=
MicrosoftAppPassword=
MicrosoftAppTenantId=
AllowedCallers=*
LuisAppId=
LuisAPIKey=
LuisAPIHostName=
application.properties
MicrosoftAppId=
MicrosoftAppPassword=
server.port=39783
LuisAppId=
LuisAPIKey=
LuisAPIHostName=
# This is a comma separate list with the App IDs that will have access to the skill.
# This setting is used in AllowedCallersClaimsValidator.
# Examples:
# * allows all callers.
# AppId1,AppId2 only allows access to parent bots with "AppId1" and "AppId2".
AllowedCallers=*
dialog-skill-bot/config.py
PORT = 39783
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", "")
# Callers to only those specified, '*' allows any caller.
# Example: os.environ.get("AllowedCallers", ["aaaaaaaa-1111-aaaa-aaaa-aaaaaaaa"])
ALLOWED_CALLERS = os.environ.get("AllowedCallers", ["*"])
LUIS_APP_ID = os.environ.get("LuisAppId", "")
LUIS_API_KEY = os.environ.get("LuisAPIKey", "")
アクティビティルーティング ロジック
このスキルでは、いくつかの異なる機能がサポートされています。 フライトを予約したり、都市の天気を取得したりできます。 また、これらのいずれかのコンテキストの外部でメッセージを受信した場合は、LUIS を使用してメッセージの解釈を試みることができます。
スキルのマニフェストには、これらのアクション、その入力パラメーターと出力パラメーター、スキルのエンドポイントが記述されています。
注目すべき点として、このスキルでは "BookFlight" イベントや "GetWeather" イベントを処理できます。 また、メッセージ アクティビティを処理することもできます。
このスキルでは、アクティビティルーティング ダイアログが定義されており、スキル コンシューマーからの最初の受信アクティビティに基づいて、どのアクションを開始するかを選択するために使用されます。
LUIS モデルが提供されている場合は、それによって、最初のメッセージでフライトの予約の意図や天気の取得の意図が認識されます。
フライト予約アクションは、個別のダイアログとして実装された、複数ステップ プロセスです。 このアクションが開始されると、そのダイアログによって受信アクティビティが処理されます。 天気取得アクションには、完全に実装されたボットで置き換えられるプレースホルダー ロジックがあります。
アクティビティルーティング ダイアログには、次のことを行うコードが含まれています。
スキルで使用されるダイアログは、コンポーネント ダイアログ クラスから継承されたものです。 コンポーネント ダイアログの詳細については、ダイアログの複雑さを管理する方法を参照してください。
ダイアログを初期化する
アクティビティルーティング ダイアログには、フライトを予約するための子ダイアログが含まれています。 メイン ウォーターフォール ダイアログには、受信した最初のアクティビティに基づいてアクションを開始する 1 つのステップがあります。
また、LUIS 認識エンジンも受け入れます。 この認識エンジンが初期化されている場合、ダイアログではそれを使用して最初のメッセージ アクティビティの意図が解釈されます。
DialogSkillBot\Dialogs\ActivityRouterDialog.cs
private readonly DialogSkillBotRecognizer _luisRecognizer;
public ActivityRouterDialog(DialogSkillBotRecognizer luisRecognizer)
: base(nameof(ActivityRouterDialog))
{
_luisRecognizer = luisRecognizer;
AddDialog(new BookingDialog());
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] { ProcessActivityAsync }));
// The initial child Dialog to run.
InitialDialogId = nameof(WaterfallDialog);
}
dialogSkillBot/dialogs/activityRouterDialog.js
*/
class ActivityRouterDialog extends ComponentDialog {
constructor(conversationState, luisRecognizer) {
super(ACTIVITY_ROUTER_DIALOG);
if (!conversationState) throw new Error('[MainDialog]: Missing parameter \'conversationState\' is required');
this.luisRecognizer = luisRecognizer;
// Define the main dialog and its related components.
// This is a sample "book a flight" dialog.
this.addDialog(new BookingDialog())
.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
this.processActivity.bind(this)
]));
DialogSkillBot\Dialogs\ActivityRouterDialog.java
private final DialogSkillBotRecognizer luisRecognizer;
public ActivityRouterDialog(DialogSkillBotRecognizer luisRecognizer) {
super("ActivityRouterDialog");
this.luisRecognizer = luisRecognizer;
addDialog(new BookingDialog());
List<WaterfallStep> stepList = new ArrayList<WaterfallStep>();
stepList.add(this::processActivity);
addDialog(new WaterfallDialog("WaterfallDialog", stepList));
// The initial child Dialog to run.
setInitialDialogId("WaterfallDialog");
}
dialog-skill-bot/dialogs/activity_router_dialog.py
def __init__(self, luis_recognizer: DialogSkillBotRecognizer):
super().__init__(ActivityRouterDialog.__name__)
self._luis_recognizer = luis_recognizer
self.add_dialog(BookingDialog())
self.add_dialog(
WaterfallDialog(WaterfallDialog.__name__, [self.process_activity])
)
self.initial_dialog_id = WaterfallDialog.__name__
最初のアクティビティを処理する
メイン ウォーターフォール ダイアログの最初の (および唯一の) ステップでは、受信アクティビティの種類がスキルによって確認されます。
- イベント アクティビティは、イベントの名前に基づいて適切なアクションを開始する on event activity ハンドラーに転送されます。
- メッセージ アクティビティは、何をすべきかを決定する前に追加の処理を実行する on message activity ハンドラーに転送されます。
受信アクティビティの種類またはイベントの名前を認識できない場合、スキルはエラー メッセージを送信して終了します。
DialogSkillBot\Dialogs\ActivityRouterDialog.cs
private async Task<DialogTurnResult> ProcessActivityAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// A skill can send trace activities, if needed.
await stepContext.Context.TraceActivityAsync($"{GetType().Name}.ProcessActivityAsync()", label: $"Got ActivityType: {stepContext.Context.Activity.Type}", cancellationToken: cancellationToken);
switch (stepContext.Context.Activity.Type)
{
case ActivityTypes.Event:
return await OnEventActivityAsync(stepContext, cancellationToken);
case ActivityTypes.Message:
return await OnMessageActivityAsync(stepContext, cancellationToken);
default:
// We didn't get an activity type we can handle.
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Unrecognized ActivityType: \"{stepContext.Context.Activity.Type}\".", inputHint: InputHints.IgnoringInput), cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Complete);
}
}
// This method performs different tasks based on the event name.
private async Task<DialogTurnResult> OnEventActivityAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var activity = stepContext.Context.Activity;
await stepContext.Context.TraceActivityAsync($"{GetType().Name}.OnEventActivityAsync()", label: $"Name: {activity.Name}. Value: {GetObjectAsJsonString(activity.Value)}", cancellationToken: cancellationToken);
// Resolve what to execute based on the event name.
switch (activity.Name)
{
case "BookFlight":
return await BeginBookFlight(stepContext, cancellationToken);
case "GetWeather":
return await BeginGetWeather(stepContext, cancellationToken);
default:
// We didn't get an event name we can handle.
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Unrecognized EventName: \"{activity.Name}\".", inputHint: InputHints.IgnoringInput), cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Complete);
}
}
dialogSkillBot/dialogs/activityRouterDialog.js
}
async processActivity(stepContext) {
// A skill can send trace activities, if needed.
const traceActivity = {
type: ActivityTypes.Trace,
timestamp: new Date(),
text: 'ActivityRouterDialog.processActivity()',
label: `Got activityType: ${ stepContext.context.activity.type }`
};
await stepContext.context.sendActivity(traceActivity);
switch (stepContext.context.activity.type) {
case ActivityTypes.Event:
return await this.onEventActivity(stepContext);
case ActivityTypes.Message:
return await this.onMessageActivity(stepContext);
default:
// Catch all for unhandled intents.
await stepContext.context.sendActivity(
`Unrecognized ActivityType: "${ stepContext.context.activity.type }".`,
undefined,
InputHints.IgnoringInput
);
return { status: DialogTurnStatus.complete };
}
/**
* This method performs different tasks based on event name.
*/
async onEventActivity(stepContext) {
const activity = stepContext.context.activity;
const traceActivity = {
type: ActivityTypes.Trace,
timestamp: new Date(),
text: 'ActivityRouterDialog.onEventActivity()',
label: `Name: ${ activity.name }, Value: ${ JSON.stringify(activity.value) }`
};
await stepContext.context.sendActivity(traceActivity);
// Resolve what to execute based on the event name.
switch (activity.name) {
case 'BookFlight':
return await this.beginBookFlight(stepContext);
case 'GetWeather':
return await this.beginGetWeather(stepContext);
default:
// We didn't get an event name we can handle.
await stepContext.context.sendActivity(
`Unrecognized EventName: "${ stepContext.context.activity.name }".`,
undefined,
InputHints.IgnoringInput
);
return { status: DialogTurnStatus.complete };
DialogSkillBot\Dialogs\ActivityRouterDialog.java
private CompletableFuture<DialogTurnResult> processActivity(WaterfallStepContext stepContext) {
// A skill can send trace activities, if needed.
TurnContext.traceActivity(
stepContext.getContext(),
String.format(
"{%s}.processActivity() Got ActivityType: %s",
this.getClass().getName(),
stepContext.getContext().getActivity().getType()
)
);
switch (stepContext.getContext().getActivity().getType()) {
case ActivityTypes.EVENT:
return onEventActivity(stepContext);
case ActivityTypes.MESSAGE:
return onMessageActivity(stepContext);
default:
String defaultMessage = String
.format("Unrecognized ActivityType: \"%s\".", stepContext.getContext().getActivity().getType());
// We didn't get an activity type we can handle.
return stepContext.getContext()
.sendActivity(MessageFactory.text(defaultMessage, defaultMessage, InputHints.IGNORING_INPUT))
.thenCompose(result -> {
return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.COMPLETE));
});
}
}
// This method performs different tasks super. on the event name.
private CompletableFuture<DialogTurnResult> onEventActivity(WaterfallStepContext stepContext) {
Activity activity = stepContext.getContext().getActivity();
TurnContext.traceActivity(
stepContext.getContext(),
String.format(
"%s.onEventActivity(), label: %s, Value: %s",
this.getClass().getName(),
activity.getName(),
GetObjectAsJsonString(activity.getValue())
)
);
// Resolve what to execute super. on the event name.
switch (activity.getName()) {
case "BookFlight":
return beginBookFlight(stepContext);
case "GetWeather":
return beginGetWeather(stepContext);
default:
String message = String.format("Unrecognized EventName: \"%s\".", activity.getName());
// We didn't get an event name we can handle.
stepContext.getContext().sendActivity(MessageFactory.text(message, message, InputHints.IGNORING_INPUT));
return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.COMPLETE));
}
}
dialog-skill-bot/dialogs/activity_router_dialog.py
async def process_activity(
self, step_context: WaterfallStepContext
) -> DialogTurnResult:
current_activity_type = step_context.context.activity.type
# A skill can send trace activities, if needed.
await step_context.context.send_trace_activity(
f"{ActivityRouterDialog.__name__}.process_activity()",
label=f"Got ActivityType: {current_activity_type}",
)
if current_activity_type == ActivityTypes.event:
return await self._on_event_activity(step_context)
if current_activity_type == ActivityTypes.message:
return await self._on_message_activity(step_context)
else:
# We didn't get an activity type we can handle.
await step_context.context.send_activity(
MessageFactory.text(
f'Unrecognized ActivityType: "{current_activity_type}".',
input_hint=InputHints.ignoring_input,
)
)
return DialogTurnResult(DialogTurnStatus.Complete)
async def _on_event_activity(
self, step_context: WaterfallStepContext
) -> DialogTurnResult:
"""
This method performs different tasks based on the event name.
"""
activity = step_context.context.activity
# Resolve what to execute based on the event name.
if activity.name == "BookFlight":
return await self._begin_book_flight(step_context)
if activity.name == "GetWeather":
return await self._begin_get_weather(step_context)
# We didn't get an activity name we can handle.
await step_context.context.send_activity(
MessageFactory.text(
f'Unrecognized ActivityName: "{activity.name}".',
input_hint=InputHints.ignoring_input,
)
)
return DialogTurnResult(DialogTurnStatus.Complete)
メッセージ アクティビティを処理する
LUIS 認識エンジンが構成されている場合、スキルでは LUIS が呼び出され、次に意図に基づいてアクションが開始されます。
LUIS 認識エンジンが構成されていない場合、または意図がサポートされていない場合、スキルはエラー メッセージを送信して終了します。
DialogSkillBot\Dialogs\ActivityRouterDialog.cs
// This method just gets a message activity and runs it through LUIS.
private async Task<DialogTurnResult> OnMessageActivityAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var activity = stepContext.Context.Activity;
await stepContext.Context.TraceActivityAsync($"{GetType().Name}.OnMessageActivityAsync()", label: $"Text: \"{activity.Text}\". Value: {GetObjectAsJsonString(activity.Value)}", cancellationToken: cancellationToken);
if (!_luisRecognizer.IsConfigured)
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text("NOTE: LUIS is not configured. To enable all capabilities, add 'LuisAppId', 'LuisAPIKey' and 'LuisAPIHostName' to the appsettings.json file.", inputHint: InputHints.IgnoringInput), cancellationToken);
}
else
{
// Call LUIS with the utterance.
var luisResult = await _luisRecognizer.RecognizeAsync<FlightBooking>(stepContext.Context, cancellationToken);
// Create a message showing the LUIS results.
var sb = new StringBuilder();
sb.AppendLine($"LUIS results for \"{activity.Text}\":");
var (intent, intentScore) = luisResult.Intents.FirstOrDefault(x => x.Value.Equals(luisResult.Intents.Values.Max()));
sb.AppendLine($"Intent: \"{intent}\" Score: {intentScore.Score}");
await stepContext.Context.SendActivityAsync(MessageFactory.Text(sb.ToString(), inputHint: InputHints.IgnoringInput), cancellationToken);
// Start a dialog if we recognize the intent.
switch (luisResult.TopIntent().intent)
{
case FlightBooking.Intent.BookFlight:
return await BeginBookFlight(stepContext, cancellationToken);
case FlightBooking.Intent.GetWeather:
return await BeginGetWeather(stepContext, cancellationToken);
default:
// Catch all for unhandled intents.
var didntUnderstandMessageText = $"Sorry, I didn't get that. Please try asking in a different way (intent was {luisResult.TopIntent().intent})";
var didntUnderstandMessage = MessageFactory.Text(didntUnderstandMessageText, didntUnderstandMessageText, InputHints.IgnoringInput);
await stepContext.Context.SendActivityAsync(didntUnderstandMessage, cancellationToken);
break;
}
}
return new DialogTurnResult(DialogTurnStatus.Complete);
}
dialogSkillBot/dialogs/activityRouterDialog.js
}
/**
* This method just gets a message activity and runs it through LUIS.
*/
async onMessageActivity(stepContext) {
const activity = stepContext.context.activity;
const traceActivity = {
type: ActivityTypes.Trace,
timestamp: new Date(),
text: 'ActivityRouterDialog.onMessageActivity()',
label: `Text: ${ activity.text }, Value: ${ JSON.stringify(activity.value) }`
};
await stepContext.context.sendActivity(traceActivity);
if (!this.luisRecognizer || !this.luisRecognizer.isConfigured) {
await stepContext.context.sendActivity(
'NOTE: LUIS is not configured. To enable all capabilities, please add \'LuisAppId\', \'LuisAPIKey\' and \'LuisAPIHostName\' to the appsettings.json file.',
undefined,
InputHints.IgnoringInput
);
} else {
// Call LUIS with the utterance.
const luisResult = await this.luisRecognizer.executeLuisQuery(stepContext.context);
const topIntent = LuisRecognizer.topIntent(luisResult);
// Create a message showing the LUIS result.
let resultString = '';
resultString += `LUIS results for "${ activity.text }":\n`;
resultString += `Intent: "${ topIntent }", Score: ${ luisResult.intents[topIntent].score }\n`;
await stepContext.context.sendActivity(resultString, undefined, InputHints.IgnoringInput);
switch (topIntent) {
case 'BookFlight':
return await this.beginBookFlight(stepContext);
case 'GetWeather':
return await this.beginGetWeather(stepContext);
default: {
// Catch all for unhandled intents.
const didntUnderstandMessageText = `Sorry, I didn't get that. Please try asking in a different way (intent was ${ topIntent })`;
await stepContext.context.sendActivity(didntUnderstandMessageText, didntUnderstandMessageText, InputHints.IgnoringInput);
break;
}
}
}
DialogSkillBot\Dialogs\ActivityRouterDialog.java
// This method just gets a message activity and runs it through LUS.
private CompletableFuture<DialogTurnResult> onMessageActivity(WaterfallStepContext stepContext) {
Activity activity = stepContext.getContext().getActivity();
TurnContext.traceActivity(
stepContext.getContext(),
String.format(
"%s.onMessageActivity(), label: %s, Value: %s",
this.getClass().getName(),
activity.getName(),
GetObjectAsJsonString(activity.getValue())
)
);
if (!luisRecognizer.getIsConfigured()) {
String message = "NOTE: LUIS instanceof not configured. To enable all capabilities, add 'LuisAppId',"
+ " 'LuisAPKey' and 'LuisAPHostName' to the appsettings.json file.";
return stepContext.getContext()
.sendActivity(MessageFactory.text(message, message, InputHints.IGNORING_INPUT))
.thenCompose(
result -> CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.COMPLETE))
);
} else {
// Call LUIS with the utterance.
return luisRecognizer.recognize(stepContext.getContext(), RecognizerResult.class)
.thenCompose(luisResult -> {
// Create a message showing the LUS results.
StringBuilder sb = new StringBuilder();
sb.append(String.format("LUIS results for \"%s\":", activity.getText()));
sb.append(
String.format(
"Intent: \"%s\" Score: %s",
luisResult.getTopScoringIntent().intent,
luisResult.getTopScoringIntent().score
)
);
return stepContext.getContext()
.sendActivity(MessageFactory.text(sb.toString(), sb.toString(), InputHints.IGNORING_INPUT))
.thenCompose(result -> {
switch (luisResult.getTopScoringIntent().intent.toLowerCase()) {
case "bookflight":
return beginBookFlight(stepContext);
case "getweather":
return beginGetWeather(stepContext);
default:
// Catch all for unhandled intents.
String didntUnderstandMessageText = String.format(
"Sorry, I didn't get that. Please try asking in a different "
+ "way (intent was %s)",
luisResult.getTopScoringIntent().intent
);
Activity didntUnderstandMessage = MessageFactory.text(
didntUnderstandMessageText,
didntUnderstandMessageText,
InputHints.IGNORING_INPUT
);
return stepContext.getContext()
.sendActivity(didntUnderstandMessage)
.thenCompose(
stepResult -> CompletableFuture
.completedFuture(new DialogTurnResult(DialogTurnStatus.COMPLETE))
);
}
});
// Start a dialog if we recognize the intent.
});
}
}
dialog-skill-bot/dialogs/activity_router_dialog.py
async def _on_message_activity(
self, step_context: WaterfallStepContext
) -> DialogTurnResult:
"""
This method just gets a message activity and runs it through LUIS.
"""
activity = step_context.context.activity
if not self._luis_recognizer.is_configured:
await step_context.context.send_activity(
MessageFactory.text(
"NOTE: LUIS is not configured. To enable all capabilities, add 'LuisAppId', 'LuisAPIKey' and"
" 'LuisAPIHostName' to the config.py file.",
input_hint=InputHints.ignoring_input,
)
)
else:
# Call LUIS with the utterance.
luis_result = await self._luis_recognizer.recognize(step_context.context)
message = f'LUIS results for "{activity.Text}":\n'
intent, intent_score = None, None
if luis_result.intents:
max_value_key = max(
luis_result.intents, key=lambda key: luis_result.intents[key]
)
intent, intent_score = max_value_key, luis_result.intents[max_value_key]
message += f'Intent: "{intent}" Score: {intent_score}\n'
await step_context.context.send_activity(
MessageFactory.text(message, input_hint=InputHints.ignoring_input,)
)
# Start a dialog if we recognize the intent.
top_intent = luis_result.get_top_scoring_intent().intent
if top_intent == "BookFlight":
return await self._begin_book_flight(step_context)
if top_intent == "GetWeather":
return await self._begin_get_weather(step_context)
# Catch all for unhandled intents.
didnt_understand_message_text = f"Sorry, I didn't get that. Please try asking in a different way (intent was {top_intent})"
await step_context.context.send_activity(
MessageFactory.text(
didnt_understand_message_text,
didnt_understand_message_text,
input_hint=InputHints.ignoring_input,
)
)
return DialogTurnResult(DialogTurnStatus.Complete)
複数ステップのアクションを開始する
フライト予約アクションでは、ユーザーから予約の詳細を取得するために、複数ステップ ダイアログが開始されます。
天気取得アクションは実装されていません。 現時点では、プレースホルダー メッセージが送信され、終了します。
DialogSkillBot\Dialogs\ActivityRouterDialog.cs
private async Task<DialogTurnResult> BeginBookFlight(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var activity = stepContext.Context.Activity;
var bookingDetails = new BookingDetails();
if (activity.Value != null)
{
bookingDetails = JsonConvert.DeserializeObject<BookingDetails>(JsonConvert.SerializeObject(activity.Value));
}
// Start the booking dialog.
var bookingDialog = FindDialog(nameof(BookingDialog));
return await stepContext.BeginDialogAsync(bookingDialog.Id, bookingDetails, cancellationToken);
}
private static async Task<DialogTurnResult> BeginGetWeather(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var activity = stepContext.Context.Activity;
var location = new Location();
if (activity.Value != null)
{
location = JsonConvert.DeserializeObject<Location>(JsonConvert.SerializeObject(activity.Value));
}
// We haven't implemented the GetWeatherDialog so we just display a TODO message.
var getWeatherMessageText = $"TODO: get weather for here (lat: {location.Latitude}, long: {location.Longitude}";
var getWeatherMessage = MessageFactory.Text(getWeatherMessageText, getWeatherMessageText, InputHints.IgnoringInput);
await stepContext.Context.SendActivityAsync(getWeatherMessage, cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Complete);
}
dialogSkillBot/dialogs/activityRouterDialog.js
}
async beginBookFlight(stepContext) {
const activity = stepContext.context.activity;
const bookingDetails = activity.value || {};
// Start the booking dialog.
const bookingDialog = this.findDialog(BOOKING_DIALOG);
}
async beginGetWeather(stepContext) {
const activity = stepContext.context.activity;
const location = activity.value || {};
// We haven't implemented the GetWeatherDialog so we just display a TODO message.
const getWeatherMessageText = `TODO: get weather for here (lat: ${ location.latitude }, long: ${ location.longitude })`;
await stepContext.context.sendActivity(getWeatherMessageText, getWeatherMessageText, InputHints.IgnoringInput);
DialogSkillBot\Dialogs\ActivityRouterDialog.java
private CompletableFuture<DialogTurnResult> beginBookFlight(WaterfallStepContext stepContext) {
Activity activity = stepContext.getContext().getActivity();
BookingDetails bookingDetails = new BookingDetails();
if (activity.getValue() != null) {
try {
bookingDetails = Serialization.safeGetAs(activity.getValue(), BookingDetails.class);
} catch (JsonProcessingException e) {
// we already initialized bookingDetails above, so the flow will run as if
// no details were sent.
}
}
// Start the booking dialog.
Dialog bookingDialog = findDialog("BookingDialog");
return stepContext.beginDialog(bookingDialog.getId(), bookingDetails);
}
private static CompletableFuture<DialogTurnResult> beginGetWeather(WaterfallStepContext stepContext) {
Activity activity = stepContext.getContext().getActivity();
Location location = new Location();
if (activity.getValue() != null) {
try {
location = Serialization.safeGetAs(activity.getValue(), Location.class);
} catch (JsonProcessingException e) {
// something went wrong, so we create an empty Location so we won't get a null
// reference below when we acess location.
location = new Location();
}
}
// We haven't implemented the GetWeatherDialog so we just display a TODO
// message.
String getWeatherMessageText = String
.format("TODO: get weather for here (lat: %s, long: %s)", location.getLatitude(), location.getLongitude());
Activity getWeatherMessage =
MessageFactory.text(getWeatherMessageText, getWeatherMessageText, InputHints.IGNORING_INPUT);
return stepContext.getContext().sendActivity(getWeatherMessage).thenCompose(result -> {
return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.COMPLETE));
});
}
dialog-skill-bot/dialogs/activity_router_dialog.py
async def _begin_book_flight(
self, step_context: WaterfallStepContext
) -> DialogTurnResult:
activity = step_context.context.activity
booking_details = BookingDetails()
if activity.value:
booking_details.from_json(activity.value)
# Start the booking dialog
booking_dialog = await self.find_dialog(BookingDialog.__name__)
return await step_context.begin_dialog(booking_dialog.id, booking_details)
async def _begin_get_weather(
self, step_context: WaterfallStepContext
) -> DialogTurnResult:
activity = step_context.context.activity
location = Location()
if activity.value:
location.from_json(activity.value)
# We haven't implemented the GetWeatherDialog so we just display a TODO message.
get_weather_message = f"TODO: get weather for here (lat: {location.latitude}, long: {location.longitude}"
await step_context.context.send_activity(
MessageFactory.text(
get_weather_message, get_weather_message, InputHints.ignoring_input,
)
)
return DialogTurnResult(DialogTurnStatus.Complete)
結果を返す
このスキルでは、フライト予約アクションの予約ダイアログが開始されます。 アクティビティルーティング ダイアログにはステップが 1 つしかないため、予約ダイアログが終了すると、アクティビティルーティング ダイアログも終了し、予約ダイアログからのダイアログの結果がアクティビティルーティング ダイアログのダイアログの結果になります。
天気取得アクションは、戻り値を設定することなくそのまま終了します。
複数ステップ アクションを取り消す
予約ダイアログとその子ダイアログである日付リゾルバー ダイアログは、どちらもユーザーからのメッセージを確認するベースとなるキャンセル・ヘルプダイアログから派生しています。
- "ヘルプ" または "?" の場合は、ヘルプ メッセージが表示され、次のターンで会話フローが続行されます。
- "キャンセル" または "終了" の場合は、すべてのダイアログがキャンセルされ、それによってスキルが終了します。
詳細については、ユーザーによる割り込みを処理する方法を参照してください。
サービス登録
このスキルに必要なサービスは、一般的に、スキル ボットに必要なものと同じです。
必要なサービスについては、スキルを実装する方法を参照してください。
スキル マニフェスト
"スキル マニフェスト" は、スキルが実行できるアクティビティ、その入力パラメーターと出力パラメーター、およびスキルのエンドポイントが記述された JSON ファイルです。
マニフェストには、別のボットからスキルにアクセスするために必要な情報が含まれています。
DialogSkillBot\wwwroot\manifest\dialogchildbot-manifest-1.0.json
{
"$schema": "https://schemas.botframework.com/schemas/skills/skill-manifest-2.0.0.json",
"$id": "DialogSkillBot",
"name": "Skill bot with dialogs",
"version": "1.0",
"description": "This is a sample skill definition for multiple activity types.",
"publisherName": "Microsoft",
"privacyUrl": "https://dialogskillbot.contoso.com/privacy.html",
"copyright": "Copyright (c) Microsoft Corporation. All rights reserved.",
"license": "",
"iconUrl": "https://dialogskillbot.contoso.com/icon.png",
"tags": [
"sample",
"travel",
"weather",
"luis"
],
"endpoints": [
{
"name": "default",
"protocol": "BotFrameworkV3",
"description": "Default endpoint for the skill.",
"endpointUrl": "https://dialogskillbot.contoso.com/api/messages",
"msAppId": "00000000-0000-0000-0000-000000000000"
}
],
"activities": {
"bookFlight": {
"description": "Books a flight (multi turn).",
"type": "event",
"name": "BookFlight",
"value": {
"$ref": "#/definitions/bookingInfo"
},
"resultValue": {
"$ref": "#/definitions/bookingInfo"
}
},
"getWeather": {
"description": "Retrieves and returns the weather for the user's location.",
"type": "event",
"name": "GetWeather",
"value": {
"$ref": "#/definitions/location"
},
"resultValue": {
"$ref": "#/definitions/weatherReport"
}
},
"passthroughMessage": {
"type": "message",
"description": "Receives the user's utterance and attempts to resolve it using the skill's LUIS models.",
"value": {
"type": "object"
}
}
},
"definitions": {
"bookingInfo": {
"type": "object",
"required": [
"origin"
],
"properties": {
"origin": {
"type": "string",
"description": "This is the origin city for the flight."
},
"destination": {
"type": "string",
"description": "This is the destination city for the flight."
},
"travelDate": {
"type": "string",
"description": "The date for the flight in YYYY-MM-DD format."
}
}
},
"weatherReport": {
"type": "array",
"description": "Array of forecasts for the next week.",
"items": [
{
"type": "string"
}
]
},
"location": {
"type": "object",
"description": "Location metadata.",
"properties": {
"latitude": {
"type": "number",
"title": "Latitude"
},
"longitude": {
"type": "number",
"title": "Longitude"
},
"postalCode": {
"type": "string",
"title": "Postal code"
}
}
}
}
}
dialogSkillBot/manifest/dialogchildbot-manifest-1.0.json
{
"$schema": "https://schemas.botframework.com/schemas/skills/skill-manifest-2.0.0.json",
"$id": "dialogSkillBot",
"name": "Skill bot with dialogs",
"version": "1.0",
"description": "This is a sample skill definition for multiple activity types.",
"publisherName": "Microsoft",
"privacyUrl": "https://dialogskillbot.contoso.com/privacy.html",
"copyright": "Copyright (c) Microsoft Corporation. All rights reserved.",
"license": "",
"iconUrl": "https://dialogskillbot.contoso.com/icon.png",
"tags": [
"sample",
"travel",
"weather",
"luis"
],
"endpoints": [
{
"name": "default",
"protocol": "BotFrameworkV3",
"description": "Default endpoint for the skill.",
"endpointUrl": "https://dialogskillbot.contoso.com/api/messages",
"msAppId": "00000000-0000-0000-0000-000000000000"
}
],
"activities": {
"bookFlight": {
"description": "Books a flight (multi turn).",
"type": "event",
"name": "BookFlight",
"value": {
"$ref": "#/definitions/bookingInfo"
},
"resultValue": {
"$ref": "#/definitions/bookingInfo"
}
},
"getWeather": {
"description": "Retrieves and returns the weather for the user's location.",
"type": "event",
"name": "GetWeather",
"value": {
"$ref": "#/definitions/location"
},
"resultValue": {
"$ref": "#/definitions/weatherReport"
}
},
"passthroughMessage": {
"type": "message",
"description": "Receives the user's utterance and attempts to resolve it using the skill's LUIS models.",
"value": {
"type": "object"
}
}
},
"definitions": {
"bookingInfo": {
"type": "object",
"required": [
"origin"
],
"properties": {
"origin": {
"type": "string",
"description": "This is the origin city for the flight."
},
"destination": {
"type": "string",
"description": "This is the destination city for the flight."
},
"travelDate": {
"type": "string",
"description": "The date for the flight in YYYY-MM-DD format."
}
}
},
"weatherReport": {
"type": "array",
"description": "Array of forecasts for the next week.",
"items": [
{
"type": "string"
}
]
},
"location": {
"type": "object",
"description": "Location metadata.",
"properties": {
"latitude": {
"type": "number",
"title": "Latitude"
},
"longitude": {
"type": "number",
"title": "Longitude"
},
"postalCode": {
"type": "string",
"title": "Postal code"
}
}
}
}
}
dialog-skill-bot\webapp\manifest\dialogchildbot-manifest-1.0.json
{
"$schema": "https://schemas.botframework.com/schemas/skills/skill-manifest-2.0.0.json",
"$id": "DialogSkillBot",
"name": "Skill bot with dialogs",
"version": "1.0",
"description": "This is a sample skill definition for multiple activity types.",
"publisherName": "Microsoft",
"privacyUrl": "https://dialogskillbot.contoso.com/privacy.html",
"copyright": "Copyright (c) Microsoft Corporation. All rights reserved.",
"license": "",
"iconUrl": "https://dialogskillbot.contoso.com/icon.png",
"tags": [
"sample",
"travel",
"weather",
"luis"
],
"endpoints": [
{
"name": "default",
"protocol": "BotFrameworkV3",
"description": "Default endpoint for the skill.",
"endpointUrl": "https://dialogskillbot.contoso.com/api/messages",
"msAppId": "00000000-0000-0000-0000-000000000000"
}
],
"activities": {
"bookFlight": {
"description": "Books a flight (multi turn).",
"type": "event",
"name": "BookFlight",
"value": {
"$ref": "#/definitions/bookingInfo"
},
"resultValue": {
"$ref": "#/definitions/bookingInfo"
}
},
"getWeather": {
"description": "Retrieves and returns the weather for the user's location.",
"type": "event",
"name": "GetWeather",
"value": {
"$ref": "#/definitions/location"
},
"resultValue": {
"$ref": "#/definitions/weatherReport"
}
},
"passthroughMessage": {
"type": "message",
"description": "Receives the user's utterance and attempts to resolve it using the skill's LUIS models.",
"value": {
"type": "object"
}
}
},
"definitions": {
"bookingInfo": {
"type": "object",
"required": [
"origin"
],
"properties": {
"origin": {
"type": "string",
"description": "This is the origin city for the flight."
},
"destination": {
"type": "string",
"description": "This is the destination city for the flight."
},
"travelDate": {
"type": "string",
"description": "The date for the flight in YYYY-MM-DD format."
}
}
},
"weatherReport": {
"type": "array",
"description": "Array of forecasts for the next week.",
"items": [
{
"type": "string"
}
]
},
"location": {
"type": "object",
"description": "Location metadata.",
"properties": {
"latitude": {
"type": "number",
"title": "Latitude"
},
"longitude": {
"type": "number",
"title": "Longitude"
},
"postalCode": {
"type": "string",
"title": "Postal code"
}
}
}
}
}
dialog-skill-bot/wwwroot/manifest/dialogchildbot-manifest-1.0.json
{
"$schema": "https://schemas.botframework.com/schemas/skills/skill-manifest-2.0.0.json",
"$id": "DialogSkillBot",
"name": "Skill bot with dialogs",
"version": "1.0",
"description": "This is a sample skill definition for multiple activity types.",
"publisherName": "Microsoft",
"privacyUrl": "https://dialogskillbot.contoso.com/privacy.html",
"copyright": "Copyright (c) Microsoft Corporation. All rights reserved.",
"license": "",
"iconUrl": "https://dialogskillbot.contoso.com/icon.png",
"tags": [
"sample",
"travel",
"weather",
"luis"
],
"endpoints": [
{
"name": "default",
"protocol": "BotFrameworkV3",
"description": "Default endpoint for the skill.",
"endpointUrl": "https://dialogskillbot.contoso.com/api/messages",
"msAppId": "00000000-0000-0000-0000-000000000000"
}
],
"activities": {
"bookFlight": {
"description": "Books a flight (multi turn).",
"type": "event",
"name": "BookFlight",
"value": {
"$ref": "#/definitions/bookingInfo"
},
"resultValue": {
"$ref": "#/definitions/bookingInfo"
}
},
"getWeather": {
"description": "Retrieves and returns the weather for the user's location.",
"type": "event",
"name": "GetWeather",
"value": {
"$ref": "#/definitions/location"
},
"resultValue": {
"$ref": "#/definitions/weatherReport"
}
},
"passthroughMessage": {
"type": "message",
"description": "Receives the user's utterance and attempts to resolve it using the skill's LUIS models.",
"value": {
"type": "object"
}
}
},
"definitions": {
"bookingInfo": {
"type": "object",
"required": [
"origin"
],
"properties": {
"origin": {
"type": "string",
"description": "This is the origin city for the flight."
},
"destination": {
"type": "string",
"description": "This is the destination city for the flight."
},
"travelDate": {
"type": "string",
"description": "The date for the flight in YYYY-MM-DD format."
}
}
},
"weatherReport": {
"type": "array",
"description": "Array of forecasts for the next week.",
"items": [
{
"type": "string"
}
]
},
"location": {
"type": "object",
"description": "Location metadata.",
"properties": {
"latitude": {
"type": "number",
"title": "Latitude"
},
"longitude": {
"type": "number",
"title": "Longitude"
},
"postalCode": {
"type": "string",
"title": "Postal code"
}
}
}
}
}
"スキル マニフェスト スキーマ" は、スキル マニフェストのスキーマが記述された JSON ファイルです。
最新のスキーマ バージョンは v2.1 です。
スキル ボットをテストする
スキル コンシューマーを使用してエミュレーターでスキルをテストすることができます。 これを行うには、スキル ボットとスキル コンシューマー ボットを両方同時に実行する必要があります。 スキルを構成する方法については、ダイアログを使用してスキルを利用する方法を参照してください。
最新の Bot Framework Emulator をダウンロードしてインストールします。
- ダイアログ スキル ボットとダイアログ ルート ボットを、ご利用のマシンでローカルで実行してください。 手順が必要な場合は、サンプルの
README ファイルで C#、JavaScript、Java、または Python の箇所を参照してください。
- エミュレーターを使用してボットをテストします。
- 最初に会話に参加すると、ボットによってウェルカム メッセージが表示され、呼び出したいスキルをたずねられます。 このサンプルのスキル ボットには、スキルは 1 つしかありません。
-
[DialogSkillBot] を選択します。
- 次に、スキルのアクションを選択するようにボットから求められます。 [BookFlight] を選択します。
- スキルは「ブックフライト」アクションを開始します。プロンプトに応答してください。
- スキルが完了すると、ルート ボットによって、予約の詳細が表示され、その後、呼び出したいスキルを入力するよう再度求められます。
- もう一度 [DialogSkillBot] を選択し、[BookFlight] を選択します。
- 最初のプロンプトに応答し、次に「cancel」と入力してアクションを取り消します。
- スキル ボットはアクションを完了せずに終了します。呼び出したいスキルを入力するようコンシューマーから求められます。
デバッグの詳細
スキルとスキル コンシューマーの間のトラフィックは認証されるため、このようなボットをデバッグする際には追加の手順があります。
それ以外の場合は、その他のボットをデバッグするのと同様に、スキル コンシューマーまたはスキルをデバッグできます。 詳細については、ボットのデバッグに関する記事、およびBot Framework Emulator を使用したデバッグに関する記事を参照してください。