適用於: SDK v4
Bot 本質上是無狀態的。 部署 Bot 之後,它可能不會在相同進程中執行,或從一個回合到下一個回合在同一部電腦上執行。 不過,您的 Bot 可能需要追蹤交談的內容,才能管理其行為,並記住先前問題的解答。 Bot Framework SDK 的狀態和儲存功能可讓您將狀態新增至 Bot。 Bot 會使用狀態管理和儲存物件來管理和保存狀態。 狀態管理員提供抽象層,可讓您使用屬性存取子來存取狀態屬性,與基礎記憶體類型無關。
必要條件
關於此範例
收到使用者輸入時,此範例會檢查儲存的交談狀態,以查看此使用者先前是否已提示您提供其名稱。 如果沒有,則會要求使用者的名稱,且該輸入會儲存在用戶狀態內。 如果是的話,儲存在用戶狀態內的名稱會用來與使用者及其輸入數據進行交談,以及接收的時間和輸入通道標識碼,會傳回給使用者。 時間與通道標識碼值會從使用者交談數據擷取,然後儲存至交談狀態。 下圖顯示 Bot、使用者配置檔和交談數據類別之間的關聯性。
定義類別
設定狀態管理的第一個步驟是定義類別,其中包含在使用者和交談狀態中管理的資訊。 本文中使用的範例會定義下列類別:
- 在 UserProfile.cs中,您會為 Bot 將收集的使用者資訊定義類別
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;
}
設定狀態管理的第一個步驟是定義類別,其中包含在使用者和交談狀態中管理的資訊。 本文中使用的範例會定義下列類別:
- 在 UserProfile.java中,您可以定義
UserProfile
Bot 將收集之使用者資訊的類別。
- 在 ConversationData.java中,您會定義類別
ConversationData
,以在收集用戶資訊時控制我們的交談狀態。
下列程式代碼範例顯示 UserProfile
和 ConversationData
類別的定義。
UserProfile.java
警告
您要尋找的範例看起來已經移動! 放心,我們正在努力解決這個問題。
ConversationData.java
警告
您要尋找的範例看起來已經移動! 放心,我們正在努力解決這個問題。
設定狀態管理的第一個步驟是定義類別,其中包含在使用者和交談狀態中管理的資訊。 本文中使用的範例會定義下列類別:
-
user_profile.py包含
UserProfile
類別,可儲存 Bot 所收集的用戶資訊。
-
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
建立,並將依賴項注入至機器人建構函式中。 已註冊 Bot 的其他服務包括:認證提供者、配接器和 Bot 實作。
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 */
Bots/StateManagementBot.cs
private BotState _conversationState;
private BotState _userState;
public StateManagementBot(ConversationState conversationState, UserState userState)
{
_conversationState = conversationState;
_userState = userState;
}
接下來,您會註冊 MemoryStorage
,然後用來建立 UserState
和 ConversationState
物件。 這些會建立於 index.js 中,並在建立 Bot 時取用。
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.
接下來,您會在 Application.java 中註冊 StateManagementBot
。 根據預設,ConversationState 和 UserState 都是從 BotDependencyConfiguration 類別提供,Spring 會將它們插入 getBot 方法中。
Application.java
警告
您要尋找的範例看起來已經移動! 放心,我們正在努力解決這個問題。
接下來,您會註冊 MemoryStorage
來建立 UserState
和 ConversationState
物件。 這些會在建立 Bot 時於 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
物件的句柄。 每個狀態屬性存取子都可讓您取得或設定相關聯狀態屬性的值。 使用狀態屬性之前,請先使用每個存取子從記憶體載入屬性,並從狀態快取中取得屬性。 若要取得與 state 屬性相關聯的適當範圍索引鍵,您可以呼叫 GetAsync
方法。
Bots/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
方法建立屬性存取子。 每個狀態屬性存取子都可讓您取得或設定相關聯狀態屬性的值。 使用狀態屬性之前,請先使用每個存取子從記憶體載入屬性,並從狀態快取中取得屬性。 若要取得與 state 屬性相關聯的適當範圍索引鍵,您可以呼叫 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")
從機器人存取狀態
上一節涵蓋將狀態屬性存取子新增至 Bot 的初始化時間步驟。 現在,您可以在運行時間使用這些存取子來讀取和寫入狀態資訊。 下列範例程式代碼會使用下列邏輯流程:
- 如果
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.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,則 Bot 會擷取使用者提供的名稱,並將其儲存在用戶的狀態。
- 如果
user_profile.name
為空白且 conversation_data.prompted_for_user_name
為 false,則 Bot 會要求用戶的名稱。
- 如果
user_profile.name
先前已儲存,Bot 會從使用者輸入擷取訊息時間和通道標識碼、將數據回應給使用者,並將擷取的數據儲存在交談狀態中。
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 }"
)
在每個對話框回合結束之前,Bot 會使用狀態管理物件的 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 模擬器
- 在本機電腦上執行範例。
如果您需要指示,請參閱 C#、JavaScript、Java 或 Python 的自述檔。
- 使用模擬器來測試範例 Bot。
本文說明如何將狀態加入到機器人中。 如需相關主題的詳細資訊,請參閱下表。
主題 |
備註 |
隱私權 |
如果您想要儲存使用者的個人資料,您應該確保符合 一般數據保護規定。 |
狀態管理 |
所有狀態管理操作都是非同步的,而且預設為最後寫入者獲勝。 在實際操作上,您應該盡量在 Bot 中接近同時地取得、設定和儲存狀態。 如需瞭解如何實作樂觀鎖定,請參閱 為您的 Bot 實作自定義存儲。 |
關鍵商務數據 |
使用 Bot 狀態來儲存喜好設定、使用者名稱或他們訂購的最後一件事,但不要使用它來儲存重要的商務數據。 對於重要數據,請建立您自己的記憶體元件,或直接寫入記憶體。 |
文字辨識器 |
此範例會使用 Microsoft/Recognizers-Text 連結庫來剖析及驗證用戶輸入。 如需詳細資訊,請參閱概 觀 頁面。 |
下一步
瞭解如何詢問使用者一系列問題、驗證答案,以及儲存其輸入。