Skip to content

Commit ce29ad8

Browse files
committed
Middleware docs
1 parent 23f3c07 commit ce29ad8

File tree

6 files changed

+798
-0
lines changed

6 files changed

+798
-0
lines changed

docs/concepts/middleware.mdx

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
---
2+
title: "Middleware"
3+
description: "Transform and intercept events in AG-UI agents"
4+
---
5+
6+
# Middleware
7+
8+
Middleware in AG-UI provides a powerful way to transform, filter, and augment the event streams that flow through agents. It enables you to add cross-cutting concerns like logging, authentication, rate limiting, and event filtering without modifying the core agent logic.
9+
10+
## What is Middleware?
11+
12+
Middleware sits between the agent execution and the event consumer, allowing you to:
13+
14+
1. **Transform events** – Modify or enhance events as they flow through the pipeline
15+
2. **Filter events** – Selectively allow or block certain events
16+
3. **Add metadata** – Inject additional context or tracking information
17+
4. **Handle errors** – Implement custom error recovery strategies
18+
5. **Monitor execution** – Add logging, metrics, or debugging capabilities
19+
20+
## How Middleware Works
21+
22+
Middleware forms a chain where each middleware wraps the next, creating layers of functionality. When an agent runs, the event stream flows through each middleware in sequence.
23+
24+
```typescript
25+
import { AbstractAgent } from "@ag-ui/client"
26+
27+
const agent = new MyAgent()
28+
29+
// Middleware chain: logging -> auth -> filter -> agent
30+
agent.use(loggingMiddleware, authMiddleware, filterMiddleware)
31+
32+
// When agent runs, events flow through all middleware
33+
await agent.runAgent()
34+
```
35+
36+
## Function-Based Middleware
37+
38+
For simple transformations, you can use function-based middleware. This is the most concise way to add middleware:
39+
40+
```typescript
41+
import { MiddlewareFunction } from "@ag-ui/client"
42+
import { EventType } from "@ag-ui/core"
43+
44+
const prefixMiddleware: MiddlewareFunction = (input, next) => {
45+
return next.run(input).pipe(
46+
map(event => {
47+
if (event.type === EventType.TEXT_MESSAGE_CHUNK) {
48+
return {
49+
...event,
50+
delta: `[AI]: ${event.delta}`
51+
}
52+
}
53+
return event
54+
})
55+
)
56+
}
57+
58+
agent.use(prefixMiddleware)
59+
```
60+
61+
## Class-Based Middleware
62+
63+
For more complex scenarios requiring state or configuration, use class-based middleware:
64+
65+
```typescript
66+
import { Middleware } from "@ag-ui/client"
67+
import { Observable } from "rxjs"
68+
import { tap } from "rxjs/operators"
69+
70+
class MetricsMiddleware extends Middleware {
71+
private eventCount = 0
72+
73+
constructor(private metricsService: MetricsService) {
74+
super()
75+
}
76+
77+
run(input: RunAgentInput, next: AbstractAgent): Observable<BaseEvent> {
78+
const startTime = Date.now()
79+
80+
return next.run(input).pipe(
81+
tap(event => {
82+
this.eventCount++
83+
this.metricsService.recordEvent(event.type)
84+
}),
85+
finalize(() => {
86+
const duration = Date.now() - startTime
87+
this.metricsService.recordDuration(duration)
88+
this.metricsService.recordEventCount(this.eventCount)
89+
})
90+
)
91+
}
92+
}
93+
94+
agent.use(new MetricsMiddleware(metricsService))
95+
```
96+
97+
## Built-in Middleware
98+
99+
AG-UI provides several built-in middleware components for common use cases:
100+
101+
### FilterToolCallsMiddleware
102+
103+
Filter tool calls based on allowed or disallowed lists:
104+
105+
```typescript
106+
import { FilterToolCallsMiddleware } from "@ag-ui/client"
107+
108+
// Only allow specific tools
109+
const allowedFilter = new FilterToolCallsMiddleware({
110+
allowedToolCalls: ["search", "calculate"]
111+
})
112+
113+
// Or block specific tools
114+
const blockedFilter = new FilterToolCallsMiddleware({
115+
disallowedToolCalls: ["delete", "modify"]
116+
})
117+
118+
agent.use(allowedFilter)
119+
```
120+
121+
## Middleware Patterns
122+
123+
### Logging Middleware
124+
125+
```typescript
126+
const loggingMiddleware: MiddlewareFunction = (input, next) => {
127+
console.log("Request:", input.messages)
128+
129+
return next.run(input).pipe(
130+
tap(event => console.log("Event:", event.type)),
131+
catchError(error => {
132+
console.error("Error:", error)
133+
throw error
134+
})
135+
)
136+
}
137+
```
138+
139+
### Authentication Middleware
140+
141+
```typescript
142+
class AuthMiddleware extends Middleware {
143+
constructor(private apiKey: string) {
144+
super()
145+
}
146+
147+
run(input: RunAgentInput, next: AbstractAgent): Observable<BaseEvent> {
148+
// Add authentication to the context
149+
const authenticatedInput = {
150+
...input,
151+
context: [
152+
...input.context,
153+
{ type: "auth", apiKey: this.apiKey }
154+
]
155+
}
156+
157+
return next.run(authenticatedInput)
158+
}
159+
}
160+
```
161+
162+
### Rate Limiting Middleware
163+
164+
```typescript
165+
class RateLimitMiddleware extends Middleware {
166+
private lastCall = 0
167+
168+
constructor(private minInterval: number) {
169+
super()
170+
}
171+
172+
run(input: RunAgentInput, next: AbstractAgent): Observable<BaseEvent> {
173+
const now = Date.now()
174+
const timeSinceLastCall = now - this.lastCall
175+
176+
if (timeSinceLastCall < this.minInterval) {
177+
const delay = this.minInterval - timeSinceLastCall
178+
return timer(delay).pipe(
179+
switchMap(() => {
180+
this.lastCall = Date.now()
181+
return next.run(input)
182+
})
183+
)
184+
}
185+
186+
this.lastCall = now
187+
return next.run(input)
188+
}
189+
}
190+
```
191+
192+
## Combining Middleware
193+
194+
You can combine multiple middleware to create sophisticated processing pipelines:
195+
196+
```typescript
197+
// Function middleware for simple logging
198+
const logMiddleware: MiddlewareFunction = (input, next) => {
199+
console.log(`Starting run ${input.runId}`)
200+
return next.run(input)
201+
}
202+
203+
// Class middleware for authentication
204+
const authMiddleware = new AuthMiddleware(apiKey)
205+
206+
// Built-in middleware for filtering
207+
const filterMiddleware = new FilterToolCallsMiddleware({
208+
allowedToolCalls: ["search", "summarize"]
209+
})
210+
211+
// Apply all middleware in order
212+
agent.use(
213+
logMiddleware, // First: log the request
214+
authMiddleware, // Second: add authentication
215+
filterMiddleware // Third: filter tool calls
216+
)
217+
```
218+
219+
## Execution Order
220+
221+
Middleware executes in the order it's added, with each middleware wrapping the next:
222+
223+
1. First middleware receives the original input
224+
2. It can modify the input before passing to the next middleware
225+
3. Each middleware processes events from the next in the chain
226+
4. The final middleware calls the actual agent
227+
228+
```typescript
229+
agent.use(middleware1, middleware2, middleware3)
230+
231+
// Execution flow:
232+
// → middleware1
233+
// → middleware2
234+
// → middleware3
235+
// → agent.run()
236+
// ← events flow back through middleware3
237+
// ← events flow back through middleware2
238+
// ← events flow back through middleware1
239+
```
240+
241+
## Best Practices
242+
243+
1. **Keep middleware focused** – Each middleware should have a single responsibility
244+
2. **Handle errors gracefully** – Use RxJS error handling operators
245+
3. **Avoid blocking operations** – Use async patterns for I/O operations
246+
4. **Document side effects** – Clearly indicate if middleware modifies state
247+
5. **Test middleware independently** – Write unit tests for each middleware
248+
6. **Consider performance** – Be mindful of processing overhead in the event stream
249+
250+
## Advanced Use Cases
251+
252+
### Conditional Middleware
253+
254+
Apply middleware based on runtime conditions:
255+
256+
```typescript
257+
const conditionalMiddleware: MiddlewareFunction = (input, next) => {
258+
if (input.context.some(c => c.type === "debug")) {
259+
// Apply debug logging
260+
return next.run(input).pipe(
261+
tap(event => console.debug(event))
262+
)
263+
}
264+
return next.run(input)
265+
}
266+
```
267+
268+
### Event Transformation
269+
270+
Transform specific event types:
271+
272+
```typescript
273+
const transformMiddleware: MiddlewareFunction = (input, next) => {
274+
return next.run(input).pipe(
275+
map(event => {
276+
if (event.type === EventType.TOOL_CALL_START) {
277+
// Add timestamp to tool calls
278+
return {
279+
...event,
280+
metadata: {
281+
...event.metadata,
282+
timestamp: Date.now()
283+
}
284+
}
285+
}
286+
return event
287+
})
288+
)
289+
}
290+
```
291+
292+
### Stream Control
293+
294+
Control the flow of events:
295+
296+
```typescript
297+
const throttleMiddleware: MiddlewareFunction = (input, next) => {
298+
return next.run(input).pipe(
299+
// Throttle text message chunks to prevent overwhelming the UI
300+
throttleTime(50, undefined, { leading: true, trailing: true })
301+
)
302+
}
303+
```
304+
305+
## Conclusion
306+
307+
Middleware provides a flexible and powerful way to extend AG-UI agents without modifying their core logic. Whether you need simple event transformation or complex stateful processing, the middleware system offers the tools to build robust, maintainable agent applications.

