FoundationOpenAI bridges Apple's FoundationModels APIs to OpenAI-compatible
remote model APIs.
It lets you use a remote model through the same LanguageModelSession style
API that FoundationModels uses, while the framework handles transcript
conversion, streaming responses, structured output, tool calling, reasoning
output, and request customization.
Warning
This project currently depends on APIs introduced in OS 27 at WWDC 26. Those APIs are still in beta. FoundationOpenAI will continue to evolve alongside system beta releases, but it does not provide any stability guarantees at this stage.
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_5- ChatGPT
- DeepSeek
and to be support more
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.
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.