Bot secara inheren tanpa status. Setelah bot Anda disebarkan, bot mungkin tidak berjalan dalam proses yang sama atau pada komputer yang sama dari satu giliran ke giliran berikutnya. Namun, bot Anda mungkin perlu melacak konteks percakapan sehingga dapat mengelola perilakunya dan mengingat jawaban atas pertanyaan sebelumnya. Fitur status dan penyimpanan Bot Framework SDK memungkinkan Anda menambahkan status ke bot Anda. Bot menggunakan manajemen status dan objek penyimpanan untuk mengelola dan mempertahankan status. Manajer status menyediakan lapisan abstraksi yang memungkinkan Anda mengakses properti status menggunakan pengaktif properti, terlepas dari jenis penyimpanan yang mendasar.
Kode dalam artikel ini didasarkan pada sampel Bot Manajemen Status. Anda akan memerlukan salinan sampel di C#, JavaScript, Java , atau Python.
Tentang sampel ini
Setelah menerima input pengguna, sampel ini memeriksa status percakapan yang disimpan untuk melihat apakah pengguna ini sebelumnya telah diminta untuk memberikan nama mereka. Jika tidak, nama pengguna diminta dan input tersebut disimpan dalam status pengguna. Jika demikian, nama yang disimpan dalam status pengguna digunakan untuk bercakap-cakap dengan pengguna dan data input mereka, bersama dengan waktu yang diterima dan ID saluran input, dikembalikan kepada pengguna. Nilai WAKTU dan ID saluran diambil dari data percakapan pengguna lalu disimpan ke status percakapan. Diagram berikut menunjukkan hubungan antara bot, profil pengguna, dan kelas data percakapan.
Langkah pertama dalam menyiapkan manajemen status adalah menentukan kelas yang berisi informasi yang akan dikelola dalam status pengguna dan percakapan. Contoh yang digunakan dalam artikel ini, menentukan kelas berikut:
Di UserProfile.cs, Anda menentukan UserProfile kelas untuk informasi pengguna yang akan dikumpulkan bot.
Di ConversationData.cs, Anda menentukan ConversationData kelas untuk mengontrol status percakapan kami saat mengumpulkan informasi pengguna.
Contoh kode berikut menunjukkan definisi untuk UserProfile kelas dan ConversationData .
UserProfile.cs
public class UserProfile
{
public string Name { get; set; }
}
}
ConversationData.cs
// Defines a state property used to track conversation data.
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;
}
Langkah ini tidak diperlukan di JavaScript.
Langkah pertama dalam menyiapkan manajemen status adalah menentukan kelas yang berisi informasi yang akan dikelola dalam status pengguna dan percakapan. Contoh yang digunakan dalam artikel ini menentukan kelas berikut:
Di UserProfile.java, Anda menentukan UserProfile kelas untuk informasi pengguna yang akan dikumpulkan bot.
Di ConversationData.java, Anda menentukan ConversationData kelas untuk mengontrol status percakapan kami saat mengumpulkan informasi pengguna.
Contoh kode berikut menunjukkan definisi untuk UserProfile kelas dan 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) {
Langkah pertama dalam menyiapkan manajemen status adalah menentukan kelas yang berisi informasi yang akan dikelola dalam status pengguna dan percakapan. Contoh yang digunakan dalam artikel ini, menentukan kelas berikut:
user_profile.py berisi UserProfile kelas yang menyimpan informasi pengguna yang dikumpulkan oleh bot.
conversation_data.py berisi ConversationData kelas yang mengontrol status percakapan saat mengumpulkan informasi pengguna.
Contoh kode berikut menunjukkan definisi untuk UserProfile kelas dan ConversationData .
user_profile.py
class UserProfile:
def __init__(self, name: str = None):
self.name = name
Selanjutnya, Anda mendaftar MemoryStorage yang digunakan untuk membuat UserState objek dan ConversationState . Objek status pengguna dan percakapan dibuat di Startup dan dependensi disuntikkan ke konstruktor bot. Layanan lain untuk bot yang terdaftar adalah: penyedia info masuk, adaptor, dan implementasi bot.
Startup.cs
// Create the Bot Adapter with error handling enabled.
services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();
/* END COSMOSDB STORAGE */
// Create the User state passing in the storage layer.
var userState = new UserState(storage);
services.AddSingleton(userState);
Selanjutnya, Anda mendaftar MemoryStorage yang kemudian digunakan untuk membuat UserState objek dan ConversationState . Ini dibuat dalam index.js dan dikonsumsi saat bot dibuat.
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);
bot/stateManagementBot.js
class StateManagementBot extends ActivityHandler {
constructor(conversationState, userState) {
super();
// The state management objects for the conversation and user state.
this.conversationState = conversationState;
this.userState = userState;
Selanjutnya, Anda mendaftarkan StateManagementBot di Application.java. ConversationState dan UserState disediakan secara default dari kelas BotDependencyConfiguration, dan Spring akan memasukkannya ke dalam metode getBot.
Application.java
*
* @return The Bot implementation for this application.
*/
@Bean
public Bot getBot(
ConversationState conversationState,
UserState userState
Selanjutnya, Anda mendaftar MemoryStorage yang digunakan untuk membuat UserState objek dan ConversationState . Ini dibuat dalam app.py dan dikonsumsi saat bot dibuat.
app.py
# Create MemoryStorage and state
MEMORY = MemoryStorage()
USER_STATE = UserState(MEMORY)
CONVERSATION_STATE = ConversationState(MEMORY)
bot/state_management_bot.py
class StateManagementBot(ActivityHandler):
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
Sekarang Anda membuat pengaktor properti menggunakan CreateProperty metode yang menyediakan handel ke BotState objek . Setiap pengaktif properti status memungkinkan Anda untuk mendapatkan atau mengatur nilai properti status terkait. Sebelum Anda menggunakan properti status, gunakan setiap pengaktif untuk memuat properti dari penyimpanan dan mendapatkannya dari cache status. Untuk mendapatkan kunci yang tercakup dengan benar yang terkait dengan properti status, Anda memanggil GetAsync metode .
Bots/StateManagementBot.cs
var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
Sekarang Anda membuat pengaktor properti untuk UserState dan ConversationState. Setiap pengaktif properti status memungkinkan Anda untuk mendapatkan atau mengatur nilai properti status terkait. Anda menggunakan setiap pengaktif untuk memuat properti terkait dari penyimpanan dan mengambil statusnya saat ini dari cache.
bot/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);
Sekarang Anda membuat pengaktor properti menggunakan CreateProperty metode . Setiap pengaktif properti status memungkinkan Anda untuk mendapatkan atau mengatur nilai properti status terkait. Sebelum Anda menggunakan properti status, gunakan setiap pengaktif untuk memuat properti dari penyimpanan dan mendapatkannya dari cache status. Untuk mendapatkan kunci yang tercakup dengan benar yang terkait dengan properti status, Anda memanggil get metode .
StateManagementBot.java
* @return A future task.
*/
@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);
Sekarang Anda membuat pengaktor properti untuk UserProfile dan ConversationData. Setiap pengaktif properti status memungkinkan Anda untuk mendapatkan atau mengatur nilai properti status terkait. Anda menggunakan setiap pengaktif untuk memuat properti terkait dari penyimpanan dan mengambil statusnya saat ini dari cache.
Bagian sebelumnya mencakup langkah-langkah waktu inisialisasi untuk menambahkan pengakses properti status ke bot kami. Sekarang, Anda dapat menggunakan pengaktif tersebut pada run-time untuk membaca dan menulis informasi status. Kode sampel di bawah ini menggunakan alur logika berikut:
Jika userProfile.Name kosong dan conversationData.PromptedUserForName benar, Anda mengambil nama pengguna yang disediakan dan menyimpannya dalam status pengguna.
Jika userProfile.Name kosong dan conversationData.PromptedUserForName salah, Anda meminta nama pengguna.
Jika userProfile.Name sebelumnya disimpan, Anda mengambil waktu pesan dan ID saluran dari input pengguna, menggemakan semua data kembali ke pengguna, dan menyimpan data yang diambil dalam status percakapan.
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}");
}
}
Sebelum Anda keluar dari handler giliran, Anda menggunakan metode SaveChangesAsync() objek manajemen status untuk menulis semua perubahan status kembali ke penyimpanan.
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);
}
Jika userProfile.Name kosong dan conversationData.PromptedUserForName benar, Anda mengambil nama pengguna yang disediakan dan menyimpannya dalam status pengguna.
Jika userProfile.Name kosong dan conversationData.PromptedUserForName salah, Anda meminta nama pengguna.
Jika userProfile.Name sebelumnya disimpan, Anda mengambil waktu pesan dan ID saluran dari input pengguna, menggemakan semua data kembali ke pengguna, dan menyimpan data yang diambil dalam status percakapan.
bot/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();
});
Sebelum Anda keluar dari setiap giliran dialog, Anda menggunakan metode saveChanges() objek manajemen status untuk mempertahankan semua perubahan dengan menulis status kembali ke penyimpanan.
bot/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);
}
Jika userProfile.getName() kosong dan conversationData.getPromptedUserForName() benar, Anda mengambil nama pengguna yang disediakan dan menyimpannya dalam status pengguna.
Jika userProfile.getName() kosong dan conversationData.getPromptedUserForName() salah, Anda meminta nama pengguna.
Jika userProfile.getName() sebelumnya disimpan, Anda mengambil waktu pesan dan ID saluran dari input pengguna, menggemakan semua data kembali ke pengguna, dan menyimpan data yang diambil dalam status percakapan.
StateManagementBot.java
*
* @param turnContext The context object for this turn.
* @return A future task.
*/
@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);
}
})
Sebelum Anda keluar dari handler giliran, Anda menggunakan metode saveChanges() objek manajemen status untuk menulis semua perubahan status kembali ke penyimpanan.
StateManagementBot.java
* process the activity.
* @return A future task.
*/
@Override
public CompletableFuture<Void> onTurn(TurnContext turnContext) {
return super.onTurn(turnContext)
Jika user_profile.name kosong dan conversation_data.prompted_for_user_namebenar, bot mengambil nama yang disediakan oleh pengguna dan menyimpannya dalam status pengguna.
Jika user_profile.name kosong dan conversation_data.prompted_for_user_namesalah, bot akan meminta nama pengguna.
Jika user_profile.name sebelumnya disimpan, bot mengambil waktu pesan dan ID saluran dari input pengguna, menggemakan data kembali ke pengguna, dan menyimpan data yang diambil dalam status percakapan.
bot/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 }"
)
Sebelum keluar dari setiap giliran dialog, bot menggunakan metode objek save_changes manajemen status untuk mempertahankan semua perubahan dengan menulis informasi status di penyimpanan.
Jalankan sampel secara lokal di komputer Anda. Jika Anda memerlukan instruksi, lihat README file untuk Sampel C# atau Sampel JS.
Gunakan Emulator untuk menguji bot seperti yang ditunjukkan di bawah ini.
Sumber Daya Tambahan:
Privasi: Jika Anda ingin menyimpan data pribadi pengguna, Anda harus memastikan kepatuhan terhadap Peraturan Perlindungan Data Umum.
Manajemen status: Semua panggilan manajemen status bersifat asinkron, dan penulis terakhir-menang secara default. Dalam praktiknya, Anda harus mendapatkan, mengatur, dan menyimpan status sedekat mungkin di bot Anda.
Data bisnis penting: Gunakan status bot untuk menyimpan preferensi, nama pengguna, atau hal terakhir yang mereka pesan, tetapi jangan gunakan untuk menyimpan data bisnis penting. Untuk data penting, buat komponen penyimpanan Anda sendiri atau tulis langsung ke penyimpanan.
Recognizer-Text: Sampel menggunakan pustaka Microsoft/Recognizers-Text untuk mengurai dan memvalidasi input pengguna. Untuk informasi selengkapnya, lihat halaman gambaran umum .
Langkah berikutnya
Sekarang setelah Anda tahu cara mengonfigurasi status untuk membantu Anda membaca dan menulis data bot ke penyimpanan, mari kita pelajari cara mengajukan serangkaian pertanyaan kepada pengguna, memvalidasi jawaban mereka, dan menyimpan input mereka.