コンテキスト プロバイダー

コンテキスト プロバイダーは、各呼び出しを実行して実行前にコンテキストを追加し、実行後にデータを処理します。

Note

エージェントで使用できる事前構築済みコンテキスト プロバイダーの一覧については、「 コンテキスト プロバイダーの統合」を参照してください。

組み込みパターン

エージェントの作成時にコンストラクター オプションを使用してプロバイダーを構成します。 AIContextProvider は、メモリ/コンテキスト エンリッチメントの組み込みの拡張ポイントです。

AIAgent agent = new OpenAIClient("<your_api_key>")
    .GetChatClient(modelName)
    .AsAIAgent(new ChatClientAgentOptions()
    {
        ChatOptions = new() { Instructions = "You are a helpful assistant." },
        AIContextProviders = [
            new MyCustomMemoryProvider()
        ],
    });

AgentSession session = await agent.CreateSessionAsync();
Console.WriteLine(await agent.RunAsync("Remember my name is Alice.", session));

Tip

事前構築済みの AIContextProvider 実装の一覧については、 コンテキスト プロバイダーの統合に関する情報を参照してください。

通常のパターンでは、エージェントの作成時に context_providers=[...] を使用してプロバイダーを構成します。

InMemoryHistoryProvider は、ローカルの会話メモリに使用される組み込みの履歴プロバイダーです。

from agent_framework import Agent, InMemoryHistoryProvider
from agent_framework.openai import OpenAIChatClient

agent = Agent(
    client=OpenAIChatClient(),
    name="MemoryBot",
    instructions="You are a helpful assistant.",
    context_providers=[InMemoryHistoryProvider("memory", load_messages=True)],
)

session = agent.create_session()
await agent.run("Remember that I prefer vegetarian food.", session=session)

RawAgentでは、特定のケースでは既定のソース ID InMemoryHistoryProvider()を使用して"in_memory"を自動的に追加できますが、決定論的なローカル メモリ動作が必要な場合は明示的に追加します。

セッション間でファイルに基づくメモリ

FileMemoryProviderは、file_memory_* ツールを使用して、モデルが格納および呼び出しの対象を決定する必要がある場合に使用します。 Pythonでは、scopeを省略すると、現在のセッション ID から作業フォルダーが派生するため、個別のセッションではメモリ ファイルが共有されません。 セッション間で同じメモリ ファイルを共有する安定した scope (ユーザー識別子など) を渡し、バッキング ストレージの AgentFileStore 実装を選択します。

# 1. Create the file store the provider will use to persist memory files.
#    Here we use a file-system backed store rooted at a local
#    ``agent-file-memory`` folder, but any AgentFileStore implementation can
#    be used, e.g. InMemoryAgentFileStore or a custom blob-backed store.
memory_root = Path(__file__).parent / "agent-file-memory"
store = FileSystemAgentFileStore(memory_root)

# 2. Create the FileMemoryProvider over that store.
#    The ``scope`` determines the scope and lifetime of the memories:
#    - A stable scope, like the per-user one below, gives durable memories
#      shared by every session for that user. That is what allows the second
#      conversation further down to recall what the user said in the first.
#    - Omitting ``scope`` (the default) isolates memories to a single session
#      (the working folder is derived from the session id).
file_memory_provider = FileMemoryProvider(store, scope=f"users/{USER_ID}")

# 3. Attach the provider to the agent so it gets the file_memory_* tools.
agent = Agent(
    client=client,
    name="TravelAssistant",
    instructions=(
        "You are a helpful travel assistant. Remember what the user tells you about "
        "themselves so that you can give better recommendations later."
    ),
    context_providers=[file_memory_provider],
)

エージェントの作成時に agent.Config.ContextProviders を使用してプロバイダーを構成します。 コンテキスト プロバイダーは、各エージェントの実行前に追加のコンテキストを挿入し、各実行後に状態を保持できます。

a := foundryprovider.NewAgent(endpoint, token, foundryprovider.ModelDeployment(model), foundryprovider.AgentConfig{
    Config: agent.Config{
        ContextProviders: []agent.ContextProvider{provider},
    },
})

Harness Agent でコンテキスト プロバイダーを使用する

