Skip to content

Commit fa4acb3

Browse files
authored
[AI] Rename structured output types with Firebase prefix (#15811)
1 parent 74f063d commit fa4acb3

File tree

20 files changed

+392
-332
lines changed

20 files changed

+392
-332
lines changed

FirebaseAI/Sources/GenerationConfig.swift

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public struct GenerationConfig: Sendable {
5151
/// Output schema of the generated response in [JSON Schema](https://json-schema.org/) format.
5252
///
5353
/// If set, `responseSchema` must be omitted and `responseMIMEType` is required.
54-
var responseJSONSchema: JSONSchema?
54+
var responseFirebaseGenerationSchema: FirebaseGenerationSchema?
5555

5656
/// Supported modalities of the response.
5757
var responseModalities: [ResponseModality]?
@@ -180,14 +180,15 @@ public struct GenerationConfig: Sendable {
180180
self.stopSequences = stopSequences
181181
self.responseMIMEType = responseMIMEType
182182
self.responseSchema = responseSchema
183-
responseJSONSchema = nil
183+
responseFirebaseGenerationSchema = nil
184184
self.responseModalities = responseModalities
185185
self.thinkingConfig = thinkingConfig
186186
}
187187

188188
init(temperature: Float? = nil, topP: Float? = nil, topK: Int? = nil, candidateCount: Int? = nil,
189189
maxOutputTokens: Int? = nil, presencePenalty: Float? = nil, frequencyPenalty: Float? = nil,
190-
stopSequences: [String]? = nil, responseMIMEType: String, responseJSONSchema: JSONSchema,
190+
stopSequences: [String]? = nil, responseMIMEType: String,
191+
responseFirebaseGenerationSchema: FirebaseGenerationSchema,
191192
responseModalities: [ResponseModality]? = nil, thinkingConfig: ThinkingConfig? = nil) {
192193
self.temperature = temperature
193194
self.topP = topP
@@ -199,7 +200,7 @@ public struct GenerationConfig: Sendable {
199200
self.stopSequences = stopSequences
200201
self.responseMIMEType = responseMIMEType
201202
responseSchema = nil
202-
self.responseJSONSchema = responseJSONSchema
203+
self.responseFirebaseGenerationSchema = responseFirebaseGenerationSchema
203204
self.responseModalities = responseModalities
204205
self.thinkingConfig = thinkingConfig
205206
}
@@ -240,13 +241,13 @@ public struct GenerationConfig: Sendable {
240241
config.responseModalities = overrideConfig.responseModalities ?? config.responseModalities
241242
config.thinkingConfig = overrideConfig.thinkingConfig ?? config.thinkingConfig
242243

243-
// 5. Handle Schema mutual exclusivity with precedence for `responseJSONSchema`.
244-
if let responseJSONSchema = overrideConfig.responseJSONSchema {
245-
config.responseJSONSchema = responseJSONSchema
244+
// 5. Handle Schema mutual exclusivity with precedence for `responseFirebaseGenerationSchema`.
245+
if let responseFirebaseGenerationSchema = overrideConfig.responseFirebaseGenerationSchema {
246+
config.responseFirebaseGenerationSchema = responseFirebaseGenerationSchema
246247
config.responseSchema = nil
247248
} else if let responseSchema = overrideConfig.responseSchema {
248249
config.responseSchema = responseSchema
249-
config.responseJSONSchema = nil
250+
config.responseFirebaseGenerationSchema = nil
250251
}
251252

252253
return config
@@ -257,17 +258,18 @@ public struct GenerationConfig: Sendable {
257258
/// - Parameters:
258259
/// - base: The foundational configuration (e.g., model defaults).
259260
/// - overrides: The configuration containing overrides (e.g., request specific).
260-
/// - jsonSchema: The JSON schema to enforce on the output.
261+
/// - firebaseGenerationSchema: The JSON schema to enforce on the output.
261262
/// - Returns: A non-nil `GenerationConfig` with the merged values and JSON constraints applied.
262263
static func merge(_ base: GenerationConfig?,
263264
with overrides: GenerationConfig?,
264-
enforcingJSONSchema jsonSchema: JSONSchema) -> GenerationConfig {
265+
enforcingFirebaseGenerationSchema firebaseGenerationSchema: FirebaseGenerationSchema)
266+
-> GenerationConfig {
265267
// 1. Merge base and overrides, defaulting to a fresh config if both are nil.
266268
var config = GenerationConfig.merge(base, with: overrides) ?? GenerationConfig()
267269

268270
// 2. Enforce the specific constraints for JSON Schema generation.
269271
config.responseMIMEType = "application/json"
270-
config.responseJSONSchema = jsonSchema
272+
config.responseFirebaseGenerationSchema = firebaseGenerationSchema
271273
config.responseSchema = nil // Clear conflicting legacy schema
272274

273275
// 3. Clear incompatible or conflicting options.
@@ -293,7 +295,7 @@ extension GenerationConfig: Encodable {
293295
case stopSequences
294296
case responseMIMEType = "responseMimeType"
295297
case responseSchema
296-
case responseJSONSchema = "responseJsonSchema"
298+
case responseFirebaseGenerationSchema = "responseJsonSchema"
297299
case responseModalities
298300
case thinkingConfig
299301
}
@@ -310,10 +312,10 @@ extension GenerationConfig: Encodable {
310312
try container.encodeIfPresent(stopSequences, forKey: .stopSequences)
311313
try container.encodeIfPresent(responseMIMEType, forKey: .responseMIMEType)
312314
try container.encodeIfPresent(responseSchema, forKey: .responseSchema)
313-
if let responseJSONSchema = responseJSONSchema {
315+
if let responseFirebaseGenerationSchema {
314316
let schemaEncoder = SchemaEncoder(target: .gemini)
315-
let jsonSchema = try schemaEncoder.encode(responseJSONSchema)
316-
try container.encode(jsonSchema, forKey: .responseJSONSchema)
317+
let firebaseGenerationSchema = try schemaEncoder.encode(responseFirebaseGenerationSchema)
318+
try container.encode(firebaseGenerationSchema, forKey: .responseFirebaseGenerationSchema)
317319
}
318320
try container.encodeIfPresent(responseModalities, forKey: .responseModalities)
319321
try container.encodeIfPresent(thinkingConfig, forKey: .thinkingConfig)

FirebaseAI/Sources/GenerativeModel.swift

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,12 @@ public final class GenerativeModel: Sendable {
196196

197197
@discardableResult
198198
public final nonisolated(nonsending)
199-
func respond(to prompt: any PartsRepresentable, schema: JSONSchema,
199+
func respond(to prompt: any PartsRepresentable, schema: FirebaseGenerationSchema,
200200
includeSchemaInPrompt: Bool = true, options: GenerationConfig? = nil)
201-
async throws -> GenerativeModel.Response<ModelOutput> {
201+
async throws -> GenerativeModel.Response<FirebaseGeneratedContent> {
202202
return try await respond(
203203
to: prompt,
204-
generating: ModelOutput.self,
204+
generating: FirebaseGeneratedContent.self,
205205
schema: schema,
206206
includeSchemaInPrompt: includeSchemaInPrompt,
207207
options: options
@@ -217,7 +217,7 @@ public final class GenerativeModel: Sendable {
217217
return try await respond(
218218
to: prompt,
219219
generating: type,
220-
schema: type.jsonSchema,
220+
schema: type.firebaseGenerationSchema,
221221
includeSchemaInPrompt: includeSchemaInPrompt,
222222
options: options
223223
)
@@ -230,11 +230,12 @@ public final class GenerativeModel: Sendable {
230230
includeSchemaInPrompt: false, options: options)
231231
}
232232

233-
public final func streamResponse(to prompt: any PartsRepresentable, schema: JSONSchema,
233+
public final func streamResponse(to prompt: any PartsRepresentable,
234+
schema: FirebaseGenerationSchema,
234235
includeSchemaInPrompt: Bool = true,
235236
options: GenerationConfig? = nil)
236-
-> sending GenerativeModel.ResponseStream<ModelOutput> {
237-
return streamResponse(to: prompt, generating: ModelOutput.self, schema: schema,
237+
-> sending GenerativeModel.ResponseStream<FirebaseGeneratedContent> {
238+
return streamResponse(to: prompt, generating: FirebaseGeneratedContent.self, schema: schema,
238239
includeSchemaInPrompt: includeSchemaInPrompt, options: options)
239240
}
240241

@@ -243,7 +244,7 @@ public final class GenerativeModel: Sendable {
243244
includeSchemaInPrompt: Bool = true,
244245
options: GenerationConfig? = nil)
245246
-> sending GenerativeModel.ResponseStream<Content> where Content: FirebaseGenerable {
246-
return streamResponse(to: prompt, generating: type, schema: type.jsonSchema,
247+
return streamResponse(to: prompt, generating: type, schema: type.firebaseGenerationSchema,
247248
includeSchemaInPrompt: includeSchemaInPrompt, options: options)
248249
}
249250
#endif // compiler(>=6.2)
@@ -455,15 +456,15 @@ public final class GenerativeModel: Sendable {
455456
#if compiler(>=6.2)
456457
final nonisolated(nonsending)
457458
func respond<Content>(to prompt: any PartsRepresentable, generating type: Content.Type,
458-
schema: JSONSchema?, includeSchemaInPrompt: Bool,
459+
schema: FirebaseGenerationSchema?, includeSchemaInPrompt: Bool,
459460
options: GenerationConfig?)
460461
async throws -> GenerativeModel.Response<Content> where Content: FirebaseGenerable {
461462
let parts = [ModelContent(parts: prompt)]
462463

463464
let generationConfig: GenerationConfig?
464465
if let schema {
465466
generationConfig = GenerationConfig.merge(
466-
self.generationConfig, with: options, enforcingJSONSchema: schema
467+
self.generationConfig, with: options, enforcingFirebaseGenerationSchema: schema
467468
)
468469
} else {
469470
generationConfig = GenerationConfig.merge(self.generationConfig, with: options)
@@ -475,15 +476,23 @@ public final class GenerativeModel: Sendable {
475476
throw GenerationError.decodingFailure(.init(debugDescription: "No text in response."))
476477
}
477478
let responseID = response.responseID.map { ResponseID(responseID: $0) }
478-
let modelOutput: ModelOutput
479+
let firebaseGeneratedContent: FirebaseGeneratedContent
479480
if schema == nil {
480-
modelOutput = ModelOutput(kind: .string(text), id: responseID, isComplete: true)
481+
firebaseGeneratedContent = FirebaseGeneratedContent(
482+
kind: .string(text),
483+
id: responseID,
484+
isComplete: true
485+
)
481486
} else {
482-
modelOutput = try ModelOutput(json: text, id: responseID, streaming: false)
487+
firebaseGeneratedContent = try FirebaseGeneratedContent(
488+
json: text,
489+
id: responseID,
490+
streaming: false
491+
)
483492
}
484493
return try GenerativeModel.Response<Content>(
485-
content: Content(modelOutput),
486-
rawContent: modelOutput,
494+
content: Content(firebaseGeneratedContent),
495+
rawContent: firebaseGeneratedContent,
487496
rawResponse: response
488497
)
489498
} catch let error as GenerationError {
@@ -499,15 +508,16 @@ public final class GenerativeModel: Sendable {
499508
}
500509

501510
final func streamResponse<Content>(to prompt: any PartsRepresentable,
502-
generating type: Content.Type, schema: JSONSchema?,
511+
generating type: Content.Type,
512+
schema: FirebaseGenerationSchema?,
503513
includeSchemaInPrompt: Bool, options: GenerationConfig?)
504514
-> sending GenerativeModel.ResponseStream<Content> where Content: FirebaseGenerable {
505515
let parts = [ModelContent(parts: prompt)]
506516

507517
let generationConfig: GenerationConfig?
508518
if let schema {
509519
generationConfig = GenerationConfig.merge(
510-
self.generationConfig, with: options, enforcingJSONSchema: schema
520+
self.generationConfig, with: options, enforcingFirebaseGenerationSchema: schema
511521
)
512522
} else {
513523
generationConfig = GenerationConfig.merge(self.generationConfig, with: options)
@@ -521,11 +531,15 @@ public final class GenerativeModel: Sendable {
521531
if let text = response.text {
522532
json += text
523533
let responseID = response.responseID.map { ResponseID(responseID: $0) }
524-
let modelOutput = try ModelOutput(json: json, id: responseID, streaming: true)
534+
let firebaseGeneratedContent = try FirebaseGeneratedContent(
535+
json: json,
536+
id: responseID,
537+
streaming: true
538+
)
525539
try await context.yield(
526540
GenerativeModel.ResponseStream<Content>.Snapshot(
527-
content: Content.Partial(modelOutput),
528-
rawContent: modelOutput,
541+
content: Content.Partial(firebaseGeneratedContent),
542+
rawContent: firebaseGeneratedContent,
529543
rawResponse: response
530544
)
531545
)

FirebaseAI/Sources/JSONValue.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -101,24 +101,24 @@ extension JSONValue: Encodable {
101101
extension JSONValue: Equatable {}
102102

103103
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
104-
extension JSONValue: ConvertibleToModelOutput {
105-
public var modelOutput: ModelOutput {
104+
extension JSONValue: ConvertibleToFirebaseGeneratedContent {
105+
public var firebaseGeneratedContent: FirebaseGeneratedContent {
106106
switch self {
107107
case .null:
108-
return ModelOutput(kind: .null)
108+
return FirebaseGeneratedContent(kind: .null)
109109
case let .number(value):
110-
return ModelOutput(kind: .number(value))
110+
return FirebaseGeneratedContent(kind: .number(value))
111111
case let .string(value):
112-
return ModelOutput(kind: .string(value))
112+
return FirebaseGeneratedContent(kind: .string(value))
113113
case let .bool(value):
114-
return ModelOutput(kind: .bool(value))
114+
return FirebaseGeneratedContent(kind: .bool(value))
115115
case let .object(dictionary):
116-
return ModelOutput(kind: .structure(
117-
properties: dictionary.mapValues(ModelOutput.init),
116+
return FirebaseGeneratedContent(kind: .structure(
117+
properties: dictionary.mapValues(FirebaseGeneratedContent.init),
118118
orderedKeys: dictionary.keys.map { $0 }
119119
))
120120
case let .array(values):
121-
return ModelOutput(kind: .array(values.map(ModelOutput.init)))
121+
return FirebaseGeneratedContent(kind: .array(values.map(FirebaseGeneratedContent.init)))
122122
}
123123
}
124124
}

FirebaseAI/Sources/Protocols/Public/StructuredOutput/ConvertibleFromModelOutput.swift renamed to FirebaseAI/Sources/Protocols/Public/StructuredOutput/ConvertibleFromFirebaseGeneratedContent.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,17 @@
1818
#endif // canImport(FoundationModels)
1919

2020
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
21-
public protocol ConvertibleFromModelOutput {
22-
init(_ content: ModelOutput) throws
21+
public protocol ConvertibleFromFirebaseGeneratedContent {
22+
init(_ content: FirebaseGeneratedContent) throws
2323
}
2424

2525
#if canImport(FoundationModels)
2626
@available(iOS 26.0, macOS 26.0, *)
2727
@available(tvOS, unavailable)
2828
@available(watchOS, unavailable)
29-
public extension ConvertibleFromModelOutput where Self: ConvertibleFromGeneratedContent {
30-
init(_ content: ModelOutput) throws {
29+
public extension ConvertibleFromFirebaseGeneratedContent
30+
where Self: ConvertibleFromGeneratedContent {
31+
init(_ content: FirebaseGeneratedContent) throws {
3132
try self.init(content.generatedContent)
3233
}
3334
}

FirebaseAI/Sources/Protocols/Public/StructuredOutput/ConvertibleToModelOutput.swift renamed to FirebaseAI/Sources/Protocols/Public/StructuredOutput/ConvertibleToFirebaseGeneratedContent.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,23 @@
1919

2020
#if compiler(>=6.2)
2121
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
22-
public protocol ConvertibleToModelOutput: SendableMetatype {
23-
var modelOutput: ModelOutput { get }
22+
public protocol ConvertibleToFirebaseGeneratedContent: SendableMetatype {
23+
var firebaseGeneratedContent: FirebaseGeneratedContent { get }
2424
}
2525
#else
2626
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
27-
public protocol ConvertibleToModelOutput {
28-
var modelOutput: ModelOutput { get }
27+
public protocol ConvertibleToFirebaseGeneratedContent {
28+
var firebaseGeneratedContent: FirebaseGeneratedContent { get }
2929
}
3030
#endif // compiler(>=6.2)
3131

3232
#if canImport(FoundationModels)
3333
@available(iOS 26.0, macOS 26.0, *)
3434
@available(tvOS, unavailable)
3535
@available(watchOS, unavailable)
36-
public extension ConvertibleToModelOutput where Self: ConvertibleToGeneratedContent {
37-
var modelOutput: ModelOutput {
38-
generatedContent.modelOutput
36+
public extension ConvertibleToFirebaseGeneratedContent where Self: ConvertibleToGeneratedContent {
37+
var firebaseGeneratedContent: FirebaseGeneratedContent {
38+
generatedContent.firebaseGeneratedContent
3939
}
4040
}
4141
#endif // canImport(FoundationModels)

0 commit comments

Comments
 (0)