Skip to content

Latest commit

 

History

History
96 lines (67 loc) · 4.35 KB

File metadata and controls

96 lines (67 loc) · 4.35 KB

Warning

Experimental: Using the Firebase AI SDK to build hybrid experiences on Apple platforms is an Experimental feature, which means that this feature isn't subject to any SLA or deprecation policy and could change in backwards-incompatible ways.


Gemini and Foundation models return responses as unstructured text by default. However, some use cases require structured output. For example, you might be using the response for other downstream tasks that require an established data schema.

To ensure that the model's generated output always adheres to a specific schema, you can define a data structure. You can then directly extract data from the model's output as Swift types with less post-processing.

This page describes how to generate structured output (like custom objects) in your hybrid experiences for Apple apps.

Before you begin

Make sure that you've completed the getting started guide for building hybrid experiences.

Generate Structured Output via @Generable

Generating structured output is supported for inference using both cloud-hosted and on-device models via the @Generable macro in Swift.

The generativeModelSession allows you to request structured decoding via generating: MyType.self. You can also use basic Swift types directly (e.g., generating: Float.self or generating: [String].self) if you just need a primitive value.

Here is an example for extracting a user profile. Notice the use of @Guide to control values and provide semantics. Best Practice: Keep descriptions as short as possible. Long descriptions take up additional context size and can introduce latency.

import FirebaseAI
import FirebaseAILogic

@Generable
struct UserProfile {
  @Guide(description: "A unique username for the user.")
  var username: String

  // Use constraints like `.range` to enforce limits on values
  @Guide(description: "The user's age.", .range(1...120))
  var age: Int

  @Guide(description: "A short bio about the user, no more than 100 characters.")
  var bio: String

  // Use constraints like `.count` to enforce array sizes
  @Guide(description: "A list of the user's favorite topics.", .count(3))
  var favoriteTopics: [String]
}

// Ensure you have established a `generativeModelSession`
let session = firebaseAI.generativeModelSession(
    model: .hybridModel(primary: systemModel, secondary: geminiModel)
)

let prompt = "Generate a user profile for a cat lover who enjoys hiking."

// Provide `generating: UserProfile.self` to map output to your Swift type
let response = try await session.respond(to: prompt, generating: UserProfile.self)

print("Username: \(response.content.username)")
print("Bio: \(response.content.bio)")
print("Favorite Topics: \(response.content.favoriteTopics.joined(separator: ", "))")

Note

Properties are generated by the model in the order they are declared. You can also nest custom @Generable types inside other @Generable types, and mark enumerations with associated values as @Generable.

The underlying system seamlessly maps your @Generable types to JSON schemas when querying Gemini, and handles the appropriate representation constraint when communicating with the on-device SystemLanguageModel.

Stream Structured Output

When working with large or complex structured responses, you may want to update your UI as the data is being generated rather than waiting for the entire response to complete.

You can achieve this by using the streamResponse(to:generating:) method. The @Generable macro automatically synthesizes a nested PartiallyGenerated type for your struct, where all properties become optional. As the LanguageModelSession processes the stream, it yields snapshots containing these partial results.

// ... continuing from the previous example ...

let stream = session.streamResponse(to: prompt, generating: UserProfile.self)

for try await snapshot in stream {
    // `snapshot.content` is of type `UserProfile.PartiallyGenerated`
    let partialProfile = snapshot.content
    
    // Properties might be nil if they haven't been generated yet
    if let partialUsername = partialProfile.username {
        print("Username generated so far: \\(partialUsername)")
    }
}

// Once the stream completes, you can optionally collect the final decoded object
let finalResponse = try await stream.collect()
let completeProfile = finalResponse.content