上記の手動パターンでは、選択したプロバイダーのみがアタッチされます。 Harness Agent は、作成時に順序付けされたプロバイダー セットをアセンブルします。 各 SDK の構築オプションを使用して、既定値を無効または置換し、追加のプロバイダーを追加します。

HarnessAgent では、 TodoProviderAgentModeProviderFileMemoryProvider、および AgentSkillsProvider が既定で有効になります。 これらの組み込みの後に、 HarnessAgentOptions.AIContextProviders からプロバイダーを追加します。

HarnessAgent agent = chatClient.AsHarnessAgent(new HarnessAgentOptions
{
    AIContextProviders = [new MyCustomMemoryProvider()],
    DisableAgentSkillsProvider = true,
});

既定値を削除するには、 DisableTodoProviderDisableAgentModeProviderDisableFileMemory、および DisableAgentSkillsProvider を使用します。 AgentModeProviderOptionsAgentSkillsSourceを使用してモードとスキルを構成し、ファイル メモリ ストレージをFileMemoryStoreに置き換えます。 ファイル アクセスは FileAccessStoreFileAccessProviderOptionsを通じてオプトインされ、バックグラウンド委任は BackgroundAgentsBackgroundAgentsProviderOptionsを通じてオプトインされます。 AsHarnessAgent(options)同じHarnessAgentOptionsを受け入れるnew HarnessAgent(chatClient, options)

create_harness_agent は、最初に履歴プロバイダーを並べ替え、有効にすると実行後の圧縮を行い、次に todo、mode、およびファイル メモリ プロバイダーを並べ替えます。 ファイル メモリは既定でオンになっています。スキル、ファイル アクセス、バックグラウンド エージェント、シェル コンテキストはオプトインされます。 context_providers=経由で渡されたプロバイダーは最後に追加されます。

agent = create_harness_agent(
    client,
    context_providers=[UserPreferenceProvider()],
    disable_mode=True,
    skills_paths=["./skills"],
)

history_providertodo_provider、およびmode_providerを使用して、これらの既定値をオプトアウトとしてdisable_tododisable_mode、およびdisable_file_memoryに置き換えます。 file_memory_storeを使用して、既定の{cwd}/agent-file-memory ストアを置き換えます。 file_access_storeskills_provider、またはskills_pathsbackground_agentsshell_executorを使用して省略可能なプロバイダーを有効にします。関連するセットアップ パラメーターによって、アクセス許可、手順、環境の動作が構成されます。

Harness エージェントは現在、Go SDK では使用できません。 agent.Config.ContextProvidersを使用してコンテキスト プロバイダーを明示的に追加します。

カスタム コンテキスト プロバイダー

動的命令/メッセージ/ツールを挿入したり、実行後に状態を抽出したりする必要がある場合は、カスタム コンテキスト プロバイダーを使用します。

コンテキスト プロバイダーの基本クラスは、Microsoft.Agents.AI.AIContextProviderです。 コンテキスト プロバイダーは、エージェント パイプラインに参加し、エージェント入力メッセージに貢献したりオーバーライドしたりする機能を持ち、新しいメッセージから情報を抽出できます。 AIContextProvider には、独自のカスタム コンテキスト プロバイダーを実装するためにオーバーライドできるさまざまな仮想メソッドがあります。 オーバーライドする内容の詳細については、以下のさまざまな実装オプションを参照してください。

AIContextProvider 状態

AIContextProvider インスタンスはエージェントにアタッチされ、すべてのセッションで同じインスタンスが使用されます。 つまり、 AIContextProvider は、プロバイダー インスタンスにセッション固有の状態を格納しないでください。 AIContextProviderは、フィールド内のメモリ サービス クライアントへの参照を持つことができますが、フィールド内のメモリの特定のセットの ID を持つべきではありません。

代わりに、 AIContextProvider は、メモリ ID、メッセージ、または AgentSession 自体に関連するその他の任意のセッション固有の値を格納できます。 AIContextProviderの仮想メソッドはすべて、現在のAIAgentAgentSessionへの参照を渡されます。

型指定された状態を AgentSessionに簡単に格納できるようにするために、ユーティリティ クラスが用意されています。

// First define a type containing the properties to store in state
internal class MyCustomState
{
    public string? MemoryId { get; set; }
}

