A robot természeténél fogva állapot nélküli. A robot üzembe helyezése után előfordulhat, hogy nem ugyanabban a folyamatban vagy ugyanazon a gépen fut egyikről a másikra. Előfordulhat azonban, hogy a robotnak nyomon kell követnie egy beszélgetés kontextusát, hogy kezelni tudja a viselkedését, és emlékezzen a korábbi kérdésekre adott válaszokra. A Bot Framework SDK állapot- és tárolási funkciói lehetővé teszik az állapot hozzáadását a robothoz. A robotok állapotkezelési és tárolási objektumokat használnak az állapot kezeléséhez és megőrzéséhez. Az állapotkezelő egy absztrakciós réteget biztosít, amely lehetővé teszi az állapottulajdonságok elérését az alapul szolgáló tároló típusától független tulajdonságkiegészítők használatával.
Feljegyzés
A Bot Framework JavaScript, C# és Python SDK-k továbbra is támogatottak lesznek, a Java SDK-t azonban 2023 novemberében végső hosszú távú támogatással kivonják.
A Java SDK-val létrehozott meglévő robotok továbbra is működni fognak.
Az új robotépítéshez fontolja meg a Microsoft Copilot Studio használatát, és olvassa el a megfelelő copilot-megoldás kiválasztását.
Ismerni kell a robot alapjait és azt, hogy a robotok hogyan kezelik az állapotot.
A cikkben szereplő kód az Állapotkezelési robot mintáján alapul. Szüksége lesz a minta másolatára c#, JavaScript, Java vagy Python nyelven.
A minta ismertetése
A felhasználói bemenet fogadásakor ez a minta ellenőrzi a tárolt beszélgetés állapotát, hogy a rendszer kéri-e korábban a felhasználó nevét. Ha nem, a rendszer kéri a felhasználó nevét, és a bemenetet a rendszer felhasználói állapotban tárolja. Ha igen, a rendszer a felhasználói állapotban tárolt nevet használja a felhasználóval való konververáláshoz, és a bemeneti adatok, valamint a kapott idő és a bemeneti csatorna azonosítója vissza lesz adva a felhasználónak. A rendszer lekéri az idő- és csatornaazonosító-értékeket a felhasználói beszélgetés adataiból, majd menti a beszélgetési állapotba. Az alábbi ábra a robot, a felhasználói profil és a beszélgetési adatosztályok közötti kapcsolatot mutatja be.
Az állapotkezelés beállításának első lépése a felhasználói és beszélgetési állapotban kezelendő információkat tartalmazó osztályok meghatározása. A cikkben használt példa a következő osztályokat határozza meg:
A UserProfile.cs meghatároz egy osztályt UserProfile a robot által gyűjtött felhasználói adatokhoz.
A ConversationData.cs meghatároz egy osztályt ConversationData , amely a felhasználói adatok összegyűjtése közben szabályozza a beszélgetés állapotát.
Az alábbi példakódok az osztályok és ConversationData az osztályok definícióit UserProfile mutatják be.
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;
}
Ez a lépés nem szükséges a JavaScriptben.
Az állapotkezelés beállításának első lépése a felhasználói és beszélgetési állapotban kezelendő információkat tartalmazó osztályok meghatározása. A cikkben használt példa a következő osztályokat határozza meg:
A UserProfile.java meghatároz egy osztályt UserProfile a robot által gyűjtött felhasználói adatokhoz.
A ConversationData.java meghatároz egy osztályt ConversationData , amely a felhasználói adatok összegyűjtése során szabályozza a beszélgetés állapotát.
Az alábbi példakódok az osztályok és ConversationData az osztályok definícióit UserProfile mutatják be.
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) {
Az állapotkezelés beállításának első lépése a felhasználói és beszélgetési állapotban kezelendő információkat tartalmazó osztályok meghatározása. A cikkben használt példa a következő osztályokat határozza meg:
A user_profile.py tartalmazza azt az UserProfile osztályt, amely a robot által gyűjtött felhasználói adatokat tárolja.
A conversation_data.py tartalmazza azt az ConversationData osztályt, amely a felhasználói adatok összegyűjtése során vezérli a beszélgetés állapotát.
Az alábbi példakódok az osztályok és ConversationData az osztályok definícióit UserProfile mutatják be.
user_profile.py
class UserProfile:
def __init__(self, name: str = None):
self.name = name
Ezután regisztrálja MemoryStorage a létrehozáshoz UserState és ConversationState az objektumokhoz használt regisztrációt. A felhasználó- és beszélgetésállapot-objektumok a robotkonstruktorban Startup jönnek létre, és függőséget injektálnak. A regisztrált robotok további szolgáltatásai a következők: hitelesítőadat-szolgáltató, adapter és robot implementációja.
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 */
Ezután regisztrálja a létrehozáshoz MemoryStorageUserState és ConversationState az objektumokhoz használt regisztrációt. Ezek index.js jönnek létre, és a robot létrehozásakor lesznek felhasználva.
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);
robotok/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;
Ezután regisztrálja a StateManagementBot Application.java. A ConversationState és a UserState is alapértelmezés szerint a BotDependencyConfiguration osztályból van megadva, és a Spring a getBot metódusba fogja injektálni őket.
Application.java
@Bean
public Bot getBot(
ConversationState conversationState,
UserState userState
) {
return new StateManagementBot(conversationState, userState);
}
Ezután regisztrálja MemoryStorage a létrehozáshoz UserState és ConversationState az objektumokhoz használt regisztrációt. Ezek app.py jönnek létre, és a robot létrehozásakor lesznek felhasználva.
app.py
CONVERSATION_STATE = ConversationState(MEMORY)
# Create Bot
BOT = StateManagementBot(CONVERSATION_STATE, USER_STATE)
# Listen for incoming requests on /api/messages.
robotok/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")
Most hozzon létre tulajdonságkiegészítőket azzal a CreateProperty metódussal, amely egy fogópontot biztosít az BotState objektum számára. Minden állapottulajdonság-tartozék lehetővé teszi a társított állapottulajdonság értékének lekérését vagy beállítását. Az állapottulajdonságok használata előtt az egyes tartozékokkal töltse be a tulajdonságot a tárolóból, és szerezze be az állapotgyorsítótárból. Az állapottulajdonsághoz tartozó megfelelő hatókörű kulcs lekéréséhez hívja meg a metódust GetAsync .
Robotok/StateManagementBot.cs
var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
Most hozzon létre tulajdonságkiegészítőket az és ConversationStatea UserState . Minden állapottulajdonság-tartozék lehetővé teszi a társított állapottulajdonság értékének lekérését vagy beállítását. Az egyes tartozékokkal betöltheti a társított tulajdonságot a tárolóból, és lekérheti annak aktuális állapotát a gyorsítótárból.
robotok/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);
Most a metódus használatával createProperty hozzon létre tulajdonságkiegészítőket. Minden állapottulajdonság-tartozék lehetővé teszi a társított állapottulajdonság értékének lekérését vagy beállítását. Az állapottulajdonságok használata előtt az egyes tartozékokkal töltse be a tulajdonságot a tárolóból, és szerezze be az állapotgyorsítótárból. Az állapottulajdonsághoz tartozó megfelelő hatókörű kulcs lekéréséhez hívja meg a metódust get .
Most hozzon létre tulajdonságkiegészítőket az és ConversationDataa UserProfile . Minden állapottulajdonság-tartozék lehetővé teszi a társított állapottulajdonság értékének lekérését vagy beállítását. Az egyes tartozékokkal betöltheti a társított tulajdonságot a tárolóból, és lekérheti annak aktuális állapotát a gyorsítótárból.
Az előző szakasz ismerteti az inicializálási idő lépéseit, hogy állapottulajdonság-kiegészítőket adjon hozzá a robothoz. Ezeket a tartozékokat futtatáskor használhatja állapotinformációk olvasására és írására. Az alábbi mintakód a következő logikai folyamatot használja:
Ha userProfile.Name üres, és conversationData.PromptedUserForName igaz, lekéri a megadott felhasználónevet, és ezt felhasználói állapotban tárolja.
Ha userProfile.Name üres, és conversationData.PromptedUserForName hamis, a felhasználó nevét kell megadnia.
Ha userProfile.Name korábban tárolták, lekéri az üzenet idejét és csatornaazonosítóját a felhasználói bemenetből, visszakéri az összes adatot a felhasználónak, és a lekért adatokat beszélgetési állapotban tárolja.
Robotok/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}");
}
}
Mielőtt kilép a turn handlerből, az állapotkezelési objektumok SaveChangesAsync() metódusával minden állapotváltozást visszaírhat a tárolóba.
Robotok/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);
}
Ha userProfile.Name üres, és conversationData.PromptedUserForName igaz, lekéri a megadott felhasználónevet, és ezt felhasználói állapotban tárolja.
Ha userProfile.Name üres, és conversationData.PromptedUserForName hamis, a felhasználó nevét kell megadnia.
Ha userProfile.Name korábban tárolták, lekéri az üzenet idejét és csatornaazonosítóját a felhasználói bemenetből, visszakéri az összes adatot a felhasználónak, és a lekért adatokat beszélgetési állapotban tárolja.
robotok/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();
});
Mielőtt kilép az egyes párbeszédpanel-fordulókból , az állapotkezelési objektumok saveChanges() metódusával az összes módosítást megőrizheti az állapotnak a tárba való visszaírásával.
robotok/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);
}
Ha userProfile.getName() üres, és conversationData.getPromptedUserForName() igaz, lekéri a megadott felhasználónevet, és ezt felhasználói állapotban tárolja.
Ha userProfile.getName() üres, és conversationData.getPromptedUserForName() hamis, a felhasználó nevét kell megadnia.
Ha userProfile.getName() korábban tárolták, lekéri az üzenet idejét és csatornaazonosítóját a felhasználói bemenetből, visszakéri az összes adatot a felhasználónak, és a lekért adatokat beszélgetési állapotban tárolja.
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);
}
Mielőtt kilép a turn handlerből, az állapotkezelési objektumok saveChanges() metódusával minden állapotváltozást visszaírhat a tárolóba.
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));
}
Ha user_profile.name üres, és conversation_data.prompted_for_user_name igaz, a robot lekéri a felhasználó által megadott nevet, és a felhasználó állapotában tárolja.
Ha user_profile.name üres, és conversation_data.prompted_for_user_name hamis, a robot a felhasználó nevét kéri.
Ha user_profile.name korábban tárolták, a robot lekéri az üzenetidőt és a csatornaazonosítót a felhasználói bemenetből, visszakéri az adatokat a felhasználónak, és a lekért adatokat beszélgetési állapotban tárolja.
robotok/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 }"
)
Mielőtt minden párbeszédpanel véget ér, a robot az állapotkezelési objektumok metódusával save_changes megőrzi az összes módosítást úgy, hogy állapotinformációkat ír a tárolóba.
Az összes állapotkezelési hívás aszinkron, és alapértelmezés szerint az utolsó író nyer. A gyakorlatban az állapotot a lehető legközelebb kell megkapnia, beállítania és mentenie a robotban. Az optimista zárolás implementálásának módjáról a robot egyéni tárolójának implementálása című témakörben olvashat.
Kritikus üzleti adatok
A robotállapot használatával tárolhatja a beállításokat, a felhasználónevet vagy az utolsó megrendelt dolgot, de ne használja a kritikus fontosságú üzleti adatok tárolására. Kritikus adatokhoz hozzon létre saját tárolóösszetevőket, vagy írjon közvetlenül a tárolóba.
Recognizer-Text
A minta a Microsoft/Recognizers-Text kódtárak használatával elemzi és ellenőrzi a felhasználói bemenetet. További információkért tekintse meg az áttekintési oldalt.
Következő lépések
Megtudhatja, hogyan tehet fel kérdéseket a felhasználónak, hogyan ellenőrizheti a válaszait, és hogyan mentheti a bemenetét.