Share via


Implementare un flusso di conversazione sequenziale

SI APPLICA A: SDK v4

Raccogliere informazioni ponendo domande è uno dei modi principali con cui un bot interagisce con gli utenti. La raccolta dialoghi offre funzionalità integrate utili, ad esempio classi prompt che semplificano le domande e convalidano la risposta per verificare che corrisponda a un tipo di dati specifici o che soddisfi le regole di convalida personalizzata.

È possibile gestire flussi di conversazione lineari e più complessi usando la libreria dialogs. In un'interazione lineare, il bot viene eseguito attraverso una sequenza fissa di passaggi e la conversazione termina. Una finestra di dialogo è utile quando il bot deve raccogliere informazioni dall'utente.

Questo articolo illustra come implementare il flusso di conversazione lineare creando prompt e chiamandoli da un dialogo a cascata. Per esempi su come scrivere le proprie richieste senza usare la raccolta di dialoghi, vedere l'articolo Creare richieste personalizzate per raccogliere input utente.

Nota

Gli SDK JavaScript, C# e Python di Bot Framework continueranno a essere supportati, ma Java SDK verrà ritirato con il supporto finale a lungo termine che termina a novembre 2023.

I bot esistenti creati con Java SDK continueranno a funzionare.

Per la creazione di nuovi bot, prendere in considerazione l'uso di Power Virtual Agents e leggere la scelta della soluzione chatbot appropriata.

Per altre informazioni, vedere Il futuro della compilazione di bot.

Prerequisiti

Informazioni sull'esempio

L'esempio di richieste a più turni usa un dialogo a cascata, alcune richieste e un dialogo componente per creare un'interazione lineare che pone all'utente una serie di domande. Il codice usa un dialogo per scorrere questi passaggi:

Passaggi Tipo di richiesta
Chiedere all'utente la modalità di trasporto Richiesta di scelta
Chiedere all'utente il nome Richiesta di testo
Chiedere all'utente se vuole fornire la propria età Richiesta di conferma
Se hanno risposto sì, chiedere la loro età Richiesta numero, con convalida per accettare solo età maggiori di 0 e minori di 150
Se non usa Microsoft Teams, chiedere un'immagine del profilo Prompt degli allegati, con convalida per consentire un allegato mancante
Chiedere se le informazioni raccolte sono "ok" Riutilizzo della richiesta di conferma

Infine, se hanno risposto sì, visualizzare le informazioni raccolte; in caso contrario, indicare all'utente che le informazioni non verranno mantenute.

Creare il dialogo principale

Per usare i dialoghi, installare il pacchetto NuGet Microsoft.Bot.Builder.Dialogs.

Il bot interagisce con l'utente tramite UserProfileDialog. Quando si crea la classe del DialogBot bot, viene UserProfileDialog impostato come finestra di dialogo principale. Il bot usa quindi un metodo helper Run per accedere al dialogo.

Diagramma classi per l'esempio C#.

Dialogs\UserProfileDialog.cs

Iniziare creando l'oggetto UserProfileDialog che deriva dalla ComponentDialog classe e ha sette passaggi.

Nel costruttore UserProfileDialog creare i passaggi a cascata, le richieste e il dialogo a cascata e aggiungerli al set di dialoghi. I prompt devono trovarsi nello stesso set di dialoghi in cui vengono usati.

public UserProfileDialog(UserState userState)
    : base(nameof(UserProfileDialog))
{
    _userProfileAccessor = userState.CreateProperty<UserProfile>("UserProfile");

    // This array defines how the Waterfall will execute.
    var waterfallSteps = new WaterfallStep[]
    {
        TransportStepAsync,
        NameStepAsync,
        NameConfirmStepAsync,
        AgeStepAsync,
        PictureStepAsync,
        SummaryStepAsync,
        ConfirmStepAsync,
    };

    // Add named dialogs to the DialogSet. These names are saved in the dialog state.
    AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
    AddDialog(new TextPrompt(nameof(TextPrompt)));
    AddDialog(new NumberPrompt<int>(nameof(NumberPrompt<int>), AgePromptValidatorAsync));
    AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
    AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt)));
    AddDialog(new AttachmentPrompt(nameof(AttachmentPrompt), PicturePromptValidatorAsync));

    // The initial child Dialog to run.
    InitialDialogId = nameof(WaterfallDialog);
}

Aggiungere quindi i passaggi usati dalla finestra di dialogo per richiedere l'input. Per usare una richiesta, chiamarla da un passaggio del dialogo e recuperarne il risultato nel passaggio seguente usando stepContext.Result. Le richieste non sono altro che un dialogo in due passaggi. Prima di tutto, il prompt richiede l'input. Restituisce quindi il valore valido o inizia dall'inizio con una nuova richiesta fino a quando non riceve un input valido.

