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 användarens inmatning tas emot kontrollerar exemplet den lagrade konversationsstatusen för att se om användaren tidigare blivit ombedd 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
Varning
Det verkar som om exemplet du letar efter har flyttats! Var säker på att vi arbetar med att lösa detta.
ConversationData.java
Varning
Det verkar som om exemplet du letar efter har flyttats! Var säker på att vi arbetar med att lösa detta.
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 du MemoryStorage, 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 autentiseringsleverantör, en adapter 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 du MemoryStorage som sedan används för att skapa UserState och ConversationState objekten. 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);
bots/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 du StateManagementBot 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
Varning
Det verkar som om exemplet du letar efter har flyttats! Var säker på att vi arbetar med att lösa detta.
Därefter registrerar du MemoryStorage 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.
bot/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 stategenskaperna, använder du varje accessor för att läsa in egenskapen från lagringen och hämta den från state-cachen. För att hämta den rätta nyckeln med korrekt omfång som är kopplad till tillståndsegenskapen, anropar du GetAsync-metoden.
Bots/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.
bots/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 . Varje tillståndsegenskapsåtkomst tillåter dig att hämta eller ange värdet på den associerade tillståndsegenskapen. Innan du använder tillståndsegenskaperna, använd varje åtkomstmetod för att läsa in egenskapen från lagringen och hämta den från tillståndscachen. Om du vill hämta rätt omfångsnyckel som är associerad med tillståndsegenskapen get anropar du metoden.
StateManagementBot.java
Varning
Det verkar som om exemplet du letar efter har flyttats! Var säker på att vi arbetar med att lösa detta.
Nu skapar du egenskapsåtkomster för UserProfile och ConversationData. Med varje åtkomstmetod för tillståndsvariabel kan du hämta eller ange värdet för den associerade tillståndsvariabeln. 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 åtkomstmetoder vid körning för att läsa och skriva tillståndsdata. 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.
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}");
}
}
Innan du avslutar turhanteraren använder du tillståndshanteringsobjektens SaveChangesAsync() -metod för att skriva tillbaka alla tillståndsändringar till lagringen.
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);
}
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.
bots/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.
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);
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
Varning
Det verkar som om exemplet du letar efter har flyttats! Var säker på att vi arbetar med att lösa detta.
Innan du avslutar turhanteraren använder du tillståndshanteringsobjektens metod saveChanges() för att skriva tillbaka alla tillståndsändringar till lagringen.
StateManagementBot.java
Varning
Det verkar som om exemplet du letar efter har flyttats! Var säker på att vi arbetar med att lösa detta.
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 tidigare har lagrats hämtar roboten meddelandetid och kanal-ID från användarens indata, återger data till användaren och lagrar de hämtade data i konversationstillståndet.
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 }"
)
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 sist skrivna 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.
Igenkännings-text
Exemplet använder Microsoft/Recognizers-Text-biblioteken för att tolka och verifiera användarinmatning. 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.