Skip to content
Draft
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 @@ -32,7 +32,7 @@
///
/// > Important: This method is for **internal use only** and may change at any time.
public func _startSession(tools: [any ToolRepresentable]?,
instructions: String?) throws -> any _ModelSession {
instructions: FirebaseAI.Instructions?) throws -> any _ModelSession {
switch availability {
case .available:
break
Expand Down Expand Up @@ -81,7 +81,7 @@
return LanguageModelSession(
model: self.systemLanguageModel,
tools: afmTools,
instructions: instructions
instructions: instructions?.toFoundationModels()
)
}
#endif // canImport(FoundationModels) && IS_FOUNDATION_MODELS_SUPPORTED_PLATFORM
Expand Down
37 changes: 36 additions & 1 deletion FirebaseAI/Sources/FirebaseAI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ public final class FirebaseAI: Sendable {
/// - instructions: System instructions that direct the model's behavior.
public func generativeModelSession(model: any LanguageModelProvider,
tools: [any ToolRepresentable]? = nil,
instructions: String? = nil) -> GenerativeModelSession {
instructions: FirebaseAI.Instructions? = nil)
-> GenerativeModelSession {
let tools = tools?.map { $0.toolRepresentation }

return GenerativeModelSession(
Expand All @@ -157,6 +158,26 @@ public final class FirebaseAI: Sendable {
)
}

public func generativeModelSession(model: any LanguageModelProvider,
tools: [any ToolRepresentable]? = nil,
@FirebaseAI.InstructionsBuilder instructions: () throws
-> FirebaseAI.Instructions) rethrows
-> GenerativeModelSession {
let tools = tools?.map { $0.toolRepresentation }

return try generativeModelSession(model: model, tools: tools as [any ToolRepresentable]?,
instructions: instructions())
}

public func generativeModelSession(model: any LanguageModelProvider,
tools: [any ToolRepresentable]? = nil,
instructions: String?) -> GenerativeModelSession {
let tools = tools?.map { $0.toolRepresentation }

return generativeModelSession(model: model, tools: tools as [any ToolRepresentable]?,
instructions: instructions)
}

#if canImport(FoundationModels)
/// **[Public Preview]** Initializes a `GenerativeModelSession` with the given model.
///
Expand All @@ -180,6 +201,20 @@ public final class FirebaseAI: Sendable {
return generativeModelSession(model: model, tools: tools as [any ToolRepresentable]?,
instructions: instructions)
}

@available(iOS 26.0, macOS 26.0, visionOS 26.0, *)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
public func generativeModelSession(model: any LanguageModelProvider,
tools: [any FoundationModels.Tool],
@FirebaseAI.InstructionsBuilder instructions: () throws
-> FirebaseAI.Instructions) rethrows
-> GenerativeModelSession {
let tools = tools.map { FirebaseAILogic.Tool.autoFunctionDeclaration($0) }

return try generativeModelSession(model: model, tools: tools as [any ToolRepresentable]?,
instructions: instructions)
}
#endif // canImport(FoundationModels)
#endif // compiler(>=6.2.3)

Expand Down
6 changes: 3 additions & 3 deletions FirebaseAI/Sources/GenerativeModelSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
/// ```
public final class GenerativeModelSession: Sendable {
let sessionManager: SessionManager
let instructions: String?
let instructions: FirebaseAI.Instructions?

// The maximum number of automatic back-and-forth turns the session will perform to resolve
// function calls.
Expand All @@ -74,7 +74,7 @@
/// or instances conforming to ``ToolRepresentable`` for automatic function calling.
/// - instructions: System instructions that direct the model's behavior.
init(model: any LanguageModel, tools: [any ToolRepresentable]? = nil,
instructions: String? = nil) {
instructions: FirebaseAI.Instructions? = nil) {
sessionManager = SessionManager(model: model, tools: tools)
self.instructions = instructions
}
Expand Down Expand Up @@ -438,7 +438,7 @@
}
}

func getOrStartSession(instructions: String?) throws -> any _ModelSession {
func getOrStartSession(instructions: FirebaseAI.Instructions?) throws -> any _ModelSession {
try _isResponding.withLock { isResponding in
if let currentSession = _activeSession {
return currentSession
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#endif // canImport(FoundationModels)

extension FirebaseAI {
protocol ConvertibleToGeneratedContent {
protocol ConvertibleToGeneratedContent: FirebaseAI.InstructionsRepresentable {
var firebaseGeneratedContent: FirebaseAI.GeneratedContent { get }
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#if canImport(FoundationModels)
import FoundationModels
#endif // canImport(FoundationModels)

public extension FirebaseAI {
protocol InstructionsRepresentable {
@FirebaseAI.InstructionsBuilder
var firebaseInstructionsRepresentation: FirebaseAI.Instructions { get }
}
}

extension String: FirebaseAI.InstructionsRepresentable {
public var firebaseInstructionsRepresentation: FirebaseAI.Instructions {
return FirebaseAI.Instructions(parts: [.text(self)])
}
}

extension Array: FirebaseAI.InstructionsRepresentable
where Element: FirebaseAI.InstructionsRepresentable {
public var firebaseInstructionsRepresentation: FirebaseAI.Instructions {
let allParts = flatMap { $0.firebaseInstructionsRepresentation.parts }
return FirebaseAI.Instructions(parts: allParts)
}
}

extension FirebaseAI.GeneratedContent: FirebaseAI.InstructionsRepresentable {
public var firebaseInstructionsRepresentation: FirebaseAI.Instructions {
return FirebaseAI.Instructions(parts: [.structure(self)])
}
}

#if canImport(FoundationModels)
@available(iOS 26.0, macOS 26.0, visionOS 26.0, *)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
extension FoundationModels.GeneratedContent: FirebaseAI.InstructionsRepresentable {
public var firebaseInstructionsRepresentation: FirebaseAI.Instructions {
FirebaseAI.Instructions(parts: [.structure(firebaseGeneratedContent)])
}
}
#endif // canImport(FoundationModels)
4 changes: 2 additions & 2 deletions FirebaseAI/Sources/Protocols/Public/LanguageModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
/// Returns a new session for this model.
///
/// > Important: This method is for **internal use only** and may change at any time.
func _startSession(tools: [any ToolRepresentable]?, instructions: String?) throws
-> any _ModelSession
func _startSession(tools: [any ToolRepresentable]?,
instructions: FirebaseAI.Instructions?) throws -> any _ModelSession
}

// Default implementation to allow a `LanguageModel` to conform to `LanguageModelProvider`.
Expand Down
4 changes: 2 additions & 2 deletions FirebaseAI/Sources/Types/Internal/HybridModelSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@
private let primaryModel: any LanguageModel
private let secondaryModel: any LanguageModel
private let tools: [any ToolRepresentable]?
private let instructions: String?
private let instructions: FirebaseAI.Instructions?

typealias SessionState = (primary: (any _ModelSession)?, secondary: (any _ModelSession)?)
private let lock: UnfairLock<SessionState>

init(primaryModel: any LanguageModel, secondaryModel: any LanguageModel,
tools: [any ToolRepresentable]?, instructions: String?) {
tools: [any ToolRepresentable]?, instructions: FirebaseAI.Instructions?) {
self.primaryModel = primaryModel
self.secondaryModel = secondaryModel
self.tools = tools
Expand Down
4 changes: 2 additions & 2 deletions FirebaseAI/Sources/Types/Public/GeminiModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ import Foundation
///
/// > Important: This method is for **internal use only** and may change at any time.
public func _startSession(tools: [any ToolRepresentable]?,
instructions: String?) throws -> any _ModelSession {
instructions: FirebaseAI.Instructions?) throws -> any _ModelSession {
let model = GenerativeModel(
modelName: modelName,
modelResourceName: modelResourceName,
Expand All @@ -72,7 +72,7 @@ import Foundation
safetySettings: safetySettings,
tools: tools?.map { $0.toolRepresentation },
// TODO: Add toolConfig
systemInstruction: instructions.map { ModelContent(role: "system", parts: $0) },
systemInstruction: instructions?.toModelContent(),
requestOptions: requestOptions,
urlSession: urlSession
)
Expand Down
2 changes: 1 addition & 1 deletion FirebaseAI/Sources/Types/Public/HybridModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
///
/// > Important: This method is for **internal use only** and may change at any time.
public func _startSession(tools: [any ToolRepresentable]?,
instructions: String?) throws -> any _ModelSession {
instructions: FirebaseAI.Instructions?) throws -> any _ModelSession {
return HybridModelSession(
primaryModel: primary,
secondaryModel: secondary,
Expand Down
78 changes: 78 additions & 0 deletions FirebaseAI/Sources/Types/Public/Instructions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#if canImport(FoundationModels)
import FoundationModels
#endif // canImport(FoundationModels)

public extension FirebaseAI {
struct Instructions: Sendable {
enum InstructionsPart {
case text(String)
case structure(FirebaseAI.GeneratedContent)
}

let parts: [InstructionsPart]

init(parts: [InstructionsPart]) {
self.parts = parts
}

public init(_ content: some FirebaseAI.InstructionsRepresentable) {
parts = content.firebaseInstructionsRepresentation.parts
}

public init(@FirebaseAI.InstructionsBuilder _ content: () throws -> Instructions) rethrows {
self = try content()
}

#if canImport(FoundationModels)
@available(iOS 26.0, macOS 26.0, visionOS 26.0, *)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
func toFoundationModels() -> FoundationModels.Instructions {
return FoundationModels.Instructions {
for part in parts {
switch part {
case let .text(text):
text
case let .structure(structure):
structure
}
}
}
}
#endif // canImport(FoundationModels)

func toModelContent() -> ModelContent {
return ModelContent(
role: "system",
parts: parts.map {
switch $0 {
case let .text(string):
return TextPart(string)
case let .structure(generatedContent):
return TextPart(generatedContent.jsonString)
}
}
)
}
}
}

extension FirebaseAI.Instructions: FirebaseAI.InstructionsRepresentable {
public var firebaseInstructionsRepresentation: FirebaseAI.Instructions {
self
}
}
76 changes: 76 additions & 0 deletions FirebaseAI/Sources/Types/Public/InstructionsBuilder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#if canImport(FoundationModels)
import FoundationModels
#endif // canImport(FoundationModels)

public extension FirebaseAI {
@resultBuilder
struct InstructionsBuilder {
public static func buildBlock<each I>(_ components: repeat each I)
-> FirebaseAI.Instructions where repeat each I: FirebaseAI.InstructionsRepresentable {
var allComponents: [FirebaseAI.Instructions.InstructionsPart] = []
repeat allComponents
.append(contentsOf: (each components).firebaseInstructionsRepresentation.parts)
return FirebaseAI.Instructions(parts: allComponents)
}

public static func buildArray(_ instructions: [some FirebaseAI.InstructionsRepresentable])
-> FirebaseAI.Instructions {
let allComponents = instructions.flatMap { $0.firebaseInstructionsRepresentation.parts }
return FirebaseAI.Instructions(parts: allComponents)
}

public static func buildEither(first component: some FirebaseAI.InstructionsRepresentable)
-> FirebaseAI.Instructions {
component.firebaseInstructionsRepresentation
}

public static func buildEither(second component: some FirebaseAI.InstructionsRepresentable)
-> FirebaseAI.Instructions {
component.firebaseInstructionsRepresentation
}

public static func buildOptional(_ instructions: FirebaseAI.Instructions?)
-> FirebaseAI.Instructions {
instructions ?? FirebaseAI.Instructions(parts: [])
}

public static func buildLimitedAvailability(_ instructions: some FirebaseAI
.InstructionsRepresentable) -> FirebaseAI.Instructions {
instructions.firebaseInstructionsRepresentation
}

public static func buildExpression<I>(_ expression: I)
-> I where I: FirebaseAI.InstructionsRepresentable {
expression
}

public static func buildExpression(_ expression: FirebaseAI.Instructions)
-> FirebaseAI.Instructions {
expression
}

#if canImport(FoundationModels)
@available(iOS 26.0, macOS 26.0, visionOS 26.0, *)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
public static func buildExpression(_ expression: FoundationModels
.ConvertibleToGeneratedContent) -> FirebaseAI.Instructions {
FirebaseAI.Instructions(expression.generatedContent)
}
#endif // canImport(FoundationModels)
}
}
Loading
Loading