È opportuno che un risultato DialogTurnResult restituito da un passaggio a cascata sia sempre non Null. In caso contrario, il dialogo potrebbe non funzionare come progettato. Di seguito è illustrata l'implementazione di NameStepAsync nella finestra di dialogo a cascata.

private static async Task<DialogTurnResult> NameStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    stepContext.Values["transport"] = ((FoundChoice)stepContext.Result).Value;

    return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = MessageFactory.Text("Please enter your name.") }, cancellationToken);
}

In AgeStepAsyncspecificare una richiesta di ripetizione dei tentativi quando l'input dell'utente non riesce a convalidare, perché è in un formato che il prompt non è in grado di analizzare o l'input non riesce a eseguire un criterio di convalida. In questo caso, se non è stata fornita alcuna richiesta di ripetizione dei tentativi, il prompt userà il testo del prompt iniziale per riprovare l'utente per l'input.

private async Task<DialogTurnResult> AgeStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    if ((bool)stepContext.Result)
    {
        // User said "yes" so we will be prompting for the age.
        // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
        var promptOptions = new PromptOptions
        {
            Prompt = MessageFactory.Text("Please enter your age."),
            RetryPrompt = MessageFactory.Text("The value entered must be greater than 0 and less than 150."),
        };

        return await stepContext.PromptAsync(nameof(NumberPrompt<int>), promptOptions, cancellationToken);
    }
    else
    {
        // User said "no" so we will skip the next step. Give -1 as the age.
        return await stepContext.NextAsync(-1, cancellationToken);
    }
}

UserProfile.cs

Il mezzo di trasporto, il nome e l'età dell'utente vengono salvati in un'istanza della classe UserProfile.

public class UserProfile
{
    public string Transport { get; set; }

    public string Name { get; set; }

    public int Age { get; set; }

    public Attachment Picture { get; set; }
}

Dialogs\UserProfileDialog.cs

Nell'ultimo passaggio controllare l'oggetto stepContext.Result restituito dal dialogo chiamato nel passaggio a cascata precedente. Se il valore restituito è true, la funzione di accesso del profilo utente ottiene e aggiorna il profilo utente. Per ottenere il profilo utente, chiamare GetAsync e quindi impostare i valori delle userProfile.Transportproprietà , userProfile.NameuserProfile.Age e userProfile.Picture . Riepilogare infine le informazioni per l'utente prima di chiamare EndDialogAsync, che termina la finestra di dialogo. Quando si termina un dialogo, questo viene estratto dallo stack e viene restituito un risultato facoltativo all'elemento padre del dialogo. Per elemento padre si intende il dialogo o il metodo che ha avviato il dialogo appena terminato.

    else
    {
        msg += $" Your profile will not be kept.";
    }

    await stepContext.Context.SendActivityAsync(MessageFactory.Text(msg), cancellationToken);

    // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is the end.
    return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
}

private async Task<DialogTurnResult> SummaryStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    stepContext.Values["picture"] = ((IList<Attachment>)stepContext.Result)?.FirstOrDefault();

    // Get the current profile object from user state.
    var userProfile = await _userProfileAccessor.GetAsync(stepContext.Context, () => new UserProfile(), cancellationToken);

    userProfile.Transport = (string)stepContext.Values["transport"];
    userProfile.Name = (string)stepContext.Values["name"];
    userProfile.Age = (int)stepContext.Values["age"];
    userProfile.Picture = (Attachment)stepContext.Values["picture"];

    var msg = $"I have your mode of transport as {userProfile.Transport} and your name as {userProfile.Name}";

    if (userProfile.Age != -1)
    {
        msg += $" and your age as {userProfile.Age}";
    }

    msg += ".";

    await stepContext.Context.SendActivityAsync(MessageFactory.Text(msg), cancellationToken);

    if (userProfile.Picture != null)
    {
        try
        {
            await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(userProfile.Picture, "This is your profile picture."), cancellationToken);
        }
        catch
        {
            await stepContext.Context.SendActivityAsync(MessageFactory.Text("A profile picture was saved but could not be displayed here."), cancellationToken);

Eseguire il dialogo

Bots\DialogBot.cs

Il gestore OnMessageActivityAsync usa il metodo RunAsync per iniziare o continuare il dialogo. OnTurnAsync usa gli oggetti di gestione dello stato del bot per rendere persistenti le modifiche dello stato all'archiviazione. Il metodo ActivityHandler.OnTurnAsync chiama i vari metodi del gestore attività, ad esempio OnMessageActivityAsync. In questo modo, lo stato viene salvato dopo il completamento del gestore messaggi, ma prima del completamento del turno stesso.

public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
{
    await base.OnTurnAsync(turnContext, cancellationToken);

    // Save any state changes that might have occurred during the turn.
    await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
    await UserState.SaveChangesAsync(turnContext, false, cancellationToken);
}

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    Logger.LogInformation("Running dialog with Message Activity.");

    // Run the Dialog with the new message Activity.
    await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
}

