Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#if compiler(>=6.2.3) && canImport(FoundationModels)
#if canImport(FoundationModels) && IS_FOUNDATION_MODELS_SUPPORTED_PLATFORM
import Foundation
import FoundationModels

@available(iOS 26.0, macOS 26.0, visionOS 26.0, *)
@available(iOS 26.0, macOS 26.0, visionOS 26.0, watchOS 27.0, *)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
extension FoundationModels.LanguageModelSession: _ModelSession {
/// Returns `true` if the session has history (i.e., it has already had one or more chat turns).
///
Expand Down Expand Up @@ -179,9 +178,8 @@
}
}

@available(iOS 26.0, macOS 26.0, visionOS 26.0, *)
@available(iOS 26.0, macOS 26.0, visionOS 26.0, watchOS 27.0, *)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
private extension GenerationOptionsRepresentable {
var generationOptions: FoundationModels.GenerationOptions {
guard let options = responseGenerationOptions.foundationModelsGenerationOptions else {
Expand All @@ -191,4 +189,4 @@
return options.toFoundationModels()
}
}
#endif // compiler(>=6.2.3) && canImport(FoundationModels)
#endif // canImport(FoundationModels) && IS_FOUNDATION_MODELS_SUPPORTED_PLATFORM
7 changes: 4 additions & 3 deletions FirebaseAI/Sources/FirebaseAI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,15 @@ public final class FirebaseAI: Sendable {
)
}

#if compiler(>=6.4)
#if compiler(>=6.4) && canImport(FoundationModels)
Comment thread
andrewheard marked this conversation as resolved.
@available(iOS 27.0, macOS 27.0, visionOS 27.0, watchOS 27.0, *)
@available(tvOS, unavailable)
public func geminiLanguageModel(name: String, safetySettings: [SafetySetting]? = nil,
options: GeminiGenerationOptions? = nil,
serverTools: [any GeminiTool]? = nil,
requestOptions: RequestOptions = RequestOptions())
-> GeminiLanguageModel {
return GeminiLanguageModel(
modelName: name,
modelResourceName: modelResourceName(modelName: name),
firebaseInfo: firebaseInfo,
apiConfig: apiConfig,
Expand All @@ -123,7 +124,7 @@ public final class FirebaseAI: Sendable {
requestOptions: requestOptions,
)
}
#endif // compiler(>=6.4)
#endif // compiler(>=6.4) && canImport(FoundationModels)
Comment thread
andrewheard marked this conversation as resolved.

// TODO: Remove the `#if compiler(>=6.2.3)` when Xcode 26.2 is the minimum supported version.
#if compiler(>=6.2.3)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@
}

