File: middleware.md | Updated: 11/15/2025
Menu
v5 (Latest)
AI SDK 5.x
Model Context Protocol (MCP) Tools
Copy markdown
======================================================================================================
Language model middleware is a way to enhance the behavior of language models by intercepting and modifying the calls to the language model.
It can be used to add features like guardrails, RAG, caching, and logging in a language model agnostic way. Such middleware can be developed and distributed independently from the language models that they are applied to.
Using Language Model Middleware
You can use language model middleware with the wrapLanguageModel function. It takes a language model and a language model middleware and returns a new language model that incorporates the middleware.
import { wrapLanguageModel } from 'ai';
const wrappedLanguageModel = wrapLanguageModel({ model: yourModel, middleware: yourLanguageModelMiddleware,});
The wrapped language model can be used just like any other language model, e.g. in streamText:
const result = streamText({ model: wrappedLanguageModel, prompt: 'What cities are in the United States?',});
You can provide multiple middlewares to the wrapLanguageModel function. The middlewares will be applied in the order they are provided.
const wrappedLanguageModel = wrapLanguageModel({ model: yourModel, middleware: [firstMiddleware, secondMiddleware],});
// applied as: firstMiddleware(secondMiddleware(yourModel))
The AI SDK comes with several built-in middlewares that you can use to configure language models:
extractReasoningMiddleware: Extracts reasoning information from the generated text and exposes it as a reasoning property on the result.simulateStreamingMiddleware: Simulates streaming behavior with responses from non-streaming language models.defaultSettingsMiddleware: Applies default settings to a language model.Some providers and models expose reasoning information in the generated text using special tags, e.g. <think> and </think>.
The extractReasoningMiddleware function can be used to extract this reasoning information and expose it as a reasoning property on the result.
import { wrapLanguageModel, extractReasoningMiddleware } from 'ai';
const model = wrapLanguageModel({ model: yourModel, middleware: extractReasoningMiddleware({ tagName: 'think' }),});
You can then use that enhanced model in functions like generateText and streamText.
The extractReasoningMiddleware function also includes a startWithReasoning option. When set to true, the reasoning tag will be prepended to the generated text. This is useful for models that do not include the reasoning tag at the beginning of the response. For more details, see the DeepSeek R1 guide
.
The simulateStreamingMiddleware function can be used to simulate streaming behavior with responses from non-streaming language models. This is useful when you want to maintain a consistent streaming interface even when using models that only provide complete responses.
import { wrapLanguageModel, simulateStreamingMiddleware } from 'ai';
const model = wrapLanguageModel({ model: yourModel, middleware: simulateStreamingMiddleware(),});
The defaultSettingsMiddleware function can be used to apply default settings to a language model.
import { wrapLanguageModel, defaultSettingsMiddleware } from 'ai';
const model = wrapLanguageModel({ model: yourModel, middleware: defaultSettingsMiddleware({ settings: { temperature: 0.5, maxOutputTokens: 800, providerOptions: { openai: { store: false } }, }, }),});
The AI SDK provides a Language Model Middleware specification. Community members can develop middleware that adheres to this specification, making it compatible with the AI SDK ecosystem.
Here are some community middlewares that you can explore:
The Custom tool call parser
middleware extends tool call capabilities to models that don't natively support the OpenAI-style tools parameter. This includes many self-hosted and third-party models that lack native function calling features.
Using this middleware on models that support native function calls may result in unintended performance degradation, so check whether your model supports native function calls before deciding to use it.
This middleware enables function calling capabilities by converting function schemas into prompt instructions and parsing the model's responses into structured function calls. It works by transforming the JSON function definitions into natural language instructions the model can understand, then analyzing the generated text to extract function call attempts. This approach allows developers to use the same function calling API across different model providers, even with models that don't natively support the OpenAI-style function calling format, providing a consistent function calling experience regardless of the underlying model implementation.
The @ai-sdk-tool/parser package offers three middleware variants:
createToolMiddleware: A flexible function for creating custom tool call middleware tailored to specific modelshermesToolMiddleware: Ready-to-use middleware for Hermes & Qwen format function callsgemmaToolMiddleware: Pre-configured middleware for Gemma 3 model series function call formatHere's how you can enable function calls with Gemma models that don't support them natively:
import { wrapLanguageModel } from 'ai';import { gemmaToolMiddleware } from '@ai-sdk-tool/parser';
const model = wrapLanguageModel({ model: openrouter('google/gemma-3-27b-it'), middleware: gemmaToolMiddleware,});
Find more examples at this link .
Implementing Language Model Middleware
Implementing language model middleware is advanced functionality and requires a solid understanding of the language model specification .
You can implement any of the following three function to modify the behavior of the language model:
transformParams: Transforms the parameters before they are passed to the language model, for both doGenerate and doStream.wrapGenerate: Wraps the doGenerate method of the language model
. You can modify the parameters, call the language model, and modify the result.wrapStream: Wraps the doStream method of the language model
. You can modify the parameters, call the language model, and modify the result.Here are some examples of how to implement language model middleware:
These examples are not meant to be used in production. They are just to show how you can use middleware to enhance the behavior of language models.
This example shows how to log the parameters and generated text of a language model call.
import type { LanguageModelV2Middleware, LanguageModelV2StreamPart,} from '@ai-sdk/provider';
export const yourLogMiddleware: LanguageModelV2Middleware = { wrapGenerate: async ({ doGenerate, params }) => { console.log('doGenerate called'); console.log(`params: ${JSON.stringify(params, null, 2)}`);
const result = await doGenerate();
console.log('doGenerate finished'); console.log(`generated text: ${result.text}`);
return result; },
wrapStream: async ({ doStream, params }) => { console.log('doStream called'); console.log(`params: ${JSON.stringify(params, null, 2)}`);
const { stream, ...rest } = await doStream();
let generatedText = ''; const textBlocks = new Map<string, string>();
const transformStream = new TransformStream< LanguageModelV2StreamPart, LanguageModelV2StreamPart >({ transform(chunk, controller) { switch (chunk.type) { case 'text-start': { textBlocks.set(chunk.id, ''); break; } case 'text-delta': { const existing = textBlocks.get(chunk.id) || ''; textBlocks.set(chunk.id, existing + chunk.delta); generatedText += chunk.delta; break; } case 'text-end': { console.log( `Text block ${chunk.id} completed:`, textBlocks.get(chunk.id), ); break; } }
controller.enqueue(chunk); },
flush() { console.log('doStream finished'); console.log(`generated text: ${generatedText}`); }, });
return { stream: stream.pipeThrough(transformStream), ...rest, }; },};
This example shows how to build a simple cache for the generated text of a language model call.
import type { LanguageModelV2Middleware } from '@ai-sdk/provider';
const cache = new Map<string, any>();
export const yourCacheMiddleware: LanguageModelV2Middleware = { wrapGenerate: async ({ doGenerate, params }) => { const cacheKey = JSON.stringify(params);
if (cache.has(cacheKey)) { return cache.get(cacheKey); }
const result = await doGenerate();
cache.set(cacheKey, result);
return result; },
// here you would implement the caching logic for streaming};
This example shows how to use RAG as middleware.
Helper functions like getLastUserMessageText and findSources are not part of the AI SDK. They are just used in this example to illustrate the concept of RAG.
import type { LanguageModelV2Middleware } from '@ai-sdk/provider';
export const yourRagMiddleware: LanguageModelV2Middleware = { transformParams: async ({ params }) => { const lastUserMessageText = getLastUserMessageText({ prompt: params.prompt, });
if (lastUserMessageText == null) { return params; // do not use RAG (send unmodified parameters) }
const instruction = 'Use the following information to answer the question:\n' + findSources({ text: lastUserMessageText }) .map(chunk => JSON.stringify(chunk)) .join('\n');
return addToLastUserMessage({ params, text: instruction }); },};
Guard rails are a way to ensure that the generated text of a language model call is safe and appropriate. This example shows how to use guardrails as middleware.
import type { LanguageModelV2Middleware } from '@ai-sdk/provider';
export const yourGuardrailMiddleware: LanguageModelV2Middleware = { wrapGenerate: async ({ doGenerate }) => { const { text, ...rest } = await doGenerate();
// filtering approach, e.g. for PII or other sensitive information: const cleanedText = text?.replace(/badword/g, '<REDACTED>');
return { text: cleanedText, ...rest }; },
// here you would implement the guardrail logic for streaming // Note: streaming guardrails are difficult to implement, because // you do not know the full content of the stream until it's finished.};
Configuring Per Request Custom Metadata
To send and access custom metadata in Middleware, you can use providerOptions. This is useful when building logging middleware where you want to pass additional context like user IDs, timestamps, or other contextual data that can help with tracking and debugging.
import { openai } from '@ai-sdk/openai';import { generateText, wrapLanguageModel } from 'ai';import type { LanguageModelV2Middleware } from '@ai-sdk/provider';
export const yourLogMiddleware: LanguageModelV2Middleware = { wrapGenerate: async ({ doGenerate, params }) => { console.log('METADATA', params?.providerMetadata?.yourLogMiddleware); const result = await doGenerate(); return result; },};
const { text } = await generateText({ model: wrapLanguageModel({ model: openai('gpt-4o'), middleware: yourLogMiddleware, }), prompt: 'Invent a new holiday and describe its traditions.', providerOptions: { yourLogMiddleware: { hello: 'world', }, },});
console.log(text);
On this page
Using Language Model Middleware
Implementing Language Model Middleware
Retrieval Augmented Generation (RAG)
Configuring Per Request Custom Metadata
Deploy and Scale AI Apps with Vercel.
Vercel delivers the infrastructure and developer experience you need to ship reliable AI-powered applications at scale.
Trusted by industry leaders: