Skip to content

Middleware

In the Blades framework, middleware is a powerful mechanism for implementing cross-cutting concerns (such as logging, monitoring, authentication, rate limiting, etc.). Its design allows injecting additional behavior into the Agent’s execution flow without modifying the core logic. Middleware works in the form of a function chain following the “onion model,” providing highly flexible flow control and feature enhancement. This document will guide you through a simple example of implementing a logging middleware.

Middleware is defined as follows:

type Handler interface {
Handle(context.Context, *Invocation) Generator[*Message, error]
}
type Middleware func(Handler) Handler

Middleware is a function that takes a Handler as a parameter and returns a Handler. An example of creating middleware is shown below:

func Logging() blades.Middleware {
return func(next blades.Handler) blades.Handler {
// `blades.Handler` is a interface,we need use `blades.HandleFunc` to implement it.
return blades.HandleFunc(func(ctx context.Context, req *blades.Invocation) blades.Generator[*blades.Message, error] {
log.Println("----🚀--- Incoming request ----🚀---")
log.Println("Request:", req.Message)
return next.Handle(ctx, req)
})
}
}
type Logging struct {
next blades.Handler
}
// NewLogging creates a new Logging middleware.
func NewLogging(next blades.Handler) blades.Handler {
return &Logging{next}
}
func (m *Logging) onError(start time.Time, agent blades.AgentContext, invocation *blades.Invocation, err error) {
log.Printf("logging: model(%s) prompt(%s) failed after %s: %v", agent.Model(), invocation.Message.String(), time.Since(start), err)
}
func (m *Logging) onSuccess(start time.Time, agent blades.AgentContext, invocation *blades.Invocation, output *blades.Message) {
log.Printf("logging: model(%s) prompt(%s) succeeded after %s: %s", agent.Model(), invocation.Message.String(), time.Since(start), output.String())
}
func (m *Logging) Handle(ctx context.Context, invocation *blades.Invocation) blades.Generator[*blades.Message, error] {
return func(yield func(*blades.Message, error) bool) {
start := time.Now()
agent, ok := blades.FromAgentContext(ctx)
if !ok {
yield(nil, blades.ErrNoAgentContext)
return
}
streaming := m.next.Handle(ctx, invocation)
for msg, err := range streaming {
if err != nil {
m.onError(start, agent, invocation, err)
} else {
m.onSuccess(start, agent, invocation, msg)
}
if !yield(msg, err) {
break
}
}
}
}
// Create a blades agent with logging middleware
agent := blades.NewAgent(
"Example Agent",
blades.WithModel("gpt-5"),
blades.WithInstructions("You are a helpful assistant."),
blades.WithProvider(openai.NewChatProvider()),
blades.WithMiddleware(Logging()), // Use the logging middleware
)
// Create a prompt
input := blades.UserMessage("What is the capital of France?")
// Run the agent
output, err := agent.Run(context.Background(), input)
if err != nil {
log.Fatal(err)
}
log.Println(output.Text())
// Create multiple middlewares
agent := blades.NewAgent(
"Chained Middleware Agent",
blades.WithModel("gpt-5"),
blades.WithProvider(openai.NewChatProvider()),
// Chain multiple middlewares
blades.WithMiddleware(
Logging(),
Tracing(),
Metrics(),
),
)

This design allows middleware to conveniently add various functionalities while maintaining code clarity and maintainability.