代理管道体系结构

Microsoft Agent Framework 中的代理使用分层管道体系结构来处理请求。 了解这一架构有助于您通过在适当的层添加中间件、上下文提供程序或进行客户端级别的修改,来自定义代理的行为。

ChatClientAgent 管道

C# 代理管道体系结构

构建 ChatClientAgent 的管道包含三个主要层:

  1. 智能体中间件 - 通过 .Use() 包装智能体的可选装饰器,用于日志记录、验证或转换
  2. 上下文层 - 管理聊天历史记录(ChatHistoryProvider)并注入其他上下文(AIContextProviders
  3. 聊天客户端层 - 带有处理 LLM 通信的可选中间件装饰器的 IChatClient

调用 RunAsync()时,请求按顺序流经每个层。

智能体管道

Python 代理管道体系结构

Agent 类通过类组合构建管道,具有两个主要组件:

代理 (外部组件):

  1. 智能体中间件 + 遥测 - AgentMiddlewareLayerAgentTelemetryLayer 类处理中间件调用和 OpenTelemetry 检测
  2. RawAgent - 核心代理逻辑,用于调用上下文提供程序并收集提供程序添加的中间件
  3. 上下文提供程序 - 统一的 context_providers 列表管理历史记录、附加上下文和每次运行的聊天/函数中间件

ChatClient (独立组件和可互换组件):

  1. FunctionInvocation - 处理工具调用循环,调用功能中间件 + 每次工具调用的遥测
  2. 聊天中间件 + 遥测 - 可选的中间件链和每次模型调用的检测,包括上下文提供程序添加的任何聊天中间件,每个模型调用运行一次
  3. RawChatClient - 与 LLM 通信的提供程序特定实现(Azure OpenAI、OpenAI、Anthropic 等)

调用 run()时,请求将流经代理层,然后流入 ChatClient 管道进行 LLM 通信。

代理管道体系结构

Go 代理流水线架构

在 Go 中,代理使用分层中间件管道。 中间件包装代理的 Run 函数,每个调用 next 将控制权传递给下一层。

代理运行时,其生命周期按以下顺序应用:

  1. 自定义代理中间件 - 您已注册的 agent.Config.Middlewares,会按声明顺序应用于整个代理生命周期的各个环节
  2. 历史记录提供程序 - 加载先前的消息,并随后存储请求和响应消息
  3. 上下文提供程序 - 从已注册 agent.ContextProvider 实例注入上下文、选项和状态
  4. 提供方中间件 - 由提供方注册的中间件,例如工具自动调用、结构化输出和响应编写
  5. 提供方 - 底层 LLM 提供方,如 OpenAI 或 Anthropic

代理中间件层

代理中间件会截获对代理的运行方法的每个调用,使你能够检查或修改输入和输出。

使用代理生成器模式添加中间件:

var middlewareAgent = originalAgent
    .AsBuilder()
    .Use(runFunc: MyAgentMiddleware, runStreamingFunc: MyStreamingMiddleware)
    .Build();

还可以用作 MessageAIContextProvider 代理中间件,将其他消息注入请求。 这适用于任何代理类型,而不仅仅是 ChatClientAgent

var contextAgent = originalAgent
    .AsBuilder()
    .UseAIContextProviders(new MyMessageContextProvider())
    .Build();

此层包装整个代理执行,包括上下文解析和聊天客户端调用。 这有好处,因为这些修饰器可以与任何类型的代理一起使用,例如 A2AAgent ,或者 GitHubCopilotAgent,而不仅仅是 ChatClientAgent。 这也意味着此级别的装饰器不一定能对它正在装饰的智能体做出假设,这意味着它仅限于自定义或影响通用功能。

创建代理时添加中间件:

from agent_framework import Agent

agent = Agent(
    client=my_client,
    instructions="You are helpful.",
    middleware=[my_middleware_func],
)

Agent 继承自 AgentMiddlewareLayer该类,该类在委托核心代理逻辑之前处理中间件调用。 它还继承自 AgentTelemetryLayer,后者处理向配置的 OpenTelemetry 后端发出跨度、事件和指标。 这两个层在未配置时不执行任何操作。

通过实现 Middleware 接口或使用 agent.MiddlewareFunc 来添加轻量级中间件:

type Middleware interface {
    Run(next RunFunc, ctx context.Context, messages []*message.Message,
        options ...agent.Option) iter.Seq2[*agent.ResponseUpdate, error]
}

每个中间件都会接收链中的 next 函数,并且可以在调用 next 之前修改消息或选项,在调用 next 之后处理响应,或使整个管道短路。

timing := agent.MiddlewareFunc(
    func(next agent.RunFunc, ctx context.Context, messages []*message.Message, options ...agent.Option) iter.Seq2[*agent.ResponseUpdate, error] {
        start := time.Now()
        return func(yield func(*agent.ResponseUpdate, error) bool) {
            defer log.Printf("agent run completed in %s", time.Since(start))
            for update, err := range next(ctx, messages, options...) {
                if !yield(update, err) {
                    return
                }
            }
        }
    },
)

a := foundryprovider.NewAgent(endpoint, token, foundryprovider.ModelDeployment(model), foundryprovider.AgentConfig{
    Config: agent.Config{
        Middlewares: []agent.Middleware{timing},
    },
})

有关详细的中间件和可观测性模式,请参阅 代理中间件可观测性

上下文层

上下文层在每次 LLM 调用之前运行,以生成完整的消息历史记录并注入其他上下文。

ChatClientAgent 具有两种不同的提供程序类型。

  • ChatHistoryProvider (单一) - 管理对话历史记录存储和检索
  • AIContextProviders (列表) - 注入其他上下文,如记忆、检索的文档或动态指令
var agent = new ChatClientAgent(chatClient, new ChatClientAgentOptions
{
    ChatHistoryProvider = new InMemoryChatHistoryProvider(),
    AIContextProviders = [new MyMemoryProvider(), new MyRagProvider()],
});

代理先调用每个提供程序 InvokingAsync() 的方法,然后再将消息发送到聊天客户端,并将每个提供程序的输出作为输入传递给下一个提供程序。

Agent 类使用统一 context_providers 列表,该列表可以包含历史记录提供程序和上下文提供程序。

from agent_framework import Agent, InMemoryHistoryProvider

agent = Agent(
    client=my_client,
    context_providers=[
        InMemoryHistoryProvider(),
        MyMemoryProvider(),
        MyRagProvider(),
    ],
)

上下文提供程序还可以通过 SessionContext.extend_middleware() 将聊天或函数中间件附加到单个调用。 智能体在进入 ChatClient 管道之前,按提供程序顺序展平这些添加项。

上下文提供程序在自定义中间件已进入运行之后且在提供程序中间件调用模型之前,在智能体生命周期内运行。 上下文提供程序可以在提供程序调用之前添加消息或选项,并在运行后保留状态。

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

有关详细的上下文提供程序模式,请参阅 上下文提供程序

聊天客户端层

聊天客户端层处理与 LLM 服务的实际通信。

ChatClientAgent使用一个IChatClient实例,可以用额外的中间件进行修饰。

var chatClient = new AIProjectClient(endpoint, credential)
    .GetProjectOpenAIClient()
    .GetProjectResponsesClient()
    .AsIChatClient(deploymentName)
    .AsBuilder()
    .Use(CustomChatClientMiddleware)
    .Build();

var agent = new ChatClientAgent(chatClient, instructions: "You are helpful.");

你还可以使用 AIContextProvider 作为聊天客户端的中间件,以在客户端级别上增强消息、工具和指令。 这必须在运行的 AIAgent 的上下文中使用:

var chatClient = new AIProjectClient(endpoint, credential)
    .GetProjectOpenAIClient()
    .GetProjectResponsesClient()
    .AsIChatClient(deploymentName)
    .AsBuilder()
    .UseAIContextProviders(new MyContextProvider())
    .Build();

var agent = new ChatClientAgent(chatClient, instructions: "You are helpful.");

默认情况下,ChatClientAgent 会为提供的聊天客户端添加函数调用支持。 设置 UseProvidedChatClientAsIs = true 选项以跳过此默认包装。

Agent 类接受任何实现的 SupportsChatGetResponse客户端。 ChatClient 管道处理中间件、遥测、函数调用和提供程序特定的通信:

from agent_framework import Agent
from agent_framework.foundry import FoundryChatClient

client = FoundryChatClient(
    credential=credential,
    project_endpoint=endpoint,
    model=model,
)

agent = Agent(client=client, instructions="You are helpful.")

ChatClient 中的 RawChatClient 实现了与不同 LLM 服务进行通信的提供程序特定逻辑。

提供程序中间件在历史记录和上下文提供程序之后运行,紧接在底层 LLM 提供程序之前。 诸如 OpenTelemetry 和运行日志之类的代理级辅助功能会被注册为自定义代理中间件,并封装较早的生命周期步骤。

组件 Registration Purpose
自动拨号 agent/harness/toolautocall 提供程序中间件 自动调用函数工具
结构化输出 agent.WithStructuredOutput 提供程序的中间件 处理结构化输出分析
OpenTelemetry provider/otelprovider 代理中间件 跟踪代理调用
运行记录器 agent.Config.Logger 代理中间件 记录代理交互

agent.ContextProvider 值是生命周期组件,而不是 agent.Middleware 实现。 它们在自定义代理中间件和提供程序中间件之间运行。

执行流

调用代理时,请求将流经管道:

  1. 代理中间件 执行(如果已配置)
  2. ChatHistoryProvider 将对话历史记录加载到请求消息列表中
  3. AIContextProviders 向请求添加消息、工具或说明
  4. IChatClient 中间件执行(如果已装饰)
  5. IChatClient 将请求发送到 LLM
  6. 响应通过相同的层返回
  7. ChatHistoryProviderAIContextProviders 收到新消息的通知

代理管道:

  1. 智能体中间件 + 遥测执行中间件(如果已配置)并记录跨度
  2. RawAgent 调用上下文提供程序来加载历史记录、添加上下文并收集提供程序添加的聊天/函数中间件
  3. 请求传递到 ChatClient

ChatClient 流水线:

  1. FunctionInvocation 管理工具调用循环
    • 在每次工具调用时,函数中间件+遥测都会执行,包括由上下文提供程序添加的任何函数中间件。
  2. 聊天中间件 + 遥测针对每次模型调用执行(如果已配置),包括上下文提供程序添加的任何聊天中间件
  3. RawChatClient 处理提供程序特定的 LLM 通信
  4. 响应通过相同的层返回
  5. 上下文提供程序 会收到用于存储的新消息通知。

注释

专用代理的工作方式可能与此处所述的管道不同。

  1. 自定义代理中间件 首先执行并包装整个代理生命周期。
  2. 当本地历史记录处于活动状态时,历史记录提供程序会加载当前会话的会话历史记录。
  3. 上下文提供程序 在提供程序调用之前添加消息、选项或状态。
  4. 提供程序中间件执行,包括在启用时的工具自动调用中间件和结构化输出处理。
  5. 提供程序将请求发送到模型。
  6. 响应更新会经由提供程序中间件和自定义代理中间件回传。
  7. 成功运行后,历史记录提供程序上下文提供程序存储响应状态。

其他代理类型

并非所有代理都使用完整的 ChatClientAgent 管道。 代理(例如 A2AAgentGitHubCopilotAgentCopilotStudioAgent)与远程服务通信,而不是使用本地 IChatClient。 但是,它们仍支持代理级中间件。

其他代理类型管道

由于这些代理派生自 AIAgent,因此可以使用相同的代理中间件模式:

// Agent middleware works with any AIAgent
var a2aAgent = originalA2AAgent
    .AsBuilder()
    .Use(runFunc: LoggingMiddleware)
    .UseAIContextProviders(new MyMessageContextProvider())
    .Build();

// Same pattern works for GitHubCopilotAgent
var copilotAgent = originalCopilotAgent
    .AsBuilder()
    .Use(runFunc: AuditMiddleware)
    .Build();

注释

无法向这些代理添加聊天客户端中间件,因为它们不使用 IChatClient

其他代理类型

并非每个 Python 代理都使用完整的 Agent + ChatClient 管道。 GitHubCopilotAgent例如,通过 GitHub Copilot CLI 而不是本地聊天客户端发送请求。

即便如此,Python GitHubCopilotAgent 仍支持代理中间件,现在会围绕每个调用运行 context_providers 。 由提供方添加的消息和说明包含在发送到 Copilot 的提示中,当响应可用后,提供方会接收到匹配的 after_run 回调。

注释

由于 GitHubCopilotAgent 不使用本地聊天客户端,聊天客户端中间件仍不适用。

后续步骤