Skip to main content

Overview

Middleware lets you hook into every model operation to run logic before and after each call. A middleware is an object with optional hooks that wrap operations like generate, stream, generateObject, streamObject, or embed. Each hook receives the call options, a reference to the model, and an execute function that continues the chain. Use middleware for:
  • Logging and telemetry
  • Input preprocessing and sanitization
  • Output validation and transformation
  • Custom error handling and retry logic
  • Caching
  • Access control
core-ai ships first-party OpenTelemetry and Langfuse middleware packages built on this same system. See Observability for details.

Applying middleware

Wrap a model with wrapChatModel to apply middleware. The returned model has the same ChatModel interface, so you can use it anywhere the original model was used.
You can pass a single middleware or an array:

Writing custom middleware

A ChatModelMiddleware is an object with optional hooks. Each hook you define wraps the corresponding model operation. Hooks you omit pass through to the model unchanged.
Each hook receives three arguments:
  • execute — call this to continue the chain. You can call it with no arguments to pass the original options, or pass modified options to override them.
  • options — the options for the current call (messages, temperature, tools, etc.)
  • model — the underlying model, useful for reading provider and modelId

Example: input guardrail

This middleware checks user messages for blocked terms before calling the model:

Example: automatic retries

This middleware retries failed calls with exponential backoff:

Part metadata

Middleware can use part metadata to keep annotations next to the text they describe. This is useful for provenance, validation status, classification results, or text transformation spans. On outbound calls, read metadata from text-bearing messages:
On inbound streaming calls, wrap the stream and attach metadata to text-end. createChatStream() copies that metadata to the matching AssistantTextPart in .result.
GenerateOptions.metadata is call-level context. Part metadata is application-owned state that stays on individual text parts, reasoning parts, tool calls, and tool results. providerMetadata on reasoning parts is provider-owned data for provider round-trips.

Embedding and image middleware

EmbeddingModelMiddleware and ImageModelMiddleware follow the same pattern with their respective operations.

EmbeddingModelMiddleware

ImageModelMiddleware

Composing middleware

When you pass an array of middleware, they execute in order from first to last. The first middleware in the array is the outermost layer — it runs first on the way in and last on the way out.
Each middleware can:
  • Modify options before calling execute()
  • Inspect or transform the result after execute() resolves
  • Short-circuit the chain by returning without calling execute()
  • Catch and handle errors from execute()

First-party middleware packages

core-ai provides two observability middleware packages. These are built on the same middleware system documented above, and serve as good reference examples for writing your own middleware.

OpenTelemetry

Automatic tracing with OpenTelemetry spans for all model operations

Langfuse

Langfuse observability with generation tracking and usage reporting

Next steps