Condividi tramite


Implementare un consumatore di abilità

SI APPLICA A: SDK v4

È possibile usare le competenze per estendere un altro bot. Una competenza è un bot che può eseguire un set di attività per un altro bot e usa un manifesto per descrivere la propria interfaccia. Un bot radice è un bot con cui l'utente interagisce che può richiamare una o più abilità. Un bot radice è un tipo di consumer di competenze.

  • Un utente di competenze deve usare la convalida delle attestazioni per controllare quali competenze possono accedervi.
  • Un consumer di competenze può usare più competenze.
  • Gli sviluppatori che non hanno accesso al codice sorgente della competenza possono usare le informazioni nel manifesto delle competenze per progettare il proprio consumer di competenze.

Questo articolo illustra come implementare un consumatore di abilità che usa l'abilità echo per restituire l'input dell'utente. Per un manifesto di competenza di esempio e per informazioni sull'implementazione della competenza echo, vedere Implementare una competenza.

Per informazioni su come usare un dialogo per consumare una skill, vedere Usare un dialogo per consumare una skill.

Alcuni tipi di utenti delle competenze non sono in grado di usare alcuni tipi di bot delle competenze. Nella tabella seguente vengono descritte le combinazioni supportate.

  Competenza multi-tenant Competenze per un singolo tenant Competenza di identità gestita assegnata dall'utente
Consumatore multi-tenant Supportato Non supportato Non supportato
Cliente a locazione singola Non supportato Supportato se entrambe le app appartengono allo stesso tenant Supportato se entrambe le app appartengono allo stesso tenant
Utente dell'identità gestita assegnata dall'utente Non supportato Supportato se entrambe le app appartengono allo stesso tenant Supportato se entrambe le app appartengono allo stesso tenant

Nota

Per creare agenti con la scelta di servizi di intelligenza artificiale, orchestrazione e conoscenze, è consigliabile usare Microsoft 365 Agents SDK. Agents SDK supporta C#, JavaScript o Python. Per altre informazioni su Agents SDK , vedere aka.ms/agents. Se si sta cercando una piattaforma agente basata su SaaS, prendere in considerazione Microsoft Copilot Studio. Se si dispone di un bot esistente compilato con Bot Framework SDK, è possibile aggiornare il bot ad Agents SDK. È possibile esaminare le modifiche e gli aggiornamenti principali in Bot Framework SDK per le linee guida per la migrazione di Agents SDK. I ticket di supporto per Bot Framework SDK non verranno più gestiti a partire dal 31 dicembre 2025.

Prerequisiti

Nota

A partire dalla versione 4.11, non è necessario un ID app e una password per testare un consumatore di abilità localmente nel Bot Framework Emulator. Una sottoscrizione di Azure è comunque necessaria per distribuire il consumatore su Azure o per utilizzare uno skill distribuito.

Informazioni sull'esempio

L'esempio di abilità semplici da bot a bot include i progetti per due bot:

  • Il bot dell'abilità echo, che implementa l'abilità.
  • Il bot radice semplice, che implementa un bot radice che utilizza l'abilità.

Questo articolo è incentrato sul bot radice, che include la logica di supporto nei relativi oggetti bot e adapter, nonché negli oggetti usati per lo scambio di attività con una competenza. tra cui:

  • Un client di skill, usato per inviare attività a una skill.
  • Un gestore di competenze, usato per ricevere attività da una competenza.
  • Factory dell'ID di conversazione delle abilità, utilizzata dal client e dal gestore delle abilità per tradurre il riferimento alla conversazione tra utente e radice e il riferimento alla conversazione tra radice e abilità.

Per informazioni sul bot di competenza echo, vedere Implementare una competenza.

Risorse

Per i bot distribuiti, l'autenticazione da bot a bot richiede che ogni bot partecipante disponga di informazioni di identità valide. Tuttavia, è possibile testare le abilità multi-tenant e i consumatori di abilità localmente con l'emulatore senza un ID app e una password.

