Implementera en kunskapskonsument

GÄLLER FÖR: SDK v4

Du kan använda kunskaper för att utöka en annan robot. En färdighet är en robot som kan utföra en uppsättning uppgifter för en annan robot och som använder ett manifest för att beskriva dess gränssnitt. En rotrobot är en användarriktad robot som kan anropa en eller flera kunskaper. En rotrobot är en typ av kunskapskonsument.

  • En kunskapskonsument måste använda anspråksverifiering för att hantera vilka färdigheter som kan komma åt den.
  • En kunskapskonsument kan använda flera färdigheter.
  • Utvecklare som inte har åtkomst till kompetensens källkod kan använda informationen i färdighetens manifest för att utforma sin kunskapskonsument.

Den här artikeln visar hur du implementerar en kunskapskonsument som använder ekofärdigheten för att upprepa användarens indata. Ett exempel på ett kunskapsmanifest och information om hur du implementerar ekofärdigheten finns i hur du implementerar en färdighet.

Information om hur du använder en kunskapsdialogruta för att använda en färdighet finns i använda en dialogruta för att använda en färdighet.

Vissa typer av kunskapskonsumenter kan inte använda vissa typer av kunskapsrobotar. I följande tabell beskrivs vilka kombinationer som stöds.

  Kompetens för flera klientorganisationer Kompetens för enskild klientorganisation Användartilldelad kompetens för hanterad identitet
Konsument för flera klientorganisationer Stöds Stöds inte Stöds inte
Enskild klientorganisationskonsument Stöds inte Stöds om båda apparna tillhör samma klientorganisation Stöds om båda apparna tillhör samma klientorganisation
Användartilldelad hanterad identitetskonsument Stöds inte Stöds om båda apparna tillhör samma klientorganisation Stöds om båda apparna tillhör samma klientorganisation

Kommentar

Bot Framework JavaScript-, C#- och Python-SDK:erna fortsätter att stödjas, men Java SDK dras tillbaka med slutligt långsiktigt stöd som slutar i november 2023.

Befintliga robotar som skapats med Java SDK fortsätter att fungera.

Om du vill skapa en ny robot bör du överväga att använda Power Virtual Agents och läsa om hur du väljer rätt chattrobotlösning.

Mer information finns i Framtiden för robotbygge.

Förutsättningar

Kommentar

Från och med version 4.11 behöver du inget app-ID och lösenord för att testa en kunskapskonsument lokalt i Bot Framework-emulatorn. En Azure-prenumeration krävs fortfarande för att distribuera din konsument till Azure eller för att använda en distribuerad färdighet.

Om det här exemplet

Det enkla exemplet för robot-till-robot innehåller projekt för två robotar:

  • Den ekofärdighetsrobot som implementerar kunskapen.
  • Den enkla rotroboten, som implementerar en rotrobot som använder kunskapen.

Den här artikeln fokuserar på rotroboten, som innehåller stödlogik i robot- och adapterobjekt och innehåller objekt som används för att utbyta aktiviteter med en kunskap. Dessa kan vara:

  • En kunskapsklient som används för att skicka aktiviteter till en färdighet.
  • En kunskapshanterare som används för att ta emot aktiviteter från en färdighet.
  • En kunskapskonversation-ID-fabrik som används av kunskapsklienten och hanteraren för att översätta mellan referensen för användarrotskonversation och referensen för rotfärdighetskonversation.

Information om ekofärdighetsroboten finns i Implementera en färdighet.

Resurser

För distribuerade robotar kräver robot-till-robot-autentisering att varje deltagande robot har giltig identitetsinformation. Du kan dock testa kunskaper och kunskaper för flera klientorganisationer lokalt med emulatorn utan app-ID och lösenord.

Tillämpningskonfiguration

  1. Du kan också lägga till rotrobotens identitetsinformation i konfigurationsfilen. Om antingen kunskaps- eller kunskapskonsumenten tillhandahåller identitetsinformation måste båda två.
  2. Lägg till den färdighetsvärdslutpunkt (tjänsten eller motringnings-URL:en) som färdigheterna ska svara kunskapskonsumenten på.
  3. Lägg till en post för varje färdighet som kunskapskonsumenten kommer att använda. Varje post innehåller:
    • Ett ID som kunskapskonsumenten använder för att identifiera varje färdighet.
    • Du kan också använda färdighetens app- eller klient-ID.
    • Kunskapens slutpunkt för meddelanden.

Kommentar

Om antingen kunskaps- eller kunskapskonsumenten tillhandahåller identitetsinformation måste båda två.

SimpleRootBot\appsettings.json

Du kan också lägga till rotrobotens identitetsinformation och lägga till appen eller klient-ID:t för ekofärdighetsroboten.

