Sdílet prostřednictvím


Implementace příjemce dovedností

PLATÍ PRO: SDK v4

Dovednosti můžete využít k rozšíření dalšího robota. Dovednost je robot, který může provádět sadu úloh pro jiného robota a používá manifest k popisu jeho rozhraní. Kořenový robot je robot orientovaný na uživatele, který může vyvolat jednu nebo více dovedností. Kořenový robot je typ příjemce dovedností.

  • Spotřebitel dovedností musí k řízení, ke kterým dovednostem má přístup, používat ověřování deklarací identity.
  • Spotřebitel dovedností může používat více dovedností.
  • Vývojáři, kteří nemají přístup ke zdrojovému kódu dovednosti, můžou informace v manifestu dovednosti použít k návrhu příjemce dovedností.

Tento článek ukazuje, jak implementovat příjemce dovedností, který používá dovednosti echo k ozvěnu vstupu uživatele. Ukázkový manifest dovednosti a informace o implementaci dovednosti ozvěny najdete v tématu implementace dovednosti.

Informace o používání dialogového okna dovednosti ke využívání dovednosti najdete v tématu Použití dialogového okna ke zpracování dovednosti.

Některé typy uživatelů dovedností nemůžou používat některé typy robotů dovedností. Následující tabulka popisuje podporované kombinace.

  Dovednosti s více tenanty Dovednost s jedním tenantem Dovednost spravované identity přiřazené uživatelem
Příjemce s více tenanty Podporováno Nepodporováno Nepodporováno
Příjemce s jedním tenantem Nepodporováno Podporováno, pokud obě aplikace patří do stejného tenanta Podporováno, pokud obě aplikace patří do stejného tenanta
Příjemce spravované identity přiřazené uživatelem Nepodporováno Podporováno, pokud obě aplikace patří do stejného tenanta Podporováno, pokud obě aplikace patří do stejného tenanta

Poznámka:

Sady SDK služby Bot Framework JavaScript, C# a Python budou nadále podporovány, ale sada Java SDK se vyřazuje s konečnou dlouhodobou podporou končící v listopadu 2023.

Stávající roboti sestavení pomocí sady Java SDK budou i nadále fungovat.

Pro nové vytváření robotů zvažte použití Power Virtual Agents a přečtěte si o výběru správného řešení chatovacího robota.

Další informace najdete v tématu Budoucnost vytváření robotů.

Požadavky

Poznámka:

Od verze 4.11 nepotřebujete ID a heslo aplikace k místnímu otestování příjemce dovedností v bot Framework Emulatoru. Předplatné Azure se stále vyžaduje k nasazení vašeho uživatele do Azure nebo ke využívání nasazené dovednosti.

O této ukázce

Ukázka jednoduchých dovedností robota-robota zahrnuje projekty pro dva roboty:

  • Robot dovedností echo, který implementuje dovednosti.
  • Jednoduchý kořenový robot, který implementuje kořenového robota, který tuto dovednost využívá.

Tento článek se zaměřuje na kořenového robota, který zahrnuje logiku podpory v objektech robota a adaptérů a obsahuje objekty používané k výměně aktivit s dovedností. Tady jsou některé z nich:

  • Kvalifikovaný klient, který se používá k odesílání aktivit do dovednosti.
  • Obslužná rutina dovedností, která slouží k získávání aktivit z dovednosti.
  • Továrna PRO ID konverzace dovedností, kterou používá klient dovedností a obslužná rutina k překladu mezi odkazem na konverzaci root uživatele a odkazem na konverzaci s kořenovou dovedností.

Informace o robotovi dovedností echo najdete v tématu Implementace dovednosti.

Zdroje informací

U nasazených robotů vyžaduje ověřování robota k robotovi, aby měl každý účastník platné informace o identitě. Můžete ale otestovat dovednosti s více tenanty a uživatele dovedností místně pomocí emulátoru bez ID aplikace a hesla.

Konfigurace aplikací

  1. Volitelně můžete do konfiguračního souboru přidat informace o identitě kořenového robota. Pokud dovednost nebo spotřebitel dovedností poskytuje informace o identitě, musí být obojí.
  2. Přidejte koncový bod hostitele dovedností (adresu URL služby nebo zpětného volání), do kterého by měly dovednosti odpovídat příjemci dovedností.
  3. Přidejte položku pro každou dovednost, která bude spotřebitel dovedností používat. Každá položka zahrnuje:
    • ID, které příjemce dovednosti použije k identifikaci jednotlivých dovedností.
    • Volitelně je možné, že aplikace dovednosti nebo ID klienta.
    • Koncový bod zasílání zpráv dovednosti.

