Krishna Kumar 80109d702c Add demo app with mock, Ollama, OpenAI, and Anthropic provider support
Example iOS app that seeds a task management SQLite database and
renders SwiftDBAI's DataChatView. Ships with a mock LLM for offline
use and an OllamaWithSystemPrompt wrapper that fixes AnyLanguageModel's
Ollama adapter dropping system instructions.

Tested successfully with:
- DemoLanguageModel (mock, pattern-matched SQL)
- Ollama qwen3-coder-30b (local, real SQL generation)
- OpenAI gpt-4o-mini (JOINs, GROUP BY, multi-turn)
- Anthropic claude-haiku-4.5 (complex aggregations)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 12:25:22 -05:00
2026-04-04 09:30:56 -05:00
2026-04-04 09:30:56 -05:00
2026-04-04 09:30:56 -05:00
2026-04-04 09:30:56 -05:00
2026-04-04 09:30:56 -05:00

SwiftDBAI

Chat with any SQLite database using natural language.

Swift 6.1+ Platforms License

Features

  • Drop-in SwiftUI chat view (DataChatView) -- one line to add a database chat UI
  • Headless ChatEngine for programmatic / non-UI use
  • LLM-agnostic via AnyLanguageModel -- works with OpenAI, Anthropic, Gemini, Ollama, llama.cpp, or any OpenAI-compatible endpoint
  • Automatic schema introspection -- no manual annotations required
  • Safety-first: read-only by default, operation allowlists, table-level mutation policies, destructive operation confirmation delegate
  • Configurable query timeouts, context windows, and custom validators

Installation

Add SwiftDBAI via Swift Package Manager:

dependencies: [
    .package(url: "https://github.com/<org>/SwiftDBAI.git", from: "1.0.0"),
]

Then add the dependency to your target:

.target(
    name: "MyApp",
    dependencies: ["SwiftDBAI"]
)

Quick Start

Drop a full chat UI into any SwiftUI view with DataChatView:

import SwiftDBAI
import AnyLanguageModel

struct ContentView: View {
    var body: some View {
        DataChatView(
            databasePath: "/path/to/mydata.sqlite",
            model: OllamaLanguageModel(model: "llama3")
        )
    }
}

That's it. DataChatView opens the database, introspects the schema, and renders a chat interface. The default mode is read-only (SELECT only).

To pass an existing GRDB connection and customize behavior:

DataChatView(
    database: myDatabasePool,
    model: OpenAILanguageModel(apiKey: "sk-...", model: "gpt-4o"),
    allowlist: .standard,
    additionalContext: "This database stores a recipe app's data.",
    maxSummaryRows: 100
)

Headless / Programmatic Use

Use ChatEngine directly when you don't need a UI:

import SwiftDBAI
import AnyLanguageModel
import GRDB

let pool = try DatabasePool(path: "/path/to/mydata.sqlite")
let engine = ChatEngine(
    database: pool,
    model: OpenAILanguageModel(apiKey: "sk-...", model: "gpt-4o")
)

let response = try await engine.send("How many users signed up this week?")
print(response.summary)   // "There were 42 new signups this week."
print(response.sql)        // Optional("SELECT COUNT(*) FROM users WHERE ...")

ChatEngine also accepts a ProviderConfiguration for convenience:

let engine = ChatEngine(
    database: pool,
    provider: .anthropic(apiKey: "sk-ant-...", model: "claude-sonnet-4-20250514")
)

For fine-grained control, pass a ChatEngineConfiguration:

var config = ChatEngineConfiguration(
    queryTimeout: 10,
    contextWindowSize: 20,
    maxSummaryRows: 100,
    additionalContext: "The 'status' column uses: 'active', 'inactive', 'suspended'."
)

let engine = ChatEngine(
    database: pool,
    model: model,
    allowlist: .standard,
    configuration: config
)

Choosing a Provider

SwiftDBAI works with any provider supported by AnyLanguageModel. Use ProviderConfiguration factory methods or construct model instances directly.

// OpenAI
let config = ProviderConfiguration.openAI(apiKey: "sk-...", model: "gpt-4o")

// Anthropic
let config = ProviderConfiguration.anthropic(apiKey: "sk-ant-...", model: "claude-sonnet-4-20250514")

// Gemini
let config = ProviderConfiguration.gemini(apiKey: "AIza...", model: "gemini-2.0-flash")

// Ollama (local, no API key needed)
let config = ProviderConfiguration.ollama(model: "llama3.2")

// llama.cpp (local)
let config = ProviderConfiguration.llamaCpp(model: "default")

// Any OpenAI-compatible endpoint
let config = ProviderConfiguration.openAICompatible(
    apiKey: "your-key",
    model: "llama-3.1-70b",
    baseURL: URL(string: "https://api.together.xyz/v1/")!
)

Use with ChatEngine:

let engine = ChatEngine(database: pool, provider: config)
// or
let engine = ChatEngine(database: pool, model: config.makeModel())

API keys can also come from environment variables:

let config = ProviderConfiguration.fromEnvironment(
    provider: .openAI,
    environmentVariable: "OPENAI_API_KEY",
    model: "gpt-4o"
)

Safety and Mutation Control

Operation Allowlist

By default, only SELECT queries are allowed. Opt in to writes explicitly:

Preset Allowed Operations
.readOnly (default) SELECT
.standard SELECT, INSERT, UPDATE
.unrestricted SELECT, INSERT, UPDATE, DELETE
// Custom allowlist
let allowlist = OperationAllowlist([.select, .insert])

Mutation Policy

For table-level control, use MutationPolicy:

// Allow INSERT and UPDATE only on specific tables
let policy = MutationPolicy(
    allowedOperations: [.insert, .update],
    allowedTables: ["orders", "order_items"]
)

let engine = ChatEngine(
    database: pool,
    model: model,
    mutationPolicy: policy
)

Presets: .readOnly, .readWrite, .unrestricted.

Confirmation Delegate

Destructive operations (DELETE, DROP, ALTER, TRUNCATE) require confirmation through a ToolExecutionDelegate:

struct MyDelegate: ToolExecutionDelegate {
    func confirmDestructiveOperation(
        _ context: DestructiveOperationContext
    ) async -> Bool {
        // Present confirmation UI, return true to proceed
        return await showConfirmationDialog(context.description)
    }
}

let engine = ChatEngine(
    database: pool,
    model: model,
    allowlist: .unrestricted,
    delegate: MyDelegate()
)

Without a delegate, destructive operations throw SwiftDBAIError.confirmationRequired so you can handle confirmation in your own flow.

Built-in delegates: AutoApproveDelegate (testing only), RejectAllDelegate (safest).

Architecture

User Question
    |
    v
ChatEngine
    |-- SchemaIntrospector   (auto-discovers tables, columns, keys, indexes)
    |-- PromptBuilder        (builds LLM system prompt with schema context)
    |-- LanguageModel        (generates SQL via AnyLanguageModel)
    |-- SQLQueryParser       (parses and validates against allowlist/policy)
    |-- QueryValidator       (optional custom validators)
    |-- GRDB                 (executes SQL against SQLite)
    |-- TextSummaryRenderer  (summarizes results via LLM)
    v
ChatResponse { summary, sql, queryResult }

DataChatView wraps this pipeline in a SwiftUI view with ChatViewModel managing state.

Requirements

  • iOS 17.0+ / macOS 14.0+ / visionOS 1.0+
  • Swift 6.1+
  • Xcode 16+

License

MIT. See LICENSE for details.

Description
Chat with any SQLite database using natural language. Built on AnyLanguageModel + GRDB.
Readme 1.1 MiB
Languages
Swift 100%