areroketahi/foundation-openai
FoundationOpenAI bridges Apple's FoundationModels APIs to OpenAI-compatible
Usage
Add the package to your Swift package dependencies:
.package(url: "https://github.com/AreroKetahi/foundation-openai", branch: "main")Then add the product to your target:
.product(name: "FoundationOpenAI", package: "foundation-openai")Import both FoundationModels and FoundationOpenAI:
import FoundationModels
import FoundationOpenAICreate a ChatGPTLanguageModel and use it with LanguageModelSession:
let model = ChatGPTLanguageModel.v5_4mini(
apiKey: "<OPENAI_API_KEY>"
)
let session = LanguageModelSession(model: model) {
"You are a concise assistant."
}
let response = try await session.respond(to: "Explain FoundationOpenAI in one sentence.")
print(response.content)
// or use streaming response
for try await response in session.streamResponse(
to: "Explain FoundationOpenAI in one sentence."
) {
// do somethings...
}ChatGPTLanguageModel uses OpenAI's Responses API format by default:
public var baseURL = URL(string: "https://api.openai.com/v1")!
public let apiFormat: OpenAILanguageModelAPIFormat = .responseThe predefined ChatGPT model IDs are:
.v5_4
.v5_4mini
.v5_5Supported LLMs
- [x] ChatGPT
- [x] DeepSeek
and to be support more
Custom executor configuration
Most usage can keep the default configuration:
configuration: .defaultUse a custom configuration when you need to change transcript conversion, request behavior, or tool-call strictness:
let model = ChatGPTLanguageModel(
configuration: .init(
toolCallingGenerationStrictness: .strict,
transformer: DefaultQueryTransformer(),
modifiers: []
),
model: .v5_4mini,
apiKey: "<OPENAI_API_KEY>"
)QueryTransformer controls how Transcript entries are converted into OpenAI-compatible messages. ExecutorRequestModifier allows you modify the outgoing URLRequest before it is sent.
Implementing Your Own Model
To add another OpenAI-compatible provider or model family, create a type that conforms to OpenAILanguageModel.
The type must provide:
Executor: normallyOpenAILanguageModelExecutor<Self>.Model: aRawRepresentablemodel enum whose raw value is the remote API
model ID.
capabilities: theFoundationModelscapabilities supported by the model.baseURL: the provider API base URL.apiFormat:.responsefor OpenAI Responses API, or.chatCompletionfor
Chat Completions-compatible APIs.
executorConfiguration: executor customization.model: the selected model case.apiKey: the provider API key.
Example:
import Foundation
import FoundationModels
import FoundationOpenAI
public struct MyProviderLanguageModel: OpenAILanguageModel {
public typealias Executor = OpenAILanguageModelExecutor<MyProviderLanguageModel>
public enum Model: String, Sendable, CaseIterable {
case fast = "my-provider-fast"
case pro = "my-provider-pro"
}
public let capabilities = LanguageModelCapabilities(
capabilities: [.reasoning, .toolCalling, .guidedGeneration]
)
public let baseURL = URL(string: "https://api.example.com/v1")!
public let apiFormat: OpenAILanguageModelAPIFormat = .chatCompletion
public var executorConfiguration: Executor.Configuration
public var model: Model
public var apiKey: String
public init(
configuration: Executor.Configuration = .init(),
model: Model,
apiKey: String
) {
self.executorConfiguration = configuration
self.model = model
self.apiKey = apiKey
}
}Then use it exactly like the predefined models:
let model = MyProviderLanguageModel(
model: .fast,
apiKey: "<API_KEY>"
)
let session = LanguageModelSession(model: model) {
"You are a helpful assistant."
}
let response = try await session.respond(to: "Hello")
print(response.content)If the provider follows the standard Chat Completions shape, .chatCompletion plus DefaultQueryTransformer is usually enough. If the provider has custom message, tool, or request requirements, provide your own QueryTransformer or ExecutorRequestModifier through Executor.Configuration.
Package Metadata
Repository: areroketahi/foundation-openai
Default branch: main
README: README.md