Skip to content

Commit 9cbd702

Browse files
davidmotsonDavid Motsonashvili
andauthored
Server Templates (#7503)
* Added `TemplateGenerativeModel` and `TemplateImagenModel` types with entrypoints in `FirebaseAI` * Added java types to match * Added new request types with serialization TODO: Tests --------- Co-authored-by: David Motsonashvili <[email protected]>
1 parent bb555a2 commit 9cbd702

File tree

12 files changed

+628
-11
lines changed

12 files changed

+628
-11
lines changed

firebase-ai/api.txt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ package com.google.firebase.ai {
3636
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.LiveGenerativeModel liveModel(String modelName, com.google.firebase.ai.type.LiveGenerationConfig? generationConfig = null, java.util.List<com.google.firebase.ai.type.Tool>? tools = null);
3737
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.LiveGenerativeModel liveModel(String modelName, com.google.firebase.ai.type.LiveGenerationConfig? generationConfig = null, java.util.List<com.google.firebase.ai.type.Tool>? tools = null, com.google.firebase.ai.type.Content? systemInstruction = null);
3838
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.LiveGenerativeModel liveModel(String modelName, com.google.firebase.ai.type.LiveGenerationConfig? generationConfig = null, java.util.List<com.google.firebase.ai.type.Tool>? tools = null, com.google.firebase.ai.type.Content? systemInstruction = null, com.google.firebase.ai.type.RequestOptions requestOptions = com.google.firebase.ai.type.RequestOptions());
39+
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.TemplateGenerativeModel templateGenerativeModel();
40+
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.TemplateGenerativeModel templateGenerativeModel(com.google.firebase.ai.type.RequestOptions requestOptions = com.google.firebase.ai.type.RequestOptions());
41+
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.TemplateImagenModel templateImagenModel();
42+
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.TemplateImagenModel templateImagenModel(com.google.firebase.ai.type.RequestOptions requestOptions = com.google.firebase.ai.type.RequestOptions());
3943
property public static final com.google.firebase.ai.FirebaseAI instance;
4044
field public static final com.google.firebase.ai.FirebaseAI.Companion Companion;
4145
}
@@ -83,6 +87,15 @@ package com.google.firebase.ai {
8387
method public suspend Object? connect(kotlin.coroutines.Continuation<? super com.google.firebase.ai.type.LiveSession>);
8488
}
8589

90+
@com.google.firebase.ai.type.PublicPreviewAPI public final class TemplateGenerativeModel {
91+
method public suspend Object? generateContent(String templateId, java.util.Map<java.lang.String,?> inputs, kotlin.coroutines.Continuation<? super com.google.firebase.ai.type.GenerateContentResponse>);
92+
method public kotlinx.coroutines.flow.Flow<com.google.firebase.ai.type.GenerateContentResponse> generateContentStream(String templateId, java.util.Map<java.lang.String,?> inputs);
93+
}
94+
95+
@com.google.firebase.ai.type.PublicPreviewAPI public final class TemplateImagenModel {
96+
method public suspend Object? generateImages(String templateId, java.util.Map<java.lang.String,?> inputs, kotlin.coroutines.Continuation<? super com.google.firebase.ai.type.ImagenGenerationResponse<com.google.firebase.ai.type.ImagenInlineImage>>);
97+
}
98+
8699
}
87100

