Ein Bot ist grundsätzlich zustandslos. Nachdem Ihr Bot bereitgestellt wurde, kann es sein, dass er für die einzelnen Durchläufe nicht als Teil desselben Prozesses oder auf demselben Computer ausgeführt wird. Unter Umständen muss Ihr Bot aber den Kontext einer Konversation nachverfolgen, damit er das Verhalten verwalten und Antworten auf vorherige Fragen speichern kann. Mit den Zustands- und Speicherfeatures des Bot Framework SDK können Sie Ihrem Bot den Zustand hinzufügen. Bots verwenden Zustandsverwaltungs- und Speicherobjekte, um den Zustand zu verwalten und zu speichern. Der Zustands-Manager bietet eine Abstraktionsebene, mit der Sie unabhängig von der Art des zugrunde liegenden Speichers mithilfe von Eigenschaftenaccessoren auf Zustandseigenschaften zugreifen können.
Hinweis
Die JavaScript-, C#- und Python-SDKs für Bot Framework werden weiterhin unterstützt, das Java-SDK wird jedoch eingestellt und der langfristige Support endet im November 2023.
Bestehende Bots, die mit dem Java SDK erstellt wurden, werden weiterhin funktionieren.
Der Code in diesem Artikel basiert auf dem Botbeispiel für die Zustandsverwaltung. Sie benötigen eine Kopie des Beispiels entweder in C#, JavaScript, Java oder Python.
Informationen zu diesem Beispiel
Nach dem Empfang der Benutzereingabe überprüft das Beispiel den gespeicherten Konversationszustand, um festzustellen, ob der Benutzer bereits zur Eingabe seines Namens aufgefordert wurde. Wenn dies nicht der Fall ist, wird der Name des Benutzers angefordert, und die Eingabe wird im Benutzerzustand gespeichert. Ist dies der Fall, wird der im Benutzerzustand gespeicherte Name zur Konversation mit dem Benutzer verwendet, und die Eingabedaten werden zusammen mit der Empfangszeit und der ID des Eingabekanals an den Benutzer zurückgegeben. Die Werte für die Uhrzeit und die Kanal-ID werden aus den Benutzerkonversationsdaten abgerufen und dann im Konversationszustand gespeichert. Das folgende Diagramm zeigt die Beziehung zwischen dem Bot, dem Benutzerprofil und den Konversationsdatenklassen.
Der erste Schritt beim Einrichten der Zustandsverwaltung ist das Definieren der Klassen, die die Informationen enthalten, die im Benutzer- und Konversationszustand verwaltet werden sollen. In dem in diesem Artikel verwendeten Beispiel werden die folgenden Klassen definiert:
In UserProfile.cs definieren Sie eine UserProfile-Klasse für die Benutzerinformationen, die vom Bot erfasst werden.
In ConversationData.cs definieren Sie eine ConversationData-Klasse, um den Konversationszustand während der Erfassung von Benutzerinformationen zu steuern.
Die folgenden Codebeispiele zeigen die Definitionen für die Klassen UserProfile und ConversationData.
UserProfile.cs
public class UserProfile
{
public string Name { get; set; }
}
ConversationData.cs
public class ConversationData
{
// The time-stamp of the most recent incoming message.
public string Timestamp { get; set; }
// The ID of the user's channel.
public string ChannelId { get; set; }
// Track whether we have already asked the user's name
public bool PromptedUserForName { get; set; } = false;
}
Dieser Schritt ist in JavaScript nicht erforderlich.
Der erste Schritt beim Einrichten der Zustandsverwaltung ist das Definieren der Klassen, die die Informationen enthalten, die im Benutzer- und Konversationszustand verwaltet werden sollen. In dem in diesem Artikel verwendeten Beispiel werden die folgenden Klassen definiert:
In UserProfile.java definieren Sie eine UserProfile-Klasse für die Benutzerinformationen, die vom Bot erfasst werden.
In ConversationData.java definieren Sie eine ConversationData-Klasse, um den Konversationszustand während der Erfassung von Benutzerinformationen zu steuern.
Die folgenden Codebeispiele zeigen die Definitionen für die Klassen UserProfile und ConversationData.
UserProfile.java
public class UserProfile {
private String name;
public String getName() {
return name;
}
public void setName(String withName) {
name = withName;
}
}
ConversationData.java
public class ConversationData {
// The time-stamp of the most recent incoming message.
private String timestamp;
// The ID of the user's channel.
private String channelId;
// Track whether we have already asked the user's name.
private boolean promptedUserForName = false;
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String withTimestamp) {
timestamp = withTimestamp;
}
public String getChannelId() {
return channelId;
}
public void setChannelId(String withChannelId) {
channelId = withChannelId;
}
public boolean getPromptedUserForName() {
return promptedUserForName;
}
public void setPromptedUserForName(boolean withPromptedUserForName) {
Der erste Schritt beim Einrichten der Zustandsverwaltung ist das Definieren der Klassen, die die Informationen enthalten, die im Benutzer- und Konversationszustand verwaltet werden sollen. In dem in diesem Artikel verwendeten Beispiel werden die folgenden Klassen definiert:
user_profile.py enthält die Klasse UserProfile, in der die vom Bot gesammelten Benutzerinformationen gespeichert werden.
conversation_data.py enthält die ConversationData-Klasse, die den Konversationszustand beim Sammeln der Benutzerinformationen steuert.
Die folgenden Codebeispiele zeigen die Definitionen für die Klassen UserProfile und ConversationData.
user_profile.py
class UserProfile:
def __init__(self, name: str = None):
self.name = name
Als Nächstes registrieren Sie die MemoryStorage-Klasse, die zum Erstellen von UserState- und ConversationState-Objekten verwendet wird. Die Benutzer- und Konversationszustandsobjekte werden beim Startup erstellt, und Abhängigkeiten werden in den Botkonstruktor injiziert. Weitere Dienste, die für einen Bot registriert werden, sind ein Anmeldeinformationsanbieter, ein Adapter und die Botimplementierung.
Startup.cs
// {
// TypeNameHandling = TypeNameHandling.All,
// var storage = new BlobsStorage("<blob-storage-connection-string>", "bot-state");
// With a custom JSON SERIALIZER, use this instead.
// var storage = new BlobsStorage("<blob-storage-connection-string>", "bot-state", jsonSerializer);
/* END AZURE BLOB STORAGE */
Als Nächstes registrieren Sie die MemoryStorage-Klasse, die dann zum Erstellen von UserState- und ConversationState-Objekten verwendet wird. Diese werden in index.js erstellt und bei der Erstellung des Bots verwendet.
index.js
const memoryStorage = new MemoryStorage();
// Create conversation and user state with in-memory storage provider.
const conversationState = new ConversationState(memoryStorage);
const userState = new UserState(memoryStorage);
// Create the bot.
const bot = new StateManagementBot(conversationState, userState);
bots/stateManagementBot.js
const CONVERSATION_DATA_PROPERTY = 'conversationData';
const USER_PROFILE_PROPERTY = 'userProfile';
class StateManagementBot extends ActivityHandler {
constructor(conversationState, userState) {
super();
// Create the state property accessors for the conversation data and user profile.
this.conversationDataAccessor = conversationState.createProperty(CONVERSATION_DATA_PROPERTY);
this.userProfileAccessor = userState.createProperty(USER_PROFILE_PROPERTY);
// The state management objects for the conversation and user state.
this.conversationState = conversationState;
this.userState = userState;
Als Nächstes registrieren Sie StateManagementBot in Application.java. Sowohl ConversationState als auch UserState werden standardmäßig von der BotDependencyConfiguration-Klasse bereitgestellt und Spring fügt sie in die getBot-Methode ein.
Application.java
@Bean
public Bot getBot(
ConversationState conversationState,
UserState userState
) {
return new StateManagementBot(conversationState, userState);
}
Als Nächstes registrieren Sie die MemoryStorage-Klasse, die zum Erstellen von UserState- und ConversationState-Objekten verwendet wird. Diese werden in app.py erstellt und bei der Erstellung des Bots verwendet.
app.py
CONVERSATION_STATE = ConversationState(MEMORY)
# Create Bot
BOT = StateManagementBot(CONVERSATION_STATE, USER_STATE)
# Listen for incoming requests on /api/messages.
bots/state_management_bot.py
def __init__(self, conversation_state: ConversationState, user_state: UserState):
if conversation_state is None:
raise TypeError(
"[StateManagementBot]: Missing parameter. conversation_state is required but None was given"
)
if user_state is None:
raise TypeError(
"[StateManagementBot]: Missing parameter. user_state is required but None was given"
)
self.conversation_state = conversation_state
self.user_state = user_state
self.conversation_data_accessor = self.conversation_state.create_property(
"ConversationData"
)
self.user_profile_accessor = self.user_state.create_property("UserProfile")
Nun erstellen Sie Eigenschaftenaccessoren mit der CreateProperty-Methode, die ein Handle für das BotState-Objekt bereitstellt. Mit jedem Zustandseigenschaftenaccessor können Sie den Wert der zugeordneten Zustandseigenschaft abrufen oder festlegen. Bevor Sie die Zustandseigenschaften verwenden, nutzen Sie jeden Accessor, um die Eigenschaft aus dem Speicher zu laden und aus dem Zustandscache abzurufen. Um den korrekt zugeordneten Schlüssel für die Zustandseigenschaft abzurufen, rufen Sie die GetAsync-Methode auf.
Bots/StateManagementBot.cs
var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
Nun erstellen Sie die Eigenschaftenaccessoren für UserState und ConversationState. Mit jedem Zustandseigenschaftenaccessor können Sie den Wert der zugeordneten Zustandseigenschaft abrufen oder festlegen. Sie verwenden die einzelnen Accessoren, um die jeweilige Eigenschaft aus dem Speicher zu laden und ihren aktuellen Zustand aus dem Cache abzurufen.
bots/stateManagementBot.js
// Create the state property accessors for the conversation data and user profile.
this.conversationDataAccessor = conversationState.createProperty(CONVERSATION_DATA_PROPERTY);
this.userProfileAccessor = userState.createProperty(USER_PROFILE_PROPERTY);
Nun erstellen Sie mit der createProperty-Methode Eigenschaftenaccessoren. Mit jedem Zustandseigenschaftenaccessor können Sie den Wert der zugeordneten Zustandseigenschaft abrufen oder festlegen. Bevor Sie die Zustandseigenschaften verwenden, nutzen Sie jeden Accessor, um die Eigenschaft aus dem Speicher zu laden und aus dem Zustandscache abzurufen. Um den korrekt zugeordneten Schlüssel für die Zustandseigenschaft abzurufen, rufen Sie die get-Methode auf.
Nun erstellen Sie die Eigenschaftenaccessoren für UserProfile und ConversationData. Mit jedem Zustandseigenschaftenaccessor können Sie den Wert der zugeordneten Zustandseigenschaft abrufen oder festlegen. Sie verwenden die einzelnen Accessoren, um die jeweilige Eigenschaft aus dem Speicher zu laden und ihren aktuellen Zustand aus dem Cache abzurufen.
Im vorherigen Abschnitt wurden die Schritte erläutert, die während der Initialisierung ausgeführt werden, um dem Bot Zustandseigenschaftenaccessoren hinzuzufügen. Jetzt können Sie diese Accessoren zur Laufzeit verwenden, um Zustandsinformationen zu lesen und zu schreiben. Der folgende Beispielcode verwendet den unten dargestellten Logikflow:
Wenn userProfile.Name leer ist und conversationData.PromptedUserForNametrue ist, rufen Sie den angegebenen Benutzernamen ab und speichern ihn im Benutzerzustand.
Wenn userProfile.Name leer ist und conversationData.PromptedUserForNamefalse ist, fragen Sie nach dem Namen des Benutzers.
Wenn userProfile.Name zuvor gespeichert wurde, gehen Sie wie folgt vor: Sie rufen die Uhrzeit der Nachricht und die Kanal-ID aus der Benutzereingabe ab, geben alle Daten an den Benutzer zurück und speichern die abgerufenen Daten im Konversationszustand.
Bots/StateManagementBot.cs
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
// Get the state properties from the turn context.
var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
var conversationData = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationData());
var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
var userProfile = await userStateAccessors.GetAsync(turnContext, () => new UserProfile());
if (string.IsNullOrEmpty(userProfile.Name))
{
// First time around this is set to false, so we will prompt user for name.
if (conversationData.PromptedUserForName)
{
// Set the name to what the user provided.
userProfile.Name = turnContext.Activity.Text?.Trim();
// Acknowledge that we got their name.
await turnContext.SendActivityAsync($"Thanks {userProfile.Name}. To see conversation data, type anything.");
// Reset the flag to allow the bot to go through the cycle again.
conversationData.PromptedUserForName = false;
}
else
{
// Prompt the user for their name.
await turnContext.SendActivityAsync($"What is your name?");
// Set the flag to true, so we don't prompt in the next turn.
conversationData.PromptedUserForName = true;
}
}
else
{
// Add message details to the conversation data.
// Convert saved Timestamp to local DateTimeOffset, then to string for display.
var messageTimeOffset = (DateTimeOffset)turnContext.Activity.Timestamp;
var localMessageTime = messageTimeOffset.ToLocalTime();
conversationData.Timestamp = localMessageTime.ToString();
conversationData.ChannelId = turnContext.Activity.ChannelId.ToString();
// Display state data.
await turnContext.SendActivityAsync($"{userProfile.Name} sent: {turnContext.Activity.Text}");
await turnContext.SendActivityAsync($"Message received at: {conversationData.Timestamp}");
await turnContext.SendActivityAsync($"Message received from: {conversationData.ChannelId}");
}
}
Bevor Sie den Turn-Handler beenden, schreiben Sie mit der SaveChangesAsync()-Methode der Zustandsverwaltungsobjekte alle Zustandsänderungen zurück in den Speicher.
Bots/StateManagementBot.cs
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
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);
}
Wenn userProfile.Name leer ist und conversationData.PromptedUserForNametrue ist, rufen Sie den angegebenen Benutzernamen ab und speichern ihn im Benutzerzustand.
Wenn userProfile.Name leer ist und conversationData.PromptedUserForNamefalse ist, fragen Sie nach dem Namen des Benutzers.
Wenn userProfile.Name zuvor gespeichert wurde, gehen Sie wie folgt vor: Sie rufen die Uhrzeit der Nachricht und die Kanal-ID aus der Benutzereingabe ab, geben alle Daten an den Benutzer zurück und speichern die abgerufenen Daten im Konversationszustand.
bots/stateManagementBot.js
this.onMessage(async (turnContext, next) => {
// Get the state properties from the turn context.
const userProfile = await this.userProfileAccessor.get(turnContext, {});
const conversationData = await this.conversationDataAccessor.get(
turnContext, { promptedForUserName: false });
if (!userProfile.name) {
// First time around this is undefined, so we will prompt user for name.
if (conversationData.promptedForUserName) {
// Set the name to what the user provided.
userProfile.name = turnContext.activity.text;
// Acknowledge that we got their name.
await turnContext.sendActivity(`Thanks ${ userProfile.name }. To see conversation data, type anything.`);
// Reset the flag to allow the bot to go though the cycle again.
conversationData.promptedForUserName = false;
} else {
// Prompt the user for their name.
await turnContext.sendActivity('What is your name?');
// Set the flag to true, so we don't prompt in the next turn.
conversationData.promptedForUserName = true;
}
} else {
// Add message details to the conversation data.
conversationData.timestamp = turnContext.activity.timestamp.toLocaleString();
conversationData.channelId = turnContext.activity.channelId;
// Display state data.
await turnContext.sendActivity(`${ userProfile.name } sent: ${ turnContext.activity.text }`);
await turnContext.sendActivity(`Message received at: ${ conversationData.timestamp }`);
await turnContext.sendActivity(`Message received from: ${ conversationData.channelId }`);
}
// By calling next() you ensure that the next BotHandler is run.
await next();
});
Bevor Sie die einzelnen Dialogturns beenden, speichern Sie mit der saveChanges()-Methode der Zustandsverwaltungsobjekte alle Änderungen, indem wir den Zustand in den Speicher zurückschreiben.
bots/stateManagementBot.js
/**
* Override the ActivityHandler.run() method to save state changes after the bot logic completes.
*/
async run(context) {
await super.run(context);
// Save any state changes. The load happened during the execution of the Dialog.
await this.conversationState.saveChanges(context, false);
await this.userState.saveChanges(context, false);
}
Wenn userProfile.getName() leer ist und conversationData.getPromptedUserForName()true ist, rufen Sie den angegebenen Benutzernamen ab und speichern ihn im Benutzerzustand.
Wenn userProfile.getName() leer ist und conversationData.getPromptedUserForName()false ist, fragen Sie nach dem Namen des Benutzers.
Wenn userProfile.getName() zuvor gespeichert wurde, gehen Sie wie folgt vor: Sie rufen die Uhrzeit der Nachricht und die Kanal-ID aus der Benutzereingabe ab, geben alle Daten an den Benutzer zurück und speichern die abgerufenen Daten im Konversationszustand.
StateManagementBot.java
@Override
protected CompletableFuture<Void> onMessageActivity(TurnContext turnContext) {
// Get state data from ConversationState.
StatePropertyAccessor<ConversationData> dataAccessor =
conversationState.createProperty("data");
CompletableFuture<ConversationData> dataFuture =
dataAccessor.get(turnContext, ConversationData::new);
// Get profile from UserState.
StatePropertyAccessor<UserProfile> profileAccessor = userState.createProperty("profile");
CompletableFuture<UserProfile> profileFuture =
profileAccessor.get(turnContext, UserProfile::new);
return dataFuture.thenCombine(profileFuture, (conversationData, userProfile) -> {
if (StringUtils.isBlank(userProfile.getName())) {
// First time around this is set to false, so we will prompt user for name.
if (conversationData.getPromptedUserForName()) {
// Reset the flag to allow the bot to go though the cycle again.
conversationData.setPromptedUserForName(false);
// Set the name to what the user provided and reply.
userProfile.setName(turnContext.getActivity().getText());
// Acknowledge that we got their name.
return turnContext.sendActivity(
MessageFactory.text(
"Thanks " + userProfile.getName()
+ ". To see conversation data, type anything."
)
);
} else {
// Set the flag to true, so we don't prompt in the next turn.
conversationData.setPromptedUserForName(true);
// Prompt the user for their name.
return turnContext.sendActivity(MessageFactory.text("What is your name?"));
}
} else {
OffsetDateTime messageTimeOffset = turnContext.getActivity().getTimestamp();
LocalDateTime localMessageTime = messageTimeOffset.toLocalDateTime();
//Displaying current date and time in 12 hour format with AM/PM
DateTimeFormatter dateTimeAMPMFormat = DateTimeFormatter.ofPattern("MM/dd/yyyy, hh:mm:ss a");
conversationData.setTimestamp(dateTimeAMPMFormat.format(localMessageTime));
conversationData.setChannelId(turnContext.getActivity().getChannelId());
List<Activity> sendToUser = new ArrayList<>();
sendToUser.add(
MessageFactory.text(
userProfile.getName() + " sent: " + turnContext.getActivity().getText()
)
);
sendToUser.add(
MessageFactory.text("Message received at: " + conversationData.getTimestamp()
)
);
sendToUser.add(
MessageFactory.text("Message received from: " + conversationData.getChannelId()
)
);
return turnContext.sendActivities(sendToUser);
}
})
// make the return value happy.
.thenApply(resourceResponse -> null);
}
Bevor Sie den Turn-Handler beenden, schreiben Sie mit der saveChanges()-Methode der Zustandsverwaltungsobjekte alle Zustandsänderungen zurück in den Speicher.
StateManagementBot.java
@Override
public CompletableFuture<Void> onTurn(TurnContext turnContext) {
return super.onTurn(turnContext)
// Save any state changes that might have occurred during the turn.
.thenCompose(turnResult -> conversationState.saveChanges(turnContext))
.thenCompose(saveResult -> userState.saveChanges(turnContext));
}
Wenn user_profile.name leer ist und conversation_data.prompted_for_user_nametrue ist, ruft der Bot den vom Benutzer angegebenen Namen ab und speichert ihn im Zustand des Benutzers.
Wenn user_profile.name leer ist und conversation_data.prompted_for_user_namefalse ist, fragt der Bot nach dem Namen des Benutzers.
Wenn user_profile.name zuvor gespeichert wurde, geht der Bot wie folgt vor: Er ruft die Uhrzeit der Nachricht und die Kanal-ID aus der Benutzereingabe ab, gibt die Daten an den Benutzer zurück und speichert die abgerufenen Daten im Konversationszustand.
bots/state_management_bot.py
async def on_message_activity(self, turn_context: TurnContext):
# Get the state properties from the turn context.
user_profile = await self.user_profile_accessor.get(turn_context, UserProfile)
conversation_data = await self.conversation_data_accessor.get(
turn_context, ConversationData
)
if user_profile.name is None:
# First time around this is undefined, so we will prompt user for name.
if conversation_data.prompted_for_user_name:
# Set the name to what the user provided.
user_profile.name = turn_context.activity.text
# Acknowledge that we got their name.
await turn_context.send_activity(
f"Thanks { user_profile.name }. To see conversation data, type anything."
)
# Reset the flag to allow the bot to go though the cycle again.
conversation_data.prompted_for_user_name = False
else:
# Prompt the user for their name.
await turn_context.send_activity("What is your name?")
# Set the flag to true, so we don't prompt in the next turn.
conversation_data.prompted_for_user_name = True
else:
# Add message details to the conversation data.
conversation_data.timestamp = self.__datetime_from_utc_to_local(
turn_context.activity.timestamp
)
conversation_data.channel_id = turn_context.activity.channel_id
# Display state data.
await turn_context.send_activity(
f"{ user_profile.name } sent: { turn_context.activity.text }"
)
await turn_context.send_activity(
f"Message received at: { conversation_data.timestamp }"
)
await turn_context.send_activity(
f"Message received from: { conversation_data.channel_id }"
)
Vor dem Ende jedes Dialogs speichert der Bot mit der save_changes-Methode der Zustandsverwaltungsobjekte alle Änderungen, indem er die Zustandsinformationen in den Speicher schreibt.
Laden Sie die aktuelle Version von Bot Framework Emulator herunter, und installieren Sie sie.
Führen Sie das Beispiel lokal auf Ihrem Computer aus.
Wenn Sie eine Anleitung benötigen, lesen Se die Infodateien für C#, JavaScript, Java oder Python.
Verwenden des Emulators zum Testen des Beispiel-Bots.
Weitere Informationen
In diesem Artikel wird beschrieben, wie Sie Ihrem Bot Status hinzufügen können. In der folgenden Tabelle finden Sie weitere Informationen zu verwandten Themen.
Thema
Hinweise
Datenschutz
Wenn Sie persönliche Daten von Benutzern speichern möchten, müssen Sie sicherstellen, dass die Anforderungen der Datenschutz-Grundverordnung erfüllt sind.
Zustandsverwaltung
Alle Aufrufe der Zustandsverwaltung sind asynchron, und standardmäßig gilt der Konfliktauflösungsmodus „Letzter Schreiber gewinnt“. In der Praxis sollten Sie das Abrufen, Festlegen und Speichern des Zustands in Ihrem Bot möglichst nah beieinander anordnen. Eine Erläuterung zur Implementierung optimistischer Sperren finden Sie unter Implementieren des benutzerdefinierten Speichers für Ihren Bot.
In diesem Beispiel werden die Microsoft/Recognizers-Text-Bibliotheken verwendet, um Benutzereingaben zu analysieren und zu überprüfen. Weitere Informationen finden Sie auf der Seite mit der Übersicht.
Nächste Schritte
Lernen Sie, wie Sie dem Benutzer eine Reihe von Fragen stellen, seine Antworten überprüfen und seine Eingaben speichern können.