Configurazione dell'applicazione

  1. Facoltativamente, aggiungere le informazioni sull'identità del bot radice al file di configurazione. Se la competenza o il consumatore di competenze fornisce informazioni sull'identità, entrambe devono fornire.
  2. Aggiungere l'endpoint host dell'abilità (l'URL del servizio o di callback) a cui le abilità devono rispondere all'utilizzatore delle abilità.
  3. Aggiungi una voce per ogni competenza che l'utilizzatore delle competenze userà. Ogni voce include:
    • Un ID che l'utente delle competenze utilizzerà per identificare ciascuna competenza.
    • Opzionalmente, l'app o l'ID client dell'abilità.
    • Endpoint di messaggistica dell'abilità.

Nota

Se la competenza o il consumatore di competenze fornisce informazioni sull'identità, entrambe devono fornire.

SimpleRootBot\appsettings.jsattivo

Facoltativamente, aggiungi le informazioni sull'identità del bot radice e l'app o l'ID client per il bot di abilità echo.

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

Configurazione delle competenze

In questo esempio le informazioni relative alle singole competenze nel file di configurazione vengono lette in una raccolta di oggetti competenza.

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

Fabbrica dell'ID conversazione

Questo crea l'ID della conversazione da usare con l'abilità e può recuperare l'ID della conversazione utente originale dall'ID della conversazione dell'abilità.

La fabbrica dell'ID di conversazione in questo esempio supporta uno scenario semplice in cui:

  • Il bot radice è progettato per assorbire una specifica competenza.
  • Il bot principale ha solo una conversazione attiva con una funzionalità alla volta.

L'SDK fornisce una SkillConversationIdFactory classe che può essere usata in qualsiasi competenza senza richiedere la replica del codice sorgente. La fabbrica dell'ID conversazione è configurata in Startup.cs.

Per supportare scenari più complessi, dovresti progettare la fabbrica degli ID di conversazione in modo che:

  • Il metodo CreateSkillConversationId ottiene o genera il corretto ID della conversazione dell'abilità.
  • Il metodo GetConversationReference ottiene la conversazione dell'utente corretta.

Client di abilità e gestore di abilità

L'utente delle competenze usa un cliente di competenze per inoltrare attività al servizio di competenza. A tale scopo, il client utilizza le informazioni di configurazione delle competenze e la fabbrica dell'identificativo di conversazione.

L'utente di abilità usa un gestore di abilità per ricevere attività da un'abilità. Il gestore utilizza la factory dell'ID conversazione, la configurazione di autenticazione e un provider di credenziali, e ha anche dipendenze dall'adapter del bot principale e dal gestore di attività.

SimpleRootBot\Startup.cs

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

Il traffico HTTP dalla skill entrerà nell'endpoint URL del servizio che l'utente della skill annuncia alla skill. Usare un gestore di endpoint specifico della lingua per l'inoltro del traffico al gestore delle abilità.

Il gestore di competenze predefinito:

  • Se sono presenti un ID app e una password, usa un oggetto di configurazione di autenticazione per eseguire sia l'autenticazione da bot a bot che la convalida delle attestazioni.
  • Usa il generatore di ID di conversazione per convertire dalla conversazione tra consumatore e competenza alla conversazione tra utente principale e utente.
  • Genera un messaggio proattivo, in modo che l'utente dell'abilità possa ristabilire il contesto di un utente principale e inoltrare le attività all'utente.

Logica del gestore di attività

È importante notare che la logica del consumer di competenze deve:

  • Ricordare se sono presenti competenze attive e, se necessario, inoltrare loro le attività.
  • Rileva quando un utente effettua una richiesta che deve essere inoltrata a una skill e avvia la skill.
  • Cerca un'attività endOfConversation di qualsiasi competenza attiva, per verificare quando viene completata.
  • Se necessario, aggiungere la logica per consentire all'utente o all'utilizzatore della skill di annullare una skill non ancora completata.
  • Salvare lo stato prima di effettuare la chiamata a una skill, in quanto qualsiasi risposta potrebbe tornare a un'istanza diversa del consumer di skill.

SimpleRootBot\Bots\RootBot.cs

Il bot radice ha dipendenze dallo stato della conversazione, dalle informazioni sulle abilità, dal client delle abilità e dalla configurazione generale. ASP.NET fornisce questi oggetti tramite l'inserimento di dipendenze. Il bot radice definisce anche un accessor della proprietà dello stato di conversazione per tenere traccia della competenza attiva.

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

Questo esempio include un metodo helper per l'inoltro di attività a una skill. Salva lo stato della conversazione prima di richiamare la competenza e verifica se la richiesta HTTP è stata eseguita correttamente.

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

È importante notare che il bot principale include la logica per l'inoltro delle attività all'abilità, l'avvio dell'abilità su richiesta dell'utente e l'arresto dell'abilità quando l'abilità è completata.

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

Gestore degli errori durante il turno

Quando si verifica un errore, l'adapter cancella lo stato della conversazione per reimpostare la conversazione con l'utente ed evitare la persistenza di uno stato di errore.

È consigliabile inviare un'attività di fine conversazione a qualsiasi skill attiva prima di cancellare lo stato della conversazione nell'utilizzatore di skill. In questo modo, l'abilità rilascia tutte le risorse associate alla conversazione tra utente e abilità prima che l'utente termini la conversazione.

SimpleRootBot\AdapterWithErrorHandler.cs

In questo esempio, la logica degli errori nei turni è suddivisa tra alcuni metodi di supporto.

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

Endpoint delle competenze

Il bot definisce un endpoint che inoltra le attività di skill in arrivo al gestore di skill del bot radice.

SimpleRootBot\Controllers\SkillController.cs

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

Registrazione del servizio

Includere un oggetto di configurazione dell'autenticazione insieme alla validazione dei claims e a tutti gli oggetti aggiuntivi. In questo esempio viene usata la stessa logica di configurazione dell'autenticazione per convalidare le attività di utenti e competenze.

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

Testare il bot principale

È possibile testare il consumatore di skill nell'Emulator come se fosse un normale bot; tuttavia, è necessario eseguire contemporaneamente sia il bot della skill che quello del consumatore di skill. Per informazioni su come configurare la competenza, vedere Implementare una competenza.

Scaricare e installare l'ultima versione di Bot Framework Emulator

  1. Esegui il bot di abilità echo e il bot radice semplice localmente sul tuo computer. Se hai bisogno di istruzioni, consulta il file per l'esempio di README, JavaScript, Java o Python.
  2. Usare Emulator per testare il bot come illustrato di seguito. Quando invii un messaggio end o stop all'abilità, l'abilità invia al bot radice un'attività endOfConversation, oltre al messaggio di risposta. La proprietà endOfConversation dell'attività indica che l'abilità è stata completata con successo.

Trascrizione di esempio di un'interazione con l'utente delle competenze.

Altre informazioni sul debug

Poiché il traffico tra competenze e utenti delle competenze è autenticato, sono necessari passaggi aggiuntivi durante il debugging di tali bot.

  • Il consumatore di abilità e tutte le abilità che consuma, direttamente o indirettamente, devono essere attive.
  • Se i bot vengono eseguiti localmente e se uno dei bot ha un ID app e una password, tutti i bot devono avere ID e password validi.
  • Se tutti i bot vengono distribuiti, vedere come eseguire il debug di un bot da qualsiasi canale usando devtunnel.
  • Se alcuni bot sono in esecuzione localmente e alcuni sono distribuiti, vedere come eseguire il debug di una skill o un consumer di skill.

In caso contrario, è possibile eseguire il debug di un utente della competenza o di una skill in modo molto simile al debug di altri bot. Per altre informazioni, vedere Debug di un bot e Debug con Bot Framework Emulator.

Informazioni aggiuntive

Ecco alcuni aspetti da considerare quando si implementa un bot radice più complesso.

Per consentire all'utente di annullare un'abilità che implica più passaggi

Il bot radice deve controllare il messaggio dell'utente prima di inoltrarlo alla skill attiva. Se l'utente vuole annullare il processo corrente, il bot principale può inviare un'attività endOfConversation alla skill, invece di inoltrare il messaggio.

Per scambiare dati tra il bot principale e i bot specializzati

Per inviare parametri alla skill, l'utente della skill può impostare la proprietà value sui messaggi inviati alla skill. Per ricevere i valori restituiti dalla skill, il consumatore di skill deve controllare la proprietà value quando la skill invia un'attività endOfConversation.

Per usare più competenze

  • Se una competenza è attiva, il bot radice deve determinare quale competenza è attiva e inoltrare il messaggio dell'utente alla competenza corretta.
  • Se non ci sono abilità attive, il bot radice deve determinare quale abilità avviare, eventualmente, in base allo stato del bot e all'input dell'utente.
  • Se si vuole consentire all'utente di alternare tra più competenze simultanee, il bot radice deve determinare con quali competenze attive l'utente intende interagire prima di inoltrare il messaggio dell'utente.

Per utilizzare una modalità di consegna che preveda risposte

Per usare la modalità di recapito delle risposte attese:

  • Clonare l'attività dal contesto del turno.
  • Impostare la proprietà modalità di consegna della nuova attività su "ExpectReplies" prima di inviare l'attività dal bot radice alla skill.
  • Leggi le risposte previste dal corpo della risposta d'invocazione restituita dalla risposta alla richiesta.
  • Elaborare ogni attività, all'interno del bot principale o inviandola al canale che ha avviato la richiesta originale.

Le risposte previste possono essere utili nelle situazioni in cui il bot che risponde a un'attività deve essere la stessa istanza del bot che ha ricevuto l'attività.