#if canImport(FoundationModels)
@available(iOS 26.0, macOS 26.0, visionOS 26.0, *)
@available(iOS 26.0, macOS 26.0, visionOS 26.0, watchOS 27.0, *)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
extension FoundationModels.ConvertibleToGeneratedContent {
var firebaseGeneratedContent: FirebaseAI.GeneratedContent {
return FirebaseAI.GeneratedContent(
Expand Down
50 changes: 18 additions & 32 deletions FirebaseAI/Sources/Types/Public/GeminiLanguageModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,46 +12,31 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import FirebaseCore
import Foundation
import FoundationModels
#if compiler(>=6.4) && canImport(FoundationModels)
import FirebaseCore
import Foundation
import FoundationModels

#if compiler(>=6.4)
@available(iOS 27.0, macOS 27.0, visionOS 27.0, watchOS 27.0, *)
Comment thread
andrewheard marked this conversation as resolved.
@available(tvOS, unavailable)
public struct GeminiLanguageModel {
public struct ModelConfig: Sendable, Hashable {
let firebaseAppName: String
let apiConfig: APIConfig
let useLimitedUseAppCheckTokens: Bool

let modelName: String
let modelResourceName: String
let safetySettings: [SafetySetting]?
let serverTools: [InternalGeminiTool]
let geminiOptions: GeminiGenerationOptions?
let requestOptions: RequestOptions

var firebaseAI: FirebaseAI {
let firebaseApp = FirebaseApp.app(name: firebaseAppName)
return FirebaseAI.createInstance(
app: firebaseApp,
apiConfig: apiConfig,
useLimitedUseAppCheckTokens: useLimitedUseAppCheckTokens
)
}
}

let modelConfig: ModelConfig
let modelResourceName: String
let firebaseInfo: FirebaseInfo
let toolConfig: ToolConfig?
let urlSession: URLSession
let generativeAIService: GenerativeAIService

init(modelName: String,
modelResourceName: String,
init(modelResourceName: String,
firebaseInfo: FirebaseInfo,
apiConfig: APIConfig,
safetySettings: [SafetySetting]? = nil,
serverTools: [any GeminiTool]? = nil,
toolConfig: ToolConfig? = nil,
geminiOptions: GeminiGenerationOptions? = nil,
requestOptions: RequestOptions = RequestOptions(),
urlSession: URLSession = GenAIURLSession.default) {
Expand All @@ -75,23 +60,22 @@ import FoundationModels
}

modelConfig = ModelConfig(
firebaseAppName: firebaseInfo.app.name,
apiConfig: apiConfig,
useLimitedUseAppCheckTokens: firebaseInfo.useLimitedUseAppCheckTokens,
modelName: modelName,
modelResourceName: modelResourceName,
safetySettings: safetySettings,
serverTools: serverTools,
geminiOptions: geminiOptions,
requestOptions: requestOptions
)
self.modelResourceName = modelResourceName
self.firebaseInfo = firebaseInfo
self.toolConfig = toolConfig
self.urlSession = urlSession
generativeAIService = GenerativeAIService(
firebaseInfo: firebaseInfo,
urlSession: urlSession
)
}
}

@available(iOS 27.0, macOS 27.0, visionOS 27.0, watchOS 27.0, *)
@available(tvOS, unavailable)
extension GeminiLanguageModel: FoundationModels.LanguageModel {
public var capabilities: LanguageModelCapabilities {
return LanguageModelCapabilities(capabilities: [
Expand All @@ -107,6 +91,8 @@ import FoundationModels
}
}

@available(iOS 27.0, macOS 27.0, visionOS 27.0, watchOS 27.0, *)
@available(tvOS, unavailable)
public struct GeminiGenerationOptions: Sendable, Equatable, Hashable {
/// Supported modalities of the response.
public var responseModalities: [ResponseModality]?
Expand All @@ -133,4 +119,4 @@ import FoundationModels
self.includeThoughts = includeThoughts
}
}
#endif // compiler(>=6.4)
#endif // compiler(>=6.4) && canImport(FoundationModels)
31 changes: 18 additions & 13 deletions FirebaseAI/Sources/Types/Public/GeminiLanguageModelExecutor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import CoreGraphics
import Foundation
import FoundationModels

#if canImport(FoundationModels) && IS_FOUNDATION_MODELS_SUPPORTED_PLATFORM && compiler(>=6.4)
#if compiler(>=6.4) && canImport(FoundationModels)
import CoreGraphics
import Foundation
import FoundationModels
import ImageIO

@available(iOS 27.0, macOS 27.0, visionOS 27.0, watchOS 27.0, *)
Expand All @@ -36,15 +35,14 @@ import FoundationModels
}

@available(iOS 27.0, macOS 27.0, visionOS 27.0, watchOS 27.0, *)
@available(tvOS, unavailable)
public extension GeminiLanguageModel {
struct Executor: LanguageModelExecutor {
let firebaseAI: FirebaseAI
let configuration: GeminiLanguageModel.ModelConfig

static let placeholderIDPrefix = "placeholder-id-"

public init(configuration: GeminiLanguageModel.ModelConfig) throws {
firebaseAI = configuration.firebaseAI
self.configuration = configuration
}

Expand Down Expand Up @@ -283,21 +281,25 @@ import FoundationModels
}
}

let generativeModel = firebaseAI.generativeModel(
modelName: configuration.modelName,
let generateContentRequest = GenerateContentRequest(
model: configuration.modelResourceName,
contents: parts,
generationConfig: generationConfig,
safetySettings: configuration.safetySettings,
tools: tools,
toolConfig: toolConfig,
systemInstruction: systemInstruction,
requestOptions: configuration.requestOptions
apiConfig: configuration.apiConfig,
apiMethod: .streamGenerateContent,
options: configuration.requestOptions
)

// 2. Open the stream to your provider. The transport is your choice —
// URLSession.bytes, a vendored SDK, gRPC, WebSocket, anything that
// yields an async sequence of provider events.

let stream = try TaskLocals.$isFoundationModelsRequest.withValue(true) {
try generativeModel.generateContentStream(parts)
let stream = TaskLocals.$isFoundationModelsRequest.withValue(true) {
model.generativeAIService.loadRequestStream(request: generateContentRequest)
}

// 3. For each provider event, translate it into one or more channel
Expand Down Expand Up @@ -535,6 +537,7 @@ import FoundationModels
}

@available(iOS 27.0, macOS 27.0, visionOS 27.0, watchOS 27.0, *)
@available(tvOS, unavailable)
extension Transcript.Instructions {
func toGeminiSystemInstruction() throws -> ModelContent? {
var instructions = ""
Expand Down Expand Up @@ -582,6 +585,7 @@ import FoundationModels
}

@available(iOS 27.0, macOS 27.0, visionOS 27.0, watchOS 27.0, *)
@available(tvOS, unavailable)
extension Transcript.Prompt {
func toGeminiPrompt() throws -> ModelContent {
var parts = [any Part]()
Expand Down Expand Up @@ -635,6 +639,7 @@ import FoundationModels
}

@available(iOS 27.0, macOS 27.0, visionOS 27.0, watchOS 27.0, *)
@available(tvOS, unavailable)
extension Transcript.Response {
func toGeminiResponse() throws -> ModelContent {
var parts = [any Part]()
Expand Down Expand Up @@ -679,4 +684,4 @@ import FoundationModels
return ModelContent(role: "model", parts: parts)
}
}
#endif // canImport(FoundationModels) && IS_FOUNDATION_MODELS_SUPPORTED_PLATFORM && compiler(>=6.4)
#endif // compiler(>=6.4) && canImport(FoundationModels)
19 changes: 10 additions & 9 deletions FirebaseAI/Tests/Unit/ResponseStreamTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,21 +104,22 @@
}
} errorHandler: { error in
// Assert that the error is one of the expected decoding failure types.
if let genError = error as? GenerativeModelSession.GenerationError,
case .decodingFailure = genError {
return // Expected error.
}
#if !os(watchOS)
#if compiler(>=6.4)
if #available(iOS 27.0, macOS 27.0, visionOS 27.0, watchOS 27.0, *),
error is FoundationModels.GeneratedContent.ParsingError {
return // Expected error.
}
#else
if #available(iOS 26.0, macOS 26.0, visionOS 26.0, *),
let foundationError = error as? FoundationModels.LanguageModelSession
.GenerationError,
case .decodingFailure = foundationError {
// TODO: Remove this else-if after wrapping `FoundationModels.GenerationError` errors
// into equivalent `GenerativeModelSession.GenerationError` values.

return // Expected error.
} else if let generationError = error as? GenerativeModelSession.GenerationError,
case .decodingFailure = generationError {
return // Expected error.
}
#endif // !os(watchOS)
#endif // compiler(>=6.4)

XCTFail("Expected a decoding failure error, but got \(error) instead.")
}
Expand Down
Loading