88101
package com.google.firebase.ai.java {
@@ -166,6 +179,29 @@ package com.google.firebase.ai.java {
166179
method public com.google.firebase.ai.java.LiveSessionFutures from(com.google.firebase.ai.type.LiveSession session);
167180
}
168181

182+
public abstract class TemplateGenerativeModelFutures {
183+
method public static final com.google.firebase.ai.java.TemplateGenerativeModelFutures from(com.google.firebase.ai.TemplateGenerativeModel model);
184+
method public abstract com.google.common.util.concurrent.ListenableFuture<com.google.firebase.ai.type.GenerateContentResponse> generateContent(String templateId, java.util.Map<java.lang.String,?> inputs);
185+
method public abstract org.reactivestreams.Publisher<com.google.firebase.ai.type.GenerateContentResponse> generateContentStream(String templateId, java.util.Map<java.lang.String,?> inputs);
186+
method public abstract com.google.firebase.ai.TemplateGenerativeModel getGenerativeModel();
187+
field public static final com.google.firebase.ai.java.TemplateGenerativeModelFutures.Companion Companion;
188+
}
189+
190+
public static final class TemplateGenerativeModelFutures.Companion {
191+
method public com.google.firebase.ai.java.TemplateGenerativeModelFutures from(com.google.firebase.ai.TemplateGenerativeModel model);
192+
}
193+
194+
public abstract class TemplateImagenModelFutures {
195+
method public static final com.google.firebase.ai.java.TemplateImagenModelFutures from(com.google.firebase.ai.TemplateImagenModel model);
196+
method public abstract com.google.common.util.concurrent.ListenableFuture<com.google.firebase.ai.type.ImagenGenerationResponse<com.google.firebase.ai.type.ImagenInlineImage>> generateImages(String templateId, java.util.Map<java.lang.String,?> inputs);
197+
method public abstract com.google.firebase.ai.TemplateImagenModel getImageModel();
198+
field public static final com.google.firebase.ai.java.TemplateImagenModelFutures.Companion Companion;
199+
}
200+
201+
public static final class TemplateImagenModelFutures.Companion {
202+
method public com.google.firebase.ai.java.TemplateImagenModelFutures from(com.google.firebase.ai.TemplateImagenModel model);
203+
}
204+
169205
}
170206

171207
package com.google.firebase.ai.type {

firebase-ai/src/main/kotlin/com/google/firebase/ai/FirebaseAI.kt

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,29 @@ internal constructor(
106106
)
107107
}
108108

109+
/**
110+
* Instantiates a new [TemplateGenerativeModel] given the provided parameters.
111+
*
112+
* @param requestOptions Configuration options for sending requests to the backend.
113+
* @return The initialized [TemplateGenerativeModel] instance.
114+
*/
115+
@JvmOverloads
116+
@PublicPreviewAPI
117+
public fun templateGenerativeModel(
118+
requestOptions: RequestOptions = RequestOptions(),
119+
): TemplateGenerativeModel {
120+
val templateUri = getTemplateUri(backend)
121+
return TemplateGenerativeModel(
122+
templateUri,
123+
firebaseApp.options.apiKey,
124+
firebaseApp,
125+
useLimitedUseAppCheckTokens,
126+
requestOptions,
127+
appCheckProvider.get(),
128+
internalAuthProvider.get(),
129+
)
130+
}
131+
109132
/**
110133
* Instantiates a new [LiveGenerationConfig] given the provided parameters.
111134
*
@@ -205,6 +228,29 @@ internal constructor(
205228
)
206229
}
207230

231+
/**
232+
* Instantiates a new [TemplateImagenModel] given the provided parameters.
233+
*
234+
* @param requestOptions Configuration options for sending requests to the backend.
235+
* @return The initialized [TemplateImagenModel] instance.
236+
*/
237+
@JvmOverloads
238+
@PublicPreviewAPI
239+
public fun templateImagenModel(
240+
requestOptions: RequestOptions = RequestOptions(),
241+
): TemplateImagenModel {
242+
val templateUri = getTemplateUri(backend)
243+
return TemplateImagenModel(
244+
templateUri,
245+
firebaseApp.options.apiKey,
246+
firebaseApp,
247+
useLimitedUseAppCheckTokens,
248+
requestOptions,
249+
appCheckProvider.get(),
250+
internalAuthProvider.get(),
251+
)
252+
}
253+
208254
public companion object {
209255
/** The [FirebaseAI] instance for the default [FirebaseApp] using the Google AI Backend. */
210256
@JvmStatic
@@ -258,6 +304,13 @@ internal constructor(
258304

259305
private val TAG = FirebaseAI::class.java.simpleName
260306
}
307+
308+
private fun getTemplateUri(backend: GenerativeBackend): String =
309+
when (backend.backend) {
310+
GenerativeBackendEnum.VERTEX_AI ->
311+
"projects/${firebaseApp.options.projectId}/locations/${backend.location}/templates/"
312+
GenerativeBackendEnum.GOOGLE_AI -> "projects/${firebaseApp.options.projectId}/templates/"
313+
}
261314
}
262315

263316
/** The [FirebaseAI] instance for the default [FirebaseApp] using the Google AI Backend. */

firebase-ai/src/main/kotlin/com/google/firebase/ai/ImagenModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ internal constructor(
233233
}
234234

