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 segue 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, è consigliabile usare Microsoft Copilot Studio e leggere le informazioni sulla scelta della soluzione copilota appropriata.
Per ulteriori informazioni, vedere Il futuro della creazione di bot.
Prerequisiti
- Conoscenza delle nozioni di base sui bot, della gestione dello stato e della libreria di dialoghi.
- Copia dell'esempio di richieste a più turni in C#, JavaScript, Java o Python.
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 di inserimento numerico, con convalida per accettare solo età superiori a 0 e inferiori a 150 |
Se non usa Microsoft Teams, chiedere un'immagine del profilo | Richiesta di allegati, con convalida per permettere l'assenza di un allegato |
Chiedere se le informazioni raccolte sono "ok" | Prompt di conferma per il riutilizzo |
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 DialogBot
del bot, il UserProfileDialog
viene impostato come finestra di dialogo principale. Il bot usa quindi un metodo helper Run
per accedere al dialogo.
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
. Dietro le quinte, le richieste sono 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 AgeStepAsync
, specificare una richiesta di ripetizione dei tentativi quando l'input dell'utente non viene convalidato, perché è in un formato che il prompt non è in grado di analizzare o l'input non soddisfa un criterio di convalida. In questo caso, se non è stata fornita alcuna richiesta di ripetizione, il prompt userà il testo iniziale del prompt per richiedere nuovamente l'input all'utente.
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'ultima fase, controllare l'oggetto stepContext.Result
restituito dalla finestra di dialogo chiamata nella fase a cascata precedente. Se il valore restituito è true, l'accessore del profilo utente ottiene e aggiorna il profilo utente. Per ottenere il profilo utente, chiamare GetAsync
e quindi impostare i valori delle proprietà userProfile.Transport
, userProfile.Name
, userProfile.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
utilizza gli oggetti di gestione dello stato del bot per mantenere permanenti le modifiche dello stato in 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
Registra i servizi per il bot in Startup
. Questi servizi sono disponibili per altre parti del codice tramite l'iniezione 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
- Se non è già stato fatto, installare Bot Framework Emulator.
- Esegui l'esempio localmente sul tuo computer.
- Avviare l'emulatore, connettersi al bot e inviare messaggi come mostrato di seguito.
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 .
- Una creata nello stato 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 i passaggi 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 restituire i dati raccolti come valore di ritorno 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 validatore di prompt
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 validatore deve restituire false per indicare che il valore non è stato accettato e che la finestra di dialogo deve ripetere la richiesta; in caso contrario, restituisce true per accettare l'input e uscire 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);
}