Middleware
Wrap a model to intercept requests and stream parts, for caching, reasoning extraction, defaults, and custom hooks.
wrapLanguageModel intercepts requests and stream parts. The built-ins
cover the common cases:
let model = wrapLanguageModel(
model: OllamaModel("qwen3"),
middleware: [
.cache(), // replay identical requests
.extractReasoning(tag: "think"), // lift <think> spans into reasoning
.defaultSettings(temperature: 0.2) // bake in defaults
]
).simulateStreaming() turns a non-streaming endpoint into a streaming
one. .extractJson() strips markdown code fences from the response, for
models that wrap JSON in ```json even when asked for raw output.
.addToolInputExamples() folds a tool's inputExamples into its description
for providers with no native field for them:
let search = Tool(
name: "search",
description: "Search the docs.",
parameters: Schema.object(["query": .string()]),
inputExamples: [["query": "install swift-ai-sdk"], ["query": "streaming"]]
) { arguments in try await docs.search(arguments["query"]?.stringValue ?? "") }
let model = wrapLanguageModel(
model: OpenAIModel("gpt-5"),
middleware: [.addToolInputExamples(prefix: "Input Examples:")]
)Wrapping other model kinds
Embedding, image, and whole-provider wrapping mirror the language-model version:
let embeddings = wrapEmbeddingModel(
model: OpenAIEmbeddingModel("text-embedding-3-small"),
middleware: [.defaultSettings(maxBatchSize: 96)]
)
let images = wrapImageModel(model: OpenAIImageModel("gpt-image-2"), middleware: [
ImageModelMiddleware(transformRequest: { request in
var request = request
request.prompt += ", studio lighting"
return request
})
])
let provider = wrapProvider(
provider: myProvider,
languageModelMiddleware: [.cache()],
embeddingModelMiddleware: [.defaultSettings(maxBatchSize: 96)]
)Caching
.cache() keys on the request plus the wrapped model's identity. A hit
replays the stored stream parts without calling the model; a miss streams
live, buffers the parts, and stores them once the stream completes. Errors
are never cached.
let store = InMemoryLanguageModelCache()
let model = wrapLanguageModel(model: OpenAIModel("gpt-5.6-luna"), middleware: [.cache(store: store)])The default store is in-process. Conform to LanguageModelCache
(get/set over [StreamPart]) to back it with Redis, disk, or anything
else.
Custom middleware
A middleware is a value with the hooks you need: transformRequest (edit
the request), wrapStream (post-process stream parts), or wrapCall (wrap
the whole call, deciding whether to invoke the model at all, which is what
.cache() uses):
let logger = LanguageModelMiddleware(
transformRequest: { request in
print("sending \(request.messages.count) messages")
return request
}
)Middlewares apply in array order.