適用対象: SDK v4
ボットは本質的にステートレスです。 いったんご自身のボットがデプロイされると、ターンをまたいで、そのボットを同じプロセスやマシンで実行することはできません。 しかし、お使いのボットでは、会話のコンテキストの追跡が必要になることがあります。これにより、自身の動作を管理し、以前の質問に対する回答を記憶できるようになります。 Bot Framework SDK の状態とストレージの機能を使用すると、状態をお使いのボットに追加できます。 ボットは状態管理およびストレージ オブジェクトを使用して状態を管理維持します。 この状態マネージャーは、基になるストレージの種類には関係なく、プロパティ アクセサーを使用して状態プロパティにアクセスできる抽象化レイヤーを提供します。
メモ
AI サービス、オーケストレーション、知識を選択してエージェントを構築するには、Microsoft 365 Agents SDK の使用を検討してください。 Agents SDK では、C#、JavaScript、または Python がサポートされています。 Agents SDK の詳細については、 aka.ms/agents を参照してください。 SaaS ベースのエージェント プラットフォームをお探しの場合は、 Microsoft Copilot Studio を検討してください。 Bot Framework SDK を使用して構築された既存のボットがある場合は、ボットを Agents SDK に更新できます。
Bot Framework SDK から Agents SDK への移行ガイダンスで、主要な変更と更新プログラムを確認できます。 Bot Framework SDK のサポート チケットは、2025 年 12 月 31 日の時点で提供されなくなります。
前提条件
このサンプルについて
このサンプルでは、ユーザー入力を受け取ったときに、保存済み会話状態を確認して、ユーザーが以前に名前を入力するように求められたかどうかを確かめます。 求められていない場合は、ユーザーの名前が要求され、入力した内容はユーザー状態に格納されます。 この場合、ユーザー状態に格納されている名前は、ユーザーとの対話に使用され、その入力データは、受信時刻および入力チャネル ID と共にユーザーに返されます。 時刻とチャネル ID の値はユーザーの会話データから取得され、会話状態に保存されます。 次の図は、ボット、ユーザー プロファイル、および会話データ クラスの間の関係を示しています。
クラスの定義
状態管理を設定するには、まず、ユーザーと会話状態で管理する情報を含むクラスを定義します。 この記事で使用している例では、次のクラスを定義しています。
-
UserProfile.cs では、ボットによって収集されるユーザー情報を表す
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;
}
JavaScript では、この手順は必要ありません。
状態管理を設定するには、まず、ユーザーと会話状態で管理する情報を含むクラスを定義します。 この記事で使用している例では、次のクラスを定義しています。
-
UserProfile.java では、ボットによって収集されるユーザー情報を表す
UserProfile
クラスを定義します。
-
ConversationData.java では、ユーザー情報を収集しているときに、会話状態を制御する
ConversationData
クラスを定義します。
次のコード例は、UserProfile
と ConversationData
クラスの定義を示しています。
UserProfile.java
警告
探しているサンプルが移動したようです。 私たちはこれを解決することに取り組んでいますのでご安心ください。
ConversationData.java
警告
探しているサンプルが移動したようです。 私たちはこれを解決することに取り組んでいますのでご安心ください。
状態管理を設定するには、まず、ユーザーと会話状態で管理する情報を含むクラスを定義します。 この記事で使用している例では、次のクラスを定義しています。
-
user_profile.py には、ボットによって収集されたユーザー情報を格納する
UserProfile
クラスが含まれています。
-
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
で作成され、依存関係がボット コンストラクターに挿入されます。 ボット用のサービスとして他に資格情報プロバイダー、アダプター、およびボット実装が登録されています。
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 で作成され、ボットの作成時に使用されます。
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
を登録します。 これらは 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
メソッドを使用して、プロパティ アクセサーを作成します。 各状態プロパティ アクセサーを使用すると、関連付けられている状態プロパティの値を取得または設定できます。 状態プロパティを使用する前に、各アクセサーを使用してストレージからプロパティを読み込んだうえで状態キャッシュから取得します。 状態プロパティに関連付けられている適切に範囲指定されたキーを取得するために、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
メソッドを使用してプロパティ アクセサーを作成します。 各状態プロパティ アクセサーを使用すると、関連付けられている状態プロパティの値を取得または設定できます。 状態プロパティを使用する前に、各アクセサーを使用してストレージからプロパティを読み込んだうえで状態キャッシュから取得します。 状態プロパティに関連付けられている適切に範囲指定されたキーを取得するために、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")
お使いのボットから状態にアクセスする
前のセクションでは、状態プロパティ アクセサーをボットに追加するための、初期化時の手順について説明しました。 これらのアクセサーを実行時に使用すると、状態情報を読み書きできます。 次のサンプル コードでは次のロジック フローを使用します。
-
userProfile.Name
が空で、conversationData.PromptedUserForName
が true の場合、指定されたユーザー名を取得し、ユーザーの状態内に格納します。
-
userProfile.Name
が空で、conversationData.PromptedUserForName
が false の場合、ユーザー名を要求します。
-
userProfile.Name
が前に格納されていた場合は、メッセージ時間とチャネル ID をユーザー入力から取得し、すべてのデータをユーザーにエコー バックして、取得したデータを会話状態内に格納します。
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
が前に格納されていた場合は、メッセージ時間とチャネル ID をユーザー入力から取得し、すべてのデータをユーザーにエコー バックして、取得したデータを会話状態内に格納します。
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()
が前に格納されていた場合は、メッセージ時間とチャネル ID をユーザー入力から取得し、すべてのデータをユーザーにエコー バックして、取得したデータを会話状態内に格納します。
StateManagementBot.java
警告
探しているサンプルが移動したようです。 私たちはこれを解決することに取り組んでいますのでご安心ください。
ターン ハンドラーを終了する前に、状態管理オブジェクトの saveChanges() メソッドを使用して、すべての状態変更をストレージに書き戻します。
StateManagementBot.java
警告
探しているサンプルが移動したようです。 私たちはこれを解決することに取り組んでいますのでご安心ください。
-
user_profile.name
が空で、conversation_data.prompted_for_user_name
が true の場合、ボットは、ユーザーによって指定された名前を取得し、ユーザーの状態内に格納します。
-
user_profile.name
が空で、conversation_data.prompted_for_user_name
が false の場合、ボットはユーザー名を要求します。
-
user_profile.name
が前に格納されていた場合、ボットは、メッセージ時間とチャネル ID をユーザー入力から取得し、データをユーザーにエコー バックして、取得したデータを会話状態内に格納します。
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 }"
)
各ダイアログ ターンの終了前に、ボットは、状態管理オブジェクトの 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 Emulator をダウンロードしてインストールします
- ご自身のマシンを使ってローカルでサンプルを実行します。
手順が必要な場合は、C#、JavaScript、Java、Python の README を参照してください。
- エミュレーターを使用してサンプル ボットをテストする
この記事では、ボットに状態を追加する方法について説明しました。 関連するトピックの詳細については、次の表をご覧ください。
トピック |
メモ |
プライバシー |
ユーザーの個人データを格納する場合は、一般データ保護規則に関するコンプライアンスを確保する必要があります。 |
状態管理 |
すべての状態管理呼び出しが非同期で行われます。これらの呼び出しは既定で Last-Writer-Wins です。 実際の場面では、状態の取得、設定、および保存は、ご自身のボット内で、できるだけ近いタイミングで行う必要があります。 楽観ロックを実装する方法の詳細については、「ボットのカスタム ストレージの実装」を参照してください。 |
重要なビジネス データ |
ボットの状態には、詳細設定、ユーザー名、またはユーザーが行った最後の指示を格納しますが、重要なビジネス データを格納するためには使用しないでください。 重要なデータについては、独自のストレージ コンポーネントを作成するか、ストレージに直接書き込んでください。 |
Recognizer-Text |
サンプルでは、ユーザー入力の解析および検証に Microsoft/Recognizers-Text ライブラリが使用されます。 詳細については、概要に関するページを参照してください。 |
次のステップ
ユーザーに対して一連の質問を行い、その回答の一部を検証して、入力を保存する方法を説明します。