A képességek segítségével kiterjeszthet egy másik robotot.
A képesség olyan robot, amely képes feladatokat végrehajtani egy másik robot számára, és egy jegyzék használatával írja le a felületét.
A gyökérrobot egy felhasználóval rendelkező robot, amely meghívhat egy vagy több képességet. A gyökérrobot egyfajta készségfelhasználó.
A készségfelhasználóknak jogcímérvényesítést kell használniuk annak kezeléséhez, hogy mely készségek férhetnek hozzá.
A készségfelhasználók több készséget is használhatnak.
Azok a fejlesztők, akik nem férnek hozzá a képesség forráskódjába, a képességjegyzékben szereplő információkat felhasználhatják a képességfelhasználó tervezéséhez.
Ez a cikk bemutatja, hogyan valósíthat meg olyan készségfelhasználót, amely az echo-készséget használja a felhasználó bemenetének visszhangjára. Egy minta készségjegyzékért és az echo-készség megvalósításával kapcsolatos információkért tekintse meg, hogyan valósíthat meg egy készséget.
Ha tudni szeretné, hogyan használhatja fel a készséget egy készséget használó párbeszédpanelen, olvassa el, hogyan használhatja fel a készséget egy párbeszédpanelen.
Bizonyos típusú készségfelhasználók nem tudnak bizonyos típusú képességrobotokat használni.
Az alábbi táblázat ismerteti, hogy mely kombinációk támogatottak.
Több-bérlős képesség
Egybérlős képesség
Felhasználó által hozzárendelt felügyelt identitástudás
Több-bérlős fogyasztó
Támogatott
Nem támogatott
Nem támogatott
Egybérlős fogyasztó
Nem támogatott
Támogatott, ha mindkét alkalmazás ugyanahhoz a bérlőhöz tartozik
Támogatott, ha mindkét alkalmazás ugyanahhoz a bérlőhöz tartozik
Felhasználó által hozzárendelt felügyelt identitásfelhasználó
Nem támogatott
Támogatott, ha mindkét alkalmazás ugyanahhoz a bérlőhöz tartozik
Támogatott, ha mindkét alkalmazás ugyanahhoz a bérlőhöz tartozik
Feljegyzés
A Bot Framework JavaScript, C# és Python SDK-k továbbra is támogatottak lesznek, a Java SDK-t azonban 2023 novemberében végső hosszú távú támogatással kivonják.
A Java SDK-val létrehozott meglévő robotok továbbra is működni fognak.
Új robotépítéshez fontolja meg a Power Virtual Agents használatát, és olvassa el a megfelelő csevegőrobot-megoldás kiválasztását.
A C#, JavaScript, Java vagy Python nyelven készült egyszerű robot-robot mintapéldány másolata.
Feljegyzés
A 4.11-es verziótól kezdve nincs szükség alkalmazásazonosítóra és -jelszóra egy képességfelhasználó helyi teszteléséhez a Bot Framework Emulatorban. Egy Azure-előfizetésre továbbra is szükség van a fogyasztó Azure-ban való üzembe helyezéséhez vagy egy üzembe helyezett képesség használatához.
A minta ismertetése
Az egyszerű robot-robot minta két robothoz tartozó projekteket tartalmaz:
Az echo skill robot, amely megvalósítja a képességet.
Az egyszerű gyökérrobot, amely implementál egy gyökérrobotot, amely felhasználja a képességet.
Ez a cikk a gyökérrobotra összpontosít, amely támogatja a robot és az adapter objektumait, és olyan objektumokat is tartalmaz, amelyeket a tevékenységek szakértelemmel való cseréjéhez használnak. Ezek közé tartoznak:
Képességügyfél, amellyel tevékenységeket küldhet egy képességnek.
Képességkezelő, amely egy képesség tevékenységeinek fogadására szolgál.
Egy készségalapú beszélgetés azonosító-előállító, amelyet a képességügyfél és a kezelő használ a felhasználó-gyökér beszélgetés referenciája és a gyökérszintű beszélgetési referencia közötti fordításhoz.
Az Echo skill robottal kapcsolatos információkért tekintse meg, hogyan implementálhat egy készséget.
Források
Az üzembe helyezett robotok esetében a robotok között történő hitelesítéshez minden résztvevő robotnak érvényes identitásadatokra van szüksége.
A több-bérlős készségeket és készségeket azonban helyileg tesztelheti az Emulator használatával alkalmazásazonosító és jelszó nélkül.
Alkalmazáskonfiguráció
Szükség esetén adja hozzá a gyökérrobot identitásadatait a konfigurációs fájlhoz. Ha a képesség- vagy képességfelhasználó identitásadatokat ad meg, mindkettőnek meg kell lennie.
Adja hozzá a képességgazda végpontját (a szolgáltatás vagy visszahívási URL-címet), amelyre a készségeknek válaszolniuk kell a képességfelhasználónak.
Adjon hozzá egy bejegyzést minden olyan képességhez, amelyet a készségfelhasználó használni fog. Minden bejegyzés a következőket tartalmazza:
Egy azonosító, amellyel a készségfelhasználó azonosítja az egyes képességeket.
Igény szerint a képesség alkalmazás- vagy ügyfélazonosítója.
A képesség üzenetkezelési végpontja.
Feljegyzés
Ha a képesség- vagy képességfelhasználó identitásadatokat ad meg, mindkettőnek meg kell lennie.
Ha szükséges, adja hozzá a gyökérrobot alkalmazásazonosítóját és jelszavát, és adja hozzá az echo skill robot alkalmazásazonosítóját a BotFrameworkSkills tömbhöz.
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=EchoSkillBot
BotFrameworkSkills[0].AppId= "Add the App ID for the skill here"
BotFrameworkSkills[0].SkillEndpoint=http://localhost:39783/api/messages
simple_root_bot/config.py
Ha szükséges, adja hozzá a gyökérrobot alkalmazásazonosítóját és jelszavát, és adja hozzá az echo skill robot alkalmazásazonosítóját.
public class SkillsConfiguration
{
public SkillsConfiguration(IConfiguration configuration)
{
var section = configuration?.GetSection("BotFrameworkSkills");
var skills = section?.Get<BotFrameworkSkill[]>();
if (skills != null)
{
foreach (var skill in skills)
{
Skills.Add(skill.Id, skill);
}
}
var skillHostEndpoint = configuration?.GetValue<string>(nameof(SkillHostEndpoint));
if (!string.IsNullOrWhiteSpace(skillHostEndpoint))
{
SkillHostEndpoint = new Uri(skillHostEndpoint);
}
}
public Uri SkillHostEndpoint { get; }
public Dictionary<string, BotFrameworkSkill> Skills { get; } = new Dictionary<string, BotFrameworkSkill>();
}
simple-root-bot/skillsConfiguration.js
class SkillsConfiguration {
constructor() {
this.skillsData = {};
// Note: we only have one skill in this sample but we could load more if needed.
const botFrameworkSkill = {
id: process.env.SkillId,
appId: process.env.SkillAppId,
skillEndpoint: process.env.SkillEndpoint
};
this.skillsData[botFrameworkSkill.id] = botFrameworkSkill;
this.skillHostEndpointValue = process.env.SkillHostEndpoint;
if (!this.skillHostEndpointValue) {
throw new Error('[SkillsConfiguration]: Missing configuration parameter. SkillHostEndpoint is required');
}
}
get skills() {
return this.skillsData;
}
get skillHostEndpoint() {
return this.skillHostEndpointValue;
}
}
DialogRootBot\SkillsConfiguration.java
public class SkillsConfiguration {
private URI skillHostEndpoint;
private Map<String, BotFrameworkSkill> skills = new HashMap<String, BotFrameworkSkill>();
public SkillsConfiguration(Configuration configuration) {
boolean noMoreEntries = false;
int indexCount = 0;
while (!noMoreEntries) {
String botID = configuration.getProperty(String.format("BotFrameworkSkills[%d].Id", indexCount));
String botAppId = configuration.getProperty(String.format("BotFrameworkSkills[%d].AppId", indexCount));
String skillEndPoint =
configuration.getProperty(String.format("BotFrameworkSkills[%d].SkillEndpoint", indexCount));
if (
StringUtils.isNotBlank(botID) && StringUtils.isNotBlank(botAppId)
&& StringUtils.isNotBlank(skillEndPoint)
) {
BotFrameworkSkill newSkill = new BotFrameworkSkill();
newSkill.setId(botID);
newSkill.setAppId(botAppId);
try {
newSkill.setSkillEndpoint(new URI(skillEndPoint));
} catch (URISyntaxException e) {
e.printStackTrace();
}
skills.put(botID, newSkill);
indexCount++;
} else {
noMoreEntries = true;
}
}
String skillHost = configuration.getProperty("SkillhostEndpoint");
if (!StringUtils.isEmpty(skillHost)) {
try {
skillHostEndpoint = new URI(skillHost);
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
}
/**
* @return the SkillHostEndpoint value as a Uri.
*/
public URI getSkillHostEndpoint() {
return this.skillHostEndpoint;
}
/**
* @return the Skills value as a Dictionary<String, BotFrameworkSkill>.
*/
public Map<String, BotFrameworkSkill> getSkills() {
return this.skills;
}
}
simple-root-bot/config.py
SKILLS: Dict[str, BotFrameworkSkill] = {
skill["id"]: BotFrameworkSkill(**skill) for skill in DefaultConfig.SKILLS
}
Beszélgetésazonosító-előállító
Ez létrehozza a beszélgetés azonosítóját a képességhez való használatra, és visszaállíthatja az eredeti felhasználói beszélgetési azonosítót a készség-beszélgetés azonosítójából.
A minta beszélgetésazonosító-előállítója egy egyszerű forgatókönyvet támogat, ahol:
A gyökérrobotot úgy tervezték, hogy egy adott készséget használjon fel.
A gyökérrobotnak egyszerre csak egy aktív beszélgetése van egy szakértelemmel.
Az SDK egy olyan osztályt SkillConversationIdFactory biztosít, amely bármely képességhez használható anélkül, hogy a forráskódot replikálni kellene. A beszélgetésazonosító-előállító Startup.cs van konfigurálva.
Az SDK egy olyan osztályt SkillConversationIdFactory biztosít, amely bármely képességhez használható anélkül, hogy a forráskódot replikálni kellene. A beszélgetésazonosító-előállító index.js van konfigurálva.
A Java SDK-osztályként implementálta a SkillConversationIdFactory osztályt, amely bármely képességhez használható anélkül, hogy a forráskódot replikálni kellene. A SkillConversationIdFactory kódja a botbuilder-csomag forráskódjában található [botbuilder Java SDK-kód].
simple-root-bot/skill_conversation_id_factory.py
class SkillConversationIdFactory(ConversationIdFactoryBase):
def __init__(self, storage: Storage):
if not storage:
raise TypeError("storage can't be None")
self._storage = storage
async def create_skill_conversation_id(
self,
options_or_conversation_reference: Union[
SkillConversationIdFactoryOptions, ConversationReference
],
) -> str:
if not options_or_conversation_reference:
raise TypeError("Need options or conversation reference")
if not isinstance(
options_or_conversation_reference, SkillConversationIdFactoryOptions
):
raise TypeError(
"This SkillConversationIdFactory can only handle SkillConversationIdFactoryOptions"
)
options = options_or_conversation_reference
# Create the storage key based on the SkillConversationIdFactoryOptions.
conversation_reference = TurnContext.get_conversation_reference(
options.activity
)
skill_conversation_id = (
f"{conversation_reference.conversation.id}"
f"-{options.bot_framework_skill.id}"
f"-{conversation_reference.channel_id}"
f"-skillconvo"
)
# Create the SkillConversationReference instance.
skill_conversation_reference = SkillConversationReference(
conversation_reference=conversation_reference,
oauth_scope=options.from_bot_oauth_scope,
)
# Store the SkillConversationReference using the skill_conversation_id as a key.
skill_conversation_info = {skill_conversation_id: skill_conversation_reference}
await self._storage.write(skill_conversation_info)
# Return the generated skill_conversation_id (that will be also used as the conversation ID to call the skill).
return skill_conversation_id
async def get_conversation_reference(
self, skill_conversation_id: str
) -> Union[SkillConversationReference, ConversationReference]:
if not skill_conversation_id:
raise TypeError("skill_conversation_id can't be None")
# Get the SkillConversationReference from storage for the given skill_conversation_id.
skill_conversation_info = await self._storage.read([skill_conversation_id])
return skill_conversation_info.get(skill_conversation_id)
async def delete_conversation_reference(self, skill_conversation_id: str):
await self._storage.delete([skill_conversation_id])
Az összetettebb forgatókönyvek támogatásához a beszélgetésazonosító-előállítót úgy tervezheti meg, hogy:
A képesség-beszélgetés azonosítójának létrehozása metódus lekéri vagy létrehozza a megfelelő készségbeszélgetés-azonosítót.
A beszélgetés lekérése hivatkozási módszer a megfelelő felhasználói beszélgetést kapja meg.
Skill client and skill handler
A készségfelhasználó egy képességügyfél használatával továbbítja a tevékenységeket a képességnek.
Az ügyfél ehhez a készségek konfigurációs adatait és a beszélgetésazonosító-előállítót használja.
A készségfelhasználó egy képességkezelővel fogad tevékenységeket egy képességből.
A kezelő ehhez a beszélgetésazonosító-előállítót, a hitelesítési konfigurációt és a hitelesítőadat-szolgáltatót használja, valamint függőségekkel rendelkezik a gyökérrobot adapterétől és tevékenységkezelőétől
A képességből származó HTTP-forgalom a szolgáltatás URL-végpontjába kerül, amelyet a képességfelhasználó hirdet a képesség számára. Egy nyelvspecifikus végpontkezelővel továbbítja a forgalmat a képességkezelőnek.
Az alapértelmezett képességkezelő:
Ha az alkalmazásazonosító és a jelszó megtalálható, egy hitelesítési konfigurációs objektum használatával hajtja végre a robotok között végzett hitelesítést és a jogcímek érvényesítését.
A beszélgetésazonosító-előállító használatával a fogyasztó-készség beszélgetésről a gyökérfelhasználói beszélgetésre fordítható vissza.
Proaktív üzenetet hoz létre, hogy a képességfelhasználó újra létrehozhassa a gyökérfelhasználói kontextust, és továbbíthassa a tevékenységeket a felhasználónak.
Tevékenységkezelő logika
Fontos megjegyezni, hogy a készségfogyás logikájának a következőnek kell lennie:
Ne feledje, hogy vannak-e aktív készségek, és szükség szerint továbbítsa őket.
Figyelje meg, ha egy felhasználó olyan kérést küld, amelyet továbbítani kell egy képességnek, és elindítja a képességet.
Keressen egy endOfConversation tevékenységet bármilyen aktív képességből, hogy észrevehesse, mikor fejeződik be.
Szükség esetén adjon hozzá logikát, hogy a felhasználó vagy a képességfelhasználó megszakítsa a még nem befejezett készséget.
Mentse az állapotot a képesség hívása előtt, mivel a válasz visszajöhet a képességfelhasználó egy másik példányára.
A gyökérrobot függőségeket tartalmaz a beszélgetési állapottól, a készségek információitól, a képességügyfélétől és az általános konfigurációtól. ASP.NET függőséginjektálással biztosítja ezeket az objektumokat.
A gyökérrobot egy beszélgetés állapottulajdonság-tartozékot is meghatároz, amely nyomon követi, hogy melyik képesség aktív.
public static readonly string ActiveSkillPropertyName = $"{typeof(RootBot).FullName}.ActiveSkillProperty";
private readonly IStatePropertyAccessor<BotFrameworkSkill> _activeSkillProperty;
private readonly string _botId;
private readonly ConversationState _conversationState;
private readonly BotFrameworkAuthentication _auth;
private readonly SkillConversationIdFactoryBase _conversationIdFactory;
private readonly SkillsConfiguration _skillsConfig;
private readonly BotFrameworkSkill _targetSkill;
public RootBot(BotFrameworkAuthentication auth, ConversationState conversationState, SkillsConfiguration skillsConfig, SkillConversationIdFactoryBase conversationIdFactory, IConfiguration configuration)
{
_auth = auth ?? throw new ArgumentNullException(nameof(auth));
_conversationState = conversationState ?? throw new ArgumentNullException(nameof(conversationState));
_skillsConfig = skillsConfig ?? throw new ArgumentNullException(nameof(skillsConfig));
_conversationIdFactory = conversationIdFactory ?? throw new ArgumentNullException(nameof(conversationIdFactory));
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
_botId = configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value;
// We use a single skill in this example.
var targetSkillId = "EchoSkillBot";
_skillsConfig.Skills.TryGetValue(targetSkillId, out _targetSkill);
// Create state property to track the active skill
_activeSkillProperty = conversationState.CreateProperty<BotFrameworkSkill>(ActiveSkillPropertyName);
}
Ez a minta egy segédmetódussal rendelkezik a tevékenységek képességhez való továbbításához. A képesség meghívása előtt menti a beszélgetés állapotát, és ellenőrzi, hogy a HTTP-kérés sikeres volt-e.
private async Task SendToSkill(ITurnContext turnContext, BotFrameworkSkill targetSkill, CancellationToken cancellationToken)
{
// NOTE: Always SaveChanges() before calling a skill so that any activity generated by the skill
// will have access to current accurate state.
await _conversationState.SaveChangesAsync(turnContext, force: true, cancellationToken: cancellationToken);
// Create a conversationId to interact with the skill and send the activity
var options = new SkillConversationIdFactoryOptions
{
FromBotOAuthScope = turnContext.TurnState.Get<string>(BotAdapter.OAuthScopeKey),
FromBotId = _botId,
Activity = turnContext.Activity,
BotFrameworkSkill = targetSkill
};
var skillConversationId = await _conversationIdFactory.CreateSkillConversationIdAsync(options, cancellationToken);
using var client = _auth.CreateBotFrameworkClient();
// route the activity to the skill
var response = await client.PostActivityAsync(_botId, targetSkill.AppId, targetSkill.SkillEndpoint, _skillsConfig.SkillHostEndpoint, skillConversationId, turnContext.Activity, cancellationToken);
// Check response status
if (!(response.Status >= 200 && response.Status <= 299))
{
throw new HttpRequestException($"Error invoking the skill id: \"{targetSkill.Id}\" at \"{targetSkill.SkillEndpoint}\" (status is {response.Status}). \r\n {response.Body}");
}
}
Fontos megjegyezni, hogy a gyökérrobot logikával továbbítja a tevékenységeket a képességnek, elindítja a képességet a felhasználó kérésére, és leállítja a képességet, amikor a képesség befejeződik.
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
if (turnContext.Activity.Text.Contains("skill"))
{
await turnContext.SendActivityAsync(MessageFactory.Text("Got it, connecting you to the skill..."), cancellationToken);
// Save active skill in state
await _activeSkillProperty.SetAsync(turnContext, _targetSkill, cancellationToken);
// Send the activity to the skill
await SendToSkill(turnContext, _targetSkill, cancellationToken);
return;
}
// just respond
await turnContext.SendActivityAsync(MessageFactory.Text("Me no nothin'. Say \"skill\" and I'll patch you through"), cancellationToken);
// Save conversation state
await _conversationState.SaveChangesAsync(turnContext, force: true, cancellationToken: cancellationToken);
}
protected override async Task OnEndOfConversationActivityAsync(ITurnContext<IEndOfConversationActivity> turnContext, CancellationToken cancellationToken)
{
// forget skill invocation
await _activeSkillProperty.DeleteAsync(turnContext, cancellationToken);
// Show status message, text and value returned by the skill
var eocActivityMessage = $"Received {ActivityTypes.EndOfConversation}.\n\nCode: {turnContext.Activity.Code}";
if (!string.IsNullOrWhiteSpace(turnContext.Activity.Text))
{
eocActivityMessage += $"\n\nText: {turnContext.Activity.Text}";
}
if ((turnContext.Activity as Activity)?.Value != null)
{
eocActivityMessage += $"\n\nValue: {JsonConvert.SerializeObject((turnContext.Activity as Activity)?.Value)}";
}
await turnContext.SendActivityAsync(MessageFactory.Text(eocActivityMessage), cancellationToken);
// We are back at the root
await turnContext.SendActivityAsync(MessageFactory.Text("Back in the root bot. Say \"skill\" and I'll patch you through"), cancellationToken);
// Save conversation state
await _conversationState.SaveChangesAsync(turnContext, cancellationToken: cancellationToken);
}
simple-root-bot/rootBot.js
A gyökérrobot függőségeket tartalmaz a beszélgetési állapottól, a készségek információitól és a képességügyfélétől.
A gyökérrobot egy beszélgetés állapottulajdonság-tartozékot is meghatároz, amely nyomon követi, hogy melyik képesség aktív.
constructor(conversationState, skillsConfig, skillClient, conversationIdFactory) {
super();
if (!conversationState) throw new Error('[RootBot]: Missing parameter. conversationState is required');
if (!skillsConfig) throw new Error('[RootBot]: Missing parameter. skillsConfig is required');
if (!skillClient) throw new Error('[RootBot]: Missing parameter. skillClient is required');
if (!conversationIdFactory) throw new Error('[RootBot]: Missing parameter. conversationIdFactory is required');
this.conversationState = conversationState;
this.skillsConfig = skillsConfig;
this.skillClient = skillClient;
this.conversationIdFactory = conversationIdFactory;
// Create state property to track the active skill
this.activeSkillProperty = this.conversationState.createProperty(RootBot.ActiveSkillPropertyName);
Ez a minta egy segédmetódussal rendelkezik a tevékenységek képességhez való továbbításához. A képesség meghívása előtt menti a beszélgetés állapotát, és ellenőrzi, hogy a HTTP-kérés sikeres volt-e.
async sendToSkill(context, targetSkill) {
// NOTE: Always SaveChanges() before calling a skill so that any activity generated by the skill
// will have access to current accurate state.
await this.conversationState.saveChanges(context, true);
// Create a conversationId to interact with the skill and send the activity
const skillConversationId = await this.conversationIdFactory.createSkillConversationIdWithOptions({
fromBotOAuthScope: context.turnState.get(context.adapter.OAuthScopeKey),
fromBotId: this.botId,
activity: context.activity,
botFrameworkSkill: this.targetSkill
});
// route the activity to the skill
const response = await this.skillClient.postActivity(this.botId, targetSkill.appId, targetSkill.skillEndpoint, this.skillsConfig.skillHostEndpoint, skillConversationId, context.activity);
// Check response status
if (!(response.status >= 200 && response.status <= 299)) {
throw new Error(`[RootBot]: Error invoking the skill id: "${ targetSkill.id }" at "${ targetSkill.skillEndpoint }" (status is ${ response.status }). \r\n ${ response.body }`);
}
}
Fontos megjegyezni, hogy a gyökérrobot logikával továbbítja a tevékenységeket a képességnek, elindítja a képességet a felhasználó kérésére, és leállítja a képességet, amikor a képesség befejeződik.
// See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types.
this.onMessage(async (context, next) => {
if (context.activity.text.toLowerCase() === 'skill') {
await context.sendActivity('Got it, connecting you to the skill...');
// Set active skill
await this.activeSkillProperty.set(context, this.targetSkill);
// Send the activity to the skill
await this.sendToSkill(context, this.targetSkill);
} else {
await context.sendActivity("Me no nothin'. Say 'skill' and I'll patch you through");
}
// By calling next() you ensure that the next BotHandler is run.
await next();
});
// Handle EndOfConversation returned by the skill.
this.onEndOfConversation(async (context, next) => {
// Stop forwarding activities to Skill.
await this.activeSkillProperty.set(context, undefined);
// Show status message, text and value returned by the skill
let eocActivityMessage = `Received ${ ActivityTypes.EndOfConversation }.\n\nCode: ${ context.activity.code }`;
if (context.activity.text) {
eocActivityMessage += `\n\nText: ${ context.activity.text }`;
}
if (context.activity.value) {
eocActivityMessage += `\n\nValue: ${ context.activity.value }`;
}
await context.sendActivity(eocActivityMessage);
// We are back at the root
await context.sendActivity('Back in the root bot. Say \'skill\' and I\'ll patch you through');
// Save conversation state
await this.conversationState.saveChanges(context, true);
// By calling next() you ensure that the next BotHandler is run.
await next();
});
DialogRootBot\RootBot.java
A gyökérrobot függőségeket tartalmaz a beszélgetési állapottól, a készségek információitól, a képességügyfélétől és az általános konfigurációtól. ASP.NET függőséginjektálással biztosítja ezeket az objektumokat.
A gyökérrobot egy beszélgetés állapottulajdonság-tartozékot is meghatároz, amely nyomon követi, hogy melyik képesség aktív.
public static final String ActiveSkillPropertyName = "com.microsoft.bot.sample.simplerootbot.ActiveSkillProperty";
private StatePropertyAccessor<BotFrameworkSkill> activeSkillProperty;
private String botId;
private ConversationState conversationState;
private SkillHttpClient skillClient;
private SkillsConfiguration skillsConfig;
private BotFrameworkSkill targetSkill;
public RootBot(
ConversationState conversationState,
SkillsConfiguration skillsConfig,
SkillHttpClient skillClient,
Configuration configuration
) {
if (conversationState == null) {
throw new IllegalArgumentException("conversationState cannot be null.");
}
if (skillsConfig == null) {
throw new IllegalArgumentException("skillsConfig cannot be null.");
}
if (skillClient == null) {
throw new IllegalArgumentException("skillsClient cannot be null.");
}
if (configuration == null) {
throw new IllegalArgumentException("configuration cannot be null.");
}
this.conversationState = conversationState;
this.skillsConfig = skillsConfig;
this.skillClient = skillClient;
botId = configuration.getProperty(MicrosoftAppCredentials.MICROSOFTAPPID);
if (StringUtils.isEmpty(botId)) {
throw new IllegalArgumentException(String.format("%s instanceof not set in configuration",
MicrosoftAppCredentials.MICROSOFTAPPID));
}
// We use a single skill in this example.
String targetSkillId = "EchoSkillBot";
if (!skillsConfig.getSkills().containsKey(targetSkillId)) {
throw new IllegalArgumentException(
String.format("Skill with ID \"%s\" not found in configuration", targetSkillId)
);
} else {
targetSkill = (BotFrameworkSkill) skillsConfig.getSkills().get(targetSkillId);
}
// Create state property to track the active skill
activeSkillProperty = conversationState.createProperty(ActiveSkillPropertyName);
}
Ez a minta egy segédmetódussal rendelkezik a tevékenységek képességhez való továbbításához. A képesség meghívása előtt menti a beszélgetés állapotát, és ellenőrzi, hogy a HTTP-kérés sikeres volt-e.
private CompletableFuture<Void> sendToSkill(TurnContext turnContext, BotFrameworkSkill targetSkill) {
// NOTE: Always SaveChanges() before calling a skill so that any activity generated by the skill
// will have access to current accurate state.
return conversationState.saveChanges(turnContext, true)
.thenAccept(result -> {
// route the activity to the skill
skillClient.postActivity(botId,
targetSkill,
skillsConfig.getSkillHostEndpoint(),
turnContext.getActivity(),
Object.class)
.thenApply(response -> {
// Check response status
if (!(response.getStatus() >= 200 && response.getStatus() <= 299)) {
throw new RuntimeException(
String.format(
"Error invoking the skill id: \"%s\" at \"%s\" (status instanceof %s). \r\n %s",
targetSkill.getId(),
targetSkill.getSkillEndpoint(),
response.getStatus(),
response.getBody()));
}
return CompletableFuture.completedFuture(null);
});
});
}
Fontos megjegyezni, hogy a gyökérrobot logikával továbbítja a tevékenységeket a képességnek, elindítja a képességet a felhasználó kérésére, és leállítja a képességet, amikor a képesség befejeződik.
@Override
protected CompletableFuture<Void> onMessageActivity(TurnContext turnContext) {
if (turnContext.getActivity().getText().contains("skill")) {
return turnContext.sendActivity(MessageFactory.text("Got it, connecting you to the skill..."))
.thenCompose(result -> {
activeSkillProperty.set(turnContext, targetSkill);
// Send the activity to the skill
return sendToSkill(turnContext, targetSkill);
});
}
// just respond
return turnContext.sendActivity(
MessageFactory.text("Me no nothin'. Say \"skill\" and I'll patch you through"))
.thenCompose(result -> conversationState.saveChanges(turnContext, true));
}
@Override
protected CompletableFuture<Void> onEndOfConversationActivity(TurnContext turnContext) {
// forget skill invocation
return activeSkillProperty.delete(turnContext).thenAccept(result -> {
// Show status message, text and value returned by the skill
String eocActivityMessage = String.format("Received %s.\n\nCode: %s",
ActivityTypes.END_OF_CONVERSATION,
turnContext.getActivity().getCode());
if (!StringUtils.isEmpty(turnContext.getActivity().getText())) {
eocActivityMessage += String.format("\n\nText: %s", turnContext.getActivity().getText());
}
if (turnContext.getActivity() != null && turnContext.getActivity().getValue() != null) {
eocActivityMessage += String.format("\n\nValue: %s", turnContext.getActivity().getValue());
}
turnContext.sendActivity(MessageFactory.text(eocActivityMessage)).thenCompose(sendResult ->{
// We are back at the root
return turnContext.sendActivity(
MessageFactory.text("Back in the root bot. Say \"skill\" and I'll patch you through"))
.thenCompose(secondSendResult-> conversationState.saveChanges(turnContext));
});
});
}
simple-root-bot/bots/root_bot.py
A gyökérrobot függőségeket tartalmaz a beszélgetési állapottól, a készségek információitól, a képességügyfélétől és az általános konfigurációtól.
A gyökérrobot egy beszélgetés állapottulajdonság-tartozékot is meghatároz, amely nyomon követi, hogy melyik képesség aktív.
Ez a minta egy segédmetódussal rendelkezik a tevékenységek képességhez való továbbításához. A képesség meghívása előtt menti a beszélgetés állapotát, és ellenőrzi, hogy a HTTP-kérés sikeres volt-e.
async def __send_to_skill(
self, turn_context: TurnContext, target_skill: BotFrameworkSkill
):
# NOTE: Always SaveChanges() before calling a skill so that any activity generated by the skill
# will have access to current accurate state.
await self._conversation_state.save_changes(turn_context, force=True)
# route the activity to the skill
await self._skill_client.post_activity_to_skill(
self._bot_id,
target_skill,
self._skills_config.SKILL_HOST_ENDPOINT,
turn_context.activity,
)
Fontos megjegyezni, hogy a gyökérrobot logikával továbbítja a tevékenységeket a képességnek, elindítja a képességet a felhasználó kérésére, és leállítja a képességet, amikor a képesség befejeződik.
async def on_message_activity(self, turn_context: TurnContext):
if "skill" in turn_context.activity.text:
# Begin forwarding Activities to the skill
await turn_context.send_activity(
MessageFactory.text("Got it, connecting you to the skill...")
)
skill = self._skills_config.SKILLS[TARGET_SKILL_ID]
# Save active skill in state
await self._active_skill_property.set(turn_context, skill)
# Send the activity to the skill
await self.__send_to_skill(turn_context, skill)
else:
# just respond
await turn_context.send_activity(
MessageFactory.text(
"Me no nothin'. Say \"skill\" and I'll patch you through"
)
)
async def on_end_of_conversation_activity(self, turn_context: TurnContext):
# forget skill invocation
await self._active_skill_property.delete(turn_context)
eoc_activity_message = f"Received {ActivityTypes.end_of_conversation}.\n\nCode: {turn_context.activity.code}"
if turn_context.activity.text:
eoc_activity_message = (
eoc_activity_message + f"\n\nText: {turn_context.activity.text}"
)
if turn_context.activity.value:
eoc_activity_message = (
eoc_activity_message + f"\n\nValue: {turn_context.activity.value}"
)
await turn_context.send_activity(eoc_activity_message)
# We are back
await turn_context.send_activity(
MessageFactory.text(
'Back in the root bot. Say "skill" and I\'ll patch you through'
)
)
await self._conversation_state.save_changes(turn_context, force=True)
Bekapcsolt hibakezelő
Hiba esetén az adapter törli a beszélgetés állapotát a felhasználóval folytatott beszélgetés alaphelyzetbe állításához, és elkerüli a hibaállapot megőrzését.
Ajánlott a beszélgetési tevékenység befejezését bármely aktív képességnek elküldeni, mielőtt törli a beszélgetés állapotát a készségfelhasználóban. Ez lehetővé teszi, hogy a képesség felszabadítsa a fogyasztó-készség beszélgetéshez kapcsolódó erőforrásokat, mielőtt a készségfelhasználó felengedi a beszélgetést.
Ebben a példában a turn hibalogika fel van osztva néhány segítő módszer között.
private async Task HandleTurnError(ITurnContext turnContext, Exception exception)
{
// Log any leaked exception from the application.
// NOTE: In production environment, you should consider logging this to
// Azure Application Insights. Visit https://aka.ms/bottelemetry to see how
// to add telemetry capture to your bot.
_logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");
await SendErrorMessageAsync(turnContext, exception);
await EndSkillConversationAsync(turnContext);
await ClearConversationStateAsync(turnContext);
}
private async Task SendErrorMessageAsync(ITurnContext turnContext, Exception exception)
{
try
{
// Send a message to the user
var errorMessageText = "The bot encountered an error or bug.";
var errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.IgnoringInput);
await turnContext.SendActivityAsync(errorMessage);
errorMessageText = "To continue to run this bot, please fix the bot source code.";
errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.ExpectingInput);
await turnContext.SendActivityAsync(errorMessage);
// Send a trace activity, which will be displayed in the Bot Framework Emulator
await turnContext.TraceActivityAsync("OnTurnError Trace", exception.ToString(), "https://www.botframework.com/schemas/error", "TurnError");
}
catch (Exception ex)
{
_logger.LogError(ex, $"Exception caught in SendErrorMessageAsync : {ex}");
}
}
private async Task EndSkillConversationAsync(ITurnContext turnContext)
{
if (_skillsConfig == null)
{
return;
}
try
{
// Inform the active skill that the conversation is ended so that it has
// a chance to clean up.
// Note: ActiveSkillPropertyName is set by the RooBot while messages are being
// forwarded to a Skill.
var activeSkill = await _conversationState.CreateProperty<BotFrameworkSkill>(RootBot.ActiveSkillPropertyName).GetAsync(turnContext, () => null);
if (activeSkill != null)
{
var botId = _configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value;
var endOfConversation = Activity.CreateEndOfConversationActivity();
endOfConversation.Code = "RootSkillError";
endOfConversation.ApplyConversationReference(turnContext.Activity.GetConversationReference(), true);
await _conversationState.SaveChangesAsync(turnContext, true);
using var client = _auth.CreateBotFrameworkClient();
await client.PostActivityAsync(botId, activeSkill.AppId, activeSkill.SkillEndpoint, _skillsConfig.SkillHostEndpoint, endOfConversation.Conversation.Id, (Activity)endOfConversation, CancellationToken.None);
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"Exception caught on attempting to send EndOfConversation : {ex}");
}
}
private async Task ClearConversationStateAsync(ITurnContext turnContext)
{
try
{
// Delete the conversationState for the current conversation to prevent the
// bot from getting stuck in a error-loop caused by being in a bad state.
// ConversationState should be thought of as similar to "cookie-state" in a Web pages.
await _conversationState.DeleteAsync(turnContext);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Exception caught on attempting to Delete ConversationState : {ex}");
}
}
simple-root-bot/index.js
// Create adapter.
// See https://aka.ms/about-bot-adapter to learn more about how bots work.
const adapter = new CloudAdapter(botFrameworkAuthentication);
// Catch-all for errors.
adapter.onTurnError = async (context, error) => {
// This check writes out errors to the console log, instead of to app insights.
// NOTE: In production environment, you should consider logging this to Azure
// application insights. See https://aka.ms/bottelemetry for telemetry
// configuration instructions.
console.error(`\n [onTurnError] unhandled error: ${ error }`);
await sendErrorMessage(context, error);
await endSkillConversation(context);
await clearConversationState(context);
};
async function sendErrorMessage(context, error) {
try {
// Send a message to the user.
let onTurnErrorMessage = 'The bot encountered an error or bug.';
await context.sendActivity(onTurnErrorMessage, onTurnErrorMessage, InputHints.IgnoringInput);
onTurnErrorMessage = 'To continue to run this bot, please fix the bot source code.';
await context.sendActivity(onTurnErrorMessage, onTurnErrorMessage, InputHints.ExpectingInput);
// Send a trace activity, which will be displayed in Bot Framework Emulator.
await context.sendTraceActivity(
'OnTurnError Trace',
`${ error }`,
'https://www.botframework.com/schemas/error',
'TurnError'
);
} catch (err) {
console.error(`\n [onTurnError] Exception caught in sendErrorMessage: ${ err }`);
}
}
async function endSkillConversation(context) {
try {
// Inform the active skill that the conversation is ended so that it has
// a chance to clean up.
// Note: ActiveSkillPropertyName is set by the RooBot while messages are being
// forwarded to a Skill.
const activeSkill = await conversationState.createProperty(RootBot.ActiveSkillPropertyName).get(context);
if (activeSkill) {
const botId = process.env.MicrosoftAppId;
let endOfConversation = {
type: ActivityTypes.EndOfConversation,
code: 'RootSkillError'
};
endOfConversation = TurnContext.applyConversationReference(
endOfConversation, TurnContext.getConversationReference(context.activity), true);
await conversationState.saveChanges(context, true);
await skillClient.postActivity(botId, activeSkill.appId, activeSkill.skillEndpoint, skillsConfig.skillHostEndpoint, endOfConversation.conversation.id, endOfConversation);
}
} catch (err) {
console.error(`\n [onTurnError] Exception caught on attempting to send EndOfConversation : ${ err }`);
}
}
async function clearConversationState(context) {
try {
// Delete the conversationState for the current conversation to prevent the
// bot from getting stuck in a error-loop caused by being in a bad state.
// ConversationState should be thought of as similar to "cookie-state" in a Web page.
await conversationState.delete(context);
} catch (err) {
console.error(`\n [onTurnError] Exception caught on attempting to Delete ConversationState : ${ err }`);
}
}
DialogRootBot\SkillAdapterWithErrorHandler.java
Ebben a mintában a turn hibalogika fel van osztva néhány segítő módszer között.
private class SkillAdapterErrorHandler implements OnTurnErrorHandler {
@Override
public CompletableFuture<Void> invoke(TurnContext turnContext, Throwable exception) {
return sendErrorMessage(turnContext, exception).thenAccept(result -> {
endSkillConversation(turnContext);
}).thenAccept(endResult -> {
clearConversationState(turnContext);
});
}
private CompletableFuture<Void> sendErrorMessage(TurnContext turnContext, Throwable exception) {
try {
// Send a message to the user.
String errorMessageText = "The bot encountered an error or bug.";
Activity errorMessage =
MessageFactory.text(errorMessageText, errorMessageText, InputHints.IGNORING_INPUT);
return turnContext.sendActivity(errorMessage).thenAccept(result -> {
String secondLineMessageText = "To continue to run this bot, please fix the bot source code.";
Activity secondErrorMessage =
MessageFactory.text(secondLineMessageText, secondLineMessageText, InputHints.EXPECTING_INPUT);
turnContext.sendActivity(secondErrorMessage)
.thenApply(
sendResult -> {
// Send a trace activity, which will be displayed in the Bot Framework Emulator.
// Note: we return the entire exception in the value property to help the
// developer;
// this should not be done in production.
return TurnContext.traceActivity(
turnContext,
String.format("OnTurnError Trace %s", exception.toString())
);
}
);
}).thenApply(finalResult -> null);
} catch (Exception ex) {
return Async.completeExceptionally(ex);
}
}
private CompletableFuture<Void> endSkillConversation(TurnContext turnContext) {
if (skillHttpClient == null || skillsConfiguration == null) {
return CompletableFuture.completedFuture(null);
}
// Inform the active skill that the conversation instanceof ended so that it has
// a chance to clean up.
// Note: ActiveSkillPropertyName instanceof set by the RooBot while messages are
// being
StatePropertyAccessor<BotFrameworkSkill> skillAccessor =
conversationState.createProperty(RootBot.ActiveSkillPropertyName);
// forwarded to a Skill.
return skillAccessor.get(turnContext, () -> null).thenApply(activeSkill -> {
if (activeSkill != null) {
String botId = configuration.getProperty(MicrosoftAppCredentials.MICROSOFTAPPID);
Activity endOfConversation = Activity.createEndOfConversationActivity();
endOfConversation.setCode(EndOfConversationCodes.ROOT_SKILL_ERROR);
endOfConversation
.applyConversationReference(turnContext.getActivity().getConversationReference(), true);
return conversationState.saveChanges(turnContext, true).thenCompose(saveResult -> {
return skillHttpClient.postActivity(
botId,
activeSkill,
skillsConfiguration.getSkillHostEndpoint(),
endOfConversation,
Object.class
);
});
}
return CompletableFuture.completedFuture(null);
}).thenApply(result -> null);
}
private CompletableFuture<Void> clearConversationState(TurnContext turnContext) {
try {
return conversationState.delete(turnContext);
} catch (Exception ex) {
return Async.completeExceptionally(ex);
}
}
}
simple-root-bot/adapter_with_error_handler.py
# This check writes out errors to console log
# NOTE: In production environment, you should consider logging this to Azure
# application insights.
print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr)
traceback.print_exc()
await self._send_error_message(turn_context, error)
await self._end_skill_conversation(turn_context, error)
await self._clear_conversation_state(turn_context)
async def _send_error_message(self, turn_context: TurnContext, error: Exception):
if not self._skill_client or not self._skill_config:
return
try:
# Send a message to the user.
error_message_text = "The skill encountered an error or bug."
error_message = MessageFactory.text(
error_message_text, error_message_text, InputHints.ignoring_input
)
await turn_context.send_activity(error_message)
error_message_text = (
"To continue to run this bot, please fix the bot source code."
)
error_message = MessageFactory.text(
error_message_text, error_message_text, InputHints.ignoring_input
)
await turn_context.send_activity(error_message)
# Send a trace activity, which will be displayed in Bot Framework Emulator.
await turn_context.send_trace_activity(
label="TurnError",
name="on_turn_error Trace",
value=f"{error}",
value_type="https://www.botframework.com/schemas/error",
)
except Exception as exception:
print(
f"\n Exception caught on _send_error_message : {exception}",
file=sys.stderr,
)
traceback.print_exc()
async def _end_skill_conversation(
self, turn_context: TurnContext, error: Exception
):
if not self._skill_client or not self._skill_config:
return
try:
# Inform the active skill that the conversation is ended so that it has a chance to clean up.
# Note: the root bot manages the ActiveSkillPropertyName, which has a value while the root bot
# has an active conversation with a skill.
active_skill = await self._conversation_state.create_property(
ACTIVE_SKILL_PROPERTY_NAME
).get(turn_context)
if active_skill:
bot_id = self._config.APP_ID
end_of_conversation = Activity(type=ActivityTypes.end_of_conversation)
end_of_conversation.code = "RootSkillError"
TurnContext.apply_conversation_reference(
end_of_conversation,
TurnContext.get_conversation_reference(turn_context.activity),
True,
)
await self._conversation_state.save_changes(turn_context, True)
await self._skill_client.post_activity_to_skill(
bot_id,
active_skill,
self._skill_config.SKILL_HOST_ENDPOINT,
end_of_conversation,
)
except Exception as exception:
print(
f"\n Exception caught on _end_skill_conversation : {exception}",
file=sys.stderr,
)
traceback.print_exc()
async def _clear_conversation_state(self, turn_context: TurnContext):
try:
# Delete the conversationState for the current conversation to prevent the
# bot from getting stuck in a error-loop caused by being in a bad state.
# ConversationState should be thought of as similar to "cookie-state" for a Web page.
await self._conversation_state.delete(turn_context)
except Exception as exception:
print(
f"\n Exception caught on _clear_conversation_state : {exception}",
file=sys.stderr,
)
traceback.print_exc()
Készségek végpontja
A robot meghatároz egy végpontot, amely a bejövő készségtevékenységeket továbbítja a gyökérrobot képességkezelőjének.
[ApiController]
[Route("api/skills")]
public class SkillController : ChannelServiceController
{
public SkillController(ChannelServiceHandlerBase handler)
: base(handler)
{
}
}
simple-root-bot/index.js
const handler = new CloudSkillHandler(adapter, (context) => bot.run(context), conversationIdFactory, botFrameworkAuthentication);
const skillEndpoint = new ChannelServiceRoutes(handler);
skillEndpoint.register(server, '/api/skills');
DialogRootBot\Controllers\SkillController.java
@RestController
@RequestMapping(value = {"/api/skills"})
public class SkillController extends ChannelServiceController {
public SkillController(ChannelServiceHandler handler) {
super(handler);
}
}
simple-root-bot/app.py
APP.router.add_post("/api/messages", messages)
Szolgáltatásregisztráció
Adjon meg egy hitelesítési konfigurációs objektumot minden jogcím-ellenőrzéssel, valamint az összes további objektummal együtt.
Ez a minta ugyanazt a hitelesítési konfigurációs logikát használja a felhasználók és a készségek tevékenységeinek ellenőrzéséhez.
// Register the skills configuration class
services.AddSingleton<SkillsConfiguration>();
// Register AuthConfiguration to enable custom claim validation.
services.AddSingleton(sp =>
{
var allowedSkills = sp.GetService<SkillsConfiguration>().Skills.Values.Select(s => s.AppId).ToList();
var claimsValidator = new AllowedSkillsClaimsValidator(allowedSkills);
// If TenantId is specified in config, add the tenant as a valid JWT token issuer for Bot to Skill conversation.
// The token issuer for MSI and single tenant scenarios will be the tenant where the bot is registered.
var validTokenIssuers = new List<string>();
var tenantId = sp.GetService<IConfiguration>().GetSection(MicrosoftAppCredentials.MicrosoftAppTenantIdKey)?.Value;
if (!string.IsNullOrWhiteSpace(tenantId))
{
// For SingleTenant/MSI auth, the JWT tokens will be issued from the bot's home tenant.
// Therefore, these issuers need to be added to the list of valid token issuers for authenticating activity requests.
validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV1, tenantId));
validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV2, tenantId));
validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidGovernmentTokenIssuerUrlTemplateV1, tenantId));
validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidGovernmentTokenIssuerUrlTemplateV2, tenantId));
}
return new AuthenticationConfiguration
{
ClaimsValidator = claimsValidator,
ValidTokenIssuers = validTokenIssuers
};
});
simple-root-bot/index.js
// Load skills configuration
const skillsConfig = new SkillsConfiguration();
const allowedSkills = Object.values(skillsConfig.skills).map(skill => skill.appId);
const claimsValidators = allowedCallersClaimsValidator(allowedSkills);
// If the MicrosoftAppTenantId is specified in the environment config, add the tenant as a valid JWT token issuer for Bot to Skill conversation.
// The token issuer for MSI and single tenant scenarios will be the tenant where the bot is registered.
let validTokenIssuers = [];
const { MicrosoftAppTenantId } = process.env;
if (MicrosoftAppTenantId) {
// For SingleTenant/MSI auth, the JWT tokens will be issued from the bot's home tenant.
// Therefore, these issuers need to be added to the list of valid token issuers for authenticating activity requests.
validTokenIssuers = [
`${ AuthenticationConstants.ValidTokenIssuerUrlTemplateV1 }${ MicrosoftAppTenantId }/`,
`${ AuthenticationConstants.ValidTokenIssuerUrlTemplateV2 }${ MicrosoftAppTenantId }/v2.0/`,
`${ AuthenticationConstants.ValidGovernmentTokenIssuerUrlTemplateV1 }${ MicrosoftAppTenantId }/`,
`${ AuthenticationConstants.ValidGovernmentTokenIssuerUrlTemplateV2 }${ MicrosoftAppTenantId }/v2.0/`
];
}
// Define our authentication configuration.
const authConfig = new AuthenticationConfiguration([], claimsValidators, validTokenIssuers);
const credentialsFactory = new ConfigurationServiceClientCredentialFactory({
MicrosoftAppId: process.env.MicrosoftAppId,
MicrosoftAppPassword: process.env.MicrosoftAppPassword,
MicrosoftAppType: process.env.MicrosoftAppType,
MicrosoftAppTenantId: process.env.MicrosoftAppTenantId
});
const botFrameworkAuthentication = new ConfigurationBotFrameworkAuthentication(process.env, credentialsFactory, authConfig);
DialogRootBot\Application.java
/**
* This class extends the BotDependencyConfiguration which provides the default
* implementations for a Bot application. The Application class should
* override methods in order to provide custom implementations.
*/
public class Application extends BotDependencyConfiguration {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
/**
* Returns the Bot for this application.
*
* <p>
* The @Component annotation could be used on the Bot class instead of this method
* with the @Bean annotation.
* </p>
*
* @return The Bot implementation for this application.
*/
@Bean
public Bot getBot(
ConversationState conversationState,
SkillsConfiguration skillsConfig,
SkillHttpClient skillClient,
Configuration configuration
) {
return new RootBot(conversationState, skillsConfig, skillClient, configuration);
}
@Override
public AuthenticationConfiguration getAuthenticationConfiguration(Configuration configuration) {
AuthenticationConfiguration authenticationConfiguration = new AuthenticationConfiguration();
authenticationConfiguration.setClaimsValidator(
new AllowedSkillsClaimsValidator(getSkillsConfiguration(configuration)));
return authenticationConfiguration;
}
/**
* Returns a custom Adapter that provides error handling.
*
* @param configuration The Configuration object to use.
* @return An error handling BotFrameworkHttpAdapter.
*/
@Override
public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) {
return new SkillAdapterWithErrorHandler(
configuration,
getConversationState(new MemoryStorage()),
getSkillHttpClient(
getCredentialProvider(configuration),
getSkillConversationIdFactoryBase(),
getChannelProvider(configuration)),
getSkillsConfiguration(configuration));
}
@Bean
public SkillsConfiguration getSkillsConfiguration(Configuration configuration) {
return new SkillsConfiguration(configuration);
}
@Bean
public SkillHttpClient getSkillHttpClient(
CredentialProvider credentialProvider,
SkillConversationIdFactoryBase conversationIdFactory,
ChannelProvider channelProvider
) {
return new SkillHttpClient(credentialProvider, conversationIdFactory, channelProvider);
}
@Bean
public SkillConversationIdFactoryBase getSkillConversationIdFactoryBase() {
return new SkillConversationIdFactory(getStorage());
}
@Bean public ChannelServiceHandler getChannelServiceHandler(
BotAdapter botAdapter,
Bot bot,
SkillConversationIdFactoryBase conversationIdFactory,
CredentialProvider credentialProvider,
AuthenticationConfiguration authConfig,
ChannelProvider channelProvider
) {
return new SkillHandler(
botAdapter,
bot,
conversationIdFactory,
credentialProvider,
authConfig,
channelProvider);
}
}
simple-root-bot/app.py
# Create adapter.
# See https://aka.ms/about-bot-adapter to learn more about how bots work.
SETTINGS = ConfigurationBotFrameworkAuthentication(
CONFIG,
auth_configuration=AUTH_CONFIG,
)
STORAGE = MemoryStorage()
CONVERSATION_STATE = ConversationState(STORAGE)
ID_FACTORY = SkillConversationIdFactory(STORAGE)
CREDENTIAL_PROVIDER = SimpleCredentialProvider(CONFIG.APP_ID, CONFIG.APP_PASSWORD)
CLIENT = SkillHttpClient(CREDENTIAL_PROVIDER, ID_FACTORY)
ADAPTER = AdapterWithErrorHandler(
SETTINGS, CONFIG, CONVERSATION_STATE, CLIENT, SKILL_CONFIG
)
# Create the Bot
BOT = RootBot(CONVERSATION_STATE, SKILL_CONFIG, CLIENT, CONFIG)
SKILL_HANDLER = SkillHandler(
ADAPTER, BOT, ID_FACTORY, CREDENTIAL_PROVIDER, AUTH_CONFIG
)
# Listen for incoming requests on /api/messages
async def messages(req: Request) -> Response:
# Main bot message handler.
if "application/json" in req.headers["Content-Type"]:
body = await req.json()
else:
return Response(status=HTTPStatus.UNSUPPORTED_MEDIA_TYPE)
activity = Activity().deserialize(body)
auth_header = req.headers["Authorization"] if "Authorization" in req.headers else ""
invoke_response = await ADAPTER.process_activity(auth_header, activity, BOT.on_turn)
if invoke_response:
return json_response(data=invoke_response.body, status=invoke_response.status)
return Response(status=HTTPStatus.OK)
A gyökérrobot tesztelése
Tesztelheti a készségfelhasználót az Emulátorban, mintha normál robot lenne; azonban egyszerre kell futtatnia a készség- és készségfelhasználó robotokat is.
Megtudhatja, hogyan valósíthat meg készséget a képesség konfigurálására vonatkozó információkhoz.
Futtassa az echo skill robotot és az egyszerű gyökérrobotot helyben a gépen. Ha útmutatásra van szüksége, tekintse meg a README C#, JavaScript, Java vagy Python mintafájlt.
Az Emulator használatával tesztelje a robotot az alább látható módon. Amikor egy vagy stop több end üzenetet küld a képességnek, a képesség a válaszüzenet mellett egy endOfConversation tevékenységet is küld a gyökérrobotnak. A endOfConversation tevékenység kódtulajdonság azt jelzi, hogy a képesség sikeresen befejeződött.
További információ a hibakeresésről
Mivel a készségek és a készségfelhasználók közötti forgalom hitelesítése megtörtént, az ilyen robotok hibakereséséhez további lépések is szükségesek.
A készségfelhasználónak és az általa használt összes készségnek közvetlenül vagy közvetve futnia kell.
Ha a robotok helyileg futnak, és bármelyik robot rendelkezik alkalmazásazonosítóval és jelszóval, akkor minden robotnak érvényes azonosítókkal és jelszóval kell rendelkeznie.
Ellenkező esetben ugyanúgy hibakeresést végezhet egy képességfelhasználón vagy képességen, mint más robotok. További információ: Robot hibakeresése és hibakeresés a Bot Framework Emulator használatával.
További információk
Íme néhány megfontolandó szempont egy összetettebb gyökérrobot implementálásakor.
Többlépéses képesség megszakításának engedélyezése a felhasználó számára
A gyökérrobotnak ellenőriznie kell a felhasználó üzenetét, mielőtt továbbítanák azt az aktív képességhez. Ha a felhasználó megszakítja az aktuális folyamatot, a gyökérrobot az üzenet továbbítása helyett egy tevékenységet küldhet endOfConversation a képességnek.
Adatok cseréje a gyökér- és képességrobotok között
Ha paramétereket szeretne küldeni a képességnek, a készségfelhasználó beállíthatja az értéktulajdonságot a képességnek küldött üzeneteken. Ahhoz, hogy visszaadott értékeket kapjon a képességtől, a készségfelhasználónak ellenőriznie kell az értéktulajdonságot , amikor a képesség egy tevékenységet küld endOfConversation .
Több készség használata
Ha egy képesség aktív, a gyökérrobotnak meg kell határoznia, hogy melyik képesség aktív, és továbbítania kell a felhasználó üzenetét a megfelelő képességhez.
Ha nincs aktív képesség, a gyökérrobotnak meg kell határoznia, hogy a robot állapota és a felhasználó bemenete alapján melyik készséget kell elindítania, ha van ilyen.
Ha engedélyezni szeretné a felhasználónak, hogy több egyidejű képesség között váltson, a gyökérrobotnak meg kell határoznia, hogy a felhasználó milyen aktív készségekkel kíván kommunikálni a felhasználó üzenetének továbbítása előtt.
A várt válaszok kézbesítési módjának használata
A várt válaszkézbesítési mód használata:
Klónozza a tevékenységet a turn környezetből.
Állítsa az új tevékenység kézbesítési módjának tulajdonságát "ExpectReplies" értékre, mielőtt a tevékenységet a gyökérrobotból a szakértelembe küldené.
Olvassa el a kérés válaszából visszaadott meghívási válasz törzsének várt válaszait.
Dolgozza fel az egyes tevékenységeket a gyökérroboton belül, vagy küldje el az eredeti kérést kezdeményező csatornának.
A válaszokra való várakozás hasznos lehet olyan helyzetekben, amikor a tevékenységre válaszoló robotnak a tevékenységre adott robot ugyanazon példányának kell lennie, amely megkapta a tevékenységet.