Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
216a06d
[AI] Server Prompt Templates
paulb777 Oct 3, 2025
405d5fe
copyrights
paulb777 Oct 3, 2025
c3c03be
Checkpoint after trying to add imagen implementation
paulb777 Oct 7, 2025
953b3f0
end to end testGenerateContentWithText passes
paulb777 Oct 9, 2025
daf7b4e
FirebaseAI/Tests/Unit/TemplateGenerativeModelTests.swift
paulb777 Oct 9, 2025
29c79b1
CI fixes
paulb777 Oct 10, 2025
6aa13e0
Streaming APIs
paulb777 Oct 10, 2025
e75d7e9
chat history refactor
paulb777 Oct 10, 2025
6cc6c9d
Re-sort tests
paulb777 Oct 10, 2025
ac996fd
Fix TemplateImagenModel to encode variables as inputs
google-labs-jules[bot] Oct 10, 2025
7ba2c28
Fix: Correct URL construction for image generation requests
google-labs-jules[bot] Oct 10, 2025
dd66187
checkpoint
paulb777 Oct 12, 2025
b3c28d1
Imagen generate test works
paulb777 Oct 12, 2025
705d0c2
fix template imagen response return type
paulb777 Oct 12, 2025
17fd0b8
existing tests passing
paulb777 Oct 12, 2025
c4068e9
build fix
paulb777 Oct 12, 2025
7af117c
style
paulb777 Oct 12, 2025
32bc581
Add streaming integration tests
paulb777 Oct 13, 2025
1d162dc
testChatStream
paulb777 Oct 13, 2025
d7beef0
unit tests run
paulb777 Oct 13, 2025
fd8f76a
streaming unit tests
paulb777 Oct 13, 2025
a49dee1
existing mock files for template unit tests
paulb777 Oct 13, 2025
3f49b09
streamline integration tests
paulb777 Oct 13, 2025
3f3607a
fixes
paulb777 Oct 13, 2025
64df672
Templates do not have an implicit .prompt suffix and only global loca…
paulb777 Oct 14, 2025
9f91c21
fixes
paulb777 Oct 14, 2025
fa4a3f4
self review, including file/naming updates
paulb777 Oct 14, 2025
7bd5d2c
Update Integration test xcodeproj after rebase
paulb777 Oct 16, 2025
eeb9e66
module rename updates after rebase for unit tests
paulb777 Oct 16, 2025
1840566
[Firebase AI] Refactor SPT integration tests to Swift Testing (#15426)
andrewheard Oct 17, 2025
67d40eb
Continue migration from template templates to project templates
paulb777 Oct 17, 2025
6338983
Rename template variables to template inputs
paulb777 Oct 21, 2025
a11c20c
template to templateID
paulb777 Oct 21, 2025
bc4ab28
doc updates
paulb777 Oct 21, 2025
5c784c7
Add sendMessage modelContent variations
paulb777 Oct 21, 2025
60f32ef
Convert tests to use test project prompt templates
paulb777 Oct 22, 2025
fe3ae25
Eliminate force unwraps in url creation
paulb777 Oct 24, 2025
f63c4c0
misc review fixes
paulb777 Oct 24, 2025
83a0f3a
aligned structure and naming
paulb777 Oct 24, 2025
9ee5acc
Update FirebaseAI/Sources/AILog.swift
paulb777 Oct 24, 2025
287dd12
Build fixes
paulb777 Oct 25, 2025
f705699
Make template chat APIs internal for now
paulb777 Oct 31, 2025
fae5ca9
style
paulb777 Oct 31, 2025
98a2ba4
us-central1 is now supported for vertexAI
paulb777 Oct 31, 2025
085cc97
Update FirebaseAI/Sources/AILog.swift
paulb777 Oct 31, 2025
e9c6bf0
[Firebase AI] Replace `Any` with `TemplateInputRepresentable`
andrewheard Oct 31, 2025
71cfe95
Remove `TemplateInput.Kind` since it is equivalent to `JSONValue`
andrewheard Oct 31, 2025
da2d172
Fix request encoding
andrewheard Oct 31, 2025
233d45b
Fix unit test
andrewheard Oct 31, 2025
d539549
Add availability annotations
andrewheard Oct 31, 2025
caf62f6
Merge branch 'main' into ah/pb-spt
andrewheard Nov 4, 2025
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
14 changes: 6 additions & 8 deletions FirebaseAI/Sources/TemplateChatSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,14 @@ final class TemplateChatSession: Sendable {
/// - Returns: The content generated by the model.
/// - Throws: A ``GenerateContentError`` if the request failed.
func sendMessage(_ content: [ModelContent],
inputs: [String: Any],
inputs: [String: any TemplateInputRepresentable],
options: RequestOptions = RequestOptions()) async throws
-> GenerateContentResponse {
let templateInputs = try inputs.mapValues { try TemplateInput(value: $0) }
let newContent = content.map(populateContentRole)
let response = try await model.generateContentWithHistory(
history: _history.history + newContent,
template: templateID,
inputs: templateInputs,
inputs: inputs.mapValues { $0.templateInputRepresentation },
options: options
)
_history.append(contentsOf: newContent)
Expand All @@ -82,7 +81,7 @@ final class TemplateChatSession: Sendable {
/// - Returns: The content generated by the model.
/// - Throws: A ``GenerateContentError`` if the request failed.
func sendMessage(_ message: any PartsRepresentable,
inputs: [String: Any],
inputs: [String: any TemplateInputRepresentable],
options: RequestOptions = RequestOptions()) async throws
-> GenerateContentResponse {
return try await sendMessage([ModelContent(parts: message.partsValue)],
Expand All @@ -103,15 +102,14 @@ final class TemplateChatSession: Sendable {
/// - Returns: An `AsyncThrowingStream` that yields `GenerateContentResponse` objects.
/// - Throws: A ``GenerateContentError`` if the request failed.
func sendMessageStream(_ content: [ModelContent],
inputs: [String: Any],
inputs: [String: any TemplateInputRepresentable],
options: RequestOptions = RequestOptions()) throws
-> AsyncThrowingStream<GenerateContentResponse, Error> {
let templateInputs = try inputs.mapValues { try TemplateInput(value: $0) }
let newContent = content.map(populateContentRole)
let stream = try model.generateContentStreamWithHistory(
history: _history.history + newContent,
template: templateID,
inputs: templateInputs,
inputs: inputs.mapValues { $0.templateInputRepresentation },
options: options
)
return AsyncThrowingStream { continuation in
Expand Down Expand Up @@ -158,7 +156,7 @@ final class TemplateChatSession: Sendable {
/// - Returns: An `AsyncThrowingStream` that yields `GenerateContentResponse` objects.
/// - Throws: A ``GenerateContentError`` if the request failed.
func sendMessageStream(_ message: any PartsRepresentable,
inputs: [String: Any],
inputs: [String: any TemplateInputRepresentable],
options: RequestOptions = RequestOptions()) throws
-> AsyncThrowingStream<GenerateContentResponse, Error> {
return try sendMessageStream([ModelContent(parts: message.partsValue)],
Expand Down
2 changes: 1 addition & 1 deletion FirebaseAI/Sources/TemplateGenerateContentRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ extension TemplateGenerateContentRequest: Encodable {

func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(inputs, forKey: .inputs)
try container.encode(inputs.mapValues { $0.value }, forKey: .inputs)
try container.encode(history, forKey: .history)
}
}
Expand Down
10 changes: 4 additions & 6 deletions FirebaseAI/Sources/TemplateGenerativeModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,13 @@ public final class TemplateGenerativeModel: Sendable {
/// - Returns: The content generated by the model.
/// - Throws: A ``GenerateContentError`` if the request failed.
public func generateContent(templateID: String,
inputs: [String: Any],
inputs: [String: any TemplateInputRepresentable],
options: RequestOptions = RequestOptions()) async throws
-> GenerateContentResponse {
let templateInputs = try inputs.mapValues { try TemplateInput(value: $0) }
return try await generateContentWithHistory(
history: [],
template: templateID,
inputs: templateInputs,
inputs: inputs.mapValues { $0.templateInputRepresentation },
options: options
)
}
Expand Down Expand Up @@ -90,13 +89,12 @@ public final class TemplateGenerativeModel: Sendable {
/// - Returns: An `AsyncThrowingStream` that yields `GenerateContentResponse` objects.
/// - Throws: A ``GenerateContentError`` if the request failed.
public func generateContentStream(templateID: String,
inputs: [String: Any],
inputs: [String: any TemplateInputRepresentable],
options: RequestOptions = RequestOptions()) throws
-> AsyncThrowingStream<GenerateContentResponse, Error> {
let templateInputs = try inputs.mapValues { try TemplateInput(value: $0) }
let request = TemplateGenerateContentRequest(
template: templateID,
inputs: templateInputs,
inputs: inputs.mapValues { $0.templateInputRepresentation },
history: [],
projectID: generativeAIService.firebaseInfo.projectID,
stream: true,
Expand Down
2 changes: 1 addition & 1 deletion FirebaseAI/Sources/TemplateImagenGenerationRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,6 @@ extension TemplateImagenGenerationRequest: Encodable {

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(inputs, forKey: .inputs)
try container.encode(inputs.mapValues { $0.value }, forKey: .inputs)
}
}
5 changes: 2 additions & 3 deletions FirebaseAI/Sources/TemplateImagenModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,13 @@ public final class TemplateImagenModel: Sendable {
/// - Returns: The images generated by the model.
/// - Throws: An error if the request failed.
public func generateImages(templateID: String,
inputs: [String: Any],
inputs: [String: any TemplateInputRepresentable],
options: RequestOptions = RequestOptions()) async throws
-> ImagenGenerationResponse<ImagenInlineImage> {
let templateInputs = try inputs.mapValues { try TemplateInput(value: $0) }
let projectID = generativeAIService.firebaseInfo.projectID
let request = TemplateImagenGenerationRequest<ImagenInlineImage>(
template: templateID,
inputs: templateInputs,
inputs: inputs.mapValues { $0.templateInputRepresentation },
projectID: projectID,
apiConfig: apiConfig,
options: options
Expand Down
57 changes: 12 additions & 45 deletions FirebaseAI/Sources/TemplateInput.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,53 +14,20 @@

import Foundation

enum TemplateInput: Encodable, Sendable {
case string(String)
case int(Int)
case double(Double)
case bool(Bool)
case array([TemplateInput])
case dictionary([String: TemplateInput])
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
public struct TemplateInput: Sendable {
let value: JSONValue

init(value: Any) throws {
switch value {
case let value as String:
self = .string(value)
case let value as Int:
self = .int(value)
case let value as Double:
self = .double(value)
case let value as Float:
self = .double(Double(value))
case let value as Bool:
self = .bool(value)
case let value as [Any]:
self = try .array(value.map { try TemplateInput(value: $0) })
case let value as [String: Any]:
self = try .dictionary(value.mapValues { try TemplateInput(value: $0) })
default:
throw EncodingError.invalidValue(
value,
EncodingError.Context(codingPath: [], debugDescription: "Invalid value")
)
}
public init(_ input: some TemplateInputRepresentable) {
self = .init(value: input.templateInputRepresentation.value)
}

func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case let .string(value):
try container.encode(value)
case let .int(value):
try container.encode(value)
case let .double(value):
try container.encode(value)
case let .bool(value):
try container.encode(value)
case let .array(value):
try container.encode(value)
case let .dictionary(value):
try container.encode(value)
}
init(value: JSONValue) {
self.value = value
}
}

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
extension TemplateInput: TemplateInputRepresentable {
public var templateInputRepresentation: TemplateInput { self }
}
65 changes: 65 additions & 0 deletions FirebaseAI/Sources/Types/Public/TemplateInputRepresentable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2025 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.

import Foundation

/// A type that can be represented as a ``TemplateInput``.
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
public protocol TemplateInputRepresentable: Encodable, Sendable {
var templateInputRepresentation: TemplateInput { get }
}

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
extension String: TemplateInputRepresentable {
public var templateInputRepresentation: TemplateInput { TemplateInput(value: .string(self)) }
}

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
extension Int: TemplateInputRepresentable {
public var templateInputRepresentation: TemplateInput {
TemplateInput(value: .number(Double(self)))
}
}

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
extension Double: TemplateInputRepresentable {
public var templateInputRepresentation: TemplateInput { TemplateInput(value: .number(self)) }
}

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
extension Float: TemplateInputRepresentable {
public var templateInputRepresentation: TemplateInput {
TemplateInput(value: .number(Double(self)))
}
}

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
extension Bool: TemplateInputRepresentable {
public var templateInputRepresentation: TemplateInput { TemplateInput(value: .bool(self)) }
}

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
extension Array: TemplateInputRepresentable where Element: TemplateInputRepresentable {
public var templateInputRepresentation: TemplateInput {
TemplateInput(value: .array(map { TemplateInput($0).value }))
}
}

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
extension Dictionary: TemplateInputRepresentable
where Key == String, Value: TemplateInputRepresentable {
public var templateInputRepresentation: TemplateInput {
TemplateInput(value: .object(mapValues { TemplateInput($0).value }))
}
}
4 changes: 2 additions & 2 deletions FirebaseAI/Tests/Unit/TemplateInputTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import XCTest
final class TemplateInputTests: XCTestCase {
func testInitWithFloat() throws {
let floatValue: Float = 3.14
let templateInput = try TemplateInput(value: floatValue)
guard case let .double(doubleValue) = templateInput else {
let templateInput = TemplateInput(floatValue)
guard case let .number(doubleValue) = templateInput.value else {
XCTFail("Expected a .double case, but got \(templateInput)")
return
}
Expand Down
Loading