En robot är i sig tillståndslös. När roboten har distribuerats kanske den inte körs i samma process eller på samma dator från en tur till en annan. Din robot kan dock behöva spåra kontexten för en konversation så att den kan hantera sitt beteende och komma ihåg svar på tidigare frågor. Med tillstånds- och lagringsfunktionerna i Bot Framework SDK kan du lägga till tillstånd i roboten. Robotar använder tillståndshanterings- och lagringsobjekt för att hantera och bevara tillstånd. Tillståndshanteraren tillhandahåller ett abstraktionslager som gör att du kan komma åt tillståndsegenskaper med hjälp av egenskapsåtkomster, oberoende av typen av underliggande lagring.
Kommentar
Bot Framework JavaScript-, C#- och Python-SDK:erna fortsätter att stödjas, men Java SDK dras tillbaka med slutligt långsiktigt stöd som slutar i november 2023.
Befintliga robotar som skapats med Java SDK fortsätter att fungera.
Koden i den här artikeln baseras på exemplet med tillståndshanteringsroboten. Du behöver en kopia av exemplet i C#, JavaScript, Java eller Python.
Om det här exemplet
När du tar emot användarindata kontrollerar det här exemplet det lagrade konversationstillståndet för att se om användaren tidigare har uppmanats att ange sitt namn. Annars begärs användarens namn och indata lagras i användartillstånd. I så fall används namnet som lagras i användartillståndet för att samtala med användaren och deras indata, tillsammans med den tid som tas emot och indatakanal-ID, returneras tillbaka till användaren. Värdena för tids- och kanal-ID hämtas från användarkonversationens data och sparas sedan i konversationstillstånd. Följande diagram visar relationen mellan roboten, användarprofilen och konversationsdataklasserna.
Det första steget i att konfigurera tillståndshantering är att definiera de klasser som innehåller den information som ska hanteras i användar- och konversationstillståndet. Exemplet som används i den här artikeln definierar följande klasser:
I UserProfile.cs definierar du en UserProfile klass för den användarinformation som roboten ska samla in.
I ConversationData.cs definierar du en ConversationData klass för att styra vårt konversationstillstånd när du samlar in användarinformation.
Följande kodexempel visar definitionerna för klasserna UserProfile och 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;
}
Det här steget är inte nödvändigt i JavaScript.
Det första steget i att konfigurera tillståndshantering är att definiera de klasser som innehåller den information som ska hanteras i användar- och konversationstillståndet. Exemplet som används i den här artikeln definierar följande klasser:
I UserProfile.java definierar du en UserProfile klass för den användarinformation som roboten ska samla in.
I ConversationData.java definierar du en ConversationData klass för att styra vårt konversationstillstånd när du samlar in användarinformation.
Följande kodexempel visar definitionerna för klasserna UserProfile och 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) {
Det första steget i att konfigurera tillståndshantering är att definiera de klasser som innehåller den information som ska hanteras i användar- och konversationstillståndet. Exemplet som används i den här artikeln definierar följande klasser:
User_profile.py innehåller klassen UserProfile som lagrar användarinformationen som samlas in av roboten.
Conversation_data.py innehåller klassen ConversationData som styr konversationstillståndet när användarinformation samlas in.
Följande kodexempel visar definitionerna för klasserna UserProfile och ConversationData .
user_profile.py
class UserProfile:
def __init__(self, name: str = None):
self.name = name
Därefter registrerar MemoryStorage du som används för att skapa UserState och ConversationState objekt. Användar- och konversationstillståndsobjekt skapas vid Startup och beroendet matas in i robotkonstruktorn. Andra tjänster för en robot som är registrerad är: en autentiseringsprovider, ett kort och robotimplementeringen.
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 */
Därefter registrerar MemoryStorage du som sedan används för att skapa UserState och ConversationState objekt. Dessa skapas i index.js och används när roboten skapas.
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);
robotar/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;
Därefter registrerar StateManagementBot du i Application.java. Både ConversationState och UserState tillhandahålls som standard från klassen BotDependencyConfiguration, och Spring matar in dem i metoden getBot.
Application.java
@Bean
public Bot getBot(
ConversationState conversationState,
UserState userState
) {
return new StateManagementBot(conversationState, userState);
}
Därefter registrerar MemoryStorage du som används för att skapa UserState och ConversationState objekt. Dessa skapas i app.py och används när roboten skapas.
app.py
CONVERSATION_STATE = ConversationState(MEMORY)
# Create Bot
BOT = StateManagementBot(CONVERSATION_STATE, USER_STATE)
# Listen for incoming requests on /api/messages.
robotar/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")
Nu skapar du egenskapsåtkomster med hjälp av metoden CreateProperty som tillhandahåller ett handtag till BotState objektet. Med varje tillståndsegenskapsåtkomst kan du hämta eller ange värdet för den associerade tillståndsegenskapen. Innan du använder tillståndsegenskaperna använder du varje accessor för att läsa in egenskapen från lagringen och hämta den från tillståndscacheminnet. Om du vill hämta rätt omfångsnyckel som är associerad med tillståndsegenskapen GetAsync anropar du metoden.
Robotar/StateManagementBot.cs
var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
Nu skapar du egenskapsåtkomster för UserState och ConversationState. Med varje tillståndsegenskapsåtkomst kan du hämta eller ange värdet för den associerade tillståndsegenskapen. Du använder varje accessor för att läsa in den associerade egenskapen från lagringen och hämta dess aktuella tillstånd från cacheminnet.
robotar/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);
Nu skapar du egenskapsåtkomster med hjälp av createProperty metoden . Med varje tillståndsegenskapsåtkomst kan du hämta eller ange värdet för den associerade tillståndsegenskapen. Innan du använder tillståndsegenskaperna använder du varje accessor för att läsa in egenskapen från lagringen och hämta den från tillståndscacheminnet. Om du vill hämta rätt omfångsnyckel som är associerad med tillståndsegenskapen get anropar du metoden.
Nu skapar du egenskapsåtkomster för UserProfile och ConversationData. Med varje tillståndsegenskapsåtkomst kan du hämta eller ange värdet för den associerade tillståndsegenskapen. Du använder varje accessor för att läsa in den associerade egenskapen från lagringen och hämta dess aktuella tillstånd från cacheminnet.
Föregående avsnitt beskriver initieringstidsstegen för att lägga till tillståndsegenskapsåtkomster till roboten. Nu kan du använda dessa åtkomster vid körning för att läsa och skriva tillståndsinformation. Exempelkoden nedan använder följande logikflöde:
Om userProfile.Name är tomt och conversationData.PromptedUserForName är sant hämtar du det angivna användarnamnet och lagrar det i användartillstånd.
Om userProfile.Name är tomt och conversationData.PromptedUserForName är falskt ber du om användarens namn.
Om userProfile.Name har lagrats tidigare hämtar du meddelandetid och kanal-ID från användarens indata, upprepar alla data tillbaka till användaren och lagrar hämtade data i konversationstillstånd.
Robotar/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}");
}
}
Innan du avslutar turhanteraren använder du tillståndshanteringsobjektens SaveChangesAsync() -metod för att skriva tillbaka alla tillståndsändringar till lagringen.
Robotar/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);
}
Om userProfile.Name är tomt och conversationData.PromptedUserForName är sant hämtar du det angivna användarnamnet och lagrar det i användartillstånd.
Om userProfile.Name är tomt och conversationData.PromptedUserForName är falskt ber du om användarens namn.
Om userProfile.Name har lagrats tidigare hämtar du meddelandetid och kanal-ID från användarens indata, upprepar alla data tillbaka till användaren och lagrar hämtade data i konversationstillstånd.
robotar/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();
});
Innan du avslutar varje dialogruta använder du metoden saveChanges() för tillståndshanteringsobjekt för att spara alla ändringar genom att skriva tillbaka tillståndet till lagringen.
robotar/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);
}
Om userProfile.getName() är tomt och conversationData.getPromptedUserForName() är sant hämtar du det angivna användarnamnet och lagrar det i användartillstånd.
Om userProfile.getName() är tomt och conversationData.getPromptedUserForName() är falskt ber du om användarens namn.
Om userProfile.getName() har lagrats tidigare hämtar du meddelandetid och kanal-ID från användarens indata, upprepar alla data tillbaka till användaren och lagrar hämtade data i konversationstillstånd.
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);
}
Innan du avslutar turhanteraren använder du metoden saveChanges() för tillståndshanteringsobjekt för att skriva tillbaka alla tillståndsändringar till lagringen.
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));
}
Om user_profile.name är tomt och conversation_data.prompted_for_user_name är sant hämtar roboten det namn som användaren anger och lagrar det i användarens tillstånd.
Om user_profile.name är tom och conversation_data.prompted_for_user_name är falsk frågar roboten efter användarens namn.
Om user_profile.name den tidigare har lagrats hämtar roboten meddelandetid och kanal-ID från användarens indata, ekar tillbaka data till användaren och lagrar hämtade data i konversationstillståndet.
robotar/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 }"
)
Innan varje dialogruta avslutas använder roboten metoden för tillståndshanteringsobjekt save_changes för att spara alla ändringar genom att skriva tillståndsinformation i lagringen.
Alla tillståndshanteringsanrop är asynkrona och senaste skriv-vinner som standard. I praktiken bör du hämta, ange och spara tillstånd så nära varandra i roboten som möjligt. En diskussion om hur du implementerar optimistisk låsning finns i Implementera anpassad lagring för din robot.
Viktiga affärsdata
Använd robottillstånd för att lagra inställningar, användarnamn eller det sista de beställde, men använd det inte för att lagra viktiga affärsdata. För kritiska data skapar du egna lagringskomponenter eller skriver direkt till lagringen.
Recognizer-Text
Exemplet använder Biblioteken Microsoft/Recognizers-Text för att parsa och verifiera användarindata. Mer information finns på översiktssidan.
Nästa steg
Lär dig hur du ställer en rad frågor till användaren, validerar deras svar och sparar indata.