From cd5c26922c87c0489c6bc869722319f9fac1f668 Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Paz Date: Mon, 15 Jul 2024 15:18:33 -0400 Subject: [PATCH 1/5] Start working on controlled generation samples --- .../client/generative/samples/controlled_generation.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/samples/src/main/java/com/google/ai/client/generative/samples/controlled_generation.kt b/samples/src/main/java/com/google/ai/client/generative/samples/controlled_generation.kt index 88ce2b9a..c5fceb55 100644 --- a/samples/src/main/java/com/google/ai/client/generative/samples/controlled_generation.kt +++ b/samples/src/main/java/com/google/ai/client/generative/samples/controlled_generation.kt @@ -23,4 +23,12 @@ package com.google.ai.client.generative.samples // the "Set up your API Key section" in the [Gemini API // quickstart](https://ai.google.dev/gemini-api/docs/quickstart?lang=android#set-up-api-key). -// TODO +suspend fun json_controlled_generation() { + // [START json_controlled_generation] + // [END json_controlled_generation] +} + +suspend fun json_no_schema() { + // [START json_no_schema] + // [END json_no_schema] +} From 37a3eb5b1972ce25f815070b6d6de78a926a9894 Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Paz Date: Mon, 15 Jul 2024 19:38:23 -0400 Subject: [PATCH 2/5] Controlled generation in Kotlin --- .../samples/controlled_generation.kt | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/samples/src/main/java/com/google/ai/client/generative/samples/controlled_generation.kt b/samples/src/main/java/com/google/ai/client/generative/samples/controlled_generation.kt index c5fceb55..011c1370 100644 --- a/samples/src/main/java/com/google/ai/client/generative/samples/controlled_generation.kt +++ b/samples/src/main/java/com/google/ai/client/generative/samples/controlled_generation.kt @@ -16,6 +16,11 @@ package com.google.ai.client.generative.samples +import com.google.ai.client.generativeai.GenerativeModel +import com.google.ai.client.generativeai.type.FunctionType +import com.google.ai.client.generativeai.type.Schema +import com.google.ai.client.generativeai.type.generationConfig + // Set up your API Key // ==================== // @@ -25,10 +30,64 @@ package com.google.ai.client.generative.samples suspend fun json_controlled_generation() { // [START json_controlled_generation] + val jsonSchema = Schema( + name = "recipes", + description = "List of recipes", + type = FunctionType.ARRAY, + items = Schema( + name = "recipe", + description = "A recipe", + type = FunctionType.OBJECT, + properties = mapOf( + "recipeName" to Schema( + name = "recipeName", + description = "Name of the recipe", + type = FunctionType.STRING, + nullable = false + ), + ), + required = listOf("recipeName") + ), + ) + + val generativeModel = + GenerativeModel( + // Specify a Gemini model appropriate for your use case + modelName = "gemini-1.5-pro", + // Access your API key as a Build Configuration variable (see "Set up your API key" above) + apiKey = BuildConfig.apiKey, + generationConfig = generationConfig { + responseMimeType = "application/json" + responseSchema = jsonSchema + }) + + val prompt = "List a few popular cookie recipes." + val response = generativeModel.generateContent(prompt) + print(response.text) + // [END json_controlled_generation] } suspend fun json_no_schema() { // [START json_no_schema] + + val generativeModel = + GenerativeModel( + // Specify a Gemini model appropriate for your use case + modelName = "gemini-1.5-flash", + // Access your API key as a Build Configuration variable (see "Set up your API key" above) + apiKey = BuildConfig.apiKey, + generationConfig = generationConfig { + responseMimeType = "application/json" + }) + + val prompt = """ + List a few popular cookie recipes using this JSON schema: + Recipe = {'recipeName': string} + Return: Array + """.trimIndent() + val response = generativeModel.generateContent(prompt) + print(response.text) + // [END json_no_schema] } From 85339f989972cd789f4421535f8fa86eec6a055d Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Paz Date: Mon, 15 Jul 2024 20:24:45 -0400 Subject: [PATCH 3/5] Code execution sample for java --- .../ai/client/generativeai/type/Type.kt | 7 + .../samples/controlled_generation.kt | 1 - .../samples/java/code_execution.java | 4 +- .../samples/java/controlled_generation.java | 137 +++++++++++++++++- 4 files changed, 145 insertions(+), 4 deletions(-) diff --git a/generativeai/src/main/java/com/google/ai/client/generativeai/type/Type.kt b/generativeai/src/main/java/com/google/ai/client/generativeai/type/Type.kt index 8486edab..3a693143 100644 --- a/generativeai/src/main/java/com/google/ai/client/generativeai/type/Type.kt +++ b/generativeai/src/main/java/com/google/ai/client/generativeai/type/Type.kt @@ -29,15 +29,22 @@ import org.json.JSONObject */ class FunctionType(val name: String, val parse: (String?) -> T?) { companion object { + @JvmField val STRING = FunctionType("STRING") { it } + @JvmField val INTEGER = FunctionType("INTEGER") { it?.toIntOrNull() } + @JvmField val LONG = FunctionType("INTEGER") { it?.toLongOrNull() } + @JvmField val NUMBER = FunctionType("NUMBER") { it?.toDoubleOrNull() } + @JvmField val BOOLEAN = FunctionType("BOOLEAN") { it?.toBoolean() } + @JvmField val ARRAY = FunctionType>("ARRAY") { it -> it?.let { Json.parseToJsonElement(it).jsonArray.map { element -> element.toString() } } } + @JvmField val OBJECT = FunctionType("OBJECT") { it?.let { JSONObject(it) } } } } diff --git a/samples/src/main/java/com/google/ai/client/generative/samples/controlled_generation.kt b/samples/src/main/java/com/google/ai/client/generative/samples/controlled_generation.kt index 011c1370..38df2a8f 100644 --- a/samples/src/main/java/com/google/ai/client/generative/samples/controlled_generation.kt +++ b/samples/src/main/java/com/google/ai/client/generative/samples/controlled_generation.kt @@ -70,7 +70,6 @@ suspend fun json_controlled_generation() { suspend fun json_no_schema() { // [START json_no_schema] - val generativeModel = GenerativeModel( // Specify a Gemini model appropriate for your use case diff --git a/samples/src/main/java/com/google/ai/client/generative/samples/java/code_execution.java b/samples/src/main/java/com/google/ai/client/generative/samples/java/code_execution.java index 9bc2a5cc..637f3d83 100644 --- a/samples/src/main/java/com/google/ai/client/generative/samples/java/code_execution.java +++ b/samples/src/main/java/com/google/ai/client/generative/samples/java/code_execution.java @@ -51,7 +51,7 @@ void codeExecutionBasic() { /* generationConfig */ null, /* safetySettings */ null, /* requestOptions */ new RequestOptions(), - /* tools */ Collections.singletonList(Tool.Companion.getCODE_EXECUTION())); + /* tools */ Collections.singletonList(Tool.CODE_EXECUTION)); GenerativeModelFutures model = GenerativeModelFutures.from(gm); Content inputContent = @@ -91,7 +91,7 @@ void codeExecutionChat() { /* generationConfig */ null, /* safetySettings */ null, /* requestOptions */ new RequestOptions(), - /* tools */ Collections.singletonList(Tool.Companion.getCODE_EXECUTION())); + /* tools */ Collections.singletonList(Tool.CODE_EXECUTION)); GenerativeModelFutures model = GenerativeModelFutures.from(gm); Content inputContent = diff --git a/samples/src/main/java/com/google/ai/client/generative/samples/java/controlled_generation.java b/samples/src/main/java/com/google/ai/client/generative/samples/java/controlled_generation.java index d42eee4b..f3015553 100644 --- a/samples/src/main/java/com/google/ai/client/generative/samples/java/controlled_generation.java +++ b/samples/src/main/java/com/google/ai/client/generative/samples/java/controlled_generation.java @@ -21,6 +21,141 @@ // the "Set up your API Key section" in the [Gemini API // quickstart](https://ai.google.dev/gemini-api/docs/quickstart?lang=android#set-up-api-key). +import com.google.ai.client.generativeai.GenerativeModel; +import com.google.ai.client.generativeai.java.GenerativeModelFutures; +import com.google.ai.client.generativeai.type.Content; +import com.google.ai.client.generativeai.type.FunctionType; +import com.google.ai.client.generativeai.type.GenerateContentResponse; +import com.google.ai.client.generativeai.type.GenerationConfig; +import com.google.ai.client.generativeai.type.Schema; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + class ControlledGeneration { - // TODO + void jsonControlledGeneration() { + // [START json_controlled_generation] + + Schema> schema = + new Schema( + /* name */ "recipes", + /* description */ "List of recipes", + /* format */ null, + /* nullable */ false, + /* list */ null, + /* properties */ null, + /* required */ null, + /* items */ new Schema( + /* name */ "recipe", + /* description */ "A recipe", + /* format */ null, + /* nullable */ false, + /* list */ null, + /* properties */ Map.of( + "recipeName", + new Schema( + /* name */ "recipeName", + /* description */ "Name of the recipe", + /* format */ null, + /* nullable */ false, + /* list */ null, + /* properties */ null, + /* required */ null, + /* items */ null, + /* type */ FunctionType.STRING)), + /* required */ null, + /* items */ null, + /* type */ FunctionType.OBJECT), + /* type */ FunctionType.ARRAY); + + GenerationConfig.Builder configBuilder = new GenerationConfig.Builder(); + configBuilder.responseMimeType = "application/json"; + configBuilder.responseSchema = schema; + + GenerationConfig generationConfig = configBuilder.build(); + + // Specify a Gemini model appropriate for your use case + GenerativeModel gm = + new GenerativeModel( + /* modelName */ "gemini-1.5-pro", + // Access your API key as a Build Configuration variable (see "Set up your API key" + // above) + /* apiKey */ BuildConfig.apiKey, + /* generationConfig */ generationConfig); + GenerativeModelFutures model = GenerativeModelFutures.from(gm); + + Content content = new Content.Builder().addText("List a few popular cookie recipes.").build(); + + // For illustrative purposes only. You should use an executor that fits your needs. + Executor executor = Executors.newSingleThreadExecutor(); + + ListenableFuture response = model.generateContent(content); + Futures.addCallback( + response, + new FutureCallback() { + @Override + public void onSuccess(GenerateContentResponse result) { + String resultText = result.getText(); + System.out.println(resultText); + } + + @Override + public void onFailure(Throwable t) { + t.printStackTrace(); + } + }, + executor); + // [END json_controlled_generation] + } + + void json_no_schema() { + // [START json_no_schema] + GenerationConfig.Builder configBuilder = new GenerationConfig.Builder(); + configBuilder.responseMimeType = "application/json"; + + GenerationConfig generationConfig = configBuilder.build(); + + // Specify a Gemini model appropriate for your use case + GenerativeModel gm = + new GenerativeModel( + /* modelName */ "gemini-1.5-flash", + // Access your API key as a Build Configuration variable (see "Set up your API key" + // above) + /* apiKey */ BuildConfig.apiKey, + /* generationConfig */ generationConfig); + GenerativeModelFutures model = GenerativeModelFutures.from(gm); + + Content content = + new Content.Builder() + .addText( + "List a few popular cookie recipes using this JSON schema:\n" + + "Recipe = {'recipeName': string}\n" + + "Return: Array") + .build(); + + // For illustrative purposes only. You should use an executor that fits your needs. + Executor executor = Executors.newSingleThreadExecutor(); + + ListenableFuture response = model.generateContent(content); + Futures.addCallback( + response, + new FutureCallback() { + @Override + public void onSuccess(GenerateContentResponse result) { + String resultText = result.getText(); + System.out.println(resultText); + } + + @Override + public void onFailure(Throwable t) { + t.printStackTrace(); + } + }, + executor); + // [END json_no_schema] + } } From 7fd4a8b8746665663b78b6d0c673ba0440b45e23 Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Paz Date: Mon, 15 Jul 2024 20:27:57 -0400 Subject: [PATCH 4/5] fix format --- .../google/ai/client/generativeai/type/Type.kt | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/generativeai/src/main/java/com/google/ai/client/generativeai/type/Type.kt b/generativeai/src/main/java/com/google/ai/client/generativeai/type/Type.kt index 3a693143..763667bb 100644 --- a/generativeai/src/main/java/com/google/ai/client/generativeai/type/Type.kt +++ b/generativeai/src/main/java/com/google/ai/client/generativeai/type/Type.kt @@ -29,22 +29,16 @@ import org.json.JSONObject */ class FunctionType(val name: String, val parse: (String?) -> T?) { companion object { - @JvmField - val STRING = FunctionType("STRING") { it } - @JvmField - val INTEGER = FunctionType("INTEGER") { it?.toIntOrNull() } - @JvmField - val LONG = FunctionType("INTEGER") { it?.toLongOrNull() } - @JvmField - val NUMBER = FunctionType("NUMBER") { it?.toDoubleOrNull() } - @JvmField - val BOOLEAN = FunctionType("BOOLEAN") { it?.toBoolean() } + @JvmField val STRING = FunctionType("STRING") { it } + @JvmField val INTEGER = FunctionType("INTEGER") { it?.toIntOrNull() } + @JvmField val LONG = FunctionType("INTEGER") { it?.toLongOrNull() } + @JvmField val NUMBER = FunctionType("NUMBER") { it?.toDoubleOrNull() } + @JvmField val BOOLEAN = FunctionType("BOOLEAN") { it?.toBoolean() } @JvmField val ARRAY = FunctionType>("ARRAY") { it -> it?.let { Json.parseToJsonElement(it).jsonArray.map { element -> element.toString() } } } - @JvmField - val OBJECT = FunctionType("OBJECT") { it?.let { JSONObject(it) } } + @JvmField val OBJECT = FunctionType("OBJECT") { it?.let { JSONObject(it) } } } } From faea6a2764ba43c44b08afe0acac19e277e02ce0 Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Paz Date: Tue, 16 Jul 2024 09:12:11 -0400 Subject: [PATCH 5/5] Formatting changes and changelog --- .../club-bite-carpenter-country.json | 1 + .../samples/controlled_generation.kt | 42 +++++++++---------- .../samples/java/controlled_generation.java | 1 - 3 files changed, 20 insertions(+), 24 deletions(-) create mode 100644 .changes/generativeai/club-bite-carpenter-country.json diff --git a/.changes/generativeai/club-bite-carpenter-country.json b/.changes/generativeai/club-bite-carpenter-country.json new file mode 100644 index 00000000..3edc63e3 --- /dev/null +++ b/.changes/generativeai/club-bite-carpenter-country.json @@ -0,0 +1 @@ +{"type":"MAJOR","changes":["Improve usability of the Schema type in Java"]} diff --git a/samples/src/main/java/com/google/ai/client/generative/samples/controlled_generation.kt b/samples/src/main/java/com/google/ai/client/generative/samples/controlled_generation.kt index 38df2a8f..cc5ac021 100644 --- a/samples/src/main/java/com/google/ai/client/generative/samples/controlled_generation.kt +++ b/samples/src/main/java/com/google/ai/client/generative/samples/controlled_generation.kt @@ -30,26 +30,6 @@ import com.google.ai.client.generativeai.type.generationConfig suspend fun json_controlled_generation() { // [START json_controlled_generation] - val jsonSchema = Schema( - name = "recipes", - description = "List of recipes", - type = FunctionType.ARRAY, - items = Schema( - name = "recipe", - description = "A recipe", - type = FunctionType.OBJECT, - properties = mapOf( - "recipeName" to Schema( - name = "recipeName", - description = "Name of the recipe", - type = FunctionType.STRING, - nullable = false - ), - ), - required = listOf("recipeName") - ), - ) - val generativeModel = GenerativeModel( // Specify a Gemini model appropriate for your use case @@ -58,13 +38,30 @@ suspend fun json_controlled_generation() { apiKey = BuildConfig.apiKey, generationConfig = generationConfig { responseMimeType = "application/json" - responseSchema = jsonSchema + responseSchema = Schema( + name = "recipes", + description = "List of recipes", + type = FunctionType.ARRAY, + items = Schema( + name = "recipe", + description = "A recipe", + type = FunctionType.OBJECT, + properties = mapOf( + "recipeName" to Schema( + name = "recipeName", + description = "Name of the recipe", + type = FunctionType.STRING, + nullable = false + ), + ), + required = listOf("recipeName") + ), + ) }) val prompt = "List a few popular cookie recipes." val response = generativeModel.generateContent(prompt) print(response.text) - // [END json_controlled_generation] } @@ -87,6 +84,5 @@ suspend fun json_no_schema() { """.trimIndent() val response = generativeModel.generateContent(prompt) print(response.text) - // [END json_no_schema] } diff --git a/samples/src/main/java/com/google/ai/client/generative/samples/java/controlled_generation.java b/samples/src/main/java/com/google/ai/client/generative/samples/java/controlled_generation.java index f3015553..17fb2889 100644 --- a/samples/src/main/java/com/google/ai/client/generative/samples/java/controlled_generation.java +++ b/samples/src/main/java/com/google/ai/client/generative/samples/java/controlled_generation.java @@ -39,7 +39,6 @@ class ControlledGeneration { void jsonControlledGeneration() { // [START json_controlled_generation] - Schema> schema = new Schema( /* name */ "recipes",