Zapisywanie danych dotyczących użytkownika i konwersacji
Artykuł
DOTYCZY: ZESTAW SDK w wersji 4
Bot jest z natury bezstanowy. Po wdrożeniu bota może nie działać w tym samym procesie lub na tej samej maszynie z jednej kolei do następnej. Jednak bot może wymagać śledzenia kontekstu konwersacji, aby mógł zarządzać swoim zachowaniem i pamiętać odpowiedzi na poprzednie pytania. Funkcje stanu i przechowywania zestawu Bot Framework SDK umożliwiają dodawanie stanu do bota. Boty używają zarządzania stanem i obiektów magazynu do zarządzania stanem i ich utrwalania. Menedżer stanu udostępnia warstwę abstrakcji, która umożliwia dostęp do właściwości stanu przy użyciu metod dostępu do właściwości niezależnie od typu magazynu bazowego.
Uwaga
Zestawy SDK języka JavaScript, C# i Python platformy Bot Framework będą nadal obsługiwane, jednak zestaw SDK języka Java jest wycofywany z ostatecznym długoterminowym wsparciem kończącym się w listopadzie 2023 r.
Istniejące boty utworzone za pomocą zestawu JAVA SDK będą nadal działać.
Znajomość podstaw bota i sposobu, w jaki boty zarządzają stanem jest wymagany.
Kod w tym artykule jest oparty na przykładzie bota zarządzania stanem. Będziesz potrzebować kopii przykładu w języku C#, JavaScript, Java lub Python.
Informacje o tym przykładzie
Po otrzymaniu danych wejściowych użytkownika ten przykład sprawdza stan przechowywanej konwersacji, aby sprawdzić, czy ten użytkownik został wcześniej poproszony o podanie ich nazwy. Jeśli nie, nazwa użytkownika jest żądana i dane wejściowe są przechowywane w stanie użytkownika. Jeśli tak, nazwa przechowywana w stanie użytkownika jest używana do komunikacji z użytkownikiem i ich danymi wejściowymi, wraz z czasem odebranym i identyfikatorem kanału wejściowego, zostanie zwrócona użytkownikowi. Wartości czasu i identyfikatora kanału są pobierane z danych konwersacji użytkownika, a następnie zapisywane w stanie konwersacji. Na poniższym diagramie przedstawiono relację między klasami danych bota, profilu użytkownika i konwersacji.
Pierwszym krokiem konfigurowania zarządzania stanem jest zdefiniowanie klas zawierających informacje do zarządzania w stanie użytkownika i konwersacji. W przykładzie użytym w tym artykule zdefiniowano następujące klasy:
W UserProfile.cs zdefiniujesz klasę UserProfile dla informacji o użytkowniku, które będzie zbierać bot.
W ConversationData.cs należy zdefiniować klasę ConversationData do kontrolowania stanu konwersacji podczas zbierania informacji o użytkowniku.
W poniższych przykładach kodu przedstawiono definicje klas UserProfile i 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;
}
Ten krok nie jest konieczny w języku JavaScript.
Pierwszym krokiem konfigurowania zarządzania stanem jest zdefiniowanie klas zawierających informacje do zarządzania w stanie użytkownika i konwersacji. W przykładzie użytym w tym artykule zdefiniowano następujące klasy:
W UserProfile.java zdefiniujesz klasę UserProfile dla informacji o użytkowniku, które będą zbierane przez bota.
W ConversationData.java zdefiniujesz klasę służącą ConversationData do kontrolowania stanu konwersacji podczas zbierania informacji o użytkowniku.
W poniższych przykładach kodu przedstawiono definicje klas UserProfile i 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) {
Pierwszym krokiem konfigurowania zarządzania stanem jest zdefiniowanie klas zawierających informacje do zarządzania w stanie użytkownika i konwersacji. W przykładzie użytym w tym artykule zdefiniowano następujące klasy:
User_profile.py zawiera klasęUserProfile, która przechowuje informacje o użytkowniku zebrane przez bota.
Conversation_data.py zawiera klasęConversationData, która kontroluje stan konwersacji podczas zbierania informacji o użytkowniku.
W poniższych przykładach kodu przedstawiono definicje klas UserProfile i ConversationData .
user_profile.py
class UserProfile:
def __init__(self, name: str = None):
self.name = name
Następnie należy zarejestrować MemoryStorage obiekt , który jest używany do tworzenia UserState obiektów i ConversationState . Obiekty stanu użytkownika i konwersacji są tworzone w Startup obiekcie i są wstrzykiwane do konstruktora bota. Inne usługi dla zarejestrowanego bota to: dostawca poświadczeń, karta i implementacja bota.
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 */
Następnie należy zarejestrować MemoryStorage element , który jest następnie używany do tworzenia UserState obiektów i ConversationState . Są one tworzone w index.js i używane podczas tworzenia bota.
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);
boty/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;
Następnie zarejestruj StateManagementBot się w Application.java. Zarówno ConversationState, jak i UserState są domyślnie udostępniane z klasy BotDependencyConfiguration, a platforma Spring wstrzykuje je do metody getBot.
Application.java
@Bean
public Bot getBot(
ConversationState conversationState,
UserState userState
) {
return new StateManagementBot(conversationState, userState);
}
Następnie należy zarejestrować MemoryStorage obiekt , który jest używany do tworzenia UserState obiektów i ConversationState . Są one tworzone w app.py i używane podczas tworzenia bota.
app.py
CONVERSATION_STATE = ConversationState(MEMORY)
# Create Bot
BOT = StateManagementBot(CONVERSATION_STATE, USER_STATE)
# Listen for incoming requests on /api/messages.
boty/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")
Teraz utworzysz metody dostępu do właściwości przy użyciu CreateProperty metody, która zapewnia uchwyt do BotState obiektu. Każda funkcja dostępu właściwości stanu umożliwia uzyskanie lub ustawienie wartości skojarzonej właściwości stanu. Przed użyciem właściwości stanu użyj poszczególnych metod dostępu, aby załadować właściwość z magazynu i pobrać ją z pamięci podręcznej stanu. Aby uzyskać prawidłowy klucz o określonym zakresie skojarzony z właściwością stanu, należy wywołać metodę GetAsync .
Boty/StateManagementBot.cs
var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
Teraz utworzysz metody dostępu do właściwości dla i UserStateConversationState. Każda funkcja dostępu właściwości stanu umożliwia uzyskanie lub ustawienie wartości skojarzonej właściwości stanu. Każdy element dostępu służy do ładowania skojarzonej właściwości z magazynu i pobierania jego bieżącego stanu z pamięci podręcznej.
boty/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);
Teraz utworzysz metody dostępu do właściwości przy użyciu createProperty metody . Każda funkcja dostępu właściwości stanu umożliwia uzyskanie lub ustawienie wartości skojarzonej właściwości stanu. Przed użyciem właściwości stanu użyj poszczególnych metod dostępu, aby załadować właściwość z magazynu i pobrać ją z pamięci podręcznej stanu. Aby uzyskać prawidłowy klucz o określonym zakresie skojarzony z właściwością stanu, należy wywołać metodę get .
Teraz utworzysz metody dostępu do właściwości dla i UserProfileConversationData. Każda funkcja dostępu właściwości stanu umożliwia uzyskanie lub ustawienie wartości skojarzonej właściwości stanu. Każdy element dostępu służy do ładowania skojarzonej właściwości z magazynu i pobierania jego bieżącego stanu z pamięci podręcznej.
W poprzedniej sekcji omówiono kroki inicjowania w czasie dodawania metod dostępu właściwości stanu do bota. Teraz możesz użyć tych metod dostępu w czasie wykonywania, aby odczytywać i zapisywać informacje o stanie. Poniższy przykładowy kod używa następującego przepływu logiki:
Jeśli userProfile.Name wartość jest pusta i conversationData.PromptedUserForName ma wartość true, należy pobrać podaną nazwę użytkownika i zapisać tę nazwę w stanie użytkownika.
Jeśli userProfile.Name wartość jest pusta i conversationData.PromptedUserForName ma wartość false, należy poprosić o podanie nazwy użytkownika.
Jeśli userProfile.Name wcześniej były przechowywane, pobierasz czas komunikatu i identyfikator kanału z danych wejściowych użytkownika, echo wszystkich danych z powrotem do użytkownika i przechowujesz pobrane dane w stanie konwersacji.
Boty/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}");
}
}
Przed zakończeniem procedury obsługi kolei należy użyć metody SaveChangesAsync() obiektów zarządzania stanami, aby zapisać wszystkie zmiany stanu z powrotem do magazynu.
Boty/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);
}
Jeśli userProfile.Name wartość jest pusta i conversationData.PromptedUserForName ma wartość true, należy pobrać podaną nazwę użytkownika i zapisać tę nazwę w stanie użytkownika.
Jeśli userProfile.Name wartość jest pusta i conversationData.PromptedUserForName ma wartość false, należy poprosić o podanie nazwy użytkownika.
Jeśli userProfile.Name wcześniej były przechowywane, pobierasz czas komunikatu i identyfikator kanału z danych wejściowych użytkownika, echo wszystkich danych z powrotem do użytkownika i przechowujesz pobrane dane w stanie konwersacji.
boty/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();
});
Przed zamknięciem każdego okna dialogowego należy użyć metody saveChanges() obiektów zarządzania stanem, aby utrwały wszystkie zmiany, zapisując stan z powrotem do magazynu.
boty/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);
}
Jeśli userProfile.getName() wartość jest pusta i conversationData.getPromptedUserForName() ma wartość true, należy pobrać podaną nazwę użytkownika i zapisać tę nazwę w stanie użytkownika.
Jeśli userProfile.getName() wartość jest pusta i conversationData.getPromptedUserForName() ma wartość false, należy poprosić o podanie nazwy użytkownika.
Jeśli userProfile.getName() wcześniej były przechowywane, pobierasz czas komunikatu i identyfikator kanału z danych wejściowych użytkownika, echo wszystkich danych z powrotem do użytkownika i przechowujesz pobrane dane w stanie konwersacji.
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);
}
Przed zamknięciem programu obsługi kolei należy użyć metody saveChanges() obiektów zarządzania stanem, aby zapisać wszystkie zmiany stanu z powrotem do magazynu.
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));
}
Jeśli user_profile.name jest pusty i conversation_data.prompted_for_user_name ma wartość true, bot pobiera nazwę podaną przez użytkownika i przechowuje ją w stanie użytkownika.
Jeśli user_profile.name wartość jest pusta i conversation_data.prompted_for_user_name ma wartość false, bot prosi o podanie nazwy użytkownika.
Jeśli user_profile.name wcześniej był przechowywany, bot pobiera czas komunikatu i identyfikator kanału z danych wejściowych użytkownika, przekazuje dane z powrotem do użytkownika i przechowuje pobrane dane w stanie konwersacji.
boty/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 }"
)
Przed zakończeniem każdego okna dialogowego bot używa metody obiektów save_changes zarządzania stanami do utrwalania wszystkich zmian, zapisując informacje o stanie w magazynie.
Uruchom przykład lokalnie na maszynie.
Jeśli potrzebujesz instrukcji, zapoznaj się z plikiem README dla języków C#, JavaScript, Java lub Python.
Użyj emulatora, aby przetestować przykładowego bota.
Dodatkowe informacje
W tym artykule opisano sposób dodawania stanu do bota. Aby uzyskać więcej informacji na temat tematów pokrewnych, zobacz poniższą tabelę.
Temat
Uwagi
Prywatność
Jeśli zamierzasz przechowywać dane osobowe użytkownika, należy zapewnić zgodność z ogólnym rozporządzeniem o ochronie danych.
Zarządzanie stanem
Wszystkie wywołania zarządzania stanem są asynchroniczne, a ostatni składnik zapisywania wygrywa domyślnie. W praktyce należy uzyskać, ustawić i zapisać stan tak blisko siebie w botze, jak to możliwe. Aby zapoznać się z omówieniem sposobu implementowania optymistycznej blokady, zobacz Implementowanie magazynu niestandardowego dla bota.
Krytyczne dane biznesowe
Użyj stanu bota do przechowywania preferencji, nazwy użytkownika lub ostatniej uporządkowanej rzeczy, ale nie używaj go do przechowywania krytycznych danych biznesowych. W przypadku danych krytycznych utwórz własne składniki magazynu lub zapisz bezpośrednio w magazynie.
Rozpoznawanie tekstu
W przykładzie użyto bibliotek Microsoft/Recognizers-Text do analizowania i weryfikowania danych wejściowych użytkownika. Aby uzyskać więcej informacji, zobacz stronę przeglądu .
Następne kroki
Dowiedz się, jak zadać użytkownikowi serię pytań, zweryfikować odpowiedzi i zapisać swoje dane wejściowe.