docs/docs.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"concepts/architecture",
4141
"concepts/events",
4242
"concepts/agents",
43+
"concepts/middleware",
4344
"concepts/messages",
4445
"concepts/state",
4546
"concepts/tools"
@@ -85,6 +86,7 @@
8586
"sdk/js/client/overview",
8687
"sdk/js/client/abstract-agent",
8788
"sdk/js/client/http-agent",
89+
"sdk/js/client/middleware",
8890
"sdk/js/client/subscriber"
8991
]
9092
},

docs/sdk/js/client/abstract-agent.mdx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,36 @@ subscribe(subscriber: AgentSubscriber): { unsubscribe: () => void }
9595
Returns an object with an `unsubscribe()` method to remove the subscriber when
9696
no longer needed.
9797

98+
### use()
99+
100+
Adds middleware to the agent's event processing pipeline.
101+
102+
```typescript
103+
use(...middlewares: (Middleware | MiddlewareFunction)[]): this
104+
```
105+
106+
Middleware can be either:
107+
- **Function middleware**: Simple functions that transform the event stream
108+
- **Class middleware**: Instances of the `Middleware` class for stateful operations
109+
110+
```typescript
111+
// Function middleware
112+
agent.use((input, next) => {
113+
console.log("Processing:", input.runId);
114+
return next.run(input);
115+
});
116+
117+
// Class middleware
118+
agent.use(new FilterToolCallsMiddleware({
119+
allowedToolCalls: ["search"]
120+
}));
121+
122+
// Chain multiple middleware
123+
agent.use(loggingMiddleware, authMiddleware, filterMiddleware);
124+
```
125+
126+
Middleware executes in the order added, with each wrapping the next. See the [Middleware documentation](/sdk/js/client/middleware) for more details.
127+
98128
### abortRun()
99129

100130
Cancels the current agent execution.

0 commit comments

Comments
 (0)