In diesem Artikel wird veranschaulicht, wie Sie einen Skilldialog innerhalb eines Skillconsumers verwenden.
Der Skilldialog veröffentlicht Aktivitäten des übergeordneten Bots für den Skillbot und gibt die Skillantworten an den Benutzer zurück.
Der von diesem Consumer verwendete Skillbot kann sowohl Nachrichten- als auch Ereignisaktivitäten verarbeiten.
Ein Beispiel für ein Skillmanifest und Informationen zur Implementierung des Skills finden Sie unter Verwenden von Dialogen in einem Skill.
Die JavaScript-, C#- und Python-SDKs für Bot Framework werden weiterhin unterstützt, das Java-SDK wird jedoch eingestellt und der langfristige Support endet im November 2023.
Bestehende Bots, die mit dem Java SDK erstellt wurden, werden weiterhin funktionieren.
Das Beispiel skills skillDialog enthält Projekte für zwei Bots:
dialog root bot, das eine skill dialog-Klasse verwendet, um einen Skill zu nutzen.
dialog skill bot, das einen Dialog verwendet, um von Skillconsumern stammende Aktivitäten zu behandeln.
In diesem Artikel geht es um die Verwendung einer skillDialog-Klasse in einem Stammbot zum Verwalten des Skills, zum Senden von Nachrichten- und Ereignisaktivitäten und zum Abbrechen des Skills.
Bei eingesetzten Bots erfordert die Bot-zu-Bot-Authentifizierung, dass jeder teilnehmende Bot über eine gültige Identität verfügt.
Sie können jedoch Skills und Skill-Verbraucher lokal mit dem Bot Framework Emulator ohne Identitätsinformationen testen.
Anwendungskonfiguration
Fügen Sie optional die Identitätsinformationen des Stamm-Bots zur Konfigurationsdatei hinzu.
Fügen Sie den Skill-Host-Endpunkt (die Dienst- oder Rückruf-URL) hinzu, an die die Antwort der Skills an den Skillconsumer erfolgen soll.
Fügen Sie einen Eintrag für jede Qualifikation hinzu, die vom Skill-Consumer verwendet wird. Jeder Eintrag umfasst Folgendes:
Eine ID, die vom Skill-Consumer verwendet wird, um die einzelnen Qualifikationen zu identifizieren.
Optional kann die App oder Client-ID des Skill-Bots verwendet werden.
Den Messagingendpunkt der Qualifikation.
Hinweis
Wenn entweder der Skill oder Skillconsumer eine Identität angibt, müssen beide eine Identität angeben.
Fügen Sie optional die Identitätsinformationen des Stamm-Bots hinzu und fügen Sie die App oder Client-ID für den Echo-Skill-Bot dem BotFrameworkSkills-Array hinzu.
Fügen Sie optional die App-ID und das Passwort des Stamm-Bots und die App-ID für den Echo-Skill-Bot zum BotFrameworkSkills-Array hinzu.
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
Fügen Sie optional die App-ID und das Passwort des Stamm-Bots und die App-ID für den Echo-Skill-Bot hinzu.
Der Hauptdialog des Bots enthält einen Skilldialog für jeden Skill, den dieser Bot verwendet. Mit dem Skilldialog wird der Skill in den verschiedenen skillbezogenen Objekten für Sie verwaltet. Dazu gehören z. B. der Skillclient und die Objekte der Skillkonversations-ID-Factory.
Außerdem veranschaulicht der Hauptdialog, wie Sie den Skill (über den Skilldialog) basierend auf Benutzereingaben abbrechen.
Der Skill, den dieser Bot verwendet, unterstützt verschiedene Features. Er kann einen Flug buchen oder das Wetter an einem Ort abfragen. Wenn er darüber hinaus eine Nachricht außerhalb eines dieser Kontexte empfängt und eine LUIS-Erkennung konfiguriert ist, versucht er, die Absicht des Benutzers zu interpretieren.
Conversational Language Understanding (CLU), ein Feature von Azure KI Language, ist die aktualisierte Version von LUIS.
Weitere Informationen zur Unterstützung von Language Understanding im Bot Framework SDK finden Sie unter Natürliches Sprachverständnis.
Im Skillmanifest (C#, JavaScript, Java, Python) werden die ausführbaren Aktionen, die Ein- und Ausgabeparameter und die Endpunkte des Skills beschrieben.
Beachten Sie, dass der Skill Ereignisse des Typs „BookFlight“ oder „GetWeather“ verarbeiten kann. Er kann auch Nachrichten verarbeiten.
Der Hauptdialog erbt von der Klasse für Komponentendialoge. Weitere Informationen zu Komponentendialogen finden Sie unter Verwalten der Dialogkomplexität.
Initialisieren des Hauptdialogs
Der Hauptdialog umfasst Dialoge (zum Verwalten des Konversationsflows außerhalb des Skills) und einen Skilldialog (zur Verwaltung der Skills).
Der Wasserfalldialog umfasst die folgenden Schritte, die in den nächsten Abschnitten ausführlicher beschrieben werden.
Auffordern des Benutzers zur Auswahl des zu verwendenden Skills (Der Stammbot nutzt einen Skill.)
Auffordern des Benutzers zur Auswahl der für diesen Skill zu verwendenden Aktion (Der Skillbot definiert drei Aktionen.)
Starten des ausgewählten Skills mit einer anfänglichen Aktivität basierend auf der ausgewählten Aktion
Nach Abschluss des Skills: Anzeigen der Ergebnisse (sofern vorhanden). Anschließend wird der Wasserfalldialog erneut gestartet.
Die MainDialog-Klasse wird von ComponentDialog abgeleitet.
Neben dem Konversationszustand benötigt der Dialog die Identität des Stammbots sowie Verweise auf die Skillkonversations-ID-Factory, den Skills-HTTP-Client und die Skillkonfigurationsobjekte.
Der Dialogkonstruktor überprüft seine Eingabeparameter, fügt Skilldialoge sowie Eingabeaufforderungs- und Wasserfalldialoge zum Verwalten des Konversationsflows außerhalb des Skills hinzu und erstellt einen Eigenschaften-Accessor zum Nachverfolgen des aktiven Skills (sofern vorhanden).
Der Konstruktor ruft die Hilfsmethode AddSkillDialogs auf, um für jeden Skill in der Konfigurationsdatei einen SkillDialog zu erstellen. Dies erfolgt während des Einlesens der Konfigurationsdatei in ein SkillsConfiguration-Objekt.
// 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
Die MainDialog-Klasse wird von ComponentDialog abgeleitet.
Neben dem Konversationszustand benötigt der Dialog die Identität des Stammbots sowie Verweise auf die Skillkonversations-ID-Factory, den Skills-HTTP-Client und die Skillkonfigurationsobjekte. Mit dem Code wird die Identität des Bots aus der Benutzerumgebung abgerufen.
Der Dialogkonstruktor überprüft seine Eingabeparameter, fügt Skilldialoge sowie Eingabeaufforderungs- und Wasserfalldialoge zum Verwalten des Konversationsflows außerhalb des Skills hinzu und erstellt einen Eigenschaften-Accessor zum Nachverfolgen des aktiven Skills (sofern vorhanden).
Der Konstruktor ruft die Hilfsmethode addSkillDialogs auf, um für jeden Skill in der Konfigurationsdatei einen SkillDialog zu erstellen. Dies erfolgt während des Einlesens der Konfigurationsdatei in ein SkillsConfiguration-Objekt.
Die MainDialog-Klasse wird von ComponentDialog abgeleitet.
Neben dem Konversationszustand benötigt der Dialog die App-ID des Stammbots sowie Verweise auf die Skillkonversations-ID-Factory, den Skills-HTTP-Client und die Skillkonfigurationsobjekte.
Der Dialogkonstruktor überprüft seine Eingabeparameter, fügt Skilldialoge sowie Eingabeaufforderungs- und Wasserfalldialoge zum Verwalten des Konversationsflows außerhalb des Skills hinzu und erstellt einen Eigenschaften-Accessor zum Nachverfolgen des aktiven Skills (sofern vorhanden).
Der Konstruktor ruft die Hilfsmethode addSkillDialogs auf, um für jeden Skill in der Konfigurationsdatei einen SkillDialog zu erstellen. Dies erfolgt während des Einlesens der Konfigurationsdatei in ein SkillsConfiguration-Objekt.
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
Die MainDialog-Klasse wird von ComponentDialog abgeleitet.
Neben dem Konversationszustand benötigt der Dialog die App-ID des Stammbots sowie Verweise auf die Skillkonversations-ID-Factory, den Skills-HTTP-Client und die Skillkonfigurationsobjekte.
Der Dialogkonstruktor überprüft seine Eingabeparameter, fügt Skilldialoge sowie Eingabeaufforderungs- und Wasserfalldialoge zum Verwalten des Konversationsflows außerhalb des Skills hinzu und erstellt einen Eigenschaften-Accessor zum Nachverfolgen des aktiven Skills (sofern vorhanden).
Der Konstruktor ruft die Hilfsmethode AddSkillDialogs auf, um für jeden Skill in der Konfigurationsdatei einen SkillDialog zu erstellen. Dies erfolgt während des Einlesens der Konfigurationsdatei in ein SkillConfiguration-Objekt.
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))
Auswählen eines Skills
Im ersten Schritt wird der Benutzer im Hauptdialog aufgefordert, den aufzurufenden Skill anzugeben. Dabei wird die SkillPrompt-Option verwendet, um die Antwort zu erhalten. (Dieser Bot definiert nur einen Skill.)
// 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.
return await stepContext.prompt(SKILL_PROMPT, options);
}
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)
Auswählen einer Skillaktion
Im nächsten Schritt führt der Hauptdialog Folgendes aus:
Speichern der Informationen zum Skill, den der Benutzer ausgewählt hat
Auffordern des Benutzer zur Auswahl des gewünschten Skills und Verwenden der SkillActionPrompt-Option zum Empfangen der Antwort
Mithilfe einer Hilfsmethode wird eine Liste von Aktionen abgerufen, die zur Auswahl bereitstehen
Das dieser Eingabeaufforderung zugeordnete Validierungssteuerelement sendet standardmäßig eine Nachricht an den Skill, wenn die Benutzereingabe nicht mit einer der Optionen übereinstimmt.
Die in diesem Bot enthaltenen Optionen helfen dabei, die für diesen Skill definierten Aktionen zu testen. In der Regel würden Sie die Optionen aus dem Skillmanifest lesen und dem Benutzer basierend auf dieser Liste präsentieren.
// 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.
return await stepContext.prompt(SKILL_ACTION_PROMPT, options);
}
/**
* 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;
}
return choices;
}
/**
* 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 };
}
return true;
}
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
Starten eines Skills
Im nächsten Schritt führt der Hauptdialog Folgendes aus:
Abrufen von Informationen zu dem Skill und der Skillaktivität, die der Benutzer ausgewählt hat
Verwenden einer Hilfsmethode zum Erstellen der Aktivität, die als Erstes an den Skill gesendet wird
Erstellen der Dialogoptionen, mit denen der Skilldialog gestartet werden soll. Dies schließt die Aktivität ein, die als Erstes gesendet werden soll.
Speichern des Zustands vor dem Aufrufen des Skills (Dies ist erforderlich, da die Antwort vom Skill möglicherweise an eine andere Instanz des Skillconsumers erfolgt.)
Starten des Skilldialogs und Übergeben der aufzurufenden Skill-ID und der Optionen für den Aufruf
// 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.
return await stepContext.beginDialog(selectedSkill.id, skillDialogArgs);
}
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)
Zusammenfassen der Skillergebnisse
Im letzten Schritt führt der Hauptdialog Folgendes aus:
Anzeigen des Ergebnisses für den Benutzer (sofern der Skill einen Wert zurückgegeben hat)
Löschen des aktiven Skills aus dem Dialogzustand
Entfernen der Eigenschaft des aktiven Skills aus dem Konversationszustand
Ausführen eines eigenen Neustarts (der Hauptdialog)
// 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.
return await stepContext.replaceDialog(this.initialDialogId, { text: `Done with "${ activeSkill.id }". \n\n What skill would you like to call?` });
}
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?',
)
Ermöglichen des Skillabbruchs durch den Benutzer
Im Hauptdialog wird das Standardverhalten der on continue dialog-Methode überschrieben, damit der Benutzer den aktuellen Skill ggf. abbrechen kann. Innerhalb der Methode:
Wenn ein aktiver Skill vorhanden ist und der Benutzer eine Abbruchmeldung („abort“) sendet: Abbrechen sämtlicher Dialoge und Einreihen des Hauptdialogs in die Warteschlange für den Neustart
Anschließend: Aufrufen der Basisimplementierung der on continue dialog-Methode, um die Verarbeitung des aktuellen Durchlaufs fortzusetzen
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?' });
}
return await super.onContinueDialog(innerDc);
}
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)
Aktivitätshandlerlogik
Da die Skilllogik der einzelnen Durchläufe von einem Hauptdialog verarbeitet wird, ähnelt der Aktivitätshandler dem anderer Dialogbeispiele.
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
class RootBot extends ActivityHandler {
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.
await runDialog(this.dialog, turnContext, this.conversationState.createProperty('DialogState'));
}
await next();
});
/**
* Override the ActivityHandler.run() method to save state changes after the bot logic completes.
*/
async run(context) {
await super.run(context);
// Save any state changes. The load happened during the execution of the Dialog.
await this.conversationState.saveChanges(context, false);
}
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)
Dienstregistrierung
Die Dienste, die für die Verwendung eines Skilldialogs benötigt werden, sind die gleichen, die für einen Skillconsumer im Allgemeinen benötigt werden.
Eine Erläuterung der erforderlichen Dienste finden Sie unter Implementieren eines Skillconsumers.
Testen des Stamm-Bots
Sie können den Skill-Consumer im Emulator wie einen regulären Bot testen. Sie müssen aber sowohl den Bot für die Qualifikation als auch den Bot für den Skill-Consumer ausführen. Informationen zum Konfigurieren des Skills finden Sie unter Verwenden von Dialogen in einem Skill.
Laden Sie die aktuelle Version von Bot Framework Emulator herunter, und installieren Sie sie.
Führen Sie den „dialog skill bot“ und den „dialog root bot“ lokal auf Ihrem Computer aus. Wenn Sie eine Anleitung benötigen, helfen Ihnen die README des Beispiels für C#, JavaScript, Java, Python weiter.
Testen Sie den Bot im Emulator.
Wenn Sie sich zum ersten Mal an der Konversation beteiligen, zeigt der Bot eine Begrüßung an und fragt Sie, welchen Skill Sie aufrufen möchten. Der Skillbot für dieses Beispiel hat nur einen Skill.
Wählen Sie DialogSkillBot aus.
Als Nächstes bittet Sie der Bot, eine Aktion für den Skill auszuwählen. Wählen Sie „BookFlight“ aus.
Beantworten Sie die Eingabeaufforderungen.
Der Skill wird abgeschlossen, und der Stammbot zeigt die Buchungsdetails an, bevor er erneut nach dem Skill fragt, den Sie aufrufen möchten.
Wählen Sie erneut DialogSkillBot und „BookFlight“ aus.
Beantworten Sie die erste Eingabeaufforderung, und geben Sie dann „abort“ ein, um den Skill zu unterbrechen.
Der Stammbot bricht den Skill und die Eingabeaufforderungen für den Skill ab, den Sie aufrufen möchten.
Mehr über Debuggen
Da der Datenverkehr zwischen Skills und Skill-Verbraucher authentifiziert wird, gibt es zusätzliche Schritte beim Debuggen solcher Bots.
Der Skill-Verbraucher und alle Skills, die er direkt oder indirekt verbraucht, müssen ausgeführt werden.
Wenn die Bots lokal ausgeführt werden und einer der Bots über eine App-ID und ein Passwort verfügt, müssen alle Bots gültige IDs und Passwörter besitzen.