Skip to content

Commit 3a84c3a

Browse files
westey-mCopiloteavanvalkenburg
authored
Add agent pipeline page (#916)
* Add agent pipeline page * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Add python improvements * Resize images * Move python note to end * Fix AIContextProvider->ContextProvider python text * Apply suggestions from code review Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com> * Add observability link * Move agent-pipeline higher in the TOC * Revert skills.md changes --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
1 parent 7f94737 commit 3a84c3a

6 files changed

Lines changed: 536 additions & 1 deletion

File tree

agent-framework/TOC.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ items:
2424
href: agents/index.md
2525
- name: Running Agents
2626
href: agents/running-agents.md
27+
- name: Agent Pipeline
28+
href: agents/agent-pipeline.md
2729
- name: Multimodal
2830
href: agents/multimodal.md
2931
- name: Structured Output
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
---
2+
title: Agent Pipeline Architecture
3+
description: Understand how agents build their internal pipeline of middleware, context providers, and chat clients.
4+
zone_pivot_groups: programming-languages
5+
author: eavanvalkenburg
6+
ms.topic: conceptual
7+
ms.author: edvan
8+
ms.date: 03/11/2026
9+
ms.service: agent-framework
10+
---
11+
12+
# Agent pipeline architecture
13+
14+
Agents in Microsoft Agent Framework use a layered pipeline architecture to process requests. Understanding this architecture helps you customize agent behavior by adding middleware, context providers, or client-level modifications at the appropriate layer.
15+
16+
::: zone pivot="programming-language-csharp"
17+
18+
## ChatClientAgent Pipeline
19+
20+
![C# Agent Pipeline Architecture](../media/agent-pipeline-csharp.svg)
21+
22+
The `ChatClientAgent` builds a pipeline with three main layers:
23+
24+
1. **Agent middleware** - Optional decorators that wrap the agent via `.Use()` for logging, validation, or transformation
25+
2. **Context layer** - Manages chat history (`ChatHistoryProvider`) and injects additional context (`AIContextProviders`)
26+
3. **Chat client layer** - The `IChatClient` with optional middleware decorators that handle LLM communication
27+
28+
When you call `RunAsync()`, your request flows through each layer in sequence.
29+
30+
::: zone-end
31+
32+
::: zone pivot="programming-language-python"
33+
34+
## Agent Pipeline
35+
36+
![Python Agent Pipeline Architecture](../media/agent-pipeline-python.svg)
37+
38+
The `Agent` class builds a pipeline through class composition with two main components:
39+
40+
**Agent** (outer component):
41+
42+
1. **Agent Middleware + Telemetry** - the `AgentMiddlewareLayer` and `AgentTelemetryLayer` classes handle middleware invocation and OpenTelemetry instrumentation
43+
2. **RawAgent** - Core agent logic that invokes context providers
44+
3. **Context Providers** - Unified `context_providers` list manages history and additional context
45+
46+
**ChatClient** (separate and interchangeable component):
47+
48+
1. **Chat Middleware + Telemetry** - Optional middleware chain and instrumentation layers
49+
2. **FunctionInvocation** - Handles tool calling loop, invoking Function Middleware + Telemetry per tool call
50+
3. **RawChatClient** - Provider-specific implementation (Azure OpenAI, OpenAI, Anthropic, etc.) that communicates with the LLM
51+
52+
When you call `run()`, your request flows through the Agent layers, then into the ChatClient pipeline for LLM communication.
53+
54+
::: zone-end
55+
56+
### Agent middleware layer
57+
58+
Agent middleware intercepts every call to the agent's run method, allowing you to inspect or modify inputs and outputs.
59+
60+
::: zone pivot="programming-language-csharp"
61+
62+
Add middleware using the agent builder pattern:
63+
64+
```csharp
65+
var middlewareAgent = originalAgent
66+
.AsBuilder()
67+
.Use(runFunc: MyAgentMiddleware, runStreamingFunc: MyStreamingMiddleware)
68+
.Build();
69+
```
70+
71+
You can also use `MessageAIContextProvider` as agent middleware to inject additional messages into the request. This works with any agent type, not just `ChatClientAgent`:
72+
73+
```csharp
74+
var contextAgent = originalAgent
75+
.AsBuilder()
76+
.UseAIContextProviders(new MyMessageContextProvider())
77+
.Build();
78+
```
79+
80+
This layer wraps the entire agent execution, including context resolution and chat client calls.
81+
This has benefits, in that these decorators can be used with any type of agent, e.g. `A2AAgent` or `GitHubCopilotAgent`, not just `ChatClientAgent`.
82+
This also means that decorators at this level cannot necessarily make assumptions about the agent that it is decorating, meaning that it is restricted to customizing or affecting common functionality.
83+
84+
::: zone-end
85+
86+
::: zone pivot="programming-language-python"
87+
88+
Add middleware when creating the agent:
89+
90+
```python
91+
from agent_framework import Agent
92+
93+
agent = Agent(
94+
client=my_client,
95+
instructions="You are helpful.",
96+
middleware=[my_middleware_func],
97+
)
98+
```
99+
100+
The `Agent` class inherits from `AgentMiddlewareLayer`, which handles middleware invocation before delegating to the core agent logic.
101+
It also inherits from `AgentTelemetryLayer` which handles emitting spans, events and metrics to a configured OpenTelemetry backend.
102+
Both of these layers, do nothing when they are not configured.
103+
::: zone-end
104+
105+
For detailed middleware and observability patterns, see [Agent Middleware](./middleware/index.md) and [Observability](./observability.md).
106+
107+
### Context layer
108+
109+
The context layer runs before each LLM call to build the full message history and inject additional context.
110+
111+
::: zone pivot="programming-language-csharp"
112+
113+
`ChatClientAgent` has two distinct provider types:
114+
115+
- **`ChatHistoryProvider`** (single) - Manages conversation history storage and retrieval
116+
- **`AIContextProviders`** (list) - Injects additional context like memories, retrieved documents, or dynamic instructions
117+
118+
```csharp
119+
var agent = new ChatClientAgent(chatClient, new ChatClientAgentOptions
120+
{
121+
ChatHistoryProvider = new InMemoryChatHistoryProvider(),
122+
AIContextProviders = [new MyMemoryProvider(), new MyRagProvider()],
123+
});
124+
```
125+
126+
The agent calls each provider's `InvokingAsync()` method before sending messages to the chat client with each provider's output passed as input to the next provider.
127+
128+
::: zone-end
129+
130+
::: zone pivot="programming-language-python"
131+
132+
The `Agent` class uses a unified `context_providers` list that can include both history providers and context providers:
133+
134+
```python
135+
from agent_framework import Agent, InMemoryHistoryProvider
136+
137+
agent = Agent(
138+
client=my_client,
139+
context_providers=[
140+
InMemoryHistoryProvider(),
141+
MyMemoryProvider(),
142+
MyRagProvider(),
143+
],
144+
)
145+
```
146+
147+
::: zone-end
148+
149+
For detailed context provider patterns, see [Context Providers](./conversations/context-providers.md).
150+
151+
### Chat client layer
152+
153+
The chat client layer handles the actual communication with the LLM service.
154+
155+
::: zone pivot="programming-language-csharp"
156+
157+
`ChatClientAgent` uses an `IChatClient` instance, which can be decorated with additional middleware:
158+
159+
```csharp
160+
var chatClient = new AzureOpenAIClient(endpoint, credential)
161+
.GetChatClient(deploymentName)
162+
.AsIChatClient()
163+
.AsBuilder()
164+
.Use(CustomChatClientMiddleware)
165+
.Build();
166+
167+
var agent = new ChatClientAgent(chatClient, instructions: "You are helpful.");
168+
```
169+
170+
You can also use `AIContextProvider` as chat client middleware to enrich messages, tools, and instructions at the client level. This must be used within the context of a running `AIAgent`:
171+
172+
```csharp
173+
var chatClient = new AzureOpenAIClient(endpoint, credential)
174+
.GetChatClient(deploymentName)
175+
.AsIChatClient()
176+
.AsBuilder()
177+
.UseAIContextProviders(new MyContextProvider())
178+
.Build();
179+
180+
var agent = new ChatClientAgent(chatClient, instructions: "You are helpful.");
181+
```
182+
183+
By default, `ChatClientAgent` wraps the provided chat client with function-calling support. Set `UseProvidedChatClientAsIs = true` in options to skip this default wrapping.
184+
185+
::: zone-end
186+
187+
::: zone pivot="programming-language-python"
188+
189+
The `Agent` class accepts any client that implements `SupportsChatGetResponse`. The ChatClient pipeline handles middleware, telemetry, function invocation, and provider-specific communication:
190+
191+
```python
192+
from agent_framework import Agent
193+
from agent_framework.azure import AzureOpenAIResponsesClient
194+
195+
client = AzureOpenAIResponsesClient(
196+
credential=credential,
197+
project_endpoint=endpoint,
198+
deployment_name=model,
199+
)
200+
201+
agent = Agent(client=client, instructions="You are helpful.")
202+
```
203+
204+
The `RawChatClient` within the ChatClient implements the provider-specific logic for communicating with different LLM services.
205+
206+
::: zone-end
207+
208+
### Execution flow
209+
210+
When you invoke an agent, the request flows through the pipeline:
211+
212+
::: zone pivot="programming-language-csharp"
213+
214+
1. **Agent middleware** executes (if configured)
215+
2. **ChatHistoryProvider** loads conversation history into the request message list
216+
3. **AIContextProviders** add messages, tools, or instructions to the request
217+
4. **IChatClient middleware** executes (if decorated)
218+
5. **IChatClient** sends the request to the LLM
219+
6. Response flows back through the same layers
220+
7. **ChatHistoryProvider** and **AIContextProviders** are notified of new messages
221+
222+
::: zone-end
223+
224+
::: zone pivot="programming-language-python"
225+
226+
**Agent pipeline:**
227+
228+
1. **Agent Middleware + Telemetry** executes middleware (if configured) and records spans
229+
2. **RawAgent** invokes context providers to load history and add context
230+
3. Request is passed to the ChatClient
231+
232+
**ChatClient pipeline:**
233+
234+
4. **Chat Middleware + Telemetry** executes (if configured)
235+
5. **FunctionInvocation** sends request to the LLM and handles tool calling loop
236+
- For each tool call, **Function Middleware + Telemetry** executes
237+
6. **RawChatClient** handles provider-specific LLM communication
238+
7. Response flows back through the same layers
239+
8. **Context providers** are notified of new messages for storage
240+
241+
> [!NOTE]
242+
> Specialized agents may work differently to the pipeline described here.
243+
244+
::: zone-end
245+
246+
::: zone pivot="programming-language-csharp"
247+
248+
## Other agent types
249+
250+
Not all agents use the full `ChatClientAgent` pipeline. Agents like `A2AAgent`, `GitHubCopilotAgent`, or `CopilotStudioAgent` communicate with remote services rather than using a local `IChatClient`. However, they still support agent-level middleware.
251+
252+
![Other Agent Types Pipeline](../media/agent-pipeline-other.svg)
253+
254+
Since these agents derive from `AIAgent`, you can use the same agent middleware patterns:
255+
256+
```csharp
257+
// Agent middleware works with any AIAgent
258+
var a2aAgent = originalA2AAgent
259+
.AsBuilder()
260+
.Use(runFunc: LoggingMiddleware)
261+
.UseAIContextProviders(new MyMessageContextProvider())
262+
.Build();
263+
264+
// Same pattern works for GitHubCopilotAgent
265+
var copilotAgent = originalCopilotAgent
266+
.AsBuilder()
267+
.Use(runFunc: AuditMiddleware)
268+
.Build();
269+
```
270+
271+
> [!NOTE]
272+
> You cannot add chat client middleware to these agents because they don't use `IChatClient`.
273+
274+
::: zone-end
275+
276+
::: zone pivot="programming-language-python"
277+
::: zone-end
278+
279+
## Next steps
280+
281+
> [!div class="nextstepaction"]
282+
> [Multimodal](./multimodal.md)
283+
284+
### Related content
285+
286+
- [Middleware](./middleware/index.md) - Add cross-cutting behavior to your agents
287+
- [Context Providers](./conversations/context-providers.md) - Detailed patterns for history and context injection
288+
- [Running Agents](./running-agents.md) - How to invoke agents

agent-framework/agents/running-agents.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,4 +320,4 @@ for message in response.messages:
320320
## Next steps
321321

322322
> [!div class="nextstepaction"]
323-
> [Multimodal](./multimodal.md)
323+
> [Agent Pipeline](./agent-pipeline.md)
Lines changed: 89 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)