Registrare i servizi per il bot

Questo bot usa i servizi seguenti:

  • Servizi di base per un bot: un provider di credenziali, un adattatore e l'implementazione del bot.
  • Servizi per la gestione dello stato: archiviazione, stato utente e stato conversazione.
  • Il dialogo usato dal bot.

Startup.cs

Registrare i servizi per il bot in Startup. Questi servizi sono disponibili per altre parti del codice tramite l'inserimento delle dipendenze.

{
    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHttpClient().AddControllers().AddNewtonsoftJson(options =>
        {
            options.SerializerSettings.MaxDepth = HttpHelper.BotMessageSerializerSettings.MaxDepth;
        });

        // Create the Bot Framework Authentication to be used with the Bot Adapter.
        services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>();

        // Create the Bot Adapter with error handling enabled.
        services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();

        // Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.)
        services.AddSingleton<IStorage, MemoryStorage>();

        // Create the User state. (Used in this bot's Dialog implementation.)
        services.AddSingleton<UserState>();

        // Create the Conversation state. (Used by the Dialog system itself.)
        services.AddSingleton<ConversationState>();

Nota

L'archiviazione di memoria viene usata solo a scopo di test e non è destinata all'uso in produzione. Assicurarsi di usare un tipo di archiviazione permanente per un bot di produzione.

Esegui il test del tuo bot

  1. Se non è già stato fatto, installare Bot Framework Emulator.
  2. Eseguire l'esempio in locale nel computer.
  3. Avviare l'emulatore, connettersi al bot e inviare messaggi come mostrato di seguito.

Trascrizione di esempio di una conversazione con il bot prompt a più turni.

Informazioni aggiuntive

Informazioni sullo stato del dialogo e del bot

In questo bot vengono definite due funzioni di accesso alle proprietà di stato:

  • Una è stata creata nello stato della conversazione per la proprietà dello stato del dialogo. Lo stato del dialogo tiene traccia della posizione in cui l'utente si trova all'interno dei dialoghi di un set di dialoghi e viene aggiornato dal contesto del dialogo, ad esempio quando vengono chiamati i metodi begin dialog o continue dialog .
  • L'altra è stata creata nello stato dell'utente per la proprietà del profilo utente. Il bot usa questa opzione per tenere traccia delle informazioni relative all'utente ed è necessario gestire in modo esplicito questo stato nel codice della finestra di dialogo.

I metodi get e set di una funzione di accesso alle proprietà di stato ottengono e impostano il valore della proprietà nella cache dell'oggetto gestione stato. La cache viene popolata la prima volta che in un turno viene richiesto il valore di una proprietà di stato, ma deve essere resa persistente in modo esplicito. Per rendere persistenti le modifiche apportate a entrambe queste proprietà di stato, viene eseguita una chiamata al metodo save changes dell'oggetto di gestione dello stato corrispondente.

Questo esempio consente di aggiornare lo stato del profilo utente all'interno del dialogo. Questa procedura può funzionare per alcuni bot, ma non funzionerà se si vuole riutilizzare un dialogo tra bot.

Sono disponibili diverse opzioni per mantenere separati lo stato del dialogo e lo stato del bot. Ad esempio, dopo che il dialogo ha raccolto le informazioni complete, è possibile:

  • Usare il metodo end dialog per fornire i dati raccolto come valore restituito al contesto padre. Può trattarsi del gestore dei turni del bot o di una finestra di dialogo attiva precedente nello stack di dialoghi ed è il modo in cui sono progettate le classi di prompt.
  • Generare una richiesta relativa a un servizio appropriato. Questa soluzione può funzionare anche se il bot funge da front-end di un servizio più esteso.

Definizione di un metodo per il validator di richiesta

UserProfileDialog.cs

Di seguito è riportato un esempio di codice validator per la definizione del AgePromptValidatorAsync metodo. promptContext.Recognized.Value contiene il valore analizzato, che in questo caso è un intero per la richiesta del numero. promptContext.Recognized.Succeeded indica se la richiesta è riuscita o meno ad analizzare l'input dell'utente. Il validator deve restituire false per indicare che il valore non è stato accettato e che la finestra di dialogo di richiesta deve ripetere la richiesta dell'utente; in caso contrario, restituisce true per accettare l'input e restituire dalla finestra di dialogo del prompt. È possibile modificare il valore nel validator in base allo scenario.

    }

    // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
    return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions { Prompt = MessageFactory.Text("Is this ok?") }, cancellationToken);
}

Passaggi successivi