// Create the helper
var sessionStateHelper = new ProviderSessionState<MyCustomState>(
    // stateInitializer is called when there is no state in the session for this AIContextProvider yet
    stateInitializer: currentSession => new MyCustomState() { MemoryId = Guid.NewGuid().ToString() },
    // The key under which to store state in the session for this provider. Make sure it does not clash with the keys of other providers.
    stateKey: this.GetType().Name,
    // An optional jsonSerializerOptions to control the serialization/deserialization of the custom state object
    jsonSerializerOptions: myJsonSerializerOptions);

// Using the helper you can read state:
MyCustomState state = sessionStateHelper.GetOrInitializeState(session);
Console.WriteLine(state.MemoryId);

// And write state:
sessionStateHelper.SaveState(session, state);

単純な AIContextProvider 実装

最も単純な AIContextProvider 実装では、通常、次の 2 つのメソッドをオーバーライドします。

  • AIContextProvider.ProvideAIContextAsync - 関連するデータを読み込み、追加の命令、メッセージ、またはツールを返します。
  • AIContextProvider.StoreAIContextAsync - 新しいメッセージから関連するデータを抽出し、格納します。

メモリ サービスと統合される単純な AIContextProvider の例を次に示します。

internal sealed class SimpleServiceMemoryProvider : AIContextProvider
{
    private readonly ProviderSessionState<State> _sessionState;
    private readonly ServiceClient _client;

    public SimpleServiceMemoryProvider(ServiceClient client, Func<AgentSession?, State>? stateInitializer = null)
        : base(null, null)
    {
        this._sessionState = new ProviderSessionState<State>(
            stateInitializer ?? (_ => new State()),
            this.GetType().Name);
        this._client = client;
    }

    public override string StateKey => this._sessionState.StateKey;

    protected override ValueTask<AIContext> ProvideAIContextAsync(InvokingContext context, CancellationToken cancellationToken = default)
    {
        var state = this._sessionState.GetOrInitializeState(context.Session);

        if (state.MemoriesId == null)
        {
            // No stored memories yet.
            return new ValueTask<AIContext>(new AIContext());
        }

        // Find memories that match the current user input.
        var memories = this._client.LoadMemories(state.MemoriesId, string.Join("\n", context.AIContext.Messages?.Select(x => x.Text) ?? []));

        // Return a new message that contains the text from any memories that were found.
        return new ValueTask<AIContext>(new AIContext
        {
            Messages = [new ChatMessage(ChatRole.User, "Here are some memories to help answer the user question: " + string.Join("\n", memories.Select(x => x.Text)))]
        });
    }

    protected override async ValueTask StoreAIContextAsync(InvokedContext context, CancellationToken cancellationToken = default)
    {
        var state = this._sessionState.GetOrInitializeState(context.Session);
        // Create a memory container in the service for this session
        // and save the returned id in the session.
        state.MemoriesId ??= this._client.CreateMemoryContainer();
        this._sessionState.SaveState(context.Session, state);

        // Use the service to extract memories from the user input and agent response.
        await this._client.StoreMemoriesAsync(state.MemoriesId, context.RequestMessages.Concat(context.ResponseMessages ?? []), cancellationToken);
    }

    public class State
    {
        public string? MemoriesId { get; set; }
    }
}

AIContextProvider の高度な実装

より高度な実装では、次のメソッドをオーバーライドすることを選択できます。

  • AIContextProvider.InvokeingCoreAsync - エージェントが LLM を呼び出す前に呼び出され、要求メッセージの一覧、ツール、および命令を変更できます。
  • AIContextProvider.InvokedCoreAsync - エージェントが LLM を呼び出した後に呼び出され、すべての要求メッセージと応答メッセージへのアクセスが許可されます。

AIContextProvider は、 InvokingCoreAsyncInvokedCoreAsyncの基本実装を提供します。

