Save user and conversation data (Gebruikers- en gespreksgegevens opslaan)
Artikel
VAN TOEPASSING OP: SDK v4
Een bot is inherent staatloos. Zodra uw bot is geïmplementeerd, wordt deze mogelijk niet uitgevoerd in hetzelfde proces of op dezelfde computer van de ene naar de andere. Uw bot moet echter mogelijk de context van een gesprek bijhouden, zodat het gedrag ervan kan worden beheerd en antwoorden op eerdere vragen kan onthouden. Met de status- en opslagfuncties van de Bot Framework SDK kunt u status toevoegen aan uw bot. Bots gebruiken statusbeheer en opslagobjecten om de status te beheren en te behouden. De statusbeheerder biedt een abstractielaag waarmee u toegang hebt tot statuseigenschappen met behulp van eigenschapstoegangsors, onafhankelijk van het type onderliggende opslag.
Notitie
De Sdk's voor Bot Framework JavaScript, C# en Python blijven ondersteund, maar de Java SDK wordt buiten gebruik gesteld met definitieve langetermijnondersteuning die eindigt op november 2023.
Bestaande bots die zijn gebouwd met de Java SDK blijven functioneren.
Kennis van de basisbeginselen van bots en hoe bots de status beheren is vereist.
De code in dit artikel is gebaseerd op het voorbeeld van de statusbeheerbot. U hebt een kopie van het voorbeeld nodig in C#, JavaScript, Java of Python.
Over dit voorbeeld
Na ontvangst van gebruikersinvoer controleert dit voorbeeld de status van het opgeslagen gesprek om te zien of deze gebruiker eerder is gevraagd om zijn of haar naam op te geven. Als dat niet het is, wordt de naam van de gebruiker aangevraagd en wordt die invoer opgeslagen in de gebruikersstatus. Zo ja, dan wordt de naam die in de gebruikersstatus is opgeslagen, gebruikt om te communiceren met de gebruiker en de invoergegevens, samen met de tijd die is ontvangen en de invoerkanaal-id, terug te keren naar de gebruiker. De tijd- en kanaal-id-waarden worden opgehaald uit de gespreksgegevens van de gebruiker en vervolgens opgeslagen in de gespreksstatus. In het volgende diagram ziet u de relatie tussen de gegevensklassen van de bot, het gebruikersprofiel en de gespreksgegevensklassen.
De eerste stap bij het instellen van statusbeheer is het definiëren van de klassen met de informatie die moet worden beheerd in de gebruikers- en gespreksstatus. In het voorbeeld dat in dit artikel wordt gebruikt, worden de volgende klassen gedefinieerd:
In UserProfile.cs definieert u een UserProfile klasse voor de gebruikersgegevens die door de bot worden verzameld.
In ConversationData.cs definieert u een ConversationData klasse om de gespreksstatus te beheren tijdens het verzamelen van gebruikersgegevens.
In de volgende codevoorbeelden ziet u de definities voor de UserProfile en ConversationData klassen.
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;
}
Deze stap is niet nodig in JavaScript.
De eerste stap bij het instellen van statusbeheer is het definiëren van de klassen met de informatie die moet worden beheerd in de gebruikers- en gespreksstatus. In het voorbeeld dat in dit artikel wordt gebruikt, worden de volgende klassen gedefinieerd:
In UserProfile.java definieert u een UserProfile klasse voor de gebruikersgegevens die door de bot worden verzameld.
In ConversationData.java definieert u een ConversationData klasse om de gespreksstatus te beheren tijdens het verzamelen van gebruikersgegevens.
In de volgende codevoorbeelden ziet u de definities voor de UserProfile en ConversationData klassen.
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) {
De eerste stap bij het instellen van statusbeheer is het definiëren van de klassen met de informatie die moet worden beheerd in de gebruikers- en gespreksstatus. In het voorbeeld dat in dit artikel wordt gebruikt, worden de volgende klassen gedefinieerd:
De user_profile.py bevat de UserProfile klasse waarin de gebruikersgegevens worden opgeslagen die door de bot zijn verzameld.
De conversation_data.py bevat de ConversationData klasse die de gespreksstatus bepaalt tijdens het verzamelen van gebruikersgegevens.
In de volgende codevoorbeelden ziet u de definities voor de UserProfile en ConversationData klassen.
user_profile.py
class UserProfile:
def __init__(self, name: str = None):
self.name = name
Vervolgens registreert MemoryStorage u dat wordt gebruikt voor het maken UserState en maken van ConversationState objecten. De gebruikers- en gespreksstatusobjecten worden gemaakt op Startup en afhankelijkheden die zijn geïnjecteerd in de botconstructor. Andere services voor een bot die zijn geregistreerd, zijn: een referentieprovider, een adapter en de bot-implementatie.
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 */
Vervolgens registreert MemoryStorage u dat wordt gebruikt voor het maken UserState en maken van ConversationState objecten. Deze worden gemaakt in index.js en gebruikt wanneer de bot wordt gemaakt.
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;
Vervolgens registreert u de StateManagementBot Application.java. Zowel ConversationState als UserState worden standaard geleverd vanuit de BotDependencyConfiguration-klasse en Spring injecteert deze in de getBot-methode.
Application.java
@Bean
public Bot getBot(
ConversationState conversationState,
UserState userState
) {
return new StateManagementBot(conversationState, userState);
}
Vervolgens registreert MemoryStorage u dat wordt gebruikt voor het maken UserState en maken van ConversationState objecten. Deze worden gemaakt in app.py en gebruikt wanneer de bot wordt gemaakt.
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")
Toegangsrechten voor statuseigenschappen toevoegen
Nu maakt u eigenschapstoegangsors met behulp van de CreateProperty methode die een ingang voor het BotState object biedt. Met elke toegangsrechten voor statuseigenschappen kunt u de waarde van de bijbehorende statuseigenschap ophalen of instellen. Voordat u de statuseigenschappen gebruikt, gebruikt u elke accessor om de eigenschap uit de opslag te laden en deze op te halen uit de statuscache. Als u de juiste scoped sleutel wilt ophalen die is gekoppeld aan de statuseigenschap, roept u de GetAsync methode aan.
Bots/StateManagementBot.cs
var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
Nu maakt u eigenschapstoegangsors voor UserState en ConversationState. Met elke toegangsrechten voor statuseigenschappen kunt u de waarde van de bijbehorende statuseigenschap ophalen of instellen. U gebruikt elke accessor om de bijbehorende eigenschap uit de opslag te laden en de huidige status ervan op te halen uit de cache.
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);
Nu maakt u eigenschapstoegangsors met behulp van de createProperty methode. Met elke toegangsrechten voor statuseigenschappen kunt u de waarde van de bijbehorende statuseigenschap ophalen of instellen. Voordat u de statuseigenschappen gebruikt, gebruikt u elke accessor om de eigenschap uit de opslag te laden en deze op te halen uit de statuscache. Als u de juiste scoped sleutel wilt ophalen die is gekoppeld aan de statuseigenschap, roept u de get methode aan.
Nu maakt u eigenschapstoegangsors voor UserProfile en ConversationData. Met elke toegangsrechten voor statuseigenschappen kunt u de waarde van de bijbehorende statuseigenschap ophalen of instellen. U gebruikt elke accessor om de bijbehorende eigenschap uit de opslag te laden en de huidige status ervan op te halen uit de cache.
In de voorgaande sectie worden de initialisatiestappen beschreven voor het toevoegen van toegangsrechten voor statuseigenschappen aan onze bot. U kunt deze accessors nu tijdens runtime gebruiken om statusinformatie te lezen en te schrijven. In de onderstaande voorbeeldcode wordt de volgende logicastroom gebruikt:
Als userProfile.Name dit leeg is en conversationData.PromptedUserForName waar is, haalt u de opgegeven gebruikersnaam op en slaat u deze op in de gebruikersstatus.
Als userProfile.Name deze leeg is en conversationData.PromptedUserForName onwaar is, vraagt u om de naam van de gebruiker.
Als userProfile.Name dit eerder is opgeslagen, haalt u de berichttijd en kanaal-id op uit de invoer van de gebruiker, echot u alle gegevens terug naar de gebruiker en slaat u de opgehaalde gegevens op binnen de gespreksstatus.
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}");
}
}
Voordat u de draaihandler afsluit, gebruikt u de saveChangesAsync()-methode van de statusbeheerobjecten om alle statuswijzigingen terug te schrijven naar de opslag.
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);
}
Als userProfile.Name dit leeg is en conversationData.PromptedUserForName waar is, haalt u de opgegeven gebruikersnaam op en slaat u deze op in de gebruikersstatus.
Als userProfile.Name deze leeg is en conversationData.PromptedUserForName onwaar is, vraagt u om de naam van de gebruiker.
Als userProfile.Name dit eerder is opgeslagen, haalt u de berichttijd en kanaal-id op uit de invoer van de gebruiker, echot u alle gegevens terug naar de gebruiker en slaat u de opgehaalde gegevens op binnen de gespreksstatus.
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();
});
Voordat u elke dialoogvensterdraai sluit, gebruikt u de methode saveChanges() van de statusbeheerobjecten om alle wijzigingen vast te houden door de status terug te schrijven naar de opslag.
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);
}
Als userProfile.getName() dit leeg is en conversationData.getPromptedUserForName() waar is, haalt u de opgegeven gebruikersnaam op en slaat u deze op in de gebruikersstatus.
Als userProfile.getName() deze leeg is en conversationData.getPromptedUserForName() onwaar is, vraagt u om de naam van de gebruiker.
Als userProfile.getName() dit eerder is opgeslagen, haalt u de berichttijd en kanaal-id op uit de invoer van de gebruiker, echot u alle gegevens terug naar de gebruiker en slaat u de opgehaalde gegevens op binnen de gespreksstatus.
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);
}
Voordat u de draaihandler afsluit, gebruikt u de saveChanges()-methode van de statusbeheerobjecten om alle statuswijzigingen terug te schrijven naar de opslag.
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));
}
Als user_profile.name deze leeg is en conversation_data.prompted_for_user_name waar is, haalt de bot de naam op die is opgegeven door de gebruiker en slaat deze op in de status van de gebruiker.
Als user_profile.name deze leeg is en conversation_data.prompted_for_user_name onwaar is, vraagt de bot om de naam van de gebruiker.
Als user_profile.name de bot eerder is opgeslagen, haalt de bot berichttijd en kanaal-id op uit de gebruikersinvoer, echot de gegevens terug naar de gebruiker en slaat de opgehaalde gegevens op in de gespreksstatus.
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 }"
)
Voordat elk dialoogvenster wordt beëindigd, gebruikt de bot de methode voor statusbeheerobjecten save_changes om alle wijzigingen vast te houden door statusinformatie in de opslag te schrijven.
Voer het voorbeeld lokaal uit op uw computer.
Als u instructies nodig hebt, raadpleegt u de LEESMIJ voor C#, JavaScript, Java of Python.
Gebruik de emulator om uw voorbeeldbot te testen.
Aanvullende informatie
In dit artikel wordt beschreven hoe u de status aan uw bot kunt toevoegen. Zie de volgende tabel voor meer informatie over verwante onderwerpen.
Onderwerp
Opmerkingen
Privacy
Als u de persoonlijke gegevens van de gebruiker wilt opslaan, moet u ervoor zorgen dat de algemene verordening gegevensbescherming wordt nageleefd.
Statusbeheer
Alle statusbeheeraanroepen zijn standaard asynchroon en last-writer-wins. In de praktijk moet u de status zo dicht mogelijk bij elkaar krijgen, instellen en opslaan in uw bot. Zie Aangepaste opslag implementeren voor uw bot voor een discussie over het implementeren van optimistische vergrendeling.
Kritieke bedrijfsgegevens
Gebruik de botstatus om voorkeuren, gebruikersnaam of het laatste wat ze hebben besteld op te slaan, maar gebruik deze niet om kritieke bedrijfsgegevens op te slaan. Voor kritieke gegevens maakt u uw eigen opslagonderdelen of schrijft u rechtstreeks naar de opslag.
Recognizer-Text
In het voorbeeld worden de Microsoft/Recognizers-Text-bibliotheken gebruikt om gebruikersinvoer te parseren en valideren. Zie de overzichtspagina voor meer informatie.
Volgende stappen
Meer informatie over het stellen van een reeks vragen, het valideren van hun antwoorden en het opslaan van hun invoer.