上下文供应者

上下文提供程序围绕每个调用运行,在执行前添加上下文,并在执行后处理数据。

注释

有关可与代理一起使用的预构建上下文提供程序的列表,请参阅 集成

内置模式

创建代理时,通过构造函数选项配置提供程序。 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));

小窍门

有关预建 AIContextProvider 实现的列表,请参阅 集成

常规模式是在创建代理时,通过 context_providers=[...] 配置提供程序。

InMemoryHistoryProvider 是用于本地聊天内存的内置历史记录提供程序。

from agent_framework import InMemoryHistoryProvider
from agent_framework.openai import OpenAIChatClient

agent = OpenAIChatClient().as_agent(
    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",但当你希望本地内存行为具有确定性时,请显式添加它。

在创建代理时,通过 agent.Config.ContextProviders 配置提供程序。 上下文提供程序会在每次代理运行之前注入额外上下文,并且可以在每次运行后持久化状态。

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

自定义上下文提供程序

需要注入动态指令/消息/工具或在运行后提取状态时,请使用自定义上下文提供程序。

上下文提供程序的基类为 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 实现通常会重写两个方法:

  • 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.InvokingCoreAsync - 在代理调用 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(".")

注释

ContextProviderHistoryProvider 是规范Python基类。

上下文提供者还可以通过调用context.extend_middleware(self.source_id, middleware)为当前调用添加聊天中间件或功能中间件。 在调用聊天客户端之前,智能体会使用 context.get_middleware() 整理这些添加内容,并按提供程序顺序应用。

动态工具选择

上下文提供程序可以通过 context.extend_tools(self.source_id, tools) 为当前调用添加工具。 有关在函数调用循环过程中逐步加载工具的信息,请参阅 dynamic_tool_exposure 示例。 有关工具箱的详细信息,请参阅 Foundry 工具箱

自定义历史记录提供程序

历史记录提供程序是专用于加载/存储消息的上下文提供程序。

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])

重要

在Python中,可以配置多个历史记录提供程序,但 only one应使用 load_messages=True。 使用额外的提供程序来利用 load_messages=Falsestore_context_messages=True 进行诊断/评估,以便从其他提供程序以及输入/输出中捕获上下文。 如果需要在工具循环中围绕每个模型调用保留本地历史记录,请参阅 存储

示例模式:

primary = DatabaseHistoryProvider(db)
audit = InMemoryHistoryProvider("audit", load_messages=False, store_context_messages=True)
agent = OpenAIChatClient().as_agent(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
},

后续步骤