Custom Providers
A custom provider is any Swift type that conforms to one or more SwiftyAI model protocols. Start with the smallest protocol your feature needs, then add streaming, tools, or media only when the provider can really support them.
| Protocol | Implement when |
|---|---|
AIModel | The provider can return one full text response |
AIStreamModel | The provider can yield text chunks |
AIToolCallingModel | The provider can request tools in a structured way |
| Media protocols | The provider exposes image, transcription, speech, or video endpoints |
| Middleware wrappers | You only need to adapt requests or responses around an existing provider |
Text Provider
import SwiftyAI
struct EchoModel: AIModel {
func generate(_ prompt: String) async throws -> AIResponse {
AIResponse(text: "Echo: \(prompt)", model: "echo")
}
}
let response = try await generateText(
model: EchoModel(),
prompt: "Hello"
)AIModel has default implementations for options and multimodal prompts, so a simple text provider only needs generate(_ prompt: String).
Streaming Provider
struct EchoStreamModel: AIStreamModel {
func generate(_ prompt: String) async throws -> AIResponse {
AIResponse(text: prompt, model: "echo-stream")
}
func stream(_ prompt: String) -> AsyncThrowingStream<AIStreamChunk, Error> {
AsyncThrowingStream { continuation in
Task {
for word in prompt.split(separator: " ") {
continuation.yield(AIStreamChunk(text: "\(word) "))
}
continuation.finish()
}
}
}
}The AIStreamModel extension supplies the other stream overloads unless you need custom chat-history or multimodal behavior.
Middleware Wrapping
Middleware lets you adapt a provider without changing the provider itself.
let wrapped = wrapStreamingLanguageModel(
EchoStreamModel(),
middleware: [
defaultSettingsMiddleware(system: "Always answer in one sentence."),
extractReasoningMiddleware()
],
streamMiddleware: [
simulateStreamingMiddleware(chunkSize: 12, delay: .milliseconds(40))
]
)Use defaultSettingsMiddleware for defaults, extractJsonMiddleware for JSON cleanup, extractReasoningMiddleware for stripping reasoning tags, and simulateStreamingMiddleware when a text model needs a stream-like UI.
Registry Integration
let registry = createProviderRegistry([
"local": customProvider(
languageModels: ["echo": EchoModel()],
streamModels: ["echo-stream": EchoStreamModel()]
)
])
let response = try await generateText(
model: "local/echo",
registry: registry,
prompt: "Testing"
)This keeps custom providers test-local and avoids global app configuration.