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) HandlerMiddleware 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 middlewareagent := 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 promptinput := blades.UserMessage("What is the capital of France?")// Run the agentoutput, err := agent.Run(context.Background(), input)if err != nil { log.Fatal(err)}
log.Println(output.Text())// Create multiple middlewaresagent := 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.