{
  "MicrosoftAppType": "",
  "MicrosoftAppId": "",
  "MicrosoftAppPassword": "",
  "MicrosoftAppTenantId": "",
  "SkillHostEndpoint": "http://localhost:3978/api/skills/",
  "BotFrameworkSkills": [
    {
      "Id": "EchoSkillBot",
      "AppId": "",
      "SkillEndpoint": "http://localhost:39783/api/messages"
    }
  ]
}

Konfiguration av kunskaper

Det här exemplet läser information för varje färdighet i konfigurationsfilen i en samling kunskapsobjekt.

SimpleRootBot\SkillsConfiguration.cs

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>();
}

Konversations-ID-fabrik

Detta skapar konversations-ID:t för användning med kunskapen och kan återställa det ursprungliga användarkonversations-ID:t från kunskapskonversationens ID.

Konversations-ID-fabriken för det här exemplet stöder ett enkelt scenario där:

  • Rotroboten är utformad för att använda en specifik färdighet.
  • Rotroboten har bara en aktiv konversation med en färdighet i taget.

SDK:et tillhandahåller en SkillConversationIdFactory klass som kan användas för alla kunskaper utan att källkoden behöver replikeras. Konversations-ID-fabriken har konfigurerats i Startup.cs.

Om du vill ha stöd för mer komplexa scenarier utformar du din konversations-ID-fabrik så att:

  • Metoden create skill conversation ID (Skapa kunskapskonversation) hämtar eller genererar lämpligt kunskapskonversations-ID.
  • Referensmetoden hämta konversation hämtar rätt användarkonversation.

Kunskapsklient och kunskapshanterare

Kunskapskonsumenten använder en kunskapsklient för att vidarebefordra aktiviteter till färdigheten. Klienten använder kunskapskonfigurationsinformationen och konversations-ID-fabriken för att göra det.

Kunskapskonsumenten använder en kompetenshanterare för att ta emot aktiviteter från en färdighet. Hanteraren använder konversations-ID-fabriken, autentiseringskonfigurationen och en provider för autentiseringsuppgifter för att göra det, och har även beroenden på rotrobotens adapter och aktivitetshanterare

SimpleRootBot\Startup.cs

services.AddSingleton<IBotFrameworkHttpAdapter>(sp => sp.GetService<CloudAdapter>());
services.AddSingleton<BotAdapter>(sp => sp.GetService<CloudAdapter>());

HTTP-trafik från färdigheten kommer till tjänstens URL-slutpunkt som kunskapskonsumenten annonserar till färdigheten. Använd en språkspecifik slutpunktshanterare för att vidarebefordra trafik till kunskapshanteraren.

Standardfärdighetshanteraren:

  • Om det finns ett app-ID och lösenord använder du ett autentiseringskonfigurationsobjekt för att utföra både robot-till-robot-autentisering och anspråksverifiering.
  • Använder konversations-ID-fabriken för att översätta från konsumentfärdighetskonversationen tillbaka till rotanvändarkonversationen.
  • Genererar ett proaktivt meddelande så att kunskapskonsumenten kan återupprätta en rotanvändares turkontext och vidarebefordra aktiviteter till användaren.

Logik för aktivitetshanterare

Observera att kunskapskonsumentlogik bör:

  • Kom ihåg om det finns några aktiva färdigheter och vidarebefordra aktiviteter till dem efter behov.
  • Observera när en användare gör en begäran som ska vidarebefordras till en färdighet och starta färdigheten.
  • Leta efter en endOfConversation aktivitet från alla aktiva färdigheter för att se när den är klar.
  • Om det är lämpligt lägger du till logik så att användaren eller kunskapskonsumenten kan avbryta en färdighet som inte har slutförts ännu.
  • Spara tillstånd innan du anropar en färdighet, eftersom alla svar kan komma tillbaka till en annan instans av kunskapskonsumenten.

SimpleRootBot\Bots\RootBot.cs

Rotroboten har beroenden av konversationstillstånd, kunskapsinformation, kunskapsklienten och den allmänna konfigurationen. ASP.NET tillhandahåller dessa objekt via beroendeinmatning. Rotroboten definierar också en egenskapsåtkomst för konversationstillstånd för att spåra vilken kompetens som är aktiv.

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);
}

Det här exemplet har en hjälpmetod för vidarebefordran av aktiviteter till en färdighet. Den sparar konversationstillståndet innan du anropar kunskapen och kontrollerar om HTTP-begäran lyckades.

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}");
    }
}

Observera att rotroboten innehåller logik för vidarebefordran av aktiviteter till färdigheten, startar färdigheten på användarens begäran och stoppar färdigheten när färdigheten slutförs.

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);
}

Aktivera hanteraren för att aktivera fel