InvokingCoreAsync基本実装では、次の処理が行われます。

  • は、呼び出し元によってエージェントに渡されたメッセージのみに入力メッセージ リストをフィルター処理します。 このフィルターは、provideInputMessageFilter コンストラクターの AIContextProvider パラメーターを使用してオーバーライドできることに注意してください。
  • は、フィルター処理された要求メッセージ、既存のツール、および命令を使用して ProvideAIContextAsync を呼び出します。
  • は、 ProvideAIContextAsync によって返されるすべてのメッセージにソース情報をスタンプし、これらのメッセージがこのコンテキスト プロバイダーから送信されていることを示します。
  • は、 ProvideAIContextAsync によって返されたメッセージ、ツール、命令を既存のメッセージとマージして、エージェントによって使用される入力を生成します。 メッセージ、ツール、および命令は、既存のものに追加されます。

InvokedCoreAsync ベースでは、次の処理が行われます。

  • は、実行が失敗したかどうかを確認し、失敗した場合は、それ以上の処理を行わずに戻ります。
  • は、呼び出し元によってエージェントに渡されたメッセージのみに入力メッセージ リストをフィルター処理します。 このフィルターは、storeInputMessageFilter コンストラクターの AIContextProvider パラメーターを使用してオーバーライドできることに注意してください。
  • は、フィルター処理された要求メッセージとすべての応答メッセージをストレージの StoreAIContextAsync に渡します。

これらのメソッドをオーバーライドして AIContextProviderを実装することはできますが、実装者は必要に応じて基本機能自体を実装する必要があります。 このような実装の例を次に示します。

internal sealed class AdvancedServiceMemoryProvider : AIContextProvider
{
    private readonly ProviderSessionState<State> _sessionState;
    private readonly ServiceClient _client;

    public AdvancedServiceMemoryProvider(ServiceClient client, Func<AgentSession?, State>? stateInitializer = null)
        : base(null, null)
    {
        this._sessionState = new ProviderSessionState<State>(
            stateInitializer ?? (_ => new State()),
            this.GetType().Name);
        this._client = client;
    }

    public override string StateKey => this._sessionState.StateKey;

    protected override async ValueTask<AIContext> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
    {
        var state = this._sessionState.GetOrInitializeState(context.Session);

        if (state.MemoriesId == null)
        {
            // No stored memories yet.
            return new AIContext();
        }

        // We only want to search for memories based on user input, and exclude chat history or other AI context provider messages.
        var filteredInputMessages = context.AIContext.Messages?.Where(m => m.GetAgentRequestMessageSourceType() == AgentRequestMessageSourceType.External);

        // Find memories that match the current user input.
        var memories = this._client.LoadMemories(state.MemoriesId, string.Join("\n", filteredInputMessages?.Select(x => x.Text) ?? []));

        // Create a message for the memories, and stamp it to indicate where it came from.
        var memoryMessages =
            [new ChatMessage(ChatRole.User, "Here are some memories to help answer the user question: " + string.Join("\n", memories.Select(x => x.Text)))]
            .Select(m => m.WithAgentRequestMessageSource(AgentRequestMessageSourceType.AIContextProvider, this.GetType().FullName!));

        // Return a new merged AIContext.
        return new AIContext
        {
            Instructions = context.AIContext.Instructions,
            Messages = context.AIContext.Messages.Concat(memoryMessages),
            Tools = context.AIContext.Tools
        };
    }

    protected override async ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
    {
        if (context.InvokeException is not null)
        {
            return;
        }

        var state = this._sessionState.GetOrInitializeState(context.Session);
        // Create a memory container in the service for this session
        // and save the returned id in the session.
        state.MemoriesId ??= this._client.CreateMemoryContainer();
        this._sessionState.SaveState(context.Session, state);

        // We only want to store memories based on user input and agent output, and exclude messages from chat history or other AI context providers to avoid feedback loops.
        var filteredRequestMessages = context.RequestMessages.Where(m => m.GetAgentRequestMessageSourceType() == AgentRequestMessageSourceType.External);

        // Use the service to extract memories from the user input and agent response.
        await this._client.StoreMemoriesAsync(state.MemoriesId, filteredRequestMessages.Concat(context.ResponseMessages ?? []), cancellationToken);
    }

    public class State
    {
        public string? MemoriesId { get; set; }
    }
}
from typing import Any

from agent_framework import AgentSession, ContextProvider, SessionContext


