diff --git a/.changes/common/coach-cap-control-dinosaurs.json b/.changes/common/coach-cap-control-dinosaurs.json new file mode 100644 index 00000000..e8f66b5f --- /dev/null +++ b/.changes/common/coach-cap-control-dinosaurs.json @@ -0,0 +1 @@ +{"type":"MINOR","changes":["make FunctionCall.args nullable"]} diff --git a/.changes/generativeai/board-burn-cushion-calculator.json b/.changes/generativeai/board-burn-cushion-calculator.json new file mode 100644 index 00000000..d43c970c --- /dev/null +++ b/.changes/generativeai/board-burn-cushion-calculator.json @@ -0,0 +1 @@ +{"type":"MAJOR","changes":["make FunctionCallPart.args nullable"]} diff --git a/common/src/main/kotlin/com/google/ai/client/generativeai/common/shared/Types.kt b/common/src/main/kotlin/com/google/ai/client/generativeai/common/shared/Types.kt index 7dc31858..405b5bd9 100644 --- a/common/src/main/kotlin/com/google/ai/client/generativeai/common/shared/Types.kt +++ b/common/src/main/kotlin/com/google/ai/client/generativeai/common/shared/Types.kt @@ -64,7 +64,7 @@ data class CodeExecutionResultPart(val codeExecutionResult: CodeExecutionResult) @Serializable data class FunctionResponse(val name: String, val response: JsonObject) -@Serializable data class FunctionCall(val name: String, val args: Map) +@Serializable data class FunctionCall(val name: String, val args: Map? = null) @Serializable data class FileDataPart(@SerialName("file_data") val fileData: FileData) : Part diff --git a/common/src/test/java/com/google/ai/client/generativeai/common/UnarySnapshotTests.kt b/common/src/test/java/com/google/ai/client/generativeai/common/UnarySnapshotTests.kt index dcb37a93..9b8df300 100644 --- a/common/src/test/java/com/google/ai/client/generativeai/common/UnarySnapshotTests.kt +++ b/common/src/test/java/com/google/ai/client/generativeai/common/UnarySnapshotTests.kt @@ -316,7 +316,8 @@ internal class UnarySnapshotTests { val response = apiController.generateContent(textGenerateContentRequest("prompt")) val callPart = (response.candidates!!.first().content!!.parts.first() as FunctionCallPart) - callPart.functionCall.args["season"] shouldBe null + callPart.functionCall.args shouldNotBe null + callPart.functionCall.args?.get("season") shouldBe null } } @@ -333,7 +334,22 @@ internal class UnarySnapshotTests { it.parts.first().shouldBeInstanceOf() } - callPart.functionCall.args["current"] shouldBe "true" + callPart.functionCall.args shouldNotBe null + callPart.functionCall.args?.get("current") shouldBe "true" + } + } + + @Test + fun `function call has no arguments field`() = + goldenUnaryFile("success-function-call-empty-arguments.json") { + withTimeout(testTimeout) { + val response = apiController.generateContent(textGenerateContentRequest("prompt")) + val content = response.candidates.shouldNotBeNullOrEmpty().first().content + content.shouldNotBeNull() + val callPart = content.parts.shouldNotBeNullOrEmpty().first() as FunctionCallPart + + callPart.functionCall.name shouldBe "current_time" + callPart.functionCall.args shouldBe null } } diff --git a/common/src/test/resources/golden-files/unary/success-function-call-empty-arguments.json b/common/src/test/resources/golden-files/unary/success-function-call-empty-arguments.json new file mode 100644 index 00000000..dc0b75ad --- /dev/null +++ b/common/src/test/resources/golden-files/unary/success-function-call-empty-arguments.json @@ -0,0 +1,18 @@ +{ + "candidates": [ + { + "content": { + "parts": [ + { + "functionCall": { + "name": "current_time" + } + } + ], + "role": "model" + }, + "finishReason": "STOP", + "index": 0 + } + ] +} diff --git a/generativeai/src/main/java/com/google/ai/client/generativeai/type/Part.kt b/generativeai/src/main/java/com/google/ai/client/generativeai/type/Part.kt index 9087a535..b9c005ae 100644 --- a/generativeai/src/main/java/com/google/ai/client/generativeai/type/Part.kt +++ b/generativeai/src/main/java/com/google/ai/client/generativeai/type/Part.kt @@ -53,7 +53,7 @@ class FileDataPart(val uri: String, val mimeType: String) : Part fun Part.asFileDataPartOrNull(): FileDataPart? = this as? FileDataPart /** Represents function call name and params received from requests. */ -class FunctionCallPart(val name: String, val args: Map) : Part +class FunctionCallPart(val name: String, val args: Map?) : Part /** Represents function call output to be returned to the model when it requests a function call */ class FunctionResponsePart(val name: String, val response: JSONObject) : Part diff --git a/generativeai/src/test/java/com/google/ai/client/generativeai/GenerativeModelTests.kt b/generativeai/src/test/java/com/google/ai/client/generativeai/GenerativeModelTests.kt index bf1db18d..396ded0c 100644 --- a/generativeai/src/test/java/com/google/ai/client/generativeai/GenerativeModelTests.kt +++ b/generativeai/src/test/java/com/google/ai/client/generativeai/GenerativeModelTests.kt @@ -209,8 +209,9 @@ internal class GenerativeModelTests { response.functionCalls.firstOrNull()?.let { it.shouldNotBeNull() it.name shouldBe "getExchangeRate" - it.args shouldContain ("currencyFrom" to "USD") - it.args shouldContain ("currencyTo" to "EUR") + it.args.shouldNotBeNull() + it.args?.shouldContain("currencyFrom" to "USD") + it.args?.shouldContain("currencyTo" to "EUR") } coEvery { mockApiController.generateContent(any()) } returns