From c1dc32f84d0e4d14a16345dcb404c8b2bef05338 Mon Sep 17 00:00:00 2001 From: Kaituo Huang Date: Tue, 28 Oct 2025 16:27:11 -0700 Subject: [PATCH] feat: Added Operations.get which is a generic method which will handle all Operation types. PiperOrigin-RevId: 825254244 --- .../google/genai/examples/GenerateVideos.java | 3 +- .../genai/examples/GenerateVideosAsync.java | 4 +- .../com/google/genai/AsyncOperations.java | 30 +++++--- .../com/google/genai/JsonSerializable.java | 5 +- .../java/com/google/genai/LiveConverters.java | 1 + .../java/com/google/genai/Operations.java | 58 +++++++--------- .../google/genai/OperationsConverters.java | 44 ++++++++---- .../com/google/genai/TokensConverters.java | 1 + .../genai/types/GenerateVideosOperation.java | 46 +++++-------- .../com/google/genai/types/Operation.java | 69 +++++++++++++++++++ 10 files changed, 169 insertions(+), 92 deletions(-) create mode 100644 src/main/java/com/google/genai/types/Operation.java diff --git a/examples/src/main/java/com/google/genai/examples/GenerateVideos.java b/examples/src/main/java/com/google/genai/examples/GenerateVideos.java index 4de8a89dd4f..85a793364bf 100644 --- a/examples/src/main/java/com/google/genai/examples/GenerateVideos.java +++ b/examples/src/main/java/com/google/genai/examples/GenerateVideos.java @@ -92,8 +92,7 @@ public static void main(String[] args) { while (!generateVideosOperation.done().filter(Boolean::booleanValue).isPresent()) { try { Thread.sleep(10000); // Sleep for 10 seconds. - generateVideosOperation = - client.operations.getVideosOperation(generateVideosOperation, null); + generateVideosOperation = client.operations.get(generateVideosOperation, null); System.out.println("Waiting for operation to complete..."); } catch (InterruptedException e) { System.out.println("Thread was interrupted while sleeping."); diff --git a/examples/src/main/java/com/google/genai/examples/GenerateVideosAsync.java b/examples/src/main/java/com/google/genai/examples/GenerateVideosAsync.java index ecb7758eb76..29b0a96be49 100644 --- a/examples/src/main/java/com/google/genai/examples/GenerateVideosAsync.java +++ b/examples/src/main/java/com/google/genai/examples/GenerateVideosAsync.java @@ -98,7 +98,7 @@ public static void main(String[] args) { try { Thread.sleep(10000); // Sleep for 10 seconds. try { - operation = client.async.operations.getVideosOperation(operation, null).get(); + operation = client.async.operations.get(operation, null).get(); } catch (ExecutionException e) { throw new RuntimeException(e); } @@ -115,7 +115,7 @@ public static void main(String[] args) { Video generatedVideo = operation.response().get().generatedVideos().get().get(0).video().get(); - // Do something with the video. + System.out.println("Video URL: " + generatedVideo.uri().get()); }) .join(); } diff --git a/src/main/java/com/google/genai/AsyncOperations.java b/src/main/java/com/google/genai/AsyncOperations.java index 84d205d5aea..e396b9cc7a2 100644 --- a/src/main/java/com/google/genai/AsyncOperations.java +++ b/src/main/java/com/google/genai/AsyncOperations.java @@ -18,10 +18,12 @@ package com.google.genai; +import com.fasterxml.jackson.databind.JsonNode; import com.google.genai.Common.BuiltRequest; import com.google.genai.types.FetchPredictOperationConfig; import com.google.genai.types.GenerateVideosOperation; import com.google.genai.types.GetOperationConfig; +import com.google.genai.types.Operation; import java.util.concurrent.CompletableFuture; /** Async module of {@link Operations} */ @@ -35,7 +37,7 @@ public AsyncOperations(ApiClient apiClient) { this.operations = new Operations(apiClient); } - CompletableFuture privateGetVideosOperation( + CompletableFuture privateGetVideosOperation( String operationName, GetOperationConfig config) { BuiltRequest builtRequest = operations.buildRequestForPrivateGetVideosOperation(operationName, config); @@ -49,7 +51,7 @@ CompletableFuture privateGetVideosOperation( }); } - CompletableFuture privateFetchPredictVideosOperation( + CompletableFuture privateFetchPredictVideosOperation( String operationName, String resourceName, FetchPredictOperationConfig config) { BuiltRequest builtRequest = operations.buildRequestForPrivateFetchPredictVideosOperation( @@ -73,19 +75,29 @@ CompletableFuture privateFetchPredictVideosOperation( */ public CompletableFuture getVideosOperation( GenerateVideosOperation operation, GetOperationConfig config) { + return get(operation, config); + } + + /** + * Gets the status of an Operation. + * + * @param operation An Operation. + * @param config The configuration for getting the operation. + * @return An Operation with the updated status of the operation. + */ + public > CompletableFuture get( + U operation, GetOperationConfig config) { if (!operation.name().isPresent()) { - throw new Error("Operation name is required."); + throw new IllegalArgumentException("Operation name is required."); } if (this.apiClient.vertexAI()) { String resourceName = operation.name().get().split("/operations/")[0]; - - FetchPredictOperationConfig fetchConfig = FetchPredictOperationConfig.builder().build(); - - return this.privateFetchPredictVideosOperation( - operation.name().get(), resourceName, fetchConfig); + return this.privateFetchPredictVideosOperation(operation.name().get(), resourceName, null) + .thenApplyAsync(response -> operation.fromApiResponse(response, true)); } else { - return this.privateGetVideosOperation(operation.name().get(), config); + return this.privateGetVideosOperation(operation.name().get(), config) + .thenApplyAsync(response -> operation.fromApiResponse(response, false)); } } } diff --git a/src/main/java/com/google/genai/JsonSerializable.java b/src/main/java/com/google/genai/JsonSerializable.java index 4fcd2a0c1b9..a2919940de1 100644 --- a/src/main/java/com/google/genai/JsonSerializable.java +++ b/src/main/java/com/google/genai/JsonSerializable.java @@ -38,7 +38,7 @@ /** A class that can be serialized to JSON and deserialized from JSON. */ public abstract class JsonSerializable { - static final ObjectMapper objectMapper = new ObjectMapper(); + @InternalApi protected static final ObjectMapper objectMapper = new ObjectMapper(); /** Custom Jackson serializer for {@link java.time.Duration} to output "Xs" format. */ static class CustomDurationSerializer extends JsonSerializer { @@ -134,7 +134,8 @@ protected static T fromJsonString( } /** Deserializes a JsonNode to an object of the given type. */ - static T fromJsonNode(JsonNode jsonNode, Class clazz) { + @InternalApi + protected static T fromJsonNode(JsonNode jsonNode, Class clazz) { try { return objectMapper.treeToValue(jsonNode, clazz); } catch (JsonProcessingException e) { diff --git a/src/main/java/com/google/genai/LiveConverters.java b/src/main/java/com/google/genai/LiveConverters.java index 22dde2f1670..579cf9fe55e 100644 --- a/src/main/java/com/google/genai/LiveConverters.java +++ b/src/main/java/com/google/genai/LiveConverters.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +/** Internal SDK converter functions. */ final class LiveConverters { private final ApiClient apiClient; diff --git a/src/main/java/com/google/genai/Operations.java b/src/main/java/com/google/genai/Operations.java index d684435d7e5..b8bfcbd36ae 100644 --- a/src/main/java/com/google/genai/Operations.java +++ b/src/main/java/com/google/genai/Operations.java @@ -30,6 +30,7 @@ import com.google.genai.types.GetOperationConfig; import com.google.genai.types.GetOperationParameters; import com.google.genai.types.HttpOptions; +import com.google.genai.types.Operation; import java.io.IOException; import java.util.Optional; import okhttp3.ResponseBody; @@ -378,7 +379,7 @@ BuiltRequest buildRequestForPrivateGetVideosOperation( } /** A shared processResponse function for both sync and async methods. */ - GenerateVideosOperation processResponseForPrivateGetVideosOperation( + JsonNode processResponseForPrivateGetVideosOperation( ApiResponse response, GetOperationConfig config) { ResponseBody responseBody = response.getBody(); String responseString; @@ -388,21 +389,10 @@ GenerateVideosOperation processResponseForPrivateGetVideosOperation( throw new GenAiIOException("Failed to read HTTP response.", e); } - JsonNode responseNode = JsonSerializable.stringToJsonNode(responseString); - - if (this.apiClient.vertexAI()) { - responseNode = generateVideosOperationFromVertex(responseNode, null); - } - - if (!this.apiClient.vertexAI()) { - responseNode = generateVideosOperationFromMldev(responseNode, null); - } - - return JsonSerializable.fromJsonNode(responseNode, GenerateVideosOperation.class); + return JsonSerializable.stringToJsonNode(responseString); } - GenerateVideosOperation privateGetVideosOperation( - String operationName, GetOperationConfig config) { + JsonNode privateGetVideosOperation(String operationName, GetOperationConfig config) { BuiltRequest builtRequest = buildRequestForPrivateGetVideosOperation(operationName, config); try (ApiResponse response = @@ -457,7 +447,7 @@ BuiltRequest buildRequestForPrivateFetchPredictVideosOperation( } /** A shared processResponse function for both sync and async methods. */ - GenerateVideosOperation processResponseForPrivateFetchPredictVideosOperation( + JsonNode processResponseForPrivateFetchPredictVideosOperation( ApiResponse response, FetchPredictOperationConfig config) { ResponseBody responseBody = response.getBody(); String responseString; @@ -467,21 +457,10 @@ GenerateVideosOperation processResponseForPrivateFetchPredictVideosOperation( throw new GenAiIOException("Failed to read HTTP response.", e); } - JsonNode responseNode = JsonSerializable.stringToJsonNode(responseString); - - if (this.apiClient.vertexAI()) { - responseNode = generateVideosOperationFromVertex(responseNode, null); - } - - if (!this.apiClient.vertexAI()) { - throw new UnsupportedOperationException( - "This method is only supported in the Vertex AI client."); - } - - return JsonSerializable.fromJsonNode(responseNode, GenerateVideosOperation.class); + return JsonSerializable.stringToJsonNode(responseString); } - GenerateVideosOperation privateFetchPredictVideosOperation( + JsonNode privateFetchPredictVideosOperation( String operationName, String resourceName, FetchPredictOperationConfig config) { BuiltRequest builtRequest = buildRequestForPrivateFetchPredictVideosOperation(operationName, resourceName, config); @@ -502,20 +481,29 @@ GenerateVideosOperation privateFetchPredictVideosOperation( */ public GenerateVideosOperation getVideosOperation( GenerateVideosOperation operation, GetOperationConfig config) { + return get(operation, config); + } + /** + * Gets the status of an Operation. + * + * @param operation An Operation. + * @param config The configuration for getting the operation. + * @return An Operation with the updated status of the operation. + */ + public > U get(U operation, GetOperationConfig config) { if (!operation.name().isPresent()) { - throw new Error("Operation name is required."); + throw new IllegalArgumentException("Operation name is required."); } if (this.apiClient.vertexAI()) { String resourceName = operation.name().get().split("/operations/")[0]; - - FetchPredictOperationConfig fetchConfig = FetchPredictOperationConfig.builder().build(); - - return this.privateFetchPredictVideosOperation( - operation.name().get(), resourceName, fetchConfig); + JsonNode response = + this.privateFetchPredictVideosOperation(operation.name().get(), resourceName, null); + return operation.fromApiResponse(response, true); } else { - return this.privateGetVideosOperation(operation.name().get(), config); + JsonNode response = this.privateGetVideosOperation(operation.name().get(), config); + return operation.fromApiResponse(response, false); } } } diff --git a/src/main/java/com/google/genai/OperationsConverters.java b/src/main/java/com/google/genai/OperationsConverters.java index 50cb67b1cae..5c48d2fc114 100644 --- a/src/main/java/com/google/genai/OperationsConverters.java +++ b/src/main/java/com/google/genai/OperationsConverters.java @@ -22,8 +22,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.api.core.InternalApi; -final class OperationsConverters { +/** Internal SDK converter functions. */ +@InternalApi +public final class OperationsConverters { private final ApiClient apiClient; public OperationsConverters(ApiClient apiClient) { @@ -31,7 +34,9 @@ public OperationsConverters(ApiClient apiClient) { } @ExcludeFromGeneratedCoverageReport - ObjectNode fetchPredictOperationParametersToMldev(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode fetchPredictOperationParametersToMldev( + JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (!Common.isZero(Common.getValueByPath(fromObject, new String[] {"operationName"}))) { throw new IllegalArgumentException("operationName parameter is not supported in Gemini API."); @@ -49,7 +54,9 @@ ObjectNode fetchPredictOperationParametersToMldev(JsonNode fromObject, ObjectNod } @ExcludeFromGeneratedCoverageReport - ObjectNode fetchPredictOperationParametersToVertex(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode fetchPredictOperationParametersToVertex( + JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"operationName"}) != null) { Common.setValueByPath( @@ -69,7 +76,8 @@ ObjectNode fetchPredictOperationParametersToVertex(JsonNode fromObject, ObjectNo } @ExcludeFromGeneratedCoverageReport - ObjectNode generateVideosOperationFromMldev(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode generateVideosOperationFromMldev(JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"name"}) != null) { Common.setValueByPath( @@ -115,7 +123,9 @@ ObjectNode generateVideosOperationFromMldev(JsonNode fromObject, ObjectNode pare } @ExcludeFromGeneratedCoverageReport - ObjectNode generateVideosOperationFromVertex(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode generateVideosOperationFromVertex( + JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"name"}) != null) { Common.setValueByPath( @@ -159,7 +169,8 @@ ObjectNode generateVideosOperationFromVertex(JsonNode fromObject, ObjectNode par } @ExcludeFromGeneratedCoverageReport - ObjectNode generateVideosResponseFromMldev(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode generateVideosResponseFromMldev(JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"generatedSamples"}) != null) { ArrayNode keyArray = @@ -191,7 +202,8 @@ ObjectNode generateVideosResponseFromMldev(JsonNode fromObject, ObjectNode paren } @ExcludeFromGeneratedCoverageReport - ObjectNode generateVideosResponseFromVertex(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode generateVideosResponseFromVertex(JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"videos"}) != null) { ArrayNode keyArray = (ArrayNode) Common.getValueByPath(fromObject, new String[] {"videos"}); @@ -222,7 +234,8 @@ ObjectNode generateVideosResponseFromVertex(JsonNode fromObject, ObjectNode pare } @ExcludeFromGeneratedCoverageReport - ObjectNode generatedVideoFromMldev(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode generatedVideoFromMldev(JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"video"}) != null) { Common.setValueByPath( @@ -238,7 +251,8 @@ ObjectNode generatedVideoFromMldev(JsonNode fromObject, ObjectNode parentObject) } @ExcludeFromGeneratedCoverageReport - ObjectNode generatedVideoFromVertex(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode generatedVideoFromVertex(JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"_self"}) != null) { Common.setValueByPath( @@ -254,7 +268,8 @@ ObjectNode generatedVideoFromVertex(JsonNode fromObject, ObjectNode parentObject } @ExcludeFromGeneratedCoverageReport - ObjectNode getOperationParametersToMldev(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode getOperationParametersToMldev(JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"operationName"}) != null) { Common.setValueByPath( @@ -267,7 +282,8 @@ ObjectNode getOperationParametersToMldev(JsonNode fromObject, ObjectNode parentO } @ExcludeFromGeneratedCoverageReport - ObjectNode getOperationParametersToVertex(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode getOperationParametersToVertex(JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"operationName"}) != null) { Common.setValueByPath( @@ -280,7 +296,8 @@ ObjectNode getOperationParametersToVertex(JsonNode fromObject, ObjectNode parent } @ExcludeFromGeneratedCoverageReport - ObjectNode videoFromMldev(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode videoFromMldev(JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"uri"}) != null) { Common.setValueByPath( @@ -305,7 +322,8 @@ ObjectNode videoFromMldev(JsonNode fromObject, ObjectNode parentObject) { } @ExcludeFromGeneratedCoverageReport - ObjectNode videoFromVertex(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode videoFromVertex(JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"gcsUri"}) != null) { Common.setValueByPath( diff --git a/src/main/java/com/google/genai/TokensConverters.java b/src/main/java/com/google/genai/TokensConverters.java index aca63a40266..5e1c190cf9e 100644 --- a/src/main/java/com/google/genai/TokensConverters.java +++ b/src/main/java/com/google/genai/TokensConverters.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +/** Internal SDK converter functions. */ final class TokensConverters { private final ApiClient apiClient; diff --git a/src/main/java/com/google/genai/types/GenerateVideosOperation.java b/src/main/java/com/google/genai/types/GenerateVideosOperation.java index 8e8d7980379..cf93a00372a 100644 --- a/src/main/java/com/google/genai/types/GenerateVideosOperation.java +++ b/src/main/java/com/google/genai/types/GenerateVideosOperation.java @@ -20,48 +20,36 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.auto.value.AutoValue; import com.google.genai.JsonSerializable; +import com.google.genai.OperationsConverters; import java.util.Map; import java.util.Optional; /** A video generation operation. */ @AutoValue @JsonDeserialize(builder = GenerateVideosOperation.Builder.class) -public abstract class GenerateVideosOperation extends JsonSerializable { - /** - * The server-assigned name, which is only unique within the same service that originally returns - * it. If you use the default HTTP mapping, the `name` should be a resource name ending with - * `operations/{unique_id}`. - */ - @JsonProperty("name") - public abstract Optional name(); - - /** - * Service-specific metadata associated with the operation. It typically contains progress - * information and common metadata such as create time. Some services might not provide such - * metadata. Any method that returns a long-running operation should document the metadata type, - * if any. - */ - @JsonProperty("metadata") - public abstract Optional> metadata(); - - /** - * If the value is `false`, it means the operation is still in progress. If `true`, the operation - * is completed, and either `error` or `response` is available. - */ - @JsonProperty("done") - public abstract Optional done(); - - /** The error result of the operation in case of failure or cancellation. */ - @JsonProperty("error") - public abstract Optional> error(); - +public abstract class GenerateVideosOperation + extends Operation { /** The generated videos. */ @JsonProperty("response") + @Override public abstract Optional response(); + @Override + public GenerateVideosOperation fromApiResponse(JsonNode apiResponse, boolean isVertexAi) { + OperationsConverters converter = new OperationsConverters(null); + JsonNode response; + if (isVertexAi) { + response = converter.generateVideosOperationFromVertex(apiResponse, null); + } else { + response = converter.generateVideosOperationFromMldev(apiResponse, null); + } + return JsonSerializable.fromJsonNode(response, GenerateVideosOperation.class); + } + /** Instantiates a builder for GenerateVideosOperation. */ @ExcludeFromGeneratedCoverageReport public static Builder builder() { diff --git a/src/main/java/com/google/genai/types/Operation.java b/src/main/java/com/google/genai/types/Operation.java new file mode 100644 index 00000000000..c9b54702b6c --- /dev/null +++ b/src/main/java/com/google/genai/types/Operation.java @@ -0,0 +1,69 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Auto-generated code. Do not edit. + +package com.google.genai.types; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.google.genai.JsonSerializable; +import java.util.Map; +import java.util.Optional; + +/** A long-running operation. */ +public abstract class Operation> extends JsonSerializable { + /** + * The server-assigned name, which is only unique within the same service that originally returns + * it. If you use the default HTTP mapping, the `name` should be a resource name ending with + * `operations/{unique_id}`. + */ + @JsonProperty("name") + public abstract Optional name(); + + /** + * Service-specific metadata associated with the operation. It typically contains progress + * information and common metadata such as create time. Some services might not provide such + * metadata. Any method that returns a long-running operation should document the metadata type, + * if any. + */ + @JsonProperty("metadata") + public abstract Optional> metadata(); + + /** + * If the value is `false`, it means the operation is still in progress. If `true`, the operation + * is completed, and either `error` or `response` is available. + */ + @JsonProperty("done") + public abstract Optional done(); + + /** The error result of the operation in case of failure or cancellation. */ + @JsonProperty("error") + public abstract Optional> error(); + + /** The result of the operation. */ + @JsonProperty("response") + public abstract Optional response(); + + /** + * Creates a new Operation object from an API response. + * + * @param apiResponse The API response. + * @param isVertexAi Whether the API response is from Vertex AI. + * @return The new Operation object. + */ + public abstract O fromApiResponse(JsonNode apiResponse, boolean isVertexAi); +}