SwiftyAISwiftyAI

Search documentation

Find a docs page by title or section

1

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.

ProtocolImplement when
AIModelThe provider can return one full text response
AIStreamModelThe provider can yield text chunks
AIToolCallingModelThe provider can request tools in a structured way
Media protocolsThe provider exposes image, transcription, speech, or video endpoints
Middleware wrappersYou 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.