Este artigo demonstra como criar uma habilidade que dá suporte a várias ações. Ela dá suporte a essas ações por meio de diálogos. O diálogo principal recebe a entrada inicial do consumidor de habilidades e inicia a ação apropriada. Para obter informações sobre como implementar o consumidor de habilidades para o código de exemplo associado, confira como Consumir uma habilidade usando diálogos.
Este artigo pressupõe que você já esteja familiarizado com a criação de habilidades.
Para saber como criar um bot de habilidades em geral, confira como Implementar uma habilidade.
Observação
Os SDKs JavaScript, C# e Python do Bot Framework continuarão a ser compatíveis. No entanto, o SDK Java está sendo desativado, com o suporte final de longo prazo terminando em novembro de 2023.
Os bots existentes criados com o SDK para Java continuarão a funcionar.
A compreensão da linguagem coloquial (CLU), um recurso da Linguagem de IA do Azure, é a versão atualizada do LUIS.
Para obter mais informações sobre o suporte ao reconhecimento de linguagem no SDK do Bot Framework, confira Reconhecimento de linguagem natural.
Sobre este exemplo
O exemplo skills skillDialog inclui projetos para dois bots:
O bot raiz do diálogo, que usa uma classe de diálogo de habilidades para consumir uma habilidade.
O bot de skill de caixa de diálogo, que usa uma caixa de diálogo para lidar com atividades provenientes de consumidores de skill. Essa habilidade é uma adaptação da amostra de bot básico. (Para saber mais sobre o bot básico, confira como Adicionar o reconhecimento de idioma natural ao seu bot.)
Este artigo destaca como usar um diálogo em um bot de habilidades para gerenciar várias ações.
Para bots implantados. a autenticação de bot para bot requer que cada bot participante tenha uma identidade válida.
No entanto, você pode testar habilidades e consumidores de habilidades localmente com o Bot Framework Emulator sem informações de identidade.
Opcionalmente, o bot de habilidades pode usar um modelo LUIS de reserva de voos. Para usar esse modelo, use o arquivo CognitiveModels/FlightBooking.json para criar, treinar e publicar o modelo LUIS.
Configuração de aplicativo
Opcionalmente, adicione as informações de identidade da habilidade ao arquivo de configuração da habilidade.
(Se a habilidade ou o consumidor de habilidades especificar uma identidade, ambos deverão fazê-lo.)
Se você estiver usando o modelo LUIS, adicione a ID do aplicativo, a chave de API e o nome do host da API do LUIS.
{
"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": [ "*" ]
}
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", "")
Lógica de roteamento de atividades
A habilidade dá suporte a alguns recursos diferentes. Ele pode reservar um voo ou obter o clima de uma cidade. Além disso, se ele receber uma mensagem fora desses contextos, poderá usar o LUIS para tentar interpretar a mensagem.
O manifesto da habilidade descreve essas ações, os parâmetros de entrada e saída e os pontos de extremidade da habilidade.
Observe que o skill pode lidar com um evento "BookFlight" ou "GetWeather". Ela também pode processar atividades de mensagens.
A habilidade define um diálogo de roteamento de atividades usado para selecionar qual ação deve ser iniciada, com base na atividade de entrada inicial do consumidor de habilidades.
Se ela for fornecida, o modelo LUIS poderá reconhecer as intenções book-flight e get-weather em uma mensagem inicial.
A ação book-flight é um processo de várias etapas, implementado como um diálogo separado. Depois que a ação é iniciada, as atividades de entrada são processadas por esse diálogo. A ação get-weather tem uma lógica de espaço reservado que será substituída em um bot totalmente implementado.
O diálogo de roteamento de atividades inclui um código para:
Os diálogos usados na habilidade são herdados da classe de diálogo de componentes. Para obter mais informações sobre diálogos de componente, confira como Gerenciar a complexidade do diálogo.
Inicializar o diálogo
O diálogo de roteamento de atividades inclui um diálogo filho para a reserva de um voo. O diálogo principal em cascata tem uma etapa que iniciará uma ação com base na atividade inicial recebida.
Ele também aceita um reconhecedor LUIS. Se esse reconhecedor for inicializado, o diálogo o usará para interpretar a intenção de uma atividade de mensagem inicial.
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
constructor(conversationState, luisRecognizer = undefined) {
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)
]));
this.initialDialogId = WATERFALL_DIALOG;
}
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");
}
Na primeira (e única) etapa do diálogo em cascata principal, a habilidade verifica o tipo de atividade de entrada.
As atividades de evento são encaminhadas para um manipulador de atividades no evento que inicia a ação apropriada com base no nome do evento.
As atividades de mensagens são encaminhadas a um manipulador de atividades de mensagens que executa o processamento adicional antes de decidir o que fazer.
Se a habilidade não reconhecer o tipo da atividade de entrada ou o nome do evento, ela enviará uma mensagem de erro e será encerrada.
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));
}
}
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)
Processar atividades de mensagens
Se o reconhecedor LUIS estiver configurado, a habilidade chamará o LUIS e iniciará uma ação com base na intenção.
Se o reconhecedor LUIS não estiver configurado ou se não houver suporte para a intenção, a habilidade enviará uma mensagem de erro e será encerrada.
// 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.intent) {
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.intent })`;
await stepContext.context.sendActivity(didntUnderstandMessageText, didntUnderstandMessageText, InputHints.IgnoringInput);
break;
}
}
}
return { status: DialogTurnStatus.complete };
}
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.
});
}
}
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)
Iniciar uma ação de várias etapas
A ação book-flight inicia um diálogo de várias etapas para obter os detalhes de reserva do usuário.
A ação get-weather não está implementada. Atualmente, ela envia uma mensagem de espaço reservado e, em seguida, é encerrada.
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);
}
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);
return { status: DialogTurnStatus.complete };
}
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));
});
}
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)
Retornar um resultado
A habilidade inicia um diálogo de reservas para a ação book-flight. Como o diálogo de roteamento de atividades tem apenas uma etapa, quando o diálogo de reserva é encerrado, o diálogo de roteamento de atividades também é encerrado e o resultado do diálogo de reserva se torna o resultado do diálogo de roteamento de atividades.
A ação get-weather simplesmente é encerrada sem definir um valor retornado.
Como cancelar uma ação de várias etapas
O diálogo de reserva e o diálogo de resolvedor de data filho derivam do diálogo base cancel-and-help, que verifica as mensagens do usuário.
Em "ajuda" ou "?", ele exibe uma mensagem de ajuda e continua o fluxo de conversa no próximo turno.
Em "cancelar" ou "sair", ele cancela todos os diálogos, o que encerra a habilidade.
Os serviços necessários para essa habilidade são os mesmos necessários para um bot de habilidades em geral.
Confira como Implementar uma habilidade para obter uma discussão sobre os serviços necessários.
Manifesto de skills
Um manifesto de skill é um arquivo JSON que descreve as atividades que o skill pode executar, seus parâmetros de entrada e saída e os pontos de extremidade do skill.
O manifesto contém as informações necessárias para acessar o skill por meio de outro bot.
{
"$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"
}
}
}
}
}
O esquema de manifesto do skill é um arquivo JSON que descreve o esquema do manifesto de skill.
A versão mais recente do esquema é a v2.1.
Testar o bot de habilidades
Teste a habilidade no Emulator com o consumidor de habilidades. Para fazer isso, você precisará executar os bots de habilidades e de consumidor de habilidades ao mesmo tempo. Confira como Usar um diálogo para consumir uma habilidade para obter informações sobre como configurar a habilidade.
Execute o bot de skill de caixa de diálogo e o bot raiz de caixa de diálogo localmente em seu computador. Se precisar de instruções, veja o arquivo README do exemplo para C#, JavaScript, Java ou Python.
Use o Emulador para testar o bot.
Quando você ingressa na conversa pela primeira vez, o bot exibe uma mensagem de boas-vindas e pergunta qual skill você gostaria de chamar. O bot de skill para este exemplo tem apenas um skill.
Selecione DialogSkillBot.
Em seguida, o bot solicita que você escolha uma ação para a habilidade. Escolha "BookFlight".
A habilidade começa a ação book-flight; responda às solicitações.
Quando a habilidade for concluída, o bot raiz exibirá os detalhes da reserva antes de solicitar novamente a habilidade que você deseja chamar.
Selecione DialogSkillBot novamente e "BookFlight".
Responda à primeira solicitação e, em seguida, digite "Cancelar" para cancelar a ação.
O bot de habilidades será encerrado sem concluir a ação e o consumidor solicitará a habilidade que você deseja chamar.
Mais sobre depuração
Como o tráfego entre as habilidades e os consumidores de habilidades é autenticado, há etapas adicionais na depuração desses bots.
O consumidor de habilidades e todas as habilidades que ele consome, direta ou indiretamente, devem estar em execução.
Se os bots estiverem sendo executados localmente e se algum dos bots tiver um ID do aplicativo e uma senha, então todos os bots deverão ter IDs e senhas válidos.
Caso contrário, você poderá depurar um consumidor de habilidades ou uma habilidade da mesma forma como você depura outros bots. Para obter mais informações, confira Depurar um bot e Depurar com o Bot Framework Emulator.