Contents

drewster99/swift-llm-kit

A Swift package for talking to many LLM providers through one interface. It

Supported providers

Each provider below ships as a built-in preset with a stable ID and a fixed endpoint. Any other OpenAI-compatible backend can be added as a custom provider.

| Provider | Built-in ID | Notes | |---|---|---| | Anthropic (Claude) | builtin.anthropic | Native Messages API, prompt caching, adaptive and extended thinking | | OpenAI | builtin.openai | | | ChatGPT subscription (Codex) | builtin.codex-chatgpt | Signs in with the Codex CLI's ~/.codex/auth.json, not an API key | | Google Gemini | builtin.gemini | Native generateContent API | | xAI (Grok) | builtin.xai | | | DeepSeek | builtin.deepseek | | | Moonshot AI (Kimi) | builtin.moonshot | | | z.ai | builtin.zai, builtin.zai-coding | General and coding endpoints | | Meta Model API | builtin.meta-model-api | | | Mistral | builtin.mistral | | | Alibaba Cloud (DashScope) | builtin.alibabacloud, -beijing, -singapore | US, Beijing, Singapore regions | | Hugging Face | builtin.huggingface | Inference router | | OpenRouter | builtin.openrouter | | | Ollama | builtin.ollama, builtin.ollama-cloud | Local or cloud | | LM Studio | builtin.lmstudio | Local |

Features

  • One LLMProvider interface. Every backend implements send(messages:tools:), so your app doesn't need to know which vendor is answering.
  • Tool calling. Tools, tool calls, and tool results work the same way on every provider, with per-call LLMToolChoice (auto, required, textOnly, or one specific tool) and optional strict schemas.
  • Reasoning and effort, modeled correctly. Anthropic's general effort and OpenAI's reasoning_effort are kept as separate settings, and so are thinking budgets and on/off switches. Each is sent only to models known to accept it, so an unsupported parameter never causes an HTTP 400.
  • Structured output. LLMResponseFormat covers JSON-object and JSON-schema modes.
  • Multimodal input. Images and PDF documents are attached to messages.
  • A model catalog that knows capabilities. It combines each provider's /models listing, bundled metadata, LiteLLM data, and your own overrides, in a fixed priority order.
  • Capability probing. ModelProber measures what an endpoint really accepts (thinking-budget ranges, effort levels, tool choice, structured output) instead of trusting documentation.
  • Keychain-backed API keys. Keys never live in UserDefaults or plain files.
  • SwiftUI-ready settings. LLMKitManager is @Observable @MainActor, so providers, models, and configurations bind directly to views.
  • Prompt caching. Supports Anthropic's ephemeral cache (5 min or 1 h) and reports cached-input tokens from OpenAI and xAI. Request bodies are serialized deterministically, so provider-side caches keep hitting.

Requirements

  • macOS 15 or newer
  • Swift 6.1 or newer (Xcode 16.4+)

iOS and visionOS support is on the wish list, and contributions are welcome.

Installation

.package(url: "https://github.com/drewster99/swift-llm-kit", from: "0.0.210")

Then add the product to your target:

.product(name: "SwiftLLMKit", package: "swift-llm-kit")

Quick start

import SwiftLLMKit

@MainActor
func hello(apiKey: String) async throws {
    let kit = LLMKitManager(
        appIdentifier: "com.example.MyApp",
        keychainServicePrefix: "com.example.MyApp"
    )
    kit.load()

    // Stored in the Keychain — never in UserDefaults or a plain file.
    try kit.setBuiltInProviderAPIKey(id: BuiltInProviders.ID.anthropic, apiKey: apiKey)

    await kit.refreshIfNeeded()   // fetches each provider's current model list

    let configuration = ModelConfiguration(
        name: "Claude Sonnet",
        providerID: BuiltInProviders.ID.anthropic,
        modelID: "claude-sonnet-5"
    )
    kit.addConfiguration(configuration)

    let provider = try kit.makeProvider(for: configuration.id)
    let response = try await provider.send(messages: [.user("Hello!")], tools: [])
    print(response.text ?? "")
}

Tool calling

let weather = LLMToolDefinition(
    name: "get_weather",
    description: "Current weather for a city",
    parameters: [
        "type": .string("object"),
        "properties": .dictionary([
            "city": .dictionary(["type": .string("string")])
        ]),
        "required": .array([.string("city")])
    ]
)

var messages: [LLMMessage] = [.user("What's the weather in Oslo?")]
let response = try await provider.send(messages: messages, tools: [weather])
messages.append(.assistant(from: response))

for call in response.toolCalls {
    // call.arguments is the model's JSON argument string, e.g. {"city":"Oslo"}
    messages.append(.toolResult("12°C, light rain", callID: call.id))
}
let answer = try await provider.send(messages: messages, tools: [weather])
print(answer.text ?? "")

.assistant(from:) carries the provider's reasoning continuation (Anthropic thinking blocks, Gemini thought signatures, Codex reasoning items), so multi-turn tool use keeps working on reasoning models.

How it fits together

  • LLMKitManager is the central coordinator. It owns providers, model configurations, and the model catalog, and it saves them under Application Support.
  • LLMProvider is the per-request protocol, implemented by AnthropicProvider, OpenAICompatibleProvider, GeminiProvider, and OllamaProvider, plus an internal Codex Responses adapter that makeProvider(for:) returns for the ChatGPT-subscription provider.
  • ModelInfo / ModelCapabilities record what a model can do. The providers read them to decide which request fields to send.
  • ModelProber measures capabilities that no vendor publishes.

CLAUDE.md explains the design decisions in depth: why effort is two settings, why some knobs fail open and others fail closed, and how probe records are versioned.

Contributing

Contributions are very welcome. Good places to start:

  • Good first issues are small and self-contained, with pointers to the exact files involved.
  • Help wanted issues are larger features and improvements.
  • ROADMAP.md has the longer-term plans and the reasoning behind them.

See CONTRIBUTING.md for how to build, test, and submit a change.

Changelog

See GitHub Releases, plus CHANGELOG.md for the historical notes.

License

MIT. See LICENSE.

Package Metadata

Repository: drewster99/swift-llm-kit

Default branch: main

README: README.md