235235
@OptIn(PublicPreviewAPI::class)
236-
private fun ImagenGenerationResponse.Internal.validate(): ImagenGenerationResponse.Internal {
236+
internal fun ImagenGenerationResponse.Internal.validate(): ImagenGenerationResponse.Internal {
237237
if (predictions.none { it.mimeType != null }) {
238238
throw ContentBlockedException(
239239
message = predictions.first { it.raiFilteredReason != null }.raiFilteredReason
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.ai
18+
19+
import com.google.firebase.FirebaseApp
20+
import com.google.firebase.ai.common.APIController
21+
import com.google.firebase.ai.common.AppCheckHeaderProvider
22+
import com.google.firebase.ai.common.TemplateGenerateContentRequest
23+
import com.google.firebase.ai.type.Content
24+
import com.google.firebase.ai.type.FinishReason
25+
import com.google.firebase.ai.type.FirebaseAIException
26+
import com.google.firebase.ai.type.GenerateContentResponse
27+
import com.google.firebase.ai.type.PromptBlockedException
28+
import com.google.firebase.ai.type.PublicPreviewAPI
29+
import com.google.firebase.ai.type.RequestOptions
30+
import com.google.firebase.ai.type.ResponseStoppedException
31+
import com.google.firebase.ai.type.SerializationException
32+
import com.google.firebase.appcheck.interop.InteropAppCheckTokenProvider
33+
import com.google.firebase.auth.internal.InternalAuthProvider
34+
import kotlinx.coroutines.flow.Flow
35+
import kotlinx.coroutines.flow.catch
36+
import kotlinx.coroutines.flow.map
37+
import kotlinx.serialization.json.Json
38+
import kotlinx.serialization.json.jsonObject
39+
import org.json.JSONObject
40+
41+
/**
42+
* Represents a multimodal model (like Gemini), capable of generating content based on various
43+
* templated input types.
44+
*/
45+
@PublicPreviewAPI
46+
public class TemplateGenerativeModel
47+
internal constructor(
48+
private val templateUri: String,
49+
private val controller: APIController,
50+
) {
51+
52+
internal constructor(
53+
templateUri: String,
54+
apiKey: String,
55+
firebaseApp: FirebaseApp,
56+
useLimitedUseAppCheckTokens: Boolean,
57+
requestOptions: RequestOptions = RequestOptions(),
58+
appCheckTokenProvider: InteropAppCheckTokenProvider? = null,
59+
internalAuthProvider: InternalAuthProvider? = null
60+
) : this(
61+
templateUri,
62+
APIController(
63+
apiKey,
64+
"",
65+
requestOptions,
66+
"gl-kotlin/${KotlinVersion.CURRENT}-ai fire/${BuildConfig.VERSION_NAME}",
67+
firebaseApp,
68+
AppCheckHeaderProvider(
69+
TAG,
70+
useLimitedUseAppCheckTokens,
71+
appCheckTokenProvider,
72+
internalAuthProvider
73+
),
74+
),
75+
)
76+
77+
/**
78+
* Generates content from a prompt template and inputs.
79+
*
80+
* @param templateId The ID of the prompt template to use.
81+
* @param inputs A map of variables to substitute into the template.
82+
* @return The content generated by the model.
83+
* @throws [FirebaseAIException] if the request failed.
84+
* @see [FirebaseAIException] for types of errors.
85+
*/
86+
public suspend fun generateContent(
87+
templateId: String,
88+
inputs: Map<String, Any>,
89+
): GenerateContentResponse =
90+
try {
91+
controller
92+
.templateGenerateContent("$templateUri$templateId", constructRequest(inputs))
93+
.toPublic()
94+
.validate()
95+
} catch (e: Throwable) {
96+
throw FirebaseAIException.from(e)
97+
}
98+
99+
/**
100+
* Generates content as a stream from a prompt template and inputs.
101+
*
102+
* @param templateId The ID of the prompt template to use.
103+
* @param inputs A map of variables to substitute into the template.
104+
* @return A [Flow] which will emit responses as they are returned by the model.
105+
* @throws [FirebaseAIException] if the request failed.
106+
* @see [FirebaseAIException] for types of errors.
107+
*/
108+
public fun generateContentStream(
109+
templateId: String,
110+
inputs: Map<String, Any>
111+
): Flow<GenerateContentResponse> =
112+
controller
113+
.templateGenerateContentStream("$templateUri$templateId", constructRequest(inputs))
114+
.catch { throw FirebaseAIException.from(it) }
115+
.map { it.toPublic().validate() }
116+
117+
internal fun constructRequest(
118+
inputs: Map<String, Any>,
119+
history: List<Content>? = null
120+
): TemplateGenerateContentRequest {
121+
return TemplateGenerateContentRequest(
122+
Json.parseToJsonElement(JSONObject(inputs).toString()).jsonObject,
123+
history?.let { it.map { it.toTemplateInternal() } }
124+
)
125+
}
126+
127+
private fun GenerateContentResponse.validate() = apply {
128+
if (candidates.isEmpty() && promptFeedback == null) {
129+
throw SerializationException("Error deserializing response, found no valid fields")
130+
}
131+
promptFeedback?.blockReason?.let { throw PromptBlockedException(this) }
132+
candidates
133+
.mapNotNull { it.finishReason }
134+
.firstOrNull { it != FinishReason.STOP }
135+
?.let { throw ResponseStoppedException(this) }
136+
}
137+
138+
private companion object {
139+
private val TAG = TemplateGenerativeModel::class.java.simpleName
140+
}
141+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.ai
18+
19+
import com.google.firebase.FirebaseApp
20+
import com.google.firebase.ai.common.APIController
21+
import com.google.firebase.ai.common.AppCheckHeaderProvider
22+
import com.google.firebase.ai.common.TemplateGenerateImageRequest
23+
import com.google.firebase.ai.type.FirebaseAIException
24+
import com.google.firebase.ai.type.ImagenGenerationResponse
25+
import com.google.firebase.ai.type.ImagenInlineImage
26+
import com.google.firebase.ai.type.PublicPreviewAPI
27+
import com.google.firebase.ai.type.RequestOptions
28+
import com.google.firebase.appcheck.interop.InteropAppCheckTokenProvider
29+
import com.google.firebase.auth.internal.InternalAuthProvider
30+
import kotlinx.serialization.json.Json
31+
import kotlinx.serialization.json.jsonObject
32+
import org.json.JSONObject
33+
34+
/**
35+
* Represents a generative model (like Imagen), capable of generating images based a template.
36+
*
37+
* See the documentation for a list of
38+
* [supported models](https://firebase.google.com/docs/ai-logic/models).
39+
*/
40+
@PublicPreviewAPI
41+
public class TemplateImagenModel
42+
internal constructor(
43+
private val templateUri: String,
44+
private val controller: APIController,
45+
) {
46+
47+
@JvmOverloads
48+
internal constructor(
49+
templateUri: String,
50+
apiKey: String,
51+
firebaseApp: FirebaseApp,
52+
useLimitedUseAppCheckTokens: Boolean,
53+
requestOptions: RequestOptions = RequestOptions(),
54+
appCheckTokenProvider: InteropAppCheckTokenProvider? = null,
55+
internalAuthProvider: InternalAuthProvider? = null,
56+
) : this(
57+
templateUri,
58+
APIController(
59+
apiKey,
60+
"",
61+
requestOptions,
62+
"gl-kotlin/${KotlinVersion.CURRENT}-ai fire/${BuildConfig.VERSION_NAME}",
63+
firebaseApp,
64+
AppCheckHeaderProvider(
65+
TAG,
66+
useLimitedUseAppCheckTokens,
67+
appCheckTokenProvider,
68+
internalAuthProvider
69+
),
70+
),
71+
)
72+
73+
/**
74+
* Generates an image, returning the result directly to the caller.
75+
*
76+
* @param templateId The ID of server prompt template.
77+
* @param inputs the inputs needed to fill in the prompt
78+
*/
79+
public suspend fun generateImages(
80+
templateId: String,
81+
inputs: Map<String, Any>
82+
): ImagenGenerationResponse<ImagenInlineImage> =
83+
try {
84+
controller
85+
.templateGenerateImage(
86+
"$templateUri$templateId",
87+
constructTemplateGenerateImageRequest(inputs)
88+
)
89+
.validate()
90+
.toPublicInline()
91+
} catch (e: Throwable) {
92+
throw FirebaseAIException.from(e)
93+
}
94+
95+
private fun constructTemplateGenerateImageRequest(
96+
inputs: Map<String, Any>
97+
): TemplateGenerateImageRequest {
98+
return TemplateGenerateImageRequest(
99+
Json.parseToJsonElement(JSONObject(inputs).toString()).jsonObject
100+
)
101+
}
102+
103+
internal companion object {
104+
private val TAG = TemplateImagenModel::class.java.simpleName
105+
}
106+
}

0 commit comments

Comments
 (0)