Poznámka:

Pokud dovednost nebo spotřebitel dovedností poskytuje informace o identitě, musí být obojí.

SimpleRootBot\appsettings.json

Volitelně můžete přidat informace o identitě kořenového robota a přidat aplikaci nebo ID klienta pro robota dovedností echo.

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

Konfigurace dovedností

Tato ukázka čte informace pro každou dovednost v konfiguračním souboru do kolekce objektů dovedností .

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

Objekt pro vytváření ID konverzace

Tím se vytvoří ID konverzace pro použití s dovedností a může obnovit původní ID konverzace uživatele z ID konverzace dovednosti.

Továrna ID konverzace pro tuto ukázku podporuje jednoduchý scénář, ve kterém:

  • Kořenový robot je navržený tak, aby spotřebovával jednu konkrétní dovednost.
  • Kořenový robot má současně jenom jednu aktivní konverzaci s dovedností.

Sada SDK poskytuje třídu, kterou je možné použít napříč libovolnou SkillConversationIdFactory dovedností, aniž by bylo nutné replikovat zdrojový kód. Objekt pro vytváření ID konverzace je nakonfigurovaný v Startup.cs.

Pokud chcete podporovat složitější scénáře, navrhněte továrnu PRO ID konverzace tak, aby:

  • Metoda ID konverzace pro vytvoření dovednosti získá nebo vygeneruje příslušné ID konverzace dovednosti.
  • Metoda získání odkazu na konverzaci získá správnou uživatelskou konverzaci.

Dovednost klient a obslužná rutina dovedností

Spotřebitel dovedností používá klienta dovedností k předávání aktivit dovednostem dovednosti. Klient k tomu používá informace o konfiguraci dovedností a objekt pro vytváření ID konverzace.

Příjemce dovedností používá obslužnou rutinu dovedností k získání aktivit z dovednosti. Obslužná rutina k tomu používá továrnu ID konverzace, konfiguraci ověřování a zprostředkovatele přihlašovacích údajů a také závislosti na adaptéru kořenového robota a obslužné rutině aktivit.

SimpleRootBot\Startup.cs

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

Přenosy HTTP z dovednosti se dostanou do koncového bodu adresy URL služby, který uživatel dovednosti inzeruje do dovednosti. Pomocí obslužné rutiny koncového bodu specifického pro jazyk předáte provoz obslužné rutině dovedností.

Výchozí obslužná rutina dovedností:

  • Pokud je k dispozici ID a heslo aplikace, pomocí objektu konfigurace ověřování provede ověřování jak ověřování robota, tak ověřování deklarací identity.
  • Používá továrnu PRO ID konverzace k překladu z konverzace s uživatelskými dovednostmi zpět do konverzace kořenového uživatele.
  • Vygeneruje proaktivní zprávu, aby uživatel dovednosti mohl znovu vytvořit kontext uživatele root-uživatel a předat mu aktivity.

Logika obslužné rutiny aktivity

Mějte na paměti, že logika dovednosti spotřebitele by měla:

  • Nezapomeňte, jestli existují nějaké aktivní dovednosti a podle potřeby jim předávat aktivity.
  • Všimněte si, že uživatel odešle žádost, která by se měla předat dovednostem, a zahájit dovednost.
  • endOfConversation Vyhledejte aktivitu z jakékoli aktivní dovednosti, abyste si všimli, kdy se dokončí.
  • Pokud je to vhodné, přidejte logiku, která uživateli nebo uživateli dovednosti umožní zrušit dovednost, která ještě nebyla dokončena.
  • Před voláním dovednosti uložte stav, protože jakákoli odpověď se může vrátit k jiné instanci příjemce dovedností.

SimpleRootBot\Bots\RootBot.cs

Kořenový robot má závislosti na stavu konverzace, informacích o dovednostech, klientovi dovedností a obecné konfiguraci. ASP.NET tyto objekty poskytuje prostřednictvím injektáže závislostí. Kořenový robot také definuje přístupová vlastnost stavu konverzace ke sledování toho, která dovednost je aktivní.

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

Tato ukázka obsahuje pomocnou metodu pro předávání aktivit dovednostem. Uloží stav konverzace před vyvoláním dovednosti a zkontroluje, jestli požadavek HTTP proběhl úspěšně.

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

Nezapomeňte, že kořenový robot obsahuje logiku pro předávání aktivit dovednosti, spuštění dovednosti na žádost uživatele a zastavení dovednosti po dokončení dovednosti.

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

