Бот по своей природе не имеет состояния. Развернутый бот не обязан выполнять следующий шаг в том же процессе или на том же компьютере, что и предыдущий. Но иногда боту нужно отслеживать контекст беседы, чтобы управлять ее ходом и запоминать ответы на предыдущие вопросы. Возможности состояния и хранения, предоставляемые библиотекой SDK Bot Framework, позволяют добавить состояние в вашего бота. Боты используют объекты управления состоянием и хранилища для управления и сохранения состояния. Диспетчер состояний предоставляет уровень абстракции, позволяющий получить доступ к свойствам состояния через аксессоры свойств, независимо от типа базового хранилища.
Примечание.
Пакеты SDK для JavaScript, C# и Python для Bot Framework по-прежнему будут поддерживаться, однако пакет SDK для Java прекращает свою поддержку с окончанием долгосрочной поддержки в ноябре 2023 года.
Существующие боты, созданные с помощью пакета SDK для Java, будут продолжать функционировать.
Код в этой статье основан на примере бота управления состоянием. Вам потребуется копия примера в C#, JavaScript, JavaScript или Python.
Об этом примере
Получив от пользователя входные данные, этот пример проверяет сохраненное состояние беседы, чтобы выяснить, запрашивали ли у этого пользователя его имя ранее. Если нет, запрашивается имя пользователя, и этот ввод сохраняется в данных пользователя. В этом случае имя, хранящееся в пользовательском состоянии, используется для взаимодействия с пользователем, а их входные данные вместе с временем получения и идентификатором входного канала возвращаются пользователю. Значения времени и идентификатора канала извлекаются из данных беседы пользователя, а затем сохраняются в состоянии беседы. На следующей схеме показана связь между ботом, профилем пользователя и классами данных беседы.
При настройке управления состоянием первым делом нужно определить классы, которые будут содержать все нужные сведения для управления состоянием пользователя и беседы. В примере, используемом в этой статье, определяются следующие классы:
В UserProfile.cs вы определяете UserProfile класс для сведений о пользователе, собираемых ботом.
В ConversationData.cs вы определяете ConversationData класс для управления состоянием беседы при сборе сведений о пользователе.
В приведенных ниже примерах кода показаны определения классов UserProfile и 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;
}
Этот шаг не нужен в JavaScript.
При настройке управления состоянием первым делом нужно определить классы, которые будут содержать все нужные сведения для управления состоянием пользователя и беседы. Пример, используемый в этой статье, определяет следующие классы:
В UserProfile.java вы определяете UserProfile класс для сведений о пользователе, собираемых ботом.
В ConversationData.java вы определяете ConversationData класс для управления состоянием беседы при сборе сведений о пользователе.
В приведенных ниже примерах кода показаны определения классов UserProfile и ConversationData.
UserProfile.java
Предупреждение
Похоже, образец, который вы ищете, был перемещён! Будьте уверены, что мы работаем над решением этого.
ConversationData.java
Предупреждение
Похоже, образец, который вы ищете, был перемещён! Будьте уверены, что мы работаем над решением этого.
При настройке управления состоянием первым делом нужно определить классы, которые будут содержать все нужные сведения для управления состоянием пользователя и беседы. В примере, используемом в этой статье, определяются следующие классы:
User_profile.py содержит класс, в котором хранятся UserProfile сведения о пользователе, собранные ботом.
Conversation_data.py содержит ConversationData класс, который управляет состоянием беседы при сборе сведений о пользователе.
В приведенных ниже примерах кода показаны определения классов UserProfile и ConversationData.
user_profile.py
class UserProfile:
def __init__(self, name: str = None):
self.name = name
Затем вы регистрируете MemoryStorage , которое используется для создания UserState и ConversationState объектов. Объекты состояния пользователя и беседы создаются в Startup, а зависимости вводятся в конструктор бота. Также для бота регистрируются дополнительные службы: поставщик учетных данных, адаптер и реализация бота.
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 */
Затем вы зарегистрируете MemoryStorage, который затем используется для создания объектов UserState и ConversationState. Они создаются в index.js и используются при создании бота.
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;
Зарегистрируйте StateManagementBot в Application.java. Параметры ConversationState и UserState предоставляются по умолчанию из класса BotDependencyConfiguration, а Spring внедряет их в метод getBot.
Application.java
Предупреждение
Похоже, образец, который вы ищете, был перенесён! Будьте уверены, что мы работаем над решением этого.
Затем вы регистрируете MemoryStorage , которое используется для создания UserState и ConversationState объектов. Они создаются в app.py и используются при создании бота.
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")
Теперь вы создаете акцессоры свойств с помощью метода CreateProperty, который предоставляет дескриптор объекту BotState. Каждый метод доступа к свойству состояния позволяет получить или задать значение для соответствующего свойства состояния. Перед использованием свойств состояния используйте каждый метод доступа, чтобы загрузить свойство из хранилища и извлечь его из кэша состояния. Вызовите метод GetAsync, чтобы получить ключ с правильно определенной областью действия, связанный со свойством состояния.
Bots/StateManagementBot.cs
var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
Теперь вы создаете методы доступа к свойствам для UserState и ConversationState. Каждый метод доступа к свойству состояния позволяет получить или задать значение для соответствующего свойства состояния. Вы используете каждый аксессор для загрузки связанного свойства из хранилища и извлечения его текущего состояния из кэша.
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);
Теперь вы создаете аксессоры свойств с помощью метода createProperty. Каждый метод доступа к свойству состояния позволяет получить или задать значение для соответствующего свойства состояния. Перед тем как использовать свойства состояния, воспользуйтесь каждым методом доступа для загрузки свойства из хранилища и получения его из кэша состояния. Чтобы получить ключ, правильно привязанный к свойству состояния и определяющий область его применения, вызовите метод get.
StateManagementBot.java
Предупреждение
Похоже, тот образец, который вы ищете, был перемещен! Будьте уверены, что мы работаем над решением этого.
Теперь вы создаете методы доступа к свойствам для UserProfile и ConversationData. Каждый метод доступа к свойству состояния позволяет получить или задать значение для соответствующего свойства состояния. Каждый аксессор используется для загрузки соответствующего свойства из хранилища и получения его текущего состояния из кэша.
В предыдущем разделе мы рассматривали шаги, которые на этапе инициализации добавляют в бота методы доступа к свойствам состояния. Эти методы доступа теперь можно использовать во время выполнения для чтения и записи информации о состоянии. Следующий пример кода использует представленный здесь поток логики:
Если userProfile.Name пусто и conversationData.PromptedUserForName равно true, вы извлекаете указанное имя пользователя и сохраняете его в состоянии пользователя.
Если userProfile.Name он пуст и conversationData.PromptedUserForName имеет значение false, необходимо запросить имя пользователя.
Если userProfile.Name был ранее сохранен, получите время сообщения и идентификатор канала из входных данных пользователя, верните все данные пользователю и сохраните полученные данные в состоянии беседы.
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}");
}
}
Перед выходом из обработчика поворота используйте метод SaveChangesAsync() объектов управления состоянием, чтобы записать все изменения состояния в хранилище.
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);
}
Если userProfile.Name пусто и conversationData.PromptedUserForName равно true, вы получите указанное имя пользователя и сохраните его в состоянии пользователя.
Если userProfile.Name он пуст и conversationData.PromptedUserForName имеет значение false, необходимо запросить имя пользователя.
Если userProfile.Name был ранее сохранен, вы извлекаете время сообщения и идентификатор канала из пользовательского ввода, отображаете все данные обратно пользователю и сохраняете полученные данные в состояние беседы.
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();
});
Перед выходом из каждого диалогового окна используйте метод saveChanges() объектов управления состоянием для сохранения всех изменений путем записи состояния обратно в хранилище.
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);
}
Если userProfile.getName() пуст и conversationData.getPromptedUserForName() равен true, вы получите указанное имя пользователя и сохраните его в состоянии пользователя.
Если userProfile.getName() он пуст и conversationData.getPromptedUserForName() имеет значение false, необходимо запросить имя пользователя.
Если userProfile.getName() был ранее сохранен, вы извлекаете время сообщения и идентификатор канала из входных данных пользователя, отправляете все данные обратно пользователю и сохраняете полученные данные в состоянии беседы.
StateManagementBot.java
Предупреждение
Похоже, образец, который вы ищете, был перемещён! Будьте уверены, что мы работаем над решением этого.
Прежде чем выйти из обработчика поворота, используйте метод saveChanges() объектов управления состоянием для записи всех изменений состояния в хранилище.
StateManagementBot.java
Предупреждение
Похоже, что образец, который вы ищете, был перемещён! Будьте уверены, что мы работаем над решением этого.
Если файл user_profile.name пуст, а conversation_data.prompted_for_user_name имеет значение true, то бот извлекает имя, предоставленное пользователем, и сохраняет его в пользовательском состоянии.
Если user_profile.name он пуст и conversation_data.prompted_for_user_name имеет значение false, бот запрашивает имя пользователя.
Если user_profile.name был ранее сохранен, бот извлекает время сообщения и идентификатор канала из входных данных пользователя, сообщает данные обратно пользователю и сохраняет извлеченные данные в контексте разговора.
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 }"
)
Перед завершением каждого этапа диалога бот использует метод управления состоянием save_changes для сохранения всех изменений, записывая сведения о состоянии в хранилище.
Все вызовы управления состоянием асинхронны, и по умолчанию используется правило последнего записавшего. На практике следует размещать методы get, set и save state как можно ближе друг к другу в коде бота. Сведения о реализации оптимистической блокировки см. в разделе "Реализация пользовательского хранилища для бота".
Критически важные бизнес-данные
Используйте состояние бота для хранения настроек, имени пользователя или последней, которую они заказали, но не используйте его для хранения критически важных бизнес-данных. Для критически важных данных создайте собственные компоненты хранилища или записывайте их непосредственно в хранилище.
Распознаватель текста
В этом примере используются библиотеки Microsoft/Recognizer-Text для синтаксического анализа и проверки пользовательского ввода. Дополнительные сведения см. на странице обзор.
Следующие шаги
Узнайте, как задать пользователю ряд вопросов, проверить свои ответы и сохранить входные данные.