När ett fel inträffar rensar adaptern konversationstillståndet för att återställa konversationen med användaren och undvika att spara ett feltillstånd.

Det är en bra idé att skicka ett slut på konversationsaktiviteten till alla aktiva färdigheter innan du rensar konversationstillståndet i kunskapskonsumenten. På så sätt kan kunskapen frigöra alla resurser som är associerade med konversationen med konsumentfärdighet innan kunskapskonsumenten släpper konversationen.

SimpleRootBot\AdapterWithErrorHandler.cs

I det här exemplet delas logiken för turfel upp bland några få hjälpmetoder.

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}");
    }
}

Kunskapsslutpunkt

Roboten definierar en slutpunkt som vidarebefordrar inkommande kunskapsaktiviteter till rotrobotens kunskapshanterare.

SimpleRootBot\Controllers\SkillController.cs

[ApiController]
[Route("api/skills")]
public class SkillController : ChannelServiceController
{
    public SkillController(ChannelServiceHandlerBase handler)
        : base(handler)
    {
    }
}

Tjänstregistrering

Inkludera ett autentiseringskonfigurationsobjekt med valfri anspråksverifiering, plus alla ytterligare objekt. Det här exemplet använder samma autentiseringskonfigurationslogik för att verifiera aktiviteter från både användare och färdigheter.

SimpleRootBot\Startup.cs

// 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
    };
});

Testa rotroboten

Du kan testa kunskapskonsumenten i emulatorn som om det vore en vanlig robot. Du måste dock köra både kunskaps- och kunskapsrobotarna på samma gång. Se hur du implementerar en färdighet för information om hur du konfigurerar kunskapen.

Ladda ned och installera den senaste Bot Framework-emulatorn

  1. Kör ekofärdighetsroboten och den enkla rotroboten lokalt på datorn. Om du behöver instruktioner kan du läsa README filen för exemplet C#, JavaScript, Java eller Python .
  2. Använd emulatorn för att testa roboten enligt nedan. När du skickar ett end eller stop ett meddelande till färdigheten skickar färdigheten till rotroboten en endOfConversation aktivitet, utöver svarsmeddelandet. Aktivitetens endOfConversationkodegenskap anger att färdigheten har slutförts.

Exempel på transkription av en interaktion med kunskapskonsumenten.

Mer om felsökning

Eftersom trafiken mellan kompetens och kunskapskonsumenter autentiseras finns det extra steg när du felsöker sådana robotar.

Annars kan du felsöka en kunskapskonsument eller färdighet ungefär som du felsöker andra robotar. Mer information finns i Felsöka en robot och felsöka med Bot Framework-emulatorn.

Ytterligare information

Här följer några saker att tänka på när du implementerar en mer komplex rotrobot.

Så här gör du det möjligt för användaren att avbryta en färdighet i flera steg

Rotroboten bör kontrollera användarens meddelande innan den vidarebefordras till den aktiva färdigheten. Om användaren vill avbryta den aktuella processen kan rotroboten skicka en endOfConversation aktivitet till färdigheten i stället för att vidarebefordra meddelandet.

Så här utbyter du data mellan rotrobotar och kunskapsrobotar

Om du vill skicka parametrar till färdigheten kan kunskapskonsumenten ange värdeegenskapen på meddelanden som den skickar till färdigheten. För att få returvärden från färdigheten bör kunskapskonsumenten kontrollera värdeegenskapen när färdigheten skickar en endOfConversation aktivitet.

Så här använder du flera kunskaper

  • Om en färdighet är aktiv måste rotroboten avgöra vilken kompetens som är aktiv och vidarebefordra användarens meddelande till rätt kompetens.
  • Om ingen kunskap är aktiv måste rotroboten avgöra vilken kompetens som ska startas, om någon, baserat på robottillståndet och användarens indata.
  • Om du vill tillåta att användaren växlar mellan flera samtidiga kunskaper måste rotroboten avgöra vilka av de aktiva färdigheter som användaren tänker interagera med innan användarens meddelande vidarebefordras.

Så här använder du leveransläget för förväntade svar

Så här använder du leveransläget för förväntade svar :

  • Klona aktiviteten från turkontexten.
  • Ange leveranslägesegenskapen för den nya aktiviteten till "ExpectReplies" innan du skickar aktiviteten från rotroboten till kunskap.
  • Läs förväntade svar från den anropande svarstexten som returnerades från begärandesvaret.
  • Bearbeta varje aktivitet, antingen i rotroboten eller genom att skicka den till den kanal som initierade den ursprungliga begäran.

Förvänta dig svar kan vara användbara i situationer där roboten som svarar på en aktivitet måste vara samma instans av roboten som tog emot aktiviteten.