Při zapnutí obslužné rutiny chyby

Když dojde k chybě, adaptér vymaže stav konverzace, aby se obnovila konverzace s uživatelem a zabránila zachování chybového stavu.

Před vymazáním stavu konverzace v konzumentovi dovedností je vhodné odeslat konec konverzační aktivity jakékoli aktivní dovednosti. Díky tomu může dovednost uvolnit všechny prostředky přidružené ke konverzaci s uživatelskými dovednostmi předtím, než uživatel dovednosti uvolní konverzaci.

SimpleRootBot\AdapterWithErrorHandler.cs

V této ukázce je logika chyby otáčení rozdělena mezi několik pomocných metod.

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

Koncový bod dovedností

Robot definuje koncový bod, který předává příchozí aktivity dovedností obslužné rutině dovedností kořenového robota.

SimpleRootBot\Controllers\SkillController.cs

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

Registrace služby

Zahrňte objekt konfigurace ověřování s ověřením deklarací identity a všechny další objekty. Tato ukázka používá stejnou logiku konfigurace ověřování k ověřování aktivit uživatelů i dovedností.

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

Testování kořenového robota

Uživatele dovednosti můžete otestovat v emulátoru, jako by šlo o normálního robota. Musíte však současně spouštět dovednosti i roboty uživatelů. Podívejte se, jak implementovat dovednost s informacemi o konfiguraci dovednosti.

Stažení a instalace nejnovější aplikace Bot Framework Emulator

  1. Spusťte robota dovednosti echo a jednoduchého kořenového robota místně na svém počítači. Pokud potřebujete pokyny, projděte si README soubor ukázky C#, JavaScriptu, Javy nebo Pythonu .
  2. Pomocí emulátoru otestujte robota, jak je znázorněno níže. Když dovednost pošlete end nebo stop pošlete zprávu, dovednost odešle kořenovému robotovi endOfConversation aktivitu kromě zprávy odpovědi. endOfConversation Vlastnost kódu aktivity označuje, že dovednost byla úspěšně dokončena.

Příklad přepisu interakce s příjemcem dovedností

Další informace o ladění

Vzhledem k tomu, že se ověřuje provoz mezi dovednostmi a uživateli dovedností, při ladění takových robotů existují další kroky.

Jinak můžete ladit dovednost uživatele nebo dovednosti podobně jako ostatní roboty. Další informace naleznete v tématu Ladění robota a ladění pomocí bot Framework Emulator.

Další informace

Tady je několik věcí, které je potřeba zvážit při implementaci složitějšího kořenového robota.

Povolení zrušení vícestupňové dovednosti uživateli

Kořenový robot by měl před předáním zprávy uživatele do aktivní dovednosti zkontrolovat zprávu uživatele. Pokud chce uživatel zrušit aktuální proces, může kořenový robot odeslat aktivitu dovednosti místo přeposílání endOfConversation zprávy.

Výměna dat mezi kořenovými roboty a roboty dovedností

Pokud chcete posílat parametry dovednosti, může uživatel dovednosti nastavit vlastnost hodnoty u zpráv, které odesílá do dovednosti. Pokud chcete získat návratové hodnoty z dovednosti, měl by příjemce dovednosti zkontrolovat vlastnost hodnoty , když dovednost odešle endOfConversation aktivitu.

Použití více dovedností

  • Pokud je dovednost aktivní, musí kořenový robot určit, která dovednost je aktivní, a předat zprávu uživatele správné dovednosti.
  • Pokud žádná dovednost není aktivní, musí kořenový robot určit, která dovednost se má spustit( pokud existuje) na základě stavu robota a vstupu uživatele.
  • Pokud chcete uživateli umožnit přepínání mezi více souběžnými dovednostmi, musí kořenový robot určit, se kterými aktivními dovednostmi chce uživatel pracovat, než zprávu uživatele přeposílá.

Použití režimu doručení očekávaných odpovědí

Použití režimu doručení očekávaných odpovědí :

  • Naklonujte aktivitu z kontextu otáčení.
  • Před odesláním aktivity od kořenového robota do dovednosti nastavte vlastnost režimu doručení nové aktivity na "ExpectReplies".
  • Přečtěte si očekávané odpovědi z textu odpovědi vyvolání vráceného z odpovědi požadavku.
  • Zpracujte každou aktivitu buď v rámci kořenového robota, nebo ji odešlete na kanál, který inicioval původní požadavek.

Očekávané odpovědi můžou být užitečné v situacích, kdy robot, který odpoví na aktivitu, musí být stejnou instancí robota, který aktivitu přijal.