Initial implementation of SwiftDBAI
Chat with any SQLite database using natural language. Built on AnyLanguageModel (HuggingFace) for LLM-agnostic provider support and GRDB for SQLite access. Core features: - Auto schema introspection from sqlite_master (zero config) - NL → SQL generation via any AnyLanguageModel provider - Three rendering modes: text summary, data table, Swift Charts - Drop-in DataChatView (SwiftUI) and headless ChatEngine - Operation allowlist with read-only default - Mutation policy with per-table control - ToolExecutionDelegate for destructive operation confirmation - Multi-turn conversation context - 352 tests across 24 suites, all passing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
254
README.md
Normal file
254
README.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# SwiftDBAI
|
||||
|
||||
Chat with any SQLite database using natural language.
|
||||
|
||||
<!-- badges -->
|
||||

|
||||

|
||||

|
||||
|
||||
## 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](https://github.com/huggingface/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:
|
||||
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/<org>/SwiftDBAI.git", from: "1.0.0"),
|
||||
]
|
||||
```
|
||||
|
||||
Then add the dependency to your target:
|
||||
|
||||
```swift
|
||||
.target(
|
||||
name: "MyApp",
|
||||
dependencies: ["SwiftDBAI"]
|
||||
)
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
Drop a full chat UI into any SwiftUI view with `DataChatView`:
|
||||
|
||||
```swift
|
||||
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:
|
||||
|
||||
```swift
|
||||
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:
|
||||
|
||||
```swift
|
||||
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:
|
||||
|
||||
```swift
|
||||
let engine = ChatEngine(
|
||||
database: pool,
|
||||
provider: .anthropic(apiKey: "sk-ant-...", model: "claude-sonnet-4-20250514")
|
||||
)
|
||||
```
|
||||
|
||||
For fine-grained control, pass a `ChatEngineConfiguration`:
|
||||
|
||||
```swift
|
||||
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.
|
||||
|
||||
```swift
|
||||
// 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:
|
||||
|
||||
```swift
|
||||
let engine = ChatEngine(database: pool, provider: config)
|
||||
// or
|
||||
let engine = ChatEngine(database: pool, model: config.makeModel())
|
||||
```
|
||||
|
||||
API keys can also come from environment variables:
|
||||
|
||||
```swift
|
||||
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 |
|
||||
|
||||
```swift
|
||||
// Custom allowlist
|
||||
let allowlist = OperationAllowlist([.select, .insert])
|
||||
```
|
||||
|
||||
### Mutation Policy
|
||||
|
||||
For table-level control, use `MutationPolicy`:
|
||||
|
||||
```swift
|
||||
// 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`:
|
||||
|
||||
```swift
|
||||
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](LICENSE) for details.
|
||||
Reference in New Issue
Block a user