diff --git a/core/src/main/java/com/google/adk/models/chat/ChatCompletionsRequest.java b/core/src/main/java/com/google/adk/models/chat/ChatCompletionsRequest.java index 0c8cdc006..b73ac4fdf 100644 --- a/core/src/main/java/com/google/adk/models/chat/ChatCompletionsRequest.java +++ b/core/src/main/java/com/google/adk/models/chat/ChatCompletionsRequest.java @@ -575,6 +575,16 @@ private static void handleConfigOptions( schema.strict = true; format.jsonSchema = schema; request.responseFormat = format; + } else if (config.responseSchema().isPresent()) { + ResponseFormatJsonSchema format = new ResponseFormatJsonSchema(); + ResponseFormatJsonSchema.JsonSchema schema = new ResponseFormatJsonSchema.JsonSchema(); + schema.name = "response_schema"; + schema.schema = + objectMapper.convertValue( + config.responseSchema().get(), new TypeReference>() {}); + schema.strict = true; + format.jsonSchema = schema; + request.responseFormat = format; } else if (config.responseMimeType().isPresent() && config.responseMimeType().get().equals("application/json")) { request.responseFormat = new ResponseFormatJsonObject(); diff --git a/core/src/test/java/com/google/adk/models/chat/ChatCompletionsRequestTest.java b/core/src/test/java/com/google/adk/models/chat/ChatCompletionsRequestTest.java index 1bb4c36b2..fdb72c863 100644 --- a/core/src/test/java/com/google/adk/models/chat/ChatCompletionsRequestTest.java +++ b/core/src/test/java/com/google/adk/models/chat/ChatCompletionsRequestTest.java @@ -758,6 +758,73 @@ public void testFromLlmRequest_withConfigResponseMimeTypeJson() throws Exception .isInstanceOf(ChatCompletionsRequest.ResponseFormatJsonObject.class); } + @Test + public void testFromLlmRequest_withTypedResponseSchema() throws Exception { + Schema outputSchema = + Schema.builder() + .type("OBJECT") + .properties( + ImmutableMap.of( + "rootCause", Schema.builder().type("STRING").build(), + "confidence", Schema.builder().type("NUMBER").build())) + .required(ImmutableList.of("rootCause", "confidence")) + .build(); + + LlmRequest llmRequest = + LlmRequest.builder() + .model("openai-compatible-model") + .outputSchema(outputSchema) + .contents(ImmutableList.of()) + .build(); + + ChatCompletionsRequest request = ChatCompletionsRequest.fromLlmRequest(llmRequest, false); + + assertThat(request.responseFormat) + .isInstanceOf(ChatCompletionsRequest.ResponseFormatJsonSchema.class); + ChatCompletionsRequest.ResponseFormatJsonSchema format = + (ChatCompletionsRequest.ResponseFormatJsonSchema) request.responseFormat; + assertThat(format.jsonSchema.name).isEqualTo("response_schema"); + assertThat(format.jsonSchema.strict).isTrue(); + assertThat(format.jsonSchema.schema).isNotNull(); + assertThat(format.jsonSchema.schema.get("type")).isEqualTo("object"); + @SuppressWarnings("unchecked") + Map props = (Map) format.jsonSchema.schema.get("properties"); + @SuppressWarnings("unchecked") + Map rootCause = (Map) props.get("rootCause"); + assertThat(rootCause.get("type")).isEqualTo("string"); + @SuppressWarnings("unchecked") + Map confidence = (Map) props.get("confidence"); + assertThat(confidence.get("type")).isEqualTo("number"); + assertThat(format.jsonSchema.schema.get("required")) + .isEqualTo(ImmutableList.of("rootCause", "confidence")); + } + + @Test + public void testFromLlmRequest_withRawResponseJsonSchemaPrecedenceOverTypedSchema() + throws Exception { + Schema typedSchema = Schema.builder().type("OBJECT").build(); + ImmutableMap rawSchema = ImmutableMap.of("type", "object", "title", "raw"); + + LlmRequest llmRequest = + LlmRequest.builder() + .model("openai-compatible-model") + .config( + GenerateContentConfig.builder() + .responseSchema(typedSchema) + .responseJsonSchema(rawSchema) + .build()) + .contents(ImmutableList.of()) + .build(); + + ChatCompletionsRequest request = ChatCompletionsRequest.fromLlmRequest(llmRequest, false); + + assertThat(request.responseFormat) + .isInstanceOf(ChatCompletionsRequest.ResponseFormatJsonSchema.class); + ChatCompletionsRequest.ResponseFormatJsonSchema format = + (ChatCompletionsRequest.ResponseFormatJsonSchema) request.responseFormat; + assertThat(format.jsonSchema.schema).isEqualTo(rawSchema); + } + // ----- thought_signature round-trip on the request side ---------------------------------- // // The four chat source files share a single contract for round-tripping Gemini's