class UserPreferenceProvider(ContextProvider):
    def __init__(self) -> None:
        super().__init__("user-preferences")

    async def before_run(
        self,
        *,
        agent: Any,
        session: AgentSession,
        context: SessionContext,
        state: dict[str, Any],
    ) -> None:
        if favorite := state.get("favorite_food"):
            context.extend_instructions(self.source_id, f"User's favorite food is {favorite}.")

    async def after_run(
        self,
        *,
        agent: Any,
        session: AgentSession,
        context: SessionContext,
        state: dict[str, Any],
    ) -> None:
        for message in context.input_messages:
            text = (message.text or "") if hasattr(message, "text") else ""
            if isinstance(text, str) and "favorite food is" in text.lower():
                state["favorite_food"] = text.split("favorite food is", 1)[1].strip().rstrip(".")

Note

ContextProvider および HistoryProvider は、正規のPython基底クラスです。

コンテキスト プロバイダーは、 context.extend_middleware(self.source_id, middleware)を呼び出すことによって、現在の呼び出しのチャットまたは関数ミドルウェアを追加することもできます。 エージェントは、これらの追加を context.get_middleware() でフラット化し、チャット クライアントを呼び出す前にプロバイダーの順序で適用します。

動的ツールの選択

コンテキスト プロバイダーは、 context.extend_tools(self.source_id, tools)を使用して現在の呼び出し用のツールを追加できます。 関数呼び出しループ中のプログレッシブ ツールの読み込みについては、 dynamic_tool_exposureサンプルを参照してください。 マネージド ツール バンドルについては、「Foundry ツールボックスMicrosoft参照してください

カスタム履歴プロバイダー

履歴プロバイダーは、メッセージの読み込み/格納に特化したコンテキスト プロバイダーです。

from collections.abc import Sequence
from typing import Any

from agent_framework import HistoryProvider, Message


class DatabaseHistoryProvider(HistoryProvider):
    def __init__(self, db: Any) -> None:
        super().__init__("db-history", load_messages=True)
        self._db = db

    async def get_messages(
        self,
        session_id: str | None,
        *,
        state: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> list[Message]:
        key = (state or {}).get("history_key", session_id or "default")
        rows = await self._db.load_messages(key)
        return [Message.from_dict(row) for row in rows]

    async def save_messages(
        self,
        session_id: str | None,
        messages: Sequence[Message],
        *,
        state: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> None:
        if not messages:
            return
        if state is not None:
            key = state.setdefault("history_key", session_id or "default")
        else:
            key = session_id or "default"
        await self._db.save_messages(key, [m.to_dict() for m in messages])

Important

Python では、複数の履歴プロバイダーを構成できますが、を使用する必要があるのは load_messages=Trueです。 load_messages=Falsestore_context_messages=Trueを用いて診断/評価のために追加のプロバイダーを使用し、入力/出力とともに他のプロバイダーからコンテキストを取得します。 ツール ループ内の各モデル呼び出しの周りにローカル履歴を保持する必要がある場合は、「 ストレージ」を参照してください。

パターンの例:

primary = DatabaseHistoryProvider(db)
audit = InMemoryHistoryProvider("audit", load_messages=False, store_context_messages=True)
agent = Agent(client=OpenAIChatClient(), context_providers=[primary, audit])

Provideコールバックを使用してカスタム コンテキスト プロバイダーを定義します。

import (
    "context"

    "github.com/microsoft/agent-framework-go/agent"
    "github.com/microsoft/agent-framework-go/message"
)

provider := agent.NewContextProvider(agent.ContextProviderConfig{
    SourceID: "user_memory",
    Provide: func(ctx context.Context, invoking agent.InvokingContext) ([]*message.Message, []agent.Option, error) {
        return nil, []agent.Option{agent.WithInstructions("User prefers short answers.")}, nil
    },
})

コンテキスト プロバイダーは、セッション状態の読み取りと書き込みを行うことができます。

Provide: func(ctx context.Context, invoking agent.InvokingContext) ([]*message.Message, []agent.Option, error) {
    session, _ := agent.GetOption(invoking.Options, agent.WithSession)
    var state MyState
    _, _ = session.Get("my_key", &state)
    return nil, nil, nil
},
Store: func(ctx context.Context, invoked agent.InvokedContext) error {
    session, _ := agent.GetOption(invoked.Options, agent.WithSession)
    session.Set("my_key", updatedState)
    return nil
},

次のステップ