ОБЛАСТЬ ПРИМЕНЕНИЯ: ПАКЕТ SDK версии 4
Бот по своей природе не имеет состояния. Развернутый бот не обязан выполнять следующий шаг в том же процессе или на том же компьютере, что и предыдущий. Но иногда боту нужно отслеживать контекст беседы, чтобы управлять ее ходом и запоминать ответы на предыдущие вопросы. Возможности состояния и хранения, предоставляемые библиотекой SDK Bot Framework, позволяют добавить состояние в вашего бота. Боты используют объекты управления состоянием и хранилища для управления и сохранения состояния. Диспетчер состояний предоставляет уровень абстракции, позволяющий получить доступ к свойствам состояния через аксессоры свойств, независимо от типа базового хранилища.
Примечание.
Чтобы создавать агенты с помощью выбранной службы ИИ, оркестрации и знаний, рекомендуется использовать пакет SDK для агентов Microsoft 365. Пакет SDK для агентов поддерживает C#, JavaScript или Python. Дополнительные сведения о пакете SDK для агентов см. в aka.ms/agents. Если вы ищете платформу агента на основе SaaS, рассмотрите microsoft Copilot Studio. Если у вас есть существующий бот, созданный с помощью пакета SDK Bot Framework, вы можете обновить бота до пакета SDK для агентов. Вы можете ознакомиться с основными изменениями и обновлениями в руководстве по миграции с Bot Framework SDK на SDK для агентов. Запросы на поддержку пакета SDK Bot Framework больше не будут обслуживаться с 31 декабря 2025 г.
Предварительные условия
Об этом примере
Получив от пользователя входные данные, этот пример проверяет сохраненное состояние беседы, чтобы выяснить, запрашивали ли у этого пользователя его имя ранее. Если нет, запрашивается имя пользователя, и этот ввод сохраняется в данных пользователя. В этом случае имя, хранящееся в пользовательском состоянии, используется для взаимодействия с пользователем, а их входные данные вместе с временем получения и идентификатором входного канала возвращаются пользователю. Значения времени и идентификатора канала извлекаются из данных беседы пользователя, а затем сохраняются в состоянии беседы. На следующей схеме показана связь между ботом, профилем пользователя и классами данных беседы.
Определение классов
При настройке управления состоянием первым делом нужно определить классы, которые будут содержать все нужные сведения для управления состоянием пользователя и беседы. В примере, используемом в этой статье, определяются следующие классы:
- В 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
conversation_data.py
class ConversationData:
def __init__(
self,
timestamp: str = None,
channel_id: str = None,
prompted_for_user_name: bool = False,
):
self.timestamp = timestamp
self.channel_id = channel_id
self.prompted_for_user_name = prompted_for_user_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 */
Боты/StateManagementBot.cs
private BotState _conversationState;
private BotState _userState;
public StateManagementBot(ConversationState conversationState, UserState userState)
{
_conversationState = conversationState;
_userState = userState;
}
Затем вы зарегистрируете MemoryStorage
, который затем используется для создания объектов UserState
и ConversationState
. Они создаются в index.js и используются при создании бота.
index.js
// Define state store for your bot.
// See https://aka.ms/about-bot-state to learn more about bot state.
const memoryStorage = new MemoryStorage();
// Create conversation and user state with in-memory storage provider.
const conversationState = new ConversationState(memoryStorage);
const userState = new UserState(memoryStorage);
bots/stateManagementBot.js
// The accessor names for the conversation data and user profile state property accessors.
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.
Зарегистрируйте 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
, чтобы получить ключ с правильно определенной областью действия, связанный со свойством состояния.
Боты/StateManagementBot.cs
var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
Теперь вы создаете методы доступа к свойствам для UserState
и ConversationState
. Каждый метод доступа к свойству состояния позволяет получить или задать значение для соответствующего свойства состояния. Вы используете каждый аксессор для загрузки связанного свойства из хранилища и извлечения его текущего состояния из кэша.
bots/stateManagementBot.js
constructor(conversationState, userState) {
super();
// Create the state property accessors for the conversation data and user profile.
Теперь вы создаете аксессоры свойств с помощью метода createProperty
. Каждый метод доступа к свойству состояния позволяет получить или задать значение для соответствующего свойства состояния. Перед использованием свойств состояния используйте каждый метод доступа, чтобы загрузить свойство из хранилища и извлечь его из кэша состояния. Вызовите метод get
, чтобы получить ключ с правильно определенной областью действия, связанный со свойством состояния.
StateManagementBot.java
Предупреждение
Похоже, образец, который вы ищете, был перемещён! Будьте уверены, что мы работаем над решением этого.
Теперь вы создаете методы доступа к свойствам для UserProfile
и ConversationData
. Каждый метод доступа к свойству состояния позволяет получить или задать значение для соответствующего свойства состояния. Вы используете каждый аксессор для загрузки связанного свойства из хранилища и извлечения его текущего состояния из кэша.
bots/state_management_bot.py
self.conversation_data_accessor = self.conversation_state.create_property(
"ConversationData"
)
self.user_profile_accessor = self.user_state.create_property("UserProfile")
Доступ к состоянию бота
В предыдущем разделе мы рассматривали шаги, которые на этапе инициализации добавляют в бота методы доступа к свойствам состояния. Эти методы доступа теперь можно использовать во время выполнения для чтения и записи информации о состоянии. Следующий пример кода использует представленный здесь поток логики:
- Если
userProfile.Name
пусто и conversationData.PromptedUserForName
равно true, вы извлекаете указанное имя пользователя и сохраняете его в состоянии пользователя.
- Если
userProfile.Name
он пуст и conversationData.PromptedUserForName
имеет значение false, необходимо запросить имя пользователя.
- Если
userProfile.Name
был ранее сохранен, получите время сообщения и идентификатор канала из входных данных пользователя, верните все данные пользователю и сохраните полученные данные в состоянии беседы.
Боты/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() объектов управления состоянием, чтобы записать все изменения состояния в хранилище.
Боты/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.userState = userState;
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.
Перед выходом из каждого диалогового окна используйте метод 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);
- Если
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
для сохранения всех изменений, записывая сведения о состоянии в хранилище.
bots/state_management_bot.py
async def on_turn(self, turn_context: TurnContext):
await super().on_turn(turn_context)
await self.conversation_state.save_changes(turn_context)
await self.user_state.save_changes(turn_context)
Тестирование бота
- Скачайте и установите последнюю версию Bot Framework Emulator.
- Выполните этот пример на локальном компьютере.
Если вам нужны инструкции, обратитесь к README для C#, JavaScript, JavaScript или Python.
- Используйте эмулятор для тестирования примера бота.
В этой статье описано, как добавить состояние для вашего бота. Дополнительные сведения о связанных разделах см. в следующей таблице.
Тема |
Примечания. |
Конфиденциальность |
Если вы собираетесь хранить персональные данные пользователя, обеспечьте соблюдение Общего регламента по защите данных. |
Управление состоянием |
Все вызовы управления состоянием асинхронны, и по умолчанию используется правило последнего записавшего. На практике следует размещать методы get, set и save state как можно ближе друг к другу в коде бота. Сведения о реализации оптимистической блокировки см. в разделе "Реализация пользовательского хранилища для бота". |
Критически важные бизнес-данные |
Используйте состояние бота для хранения настроек, имени пользователя или последней, которую они заказали, но не используйте его для хранения критически важных бизнес-данных. Для критически важных данных создайте собственные компоненты хранилища или записывайте их непосредственно в хранилище. |
Распознаватель текста |
В этом примере используются библиотеки Microsoft/Recognizer-Text для синтаксического анализа и проверки пользовательского ввода. Дополнительные сведения см. на странице обзор. |
Следующие шаги
Узнайте, как задать пользователю ряд вопросов, проверить свои ответы и сохранить входные данные.