diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 77c0d292c..d6a5f76bd 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.7.0" + ".": "0.8.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index b10bad3ce..6d5b9e5eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,49 @@ # Changelog +## [0.8.0](https://github.com/google/adk-java/compare/v0.7.0...v0.8.0) (2026-03-06) + + +### ⚠ BREAKING CHANGES + +* remove methods with Optional params from LiveRequest.Builder +* remove deprecated methods accepting Optional params in InvocationContext +* remove deprecated BaseToolset.isToolSelected method +* remove Optional parameters from LlmResponse.Builder's methods +* remove support for legacy `transferToAgent`, superseded by `transfer_to_agent` + +### Features + +* add callbacks functionality to the agent executor ([7e8f9dc](https://github.com/google/adk-java/commit/7e8f9dcf82fe7e62aee625fbfaa8673d238ff184)) +* add example on how to expose agent via A2A protocol ([e3ea378](https://github.com/google/adk-java/commit/e3ea378051e5c4e5e5031657467145779e42db55)) +* Adding a Builder for EventsCompactionConfig ([05fbcfc](https://github.com/google/adk-java/commit/05fbcfc933923ae711cd12e7fc9e587fd8e2685c)) +* Adding a SessionKey for typeSafety ([d899f6f](https://github.com/google/adk-java/commit/d899f6f4ad52c84cb4ac8c90d0dc88c22487029c)) +* Adding plugin(Plugin... p) helper methods on App and Runner builders ([dc1a192](https://github.com/google/adk-java/commit/dc1a192a81a92870aa5a4af27a9dc90e81cdaf67)) +* implement partial event aggregation in RemoteA2AAgent ([e064067](https://github.com/google/adk-java/commit/e0640673d212b9849d312953f192f8da51fae85b)) +* remove deprecated BaseToolset.isToolSelected method ([d2f1145](https://github.com/google/adk-java/commit/d2f11456c3a99edd43b3dc0d04743ae7e9390ded)) +* remove deprecated methods accepting Optional params in InvocationContext ([88153c8](https://github.com/google/adk-java/commit/88153c833697a9b9c6ec735a69f48a92cbdfc54b)) +* remove methods with Optional params from LiveRequest.Builder ([84c62a4](https://github.com/google/adk-java/commit/84c62a48ef7b62641722824fe5ba1200606b7b17)) +* remove Optional parameters from LlmResponse.Builder's methods ([a3ac436](https://github.com/google/adk-java/commit/a3ac436bcfa241e90c07485e5da918ec8dbc2b4a)) + + +### Bug Fixes + +* Allow injecting ObjectMapper in FunctionTool, default to ObjectMapper (re. [#473](https://github.com/google/adk-java/issues/473)) ([71b1070](https://github.com/google/adk-java/commit/71b10701e753bddaa96d5e6579b759d2b9bb3e92)) +* downgrade otel.version to 1.51.0 ([117fedf](https://github.com/google/adk-java/commit/117fedf672bb67c4b078ac75ee81a7710452c5b5)) +* Ensure Gemini 3.1 models have events correctly buffered ([acffdb9](https://github.com/google/adk-java/commit/acffdb96bcd8133af99cb0b9426665ba73a83bbc)) +* Exit from rearrangeEventsForLatestFunctionResponse if size of events is less than 2 ([5bc3ef8](https://github.com/google/adk-java/commit/5bc3ef89e62eb3f32ba7e45657c9e40c88c3a5e9)) +* Fixed issue where events were marked empty if the first part had an empty text; now checks all parts for meaningful content ([a0cba25](https://github.com/google/adk-java/commit/a0cba25d691f4be72bea22b0649ecf2d2c110736)) +* prepare JSON serialization for Jackson 2.20.2 and Spring Boot 4.0.2 upgrades ([8c6591b](https://github.com/google/adk-java/commit/8c6591bc4ad86c376cdd70e1bb64f359fbf22fe9)) + + +### Miscellaneous Chores + +* revert: switch release please secret to use adk-java-releases-bot's token ([7eafd1b](https://github.com/google/adk-java/commit/7eafd1bd9b16e9ed83dfbc3d0983cfc415c0aaec)) + + +### Code Refactoring + +* remove support for legacy `transferToAgent`, superseded by `transfer_to_agent` ([c1ccb2e](https://github.com/google/adk-java/commit/c1ccb2e9d375fedcd7dbb594300e66a1a0488a91)) + ## [0.7.0](https://github.com/google/adk-java/compare/v0.6.0...v0.7.0) (2026-02-27) diff --git a/README.md b/README.md index d0471c1bf..4a5dab81f 100644 --- a/README.md +++ b/README.md @@ -50,13 +50,13 @@ If you're using Maven, add the following to your dependencies: com.google.adk google-adk - 0.7.0 + 0.8.0 com.google.adk google-adk-dev - 0.7.0 + 0.8.0 ``` diff --git a/a2a/pom.xml b/a2a/pom.xml index 05c740067..5857720fd 100644 --- a/a2a/pom.xml +++ b/a2a/pom.xml @@ -5,7 +5,7 @@ com.google.adk google-adk-parent - 0.7.1-SNAPSHOT + 0.8.1-SNAPSHOT google-adk-a2a diff --git a/a2a/src/main/java/com/google/adk/a2a/common/GenAiFieldMissingException.java b/a2a/src/main/java/com/google/adk/a2a/common/GenAiFieldMissingException.java new file mode 100644 index 000000000..a5947dcb8 --- /dev/null +++ b/a2a/src/main/java/com/google/adk/a2a/common/GenAiFieldMissingException.java @@ -0,0 +1,12 @@ +package com.google.adk.a2a.common; + +/** Exception thrown when the the genai class has an empty field. */ +public class GenAiFieldMissingException extends RuntimeException { + public GenAiFieldMissingException(String message) { + super(message); + } + + public GenAiFieldMissingException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/a2a/src/main/java/com/google/adk/a2a/converters/A2ADataPartMetadataType.java b/a2a/src/main/java/com/google/adk/a2a/converters/A2ADataPartMetadataType.java new file mode 100644 index 000000000..b5b53c49a --- /dev/null +++ b/a2a/src/main/java/com/google/adk/a2a/converters/A2ADataPartMetadataType.java @@ -0,0 +1,19 @@ +package com.google.adk.a2a.converters; + +/** Enum for the type of A2A DataPart metadata. */ +public enum A2ADataPartMetadataType { + FUNCTION_RESPONSE("function_response"), + FUNCTION_CALL("function_call"), + CODE_EXECUTION_RESULT("code_execution_result"), + EXECUTABLE_CODE("executable_code"); + + private final String type; + + private A2ADataPartMetadataType(String type) { + this.type = type; + } + + public String getType() { + return type; + } +} diff --git a/a2a/src/main/java/com/google/adk/a2a/converters/ConversationPreprocessor.java b/a2a/src/main/java/com/google/adk/a2a/converters/ConversationPreprocessor.java deleted file mode 100644 index 11bbbd326..000000000 --- a/a2a/src/main/java/com/google/adk/a2a/converters/ConversationPreprocessor.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.google.adk.a2a.converters; - -import com.google.adk.events.Event; -import com.google.common.collect.ImmutableList; -import com.google.genai.types.Content; -import com.google.genai.types.Part; -import java.util.List; -import java.util.Optional; - -/** - * Preprocesses a batch of ADK events prior to invoking a remote A2A agent. - * - *

The class splits the conversation into two logical buckets: - * - *

- * - *

This mirrors the Python A2A implementation where the in-flight user message is maintained - * separately from the persisted transcript. - * - *

**EXPERIMENTAL:** Subject to change, rename, or removal in any future patch release. Do not - * use in production code. - */ -public final class ConversationPreprocessor { - - /** - * Immutable value that surfaces the results of preprocessing. - * - *

All fields are deliberately exposed to avoid additional AutoValue dependencies in this - * internal module. - */ - public static final class PreparedInput { - /** Historical events that should remain in the session transcript. */ - public final ImmutableList historyEvents; - - /** Extracted user message content, if a qualifying text event was found. */ - public final Optional userContent; - - /** The concrete event that supplied {@link #userContent}, for callers needing metadata. */ - public final Optional userEvent; - - /** - * Creates a new instance. - * - * @param historyEvents ordered historical events retained in the session stream - * @param userContent optional content to place on the pending user message - * @param userEvent optional original event that contained {@code userContent} - */ - public PreparedInput( - ImmutableList historyEvents, - Optional userContent, - Optional userEvent) { - this.historyEvents = historyEvents; - this.userContent = userContent; - this.userEvent = userEvent; - } - } - - private ConversationPreprocessor() {} - - /** - * Splits the provided event list into history and the latest user-authored text message. - * - * @param inputEvents ordered session events, oldest to newest; may be {@code null} - * @return container encapsulating the derived history, optional user content, and the original - * user event when present - */ - public static PreparedInput extractHistoryAndUserContent(List inputEvents) { - if (inputEvents == null || inputEvents.isEmpty()) { - return new PreparedInput(ImmutableList.of(), Optional.empty(), Optional.empty()); - } - - Content userContent = null; - int lastTextIndex = -1; - Event userEvent = null; - for (int i = inputEvents.size() - 1; i >= 0; i--) { - Event ev = inputEvents.get(i); - if (ev.content().isPresent() && ev.content().get().parts().isPresent()) { - boolean hasText = false; - for (Part p : ev.content().get().parts().get()) { - if (p.text().isPresent()) { - hasText = true; - break; - } - } - if (hasText) { - userContent = ev.content().get(); - lastTextIndex = i; - userEvent = ev; - break; - } - } - } - - ImmutableList.Builder historyBuilder = ImmutableList.builder(); - for (int i = 0; i < inputEvents.size(); i++) { - if (i != lastTextIndex) { - historyBuilder.add(inputEvents.get(i)); - } - } - - return new PreparedInput( - historyBuilder.build(), Optional.ofNullable(userContent), Optional.ofNullable(userEvent)); - } -} diff --git a/a2a/src/main/java/com/google/adk/a2a/converters/EventConverter.java b/a2a/src/main/java/com/google/adk/a2a/converters/EventConverter.java index f5b1178c0..1a49b0070 100644 --- a/a2a/src/main/java/com/google/adk/a2a/converters/EventConverter.java +++ b/a2a/src/main/java/com/google/adk/a2a/converters/EventConverter.java @@ -3,14 +3,11 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import com.google.adk.agents.InvocationContext; -import com.google.adk.events.Event; import com.google.common.collect.ImmutableList; import com.google.genai.types.Content; -import com.google.genai.types.Part; import io.a2a.spec.Message; -import io.a2a.spec.TextPart; -import java.util.ArrayList; -import java.util.List; +import io.a2a.spec.Part; +import java.util.Collection; import java.util.Optional; import java.util.UUID; import org.slf4j.Logger; @@ -28,47 +25,35 @@ public final class EventConverter { private EventConverter() {} /** - * Aggregation mode for converting events to A2A messages. + * Converts an ADK InvocationContext to an A2A Message. * - *

AS_IS: Parts are aggregated as-is. + *

It combines all the events in the session, plus the user content, converted into A2A Parts, + * into a single A2A Message. * - *

EXTERNAL_HANDOFF: Parts are aggregated as-is, except for function responses, which are - * converted to text parts with the function name and response map. + *

If the context has no events, or no suitable content to build the message, an empty optional + * is returned. + * + * @param context The ADK InvocationContext to convert. + * @return The converted A2A Message. */ - public enum AggregationMode { - AS_IS, - EXTERNAL_HANDOFF - } - - public static ImmutableList> contentToParts(Optional content) { - if (content.isPresent() && content.get().parts().isPresent()) { - return content.get().parts().get().stream() - .map(PartConverter::fromGenaiPart) - .flatMap(Optional::stream) - .collect(toImmutableList()); - } - return ImmutableList.of(); - } - public static Optional convertEventsToA2AMessage(InvocationContext context) { - return convertEventsToA2AMessage(context, AggregationMode.AS_IS); - } - - public static Optional convertEventsToA2AMessage( - InvocationContext context, AggregationMode mode) { if (context.session().events().isEmpty()) { logger.warn("No events in session, cannot convert to A2A message."); return Optional.empty(); } - List> parts = new ArrayList<>(); - for (Event event : context.session().events()) { - appendContentParts(event.content(), mode, parts); - } + ImmutableList.Builder> partsBuilder = ImmutableList.builder(); context - .userContent() - .ifPresent(content -> appendContentParts(Optional.of(content), mode, parts)); + .session() + .events() + .forEach( + event -> + partsBuilder.addAll( + contentToParts(event.content(), event.partial().orElse(false)))); + partsBuilder.addAll(contentToParts(context.userContent(), false)); + + ImmutableList> parts = partsBuilder.build(); if (parts.isEmpty()) { logger.warn("No suitable content found to build A2A request message."); @@ -83,37 +68,11 @@ public static Optional convertEventsToA2AMessage( .build()); } - private static void appendContentParts( - Optional contentOpt, AggregationMode mode, List> target) { - if (contentOpt.isEmpty() || contentOpt.get().parts().isEmpty()) { - return; - } - - for (Part part : contentOpt.get().parts().get()) { - if (part.text().isPresent()) { - target.add(new TextPart(part.text().get())); - continue; - } - - if (part.functionCall().isPresent()) { - if (mode == AggregationMode.AS_IS) { - PartConverter.convertGenaiPartToA2aPart(part).ifPresent(target::add); - } - continue; - } - - if (part.functionResponse().isPresent()) { - if (mode == AggregationMode.AS_IS) { - PartConverter.convertGenaiPartToA2aPart(part).ifPresent(target::add); - } else { - String name = part.functionResponse().get().name().orElse(""); - String mapStr = String.valueOf(part.functionResponse().get().response().orElse(null)); - target.add(new TextPart(String.format("%s response: %s", name, mapStr))); - } - continue; - } - - PartConverter.fromGenaiPart(part).ifPresent(target::add); - } + public static ImmutableList> contentToParts( + Optional content, boolean isPartial) { + return content.flatMap(Content::parts).stream() + .flatMap(Collection::stream) + .map(part -> PartConverter.fromGenaiPart(part, isPartial)) + .collect(toImmutableList()); } } diff --git a/a2a/src/main/java/com/google/adk/a2a/converters/PartConverter.java b/a2a/src/main/java/com/google/adk/a2a/converters/PartConverter.java index 8e407406f..05125d170 100644 --- a/a2a/src/main/java/com/google/adk/a2a/converters/PartConverter.java +++ b/a2a/src/main/java/com/google/adk/a2a/converters/PartConverter.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.adk.a2a.common.GenAiFieldMissingException; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.genai.types.Blob; @@ -38,18 +39,14 @@ * use in production code. */ public final class PartConverter { + private static final Logger logger = LoggerFactory.getLogger(PartConverter.class); private static final ObjectMapper objectMapper = new ObjectMapper(); - // Constants for metadata types. By convention metadata keys are prefixed with "adk_" to align // with the Python and Golang libraries. public static final String A2A_DATA_PART_METADATA_TYPE_KEY = "adk_type"; public static final String A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY = "adk_is_long_running"; - public static final String A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL = "function_call"; - public static final String A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE = "function_response"; - public static final String A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT = - "code_execution_result"; - public static final String A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE = "executable_code"; + public static final String A2A_DATA_PART_METADATA_IS_PARTIAL_KEY = "adk_partial"; public static final String LANGUAGE_KEY = "language"; public static final String OUTCOME_KEY = "outcome"; public static final String CODE_KEY = "code"; @@ -58,6 +55,10 @@ public final class PartConverter { public static final String ARGS_KEY = "args"; public static final String RESPONSE_KEY = "response"; public static final String ID_KEY = "id"; + public static final String WILL_CONTINUE_KEY = "willContinue"; + public static final String PARTIAL_ARGS_KEY = "partialArgs"; + public static final String SCHEDULING_KEY = "scheduling"; + public static final String PARTS_KEY = "parts"; public static Optional toTextPart(io.a2a.spec.Part part) { if (part instanceof TextPart textPart) { @@ -96,34 +97,6 @@ public static ImmutableList toGenaiParts( .collect(toImmutableList()); } - /** - * Convert a Google GenAI Part to an A2A Part. - * - * @param part The GenAI part to convert. - * @return Optional containing the converted A2A Part, or empty if conversion fails. - */ - public static Optional convertGenaiPartToA2aPart(Part part) { - if (part == null) { - return Optional.empty(); - } - - if (part.text().isPresent()) { - // Text parts are handled directly in the Message content, not as DataPart - return Optional.empty(); - } else if (part.functionCall().isPresent()) { - return createDataPartFromFunctionCall(part.functionCall().get()); - } else if (part.functionResponse().isPresent()) { - return createDataPartFromFunctionResponse(part.functionResponse().get()); - } else if (part.executableCode().isPresent()) { - return createDataPartFromExecutableCode(part.executableCode().get()); - } else if (part.codeExecutionResult().isPresent()) { - return createDataPartFromCodeExecutionResult(part.codeExecutionResult().get()); - } - - logger.warn("Cannot convert unsupported part for Google GenAI part: " + part); - return Optional.empty(); - } - private static Optional convertFilePartToGenAiPart( FilePart filePart) { FileContent fileContent = filePart.getFile(); @@ -170,7 +143,7 @@ private static Optional convertDataPartToGenAiPart( String metadataType = metadata.getOrDefault(A2A_DATA_PART_METADATA_TYPE_KEY, "").toString(); if ((data.containsKey(NAME_KEY) && data.containsKey(ARGS_KEY)) - || metadataType.equals(A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL)) { + || metadataType.equals(A2ADataPartMetadataType.FUNCTION_CALL.getType())) { String functionName = String.valueOf(data.getOrDefault(NAME_KEY, null)); String functionId = String.valueOf(data.getOrDefault(ID_KEY, null)); Map args = coerceToMap(data.get(ARGS_KEY)); @@ -182,7 +155,7 @@ private static Optional convertDataPartToGenAiPart( } if ((data.containsKey(NAME_KEY) && data.containsKey(RESPONSE_KEY)) - || metadataType.equals(A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE)) { + || metadataType.equals(A2ADataPartMetadataType.FUNCTION_RESPONSE.getType())) { String functionName = String.valueOf(data.getOrDefault(NAME_KEY, "")); String functionId = String.valueOf(data.getOrDefault(ID_KEY, "")); Map response = coerceToMap(data.get(RESPONSE_KEY)); @@ -198,7 +171,7 @@ private static Optional convertDataPartToGenAiPart( } if ((data.containsKey(CODE_KEY) && data.containsKey(LANGUAGE_KEY)) - || metadataType.equals(A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE)) { + || metadataType.equals(A2ADataPartMetadataType.EXECUTABLE_CODE.getType())) { String code = String.valueOf(data.getOrDefault(CODE_KEY, "")); String language = String.valueOf( @@ -212,7 +185,7 @@ private static Optional convertDataPartToGenAiPart( } if ((data.containsKey(OUTCOME_KEY) && data.containsKey(OUTPUT_KEY)) - || metadataType.equals(A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT)) { + || metadataType.equals(A2ADataPartMetadataType.CODE_EXECUTION_RESULT.getType())) { String outcome = String.valueOf(data.getOrDefault(OUTCOME_KEY, Outcome.Known.OUTCOME_OK).toString()); String output = String.valueOf(data.getOrDefault(OUTPUT_KEY, "")); @@ -251,112 +224,154 @@ public static Content messageToContent(Message message) { * * @return Optional containing the converted A2A Part, or empty if conversion fails. */ - private static Optional createDataPartFromFunctionCall(FunctionCall functionCall) { - Map data = new HashMap<>(); - data.put(NAME_KEY, functionCall.name().orElse("")); - data.put(ID_KEY, functionCall.id().orElse("")); - data.put(ARGS_KEY, functionCall.args().orElse(ImmutableMap.of())); - - ImmutableMap metadata = - ImmutableMap.of(A2A_DATA_PART_METADATA_TYPE_KEY, A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL); + private static DataPart createDataPartFromFunctionCall( + FunctionCall functionCall, ImmutableMap.Builder metadata) { + ImmutableMap.Builder data = ImmutableMap.builder(); + addValueIfPresent(data, NAME_KEY, functionCall.name()); + addValueIfPresent(data, ID_KEY, functionCall.id()); + addValueIfPresent(data, ARGS_KEY, functionCall.args()); + addValueIfPresent(data, WILL_CONTINUE_KEY, functionCall.willContinue()); + addValueIfPresent(data, PARTIAL_ARGS_KEY, functionCall.partialArgs()); + + metadata.put(A2A_DATA_PART_METADATA_TYPE_KEY, A2ADataPartMetadataType.FUNCTION_CALL.getType()); + + return new DataPart(data.buildOrThrow(), metadata.buildOrThrow()); + } - return Optional.of(new DataPart(data, metadata)); + private static void addValueIfPresent( + ImmutableMap.Builder data, String key, Optional value) { + value.ifPresent(v -> data.put(key, v)); } /** * Creates an A2A DataPart from a Google GenAI FunctionResponse. * * @param functionResponse The GenAI FunctionResponse to convert. - * @return Optional containing the converted A2A Part, or empty if conversion fails. + * @return The converted A2A Part. */ - private static Optional createDataPartFromFunctionResponse( - FunctionResponse functionResponse) { - Map data = new HashMap<>(); - data.put(NAME_KEY, functionResponse.name().orElse("")); - data.put(ID_KEY, functionResponse.id().orElse("")); - data.put(RESPONSE_KEY, functionResponse.response().orElse(ImmutableMap.of())); - - ImmutableMap metadata = - ImmutableMap.of( - A2A_DATA_PART_METADATA_TYPE_KEY, A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE); - - return Optional.of(new DataPart(data, metadata)); + private static DataPart createDataPartFromFunctionResponse( + FunctionResponse functionResponse, ImmutableMap.Builder metadata) { + ImmutableMap.Builder data = ImmutableMap.builder(); + addValueIfPresent(data, NAME_KEY, functionResponse.name()); + addValueIfPresent(data, ID_KEY, functionResponse.id()); + addValueIfPresent(data, RESPONSE_KEY, functionResponse.response()); + addValueIfPresent(data, WILL_CONTINUE_KEY, functionResponse.willContinue()); + addValueIfPresent(data, SCHEDULING_KEY, functionResponse.scheduling()); + addValueIfPresent(data, PARTS_KEY, functionResponse.parts()); + + metadata.put( + A2A_DATA_PART_METADATA_TYPE_KEY, A2ADataPartMetadataType.FUNCTION_RESPONSE.getType()); + + return new DataPart(data.buildOrThrow(), metadata.buildOrThrow()); } - private static Optional createDataPartFromExecutableCode( - ExecutableCode executableCode) { - Map data = new HashMap<>(); - data.put(CODE_KEY, executableCode.code().orElse("")); + /** + * Creates an A2A DataPart from a Google GenAI CodeExecutionResult. + * + * @param codeExecutionResult The GenAI CodeExecutionResult to convert. + * @return The converted A2A Part. + */ + private static DataPart createDataPartFromCodeExecutionResult( + CodeExecutionResult codeExecutionResult, ImmutableMap.Builder metadata) { + ImmutableMap.Builder data = ImmutableMap.builder(); data.put( - LANGUAGE_KEY, - executableCode - .language() - .map(Language::toString) - .orElse(Language.Known.LANGUAGE_UNSPECIFIED.toString())); + OUTCOME_KEY, + codeExecutionResult + .outcome() + .map(Outcome::toString) + .orElse(new Outcome(Outcome.Known.OUTCOME_UNSPECIFIED).toString())); + addValueIfPresent(data, OUTPUT_KEY, codeExecutionResult.output()); - ImmutableMap metadata = - ImmutableMap.of( - A2A_DATA_PART_METADATA_TYPE_KEY, A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE); + metadata.put( + A2A_DATA_PART_METADATA_TYPE_KEY, A2ADataPartMetadataType.CODE_EXECUTION_RESULT.getType()); - return Optional.of(new DataPart(data, metadata)); + return new DataPart(data.buildOrThrow(), metadata.buildOrThrow()); } - private static Optional createDataPartFromCodeExecutionResult( - CodeExecutionResult result) { - Map data = new HashMap<>(); + /** + * Creates an A2A DataPart from a Google GenAI ExecutableCode. + * + * @param executableCode The GenAI ExecutableCode to convert. + * @return The converted A2A Part. + */ + private static DataPart createDataPartFromExecutableCode( + ExecutableCode executableCode, ImmutableMap.Builder metadata) { + ImmutableMap.Builder data = ImmutableMap.builder(); data.put( - OUTCOME_KEY, - result - .outcome() - .map(Outcome::toString) - .orElse(new Outcome(Outcome.Known.OUTCOME_UNSPECIFIED).toString())); - data.put(OUTPUT_KEY, result.output().orElse(null)); + LANGUAGE_KEY, + executableCode + .language() + .map(Language::toString) + .orElse(Language.Known.LANGUAGE_UNSPECIFIED.toString())); + addValueIfPresent(data, CODE_KEY, executableCode.code()); - ImmutableMap metadata = - ImmutableMap.of( - A2A_DATA_PART_METADATA_TYPE_KEY, A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT); + metadata.put( + A2A_DATA_PART_METADATA_TYPE_KEY, A2ADataPartMetadataType.EXECUTABLE_CODE.getType()); - return Optional.of(new DataPart(data, metadata)); + return new DataPart(data.buildOrThrow(), metadata.buildOrThrow()); } private PartConverter() {} /** Convert a GenAI part into the A2A JSON representation. */ - public static Optional> fromGenaiPart(Part part) { + public static io.a2a.spec.Part fromGenaiPart(Part part, boolean isPartial) { if (part == null) { - return Optional.empty(); + throw new GenAiFieldMissingException("GenAI part cannot be null"); } - - if (part.text().isPresent()) { - return Optional.of(new TextPart(part.text().get(), new HashMap<>())); + ImmutableMap.Builder metadata = ImmutableMap.builder(); + if (isPartial) { + metadata.put(A2A_DATA_PART_METADATA_IS_PARTIAL_KEY, true); } - if (part.fileData().isPresent()) { - FileData fileData = part.fileData().get(); - String uri = fileData.fileUri().orElse(null); - String mime = fileData.mimeType().orElse(null); - String name = fileData.displayName().orElse(null); - return Optional.of(new FilePart(new FileWithUri(mime, name, uri), new HashMap<>())); + if (part.text().isPresent()) { + addValueIfPresent(metadata, "thought", part.thought()); + return new TextPart(part.text().get(), metadata.buildOrThrow()); } - if (part.inlineData().isPresent()) { - Blob blob = part.inlineData().get(); - byte[] bytes = blob.data().orElse(null); - String encoded = bytes != null ? Base64.getEncoder().encodeToString(bytes) : null; - String mime = blob.mimeType().orElse(null); - String name = blob.displayName().orElse(null); - return Optional.of(new FilePart(new FileWithBytes(mime, name, encoded), new HashMap<>())); + if (part.fileData().isPresent() || part.inlineData().isPresent()) { + return filePartToA2A(part, metadata); } if (part.functionCall().isPresent() || part.functionResponse().isPresent() || part.executableCode().isPresent() || part.codeExecutionResult().isPresent()) { - return convertGenaiPartToA2aPart(part).map(data -> data); + return dataPartToA2A(part, metadata); } - logger.warn("Unsupported GenAI part type for JSON export: {}", part); - return Optional.empty(); + throw new IllegalArgumentException("Unsupported GenAI part type: " + part); + } + + private static DataPart dataPartToA2A(Part part, ImmutableMap.Builder metadata) { + + if (part.functionCall().isPresent()) { + return createDataPartFromFunctionCall(part.functionCall().get(), metadata); + } else if (part.functionResponse().isPresent()) { + return createDataPartFromFunctionResponse(part.functionResponse().get(), metadata); + } else if (part.codeExecutionResult().isPresent()) { + return createDataPartFromCodeExecutionResult(part.codeExecutionResult().get(), metadata); + } else if (part.executableCode().isPresent()) { + return createDataPartFromExecutableCode(part.executableCode().get(), metadata); + } + + throw new IllegalArgumentException("Unsupported GenAI data part type: " + part); + } + + private static FilePart filePartToA2A(Part part, ImmutableMap.Builder metadata) { + if (part.fileData().isPresent()) { + FileData fileData = part.fileData().get(); + String uri = fileData.fileUri().orElse(null); + String mime = fileData.mimeType().orElse(null); + String name = fileData.displayName().orElse(null); + return new FilePart(new FileWithUri(mime, name, uri), metadata.buildOrThrow()); + } + Blob blob = part.inlineData().get(); + byte[] bytes = blob.data().orElse(null); + String encoded = bytes != null ? Base64.getEncoder().encodeToString(bytes) : null; + addValueIfPresent(metadata, "video_metadata", part.videoMetadata()); + return new FilePart( + new FileWithBytes(blob.mimeType().orElse(null), blob.displayName().orElse(null), encoded), + metadata.buildOrThrow()); } @SuppressWarnings("unchecked") // safe conversion from objectMapper.readValue diff --git a/a2a/src/main/java/com/google/adk/a2a/converters/RequestConverter.java b/a2a/src/main/java/com/google/adk/a2a/converters/RequestConverter.java deleted file mode 100644 index 57f7aeffd..000000000 --- a/a2a/src/main/java/com/google/adk/a2a/converters/RequestConverter.java +++ /dev/null @@ -1,198 +0,0 @@ -package com.google.adk.a2a.converters; - -import com.google.adk.events.Event; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.genai.types.Content; -import io.a2a.spec.DataPart; -import io.a2a.spec.Message; -import io.a2a.spec.Part; -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * rfe Converter for A2A Messages to ADK Events. This is used on the A2A service side to convert - * incoming A2A requests to ADK Events. - * - *

**EXPERIMENTAL:** Subject to change, rename, or removal in any future patch release. Do not - * use in production code. - */ -public final class RequestConverter { - private static final Logger logger = LoggerFactory.getLogger(RequestConverter.class); - - private RequestConverter() {} - - /** - * Convert an A2A Message to an ADK Event. This is used when the A2A service receives a request - * and needs to process it with ADK. - * - * @param message The A2A message to convert. - * @param invocationId The invocation ID for the event. - * @return Optional containing the converted ADK Event, or empty if conversion fails. - */ - public static Optional convertA2aMessageToAdkEvent(Message message, String invocationId) { - if (message == null) { - // Create an empty user message event - logger.info("Null message received, creating empty user event"); - Event event = - Event.builder() - .id(UUID.randomUUID().toString()) - .invocationId(invocationId != null ? invocationId : UUID.randomUUID().toString()) - .author("user") - .content( - Content.builder() - .role("user") - .parts( - ImmutableList.of(com.google.genai.types.Part.builder().text("").build())) - .build()) - .timestamp(Instant.now().toEpochMilli()) - .build(); - return Optional.of(event); - } - - List genaiParts = new ArrayList<>(); - - // Convert each A2A Part to GenAI Part - if (message.getParts() != null) { - for (Part a2aPart : message.getParts()) { - Optional genaiPart = PartConverter.toGenaiPart(a2aPart); - genaiPart.ifPresent(genaiParts::add); - } - } - - if (genaiParts.isEmpty()) { - logger.warn("No convertible parts found in A2A message"); - return Optional.empty(); - } - - // Treat inbound A2A requests as user input for the ADK agent. - String author = "user"; - - // Build the Content object - Content content = Content.builder().role("user").parts(genaiParts).build(); - - // Build the Event - Event event = - Event.builder() - .id( - !message.getMessageId().isEmpty() - ? message.getMessageId() - : UUID.randomUUID().toString()) - .invocationId(invocationId != null ? invocationId : UUID.randomUUID().toString()) - .author(author) - .content(content) - .timestamp(Instant.now().toEpochMilli()) - .build(); - - return Optional.of(event); - } - - /** - * Convert an aggregated A2A Message to multiple ADK Events. This reconstructs the original event - * sequence from an aggregated message. - * - * @param message The aggregated A2A message to convert. - * @param invocationId The invocation ID for the events. - * @return List of ADK Events representing the conversation history. - */ - public static ImmutableList convertAggregatedA2aMessageToAdkEvents( - Message message, String invocationId) { - if (message == null || message.getParts() == null || message.getParts().isEmpty()) { - logger.info("Null or empty message received, creating empty user event"); - Event event = - Event.builder() - .id(UUID.randomUUID().toString()) - .invocationId(invocationId != null ? invocationId : UUID.randomUUID().toString()) - .author("user") - .content( - Content.builder() - .role("user") - .parts( - ImmutableList.of(com.google.genai.types.Part.builder().text("").build())) - .build()) - .timestamp(Instant.now().toEpochMilli()) - .build(); - return ImmutableList.of(event); - } - - List events = new ArrayList<>(); - - // Emit exactly one ADK Event per A2A Part, preserving order. - for (Part a2aPart : message.getParts()) { - Optional genaiPart = PartConverter.toGenaiPart(a2aPart); - if (genaiPart.isEmpty()) { - continue; - } - - String author = extractAuthorFromMetadata(a2aPart); - String role = determineRoleFromAuthor(author); - - events.add(createEvent(ImmutableList.of(genaiPart.get()), author, role, invocationId)); - } - - if (events.isEmpty()) { - logger.warn("No events created from aggregated message; returning single empty user event"); - Event event = - Event.builder() - .id(UUID.randomUUID().toString()) - .invocationId(invocationId) - .author("user") - .content( - Content.builder() - .role("user") - .parts( - ImmutableList.of(com.google.genai.types.Part.builder().text("").build())) - .build()) - .timestamp(Instant.now().toEpochMilli()) - .build(); - events.add(event); - } - - logger.info("Converted aggregated A2A message to {} ADK events", events.size()); - return ImmutableList.copyOf(events); - } - - private static String extractAuthorFromMetadata(Part a2aPart) { - if (a2aPart instanceof DataPart dataPart) { - Map metadata = - Optional.ofNullable(dataPart.getMetadata()).orElse(ImmutableMap.of()); - String type = - metadata.getOrDefault(PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY, "").toString(); - if (type.equals(PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL)) { - return "model"; - } - if (type.equals(PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE)) { - return "user"; - } - Map data = Optional.ofNullable(dataPart.getData()).orElse(ImmutableMap.of()); - if (data.containsKey("args")) { - return "model"; - } - if (data.containsKey("response")) { - return "user"; - } - } - return "user"; - } - - private static String determineRoleFromAuthor(String author) { - return author.equals("model") ? "model" : "user"; - } - - private static Event createEvent( - List parts, String author, String role, String invocationId) { - return Event.builder() - .id(UUID.randomUUID().toString()) - .invocationId(invocationId) - .author(author) - .content(Content.builder().role(role).parts(new ArrayList<>(parts)).build()) - .timestamp(Instant.now().toEpochMilli()) - .build(); - } -} diff --git a/a2a/src/main/java/com/google/adk/a2a/converters/ResponseConverter.java b/a2a/src/main/java/com/google/adk/a2a/converters/ResponseConverter.java index 2e32b4c8c..ccbb1b9cf 100644 --- a/a2a/src/main/java/com/google/adk/a2a/converters/ResponseConverter.java +++ b/a2a/src/main/java/com/google/adk/a2a/converters/ResponseConverter.java @@ -2,34 +2,28 @@ import static com.google.common.collect.ImmutableList.toImmutableList; -import com.google.adk.a2a.common.A2AClientError; import com.google.adk.agents.InvocationContext; import com.google.adk.events.Event; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.genai.types.Content; +import com.google.genai.types.Part; import io.a2a.client.ClientEvent; import io.a2a.client.MessageEvent; import io.a2a.client.TaskEvent; import io.a2a.client.TaskUpdateEvent; import io.a2a.spec.Artifact; -import io.a2a.spec.EventKind; -import io.a2a.spec.JSONRPCError; import io.a2a.spec.Message; -import io.a2a.spec.SendMessageResponse; import io.a2a.spec.Task; import io.a2a.spec.TaskArtifactUpdateEvent; import io.a2a.spec.TaskState; import io.a2a.spec.TaskStatusUpdateEvent; -import io.a2a.spec.TextPart; import java.time.Instant; -import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.UUID; -import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,119 +40,6 @@ public final class ResponseConverter { private ResponseConverter() {} - /** - * Converts a {@link SendMessageResponse} containing a {@link Message} result into ADK events. - * - *

Non-message results are ignored in the message-only integration and logged for awareness. - */ - public static List sendMessageResponseToEvents( - SendMessageResponse response, String invocationId, String branch) { - if (response == null) { - logger.warn("SendMessageResponse was null; returning no events."); - return ImmutableList.of(); - } - - EventKind result = response.getResult(); - if (result == null) { - - JSONRPCError error = response.getError(); - if (error != null) { - throw new A2AClientError( - String.format("SendMessageResponse error for invocation %s", invocationId), error); - } - - throw new A2AClientError( - String.format("SendMessageResponse result was null for invocation %s", invocationId)); - } - - if (result instanceof Message message) { - return messageToEvents(message, invocationId, branch); - } - - throw new IllegalArgumentException( - String.format( - "SendMessageResponse result was neither a Message nor an error for invocation %s", - invocationId)); - } - - /** Converts an A2A message back to ADK events. */ - public static List messageToEvents(Message message, String invocationId, String branch) { - List events = new ArrayList<>(); - - for (io.a2a.spec.Part part : message.getParts()) { - PartConverter.toGenaiPart(part) - .ifPresent( - genaiPart -> - events.add( - Event.builder() - .id(UUID.randomUUID().toString()) - .invocationId(invocationId) - .author(message.getRole() == Message.Role.AGENT ? "agent" : "user") - .branch(branch) - .content( - Content.builder() - .role(message.getRole() == Message.Role.AGENT ? "model" : "user") - .parts(ImmutableList.of(genaiPart)) - .build()) - .timestamp(Instant.now().toEpochMilli()) - .build())); - } - return events; - } - - private static Message emptyAgentMessage(String contextId) { - Message.Builder builder = - new Message.Builder() - .messageId(UUID.randomUUID().toString()) - .role(Message.Role.AGENT) - .parts(ImmutableList.of(new TextPart(""))); - if (contextId != null) { - builder.contextId(contextId); - } - return builder.build(); - } - - /** Converts a list of ADK events into a single aggregated A2A message. */ - public static Message eventsToMessage(List events, String contextId, String taskId) { - if (events == null || events.isEmpty()) { - return emptyAgentMessage(contextId); - } - - if (events.size() == 1) { - return eventToMessage(events.get(0), contextId); - } - - List> parts = new ArrayList<>(); - for (Event event : events) { - parts.addAll(eventParts(event)); - } - - Message.Builder builder = - new Message.Builder() - .messageId(taskId != null ? taskId : UUID.randomUUID().toString()) - .role(Message.Role.AGENT) - .parts(parts); - if (contextId != null) { - builder.contextId(contextId); - } - return builder.build(); - } - - /** Converts a single ADK event into an A2A message. */ - public static Message eventToMessage(Event event, String contextId) { - List> parts = eventParts(event); - - Message.Builder builder = - new Message.Builder() - .messageId(event.id() != null ? event.id() : UUID.randomUUID().toString()) - .role(event.author().equalsIgnoreCase("user") ? Message.Role.USER : Message.Role.AGENT) - .parts(parts); - if (contextId != null) { - builder.contextId(contextId); - } - return builder.build(); - } - /** * Converts a A2A {@link ClientEvent} to an ADK {@link Event}, based on the event type. Returns an * empty optional if the event should be ignored (e.g. if the event is not a final update for @@ -175,6 +56,7 @@ public static Optional clientEventToEvent( } else if (event instanceof TaskUpdateEvent updateEvent) { return handleTaskUpdate(updateEvent, invocationContext); } + logger.warn("Unsupported ClientEvent type: {}", event.getClass()); throw new IllegalArgumentException("Unsupported ClientEvent type: " + event.getClass()); } @@ -262,7 +144,7 @@ public static Event messageToFailedEvent(Message message, InvocationContext invo public static Event messageToEvent( Message message, InvocationContext invocationContext, boolean isPending) { - ImmutableList genaiParts = + ImmutableList genaiParts = PartConverter.toGenaiParts(message.getParts()).stream() .map(part -> part.toBuilder().thought(isPending).build()) .collect(toImmutableList()); @@ -297,19 +179,6 @@ public static Event taskToEvent(Task task, InvocationContext invocationContext) return emptyEvent(invocationContext); } - private static List> eventParts(Event event) { - List> parts = new ArrayList<>(); - Optional content = event.content(); - if (content.isEmpty() || content.get().parts().isEmpty()) { - return parts; - } - - for (com.google.genai.types.Part genaiPart : content.get().parts().get()) { - PartConverter.fromGenaiPart(genaiPart).ifPresent(parts::add); - } - return parts; - } - private static Event emptyEvent(InvocationContext invocationContext) { Event.Builder builder = Event.builder() @@ -322,7 +191,7 @@ private static Event emptyEvent(InvocationContext invocationContext) { return builder.build(); } - private static Content fromModelParts(List parts) { + private static Content fromModelParts(List parts) { return Content.builder().role("model").parts(parts).build(); } @@ -334,15 +203,4 @@ private static Event.Builder remoteAgentEventBuilder(InvocationContext invocatio .branch(invocationContext.branch().orElse(null)) .timestamp(Instant.now().toEpochMilli()); } - - /** Simple REST-friendly wrapper to carry either a message result or a task result. */ - public record MessageSendResult(@Nullable Message message, @Nullable Task task) { - public static MessageSendResult fromMessage(Message message) { - return new MessageSendResult(message, null); - } - - public static MessageSendResult fromTask(Task task) { - return new MessageSendResult(null, task); - } - } } diff --git a/a2a/src/main/java/com/google/adk/a2a/executor/AgentExecutor.java b/a2a/src/main/java/com/google/adk/a2a/executor/AgentExecutor.java index 3d66a4e07..b7b4e9953 100644 --- a/a2a/src/main/java/com/google/adk/a2a/executor/AgentExecutor.java +++ b/a2a/src/main/java/com/google/adk/a2a/executor/AgentExecutor.java @@ -308,16 +308,8 @@ private Maybe process( "Agent returned an error: " + event.errorCode().get(), null)); } - ImmutableList> parts = EventConverter.contentToParts(event.content()); - // Mark all parts as partial if the event is partial. - if (event.partial().orElse(false)) { - parts.forEach( - part -> { - Map metadata = part.getMetadata(); - metadata.put("adk_partial", true); - }); - } - + ImmutableList> parts = + EventConverter.contentToParts(event.content(), event.partial().orElse(false)); Map metadata = new HashMap<>(); if (event.customMetadata().isPresent()) { for (CustomMetadata cm : event.customMetadata().get()) { diff --git a/a2a/src/test/java/com/google/adk/a2a/converters/EventConverterTest.java b/a2a/src/test/java/com/google/adk/a2a/converters/EventConverterTest.java index f9d34bf3f..8d460c457 100644 --- a/a2a/src/test/java/com/google/adk/a2a/converters/EventConverterTest.java +++ b/a2a/src/test/java/com/google/adk/a2a/converters/EventConverterTest.java @@ -92,6 +92,8 @@ public void convertEventsToA2AMessage_preservesFunctionCallAndResponseParts() { .invocationId("invocation-1") .agent(new TestAgent()) .session(session) + .userContent( + Content.builder().role("user").parts(ImmutableList.of(userTextPart)).build()) .endInvocation(false) .build(); @@ -101,24 +103,28 @@ public void convertEventsToA2AMessage_preservesFunctionCallAndResponseParts() { // Assert assertThat(maybeMessage).isPresent(); Message message = maybeMessage.get(); - assertThat(message.getParts()).hasSize(3); + assertThat(message.getParts()).hasSize(4); assertThat(message.getParts().get(0)).isInstanceOf(TextPart.class); assertThat(message.getParts().get(1)).isInstanceOf(DataPart.class); assertThat(message.getParts().get(2)).isInstanceOf(DataPart.class); + assertThat(message.getParts().get(3)).isInstanceOf(TextPart.class); DataPart callDataPart = (DataPart) message.getParts().get(1); assertThat(callDataPart.getMetadata().get(PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY)) - .isEqualTo(PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL); + .isEqualTo(A2ADataPartMetadataType.FUNCTION_CALL.getType()); assertThat(callDataPart.getData()).containsEntry("name", "roll_die"); assertThat(callDataPart.getData()).containsEntry("id", "adk-call-1"); assertThat(callDataPart.getData()).containsEntry("args", ImmutableMap.of("sides", 6)); DataPart responseDataPart = (DataPart) message.getParts().get(2); assertThat(responseDataPart.getMetadata().get(PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY)) - .isEqualTo(PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE); + .isEqualTo(A2ADataPartMetadataType.FUNCTION_RESPONSE.getType()); assertThat(responseDataPart.getData()).containsEntry("name", "roll_die"); assertThat(responseDataPart.getData()).containsEntry("id", "adk-call-1"); assertThat(responseDataPart.getData()).containsEntry("response", ImmutableMap.of("result", 3)); + + TextPart lastTextPart = (TextPart) message.getParts().get(3); + assertThat(lastTextPart.getText()).isEqualTo("Roll a die"); } private static final class TestAgent extends BaseAgent { diff --git a/a2a/src/test/java/com/google/adk/a2a/converters/PartConverterTest.java b/a2a/src/test/java/com/google/adk/a2a/converters/PartConverterTest.java index 6ccfd9566..8e8982ffa 100644 --- a/a2a/src/test/java/com/google/adk/a2a/converters/PartConverterTest.java +++ b/a2a/src/test/java/com/google/adk/a2a/converters/PartConverterTest.java @@ -2,7 +2,9 @@ import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertThrows; +import com.google.adk.a2a.common.GenAiFieldMissingException; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.genai.types.Blob; @@ -89,7 +91,7 @@ public void toGenaiPart_withDataPartFunctionCall_returnsGenaiFunctionCallPart() data, ImmutableMap.of( PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY, - PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL)); + A2ADataPartMetadataType.FUNCTION_CALL.getType())); Optional result = PartConverter.toGenaiPart(dataPart); @@ -126,7 +128,7 @@ public void toGenaiPart_withDataPartFunctionResponse_returnsGenaiFunctionRespons data, ImmutableMap.of( PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY, - PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE)); + A2ADataPartMetadataType.FUNCTION_RESPONSE.getType())); Optional result = PartConverter.toGenaiPart(dataPart); @@ -181,78 +183,21 @@ public void toGenaiParts_convertsAllSupportedParts() { } @Test - public void convertGenaiPartToA2aPart_withNullPart_returnsEmpty() { - assertThat(PartConverter.convertGenaiPartToA2aPart(null)).isEmpty(); - } - - @Test - public void convertGenaiPartToA2aPart_withTextPart_returnsEmpty() { - Part part = Part.builder().text("text").build(); - assertThat(PartConverter.convertGenaiPartToA2aPart(part)).isEmpty(); - } - - @Test - public void convertGenaiPartToA2aPart_withFunctionCallPart_returnsDataPart() { - Part part = - Part.builder() - .functionCall( - FunctionCall.builder() - .name("func") - .id("1") - .args(ImmutableMap.of("param", "value")) - .build()) - .build(); - - Optional result = PartConverter.convertGenaiPartToA2aPart(part); - - assertThat(result).isPresent(); - DataPart dataPart = result.get(); - assertThat(dataPart.getData()) - .containsExactly("name", "func", "id", "1", "args", ImmutableMap.of("param", "value")); - assertThat(dataPart.getMetadata()) - .containsEntry( - PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY, - PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL); - } - - @Test - public void convertGenaiPartToA2aPart_withFunctionResponsePart_returnsDataPart() { - Part part = - Part.builder() - .functionResponse( - FunctionResponse.builder() - .name("func") - .id("1") - .response(ImmutableMap.of("result", "value")) - .build()) - .build(); - - Optional result = PartConverter.convertGenaiPartToA2aPart(part); - - assertThat(result).isPresent(); - DataPart dataPart = result.get(); - assertThat(dataPart.getData()) - .containsExactly("name", "func", "id", "1", "response", ImmutableMap.of("result", "value")); - assertThat(dataPart.getMetadata()) - .containsEntry( - PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY, - PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE); - } - - @Test - public void fromGenaiPart_withNullPart_returnsEmpty() { - assertThat(PartConverter.fromGenaiPart(null)).isEmpty(); + public void fromGenaiPart_withNullPart_throwsException() { + assertThrows(GenAiFieldMissingException.class, () -> PartConverter.fromGenaiPart(null, false)); } @Test public void fromGenaiPart_withTextPart_returnsTextPart() { - Part part = Part.builder().text("text").build(); + Part part = Part.builder().text("text").thought(true).build(); - Optional> result = PartConverter.fromGenaiPart(part); + io.a2a.spec.Part result = PartConverter.fromGenaiPart(part, true); - assertThat(result).isPresent(); - assertThat(result.get()).isInstanceOf(TextPart.class); - assertThat(((TextPart) result.get()).getText()).isEqualTo("text"); + assertThat(result).isInstanceOf(TextPart.class); + assertThat(((TextPart) result).getText()).isEqualTo("text"); + assertThat(((TextPart) result).getMetadata()).containsEntry("thought", true); + assertThat(((TextPart) result).getMetadata()) + .containsEntry(PartConverter.A2A_DATA_PART_METADATA_IS_PARTIAL_KEY, true); } @Test @@ -262,11 +207,10 @@ public void fromGenaiPart_withFileDataPart_returnsFilePartWithUri() { .fileData(FileData.builder().mimeType("text/plain").fileUri("http://file.txt").build()) .build(); - Optional> result = PartConverter.fromGenaiPart(part); + io.a2a.spec.Part result = PartConverter.fromGenaiPart(part, false); - assertThat(result).isPresent(); - assertThat(result.get()).isInstanceOf(FilePart.class); - FilePart filePart = (FilePart) result.get(); + assertThat(result).isInstanceOf(FilePart.class); + FilePart filePart = (FilePart) result; assertThat(filePart.getFile()).isInstanceOf(FileWithUri.class); FileWithUri fileWithUri = (FileWithUri) filePart.getFile(); assertThat(fileWithUri.mimeType()).isEqualTo("text/plain"); @@ -281,11 +225,10 @@ public void fromGenaiPart_withInlineDataPart_returnsFilePartWithBytes() { .inlineData(Blob.builder().mimeType("text/plain").data(bytes).build()) .build(); - Optional> result = PartConverter.fromGenaiPart(part); + io.a2a.spec.Part result = PartConverter.fromGenaiPart(part, false); - assertThat(result).isPresent(); - assertThat(result.get()).isInstanceOf(FilePart.class); - FilePart filePart = (FilePart) result.get(); + assertThat(result).isInstanceOf(FilePart.class); + FilePart filePart = (FilePart) result; assertThat(filePart.getFile()).isInstanceOf(FileWithBytes.class); FileWithBytes fileWithBytes = (FileWithBytes) filePart.getFile(); assertThat(fileWithBytes.mimeType()).isEqualTo("text/plain"); @@ -297,20 +240,32 @@ public void fromGenaiPart_withFunctionCallPart_returnsDataPart() { Part part = Part.builder() .functionCall( - FunctionCall.builder().name("func").id("1").args(ImmutableMap.of()).build()) + FunctionCall.builder() + .name("func") + .id("1") + .willContinue(true) + .args(ImmutableMap.of()) + .build()) .build(); - Optional> result = PartConverter.fromGenaiPart(part); + io.a2a.spec.Part result = PartConverter.fromGenaiPart(part, false); - assertThat(result).isPresent(); - assertThat(result.get()).isInstanceOf(DataPart.class); - DataPart dataPart = (DataPart) result.get(); + assertThat(result).isInstanceOf(DataPart.class); + DataPart dataPart = (DataPart) result; assertThat(dataPart.getData()) - .containsExactly("name", "func", "id", "1", "args", ImmutableMap.of()); + .containsExactly( + "name", + "func", + "id", + "1", + "args", + ImmutableMap.of(), + PartConverter.WILL_CONTINUE_KEY, + true); assertThat(dataPart.getMetadata()) .containsEntry( PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY, - PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL); + A2ADataPartMetadataType.FUNCTION_CALL.getType()); } @Test @@ -321,17 +276,16 @@ public void fromGenaiPart_withFunctionResponsePart_returnsDataPart() { FunctionResponse.builder().name("func").id("1").response(ImmutableMap.of()).build()) .build(); - Optional> result = PartConverter.fromGenaiPart(part); + io.a2a.spec.Part result = PartConverter.fromGenaiPart(part, false); - assertThat(result).isPresent(); - assertThat(result.get()).isInstanceOf(DataPart.class); - DataPart dataPart = (DataPart) result.get(); + assertThat(result).isInstanceOf(DataPart.class); + DataPart dataPart = (DataPart) result; assertThat(dataPart.getData()) .containsExactly("name", "func", "id", "1", "response", ImmutableMap.of()); assertThat(dataPart.getMetadata()) .containsEntry( PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY, - PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE); + A2ADataPartMetadataType.FUNCTION_RESPONSE.getType()); } @Test diff --git a/a2a/src/test/java/com/google/adk/a2a/converters/ResponseConverterTest.java b/a2a/src/test/java/com/google/adk/a2a/converters/ResponseConverterTest.java index 8dc70ca2a..5378bdd7b 100644 --- a/a2a/src/test/java/com/google/adk/a2a/converters/ResponseConverterTest.java +++ b/a2a/src/test/java/com/google/adk/a2a/converters/ResponseConverterTest.java @@ -12,7 +12,6 @@ import com.google.adk.sessions.Session; import com.google.common.collect.ImmutableList; import com.google.genai.types.Content; -import com.google.genai.types.Part; import io.a2a.client.MessageEvent; import io.a2a.client.TaskUpdateEvent; import io.a2a.spec.Artifact; @@ -25,7 +24,6 @@ import io.a2a.spec.TextPart; import io.reactivex.rxjava3.core.Flowable; import java.util.Optional; -import java.util.UUID; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -66,124 +64,6 @@ private static TaskStatusUpdateEvent.Builder testTaskStatusUpdateEvent() { return new TaskStatusUpdateEvent.Builder().taskId("task-1").contextId("context-1"); } - @Test - public void eventsToMessage_withNullEvents_returnsEmptyAgentMessage() { - Message message = ResponseConverter.eventsToMessage(null, "context-1", "task-1"); - assertThat(message.getContextId()).isEqualTo("context-1"); - assertThat(message.getRole()).isEqualTo(Message.Role.AGENT); - assertThat(message.getParts()).hasSize(1); - assertThat(((TextPart) message.getParts().get(0)).getText()).isEmpty(); - } - - @Test - public void eventsToMessage_withEmptyEvents_returnsEmptyAgentMessage() { - Message message = ResponseConverter.eventsToMessage(ImmutableList.of(), "context-1", "task-1"); - assertThat(message.getContextId()).isEqualTo("context-1"); - assertThat(message.getRole()).isEqualTo(Message.Role.AGENT); - assertThat(message.getParts()).hasSize(1); - assertThat(((TextPart) message.getParts().get(0)).getText()).isEmpty(); - } - - @Test - public void eventsToMessage_withSingleEvent_returnsMessage() { - Event event = - Event.builder() - .id(UUID.randomUUID().toString()) - .author("user") - .content( - Content.builder() - .role("user") - .parts(ImmutableList.of(Part.builder().text("Hello").build())) - .build()) - .build(); - - Message message = - ResponseConverter.eventsToMessage(ImmutableList.of(event), "context-1", "task-1"); - - assertThat(message.getContextId()).isEqualTo("context-1"); - assertThat(message.getRole()).isEqualTo(Message.Role.USER); - assertThat(message.getParts()).hasSize(1); - assertThat(((TextPart) message.getParts().get(0)).getText()).isEqualTo("Hello"); - } - - @Test - public void eventsToMessage_withMultipleEvents_returnsAggregatedMessage() { - Event event1 = - Event.builder() - .id(UUID.randomUUID().toString()) - .author("agent") - .content( - Content.builder() - .role("model") - .parts(ImmutableList.of(Part.builder().text("Hello ").build())) - .build()) - .build(); - Event event2 = - Event.builder() - .id(UUID.randomUUID().toString()) - .author("agent") - .content( - Content.builder() - .role("model") - .parts(ImmutableList.of(Part.builder().text("World").build())) - .build()) - .build(); - - Message message = - ResponseConverter.eventsToMessage(ImmutableList.of(event1, event2), "context-1", "task-1"); - - assertThat(message.getMessageId()).isEqualTo("task-1"); - assertThat(message.getContextId()).isEqualTo("context-1"); - assertThat(message.getRole()).isEqualTo(Message.Role.AGENT); - assertThat(message.getParts()).hasSize(2); - assertThat(((TextPart) message.getParts().get(0)).getText()).isEqualTo("Hello "); - assertThat(((TextPart) message.getParts().get(1)).getText()).isEqualTo("World"); - } - - @Test - public void eventToMessage_convertsUserEvent() { - Event event = - Event.builder() - .id("event-1") - .author("user") - .content( - Content.builder() - .role("user") - .parts(ImmutableList.of(Part.builder().text("Test").build())) - .build()) - .build(); - - Message message = ResponseConverter.eventToMessage(event, "context-1"); - - assertThat(message.getMessageId()).isEqualTo("event-1"); - assertThat(message.getContextId()).isEqualTo("context-1"); - assertThat(message.getRole()).isEqualTo(Message.Role.USER); - assertThat(message.getParts()).hasSize(1); - assertThat(((TextPart) message.getParts().get(0)).getText()).isEqualTo("Test"); - } - - @Test - public void eventToMessage_convertsAgentEvent() { - Event event = - Event.builder() - .id("event-1") - .author("agent") - .content( - Content.builder() - .role("model") - .parts(ImmutableList.of(Part.builder().text("Test").build())) - .build()) - .build(); - - Message message = ResponseConverter.eventToMessage(event, "context-1"); - - assertThat(message.getMessageId()).isEqualTo("event-1"); - assertThat(message.getContextId()).isEqualTo("context-1"); - assertThat(message.getRole()).isEqualTo(Message.Role.AGENT); - assertThat(message.getParts()).hasSize(1); - assertThat(((TextPart) message.getParts().get(0)).getText()).isEqualTo("Test"); - } - @Test public void clientEventToEvent_withMessageEvent_returnsEvent() { Message a2aMessage = diff --git a/contrib/firestore-session-service/pom.xml b/contrib/firestore-session-service/pom.xml index d3d49e824..a62bff5b6 100644 --- a/contrib/firestore-session-service/pom.xml +++ b/contrib/firestore-session-service/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-parent - 0.7.1-SNAPSHOT + 0.8.1-SNAPSHOT ../../pom.xml diff --git a/contrib/langchain4j/pom.xml b/contrib/langchain4j/pom.xml index 330339440..e2ba4a7fb 100644 --- a/contrib/langchain4j/pom.xml +++ b/contrib/langchain4j/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-parent - 0.7.1-SNAPSHOT + 0.8.1-SNAPSHOT ../../pom.xml diff --git a/contrib/samples/a2a_basic/pom.xml b/contrib/samples/a2a_basic/pom.xml index fc666cd71..82b11b96f 100644 --- a/contrib/samples/a2a_basic/pom.xml +++ b/contrib/samples/a2a_basic/pom.xml @@ -5,7 +5,7 @@ com.google.adk google-adk-samples - 0.7.1-SNAPSHOT + 0.8.1-SNAPSHOT .. diff --git a/contrib/samples/a2a_server/pom.xml b/contrib/samples/a2a_server/pom.xml index d47a0c984..84023e260 100644 --- a/contrib/samples/a2a_server/pom.xml +++ b/contrib/samples/a2a_server/pom.xml @@ -1,13 +1,11 @@ - + 4.0.0 com.google.adk google-adk-samples - 0.7.1-SNAPSHOT + 0.8.1-SNAPSHOT .. diff --git a/contrib/samples/configagent/pom.xml b/contrib/samples/configagent/pom.xml index 552a6d826..6f7bfff83 100644 --- a/contrib/samples/configagent/pom.xml +++ b/contrib/samples/configagent/pom.xml @@ -5,7 +5,7 @@ com.google.adk google-adk-samples - 0.7.1-SNAPSHOT + 0.8.1-SNAPSHOT .. diff --git a/contrib/samples/helloworld/pom.xml b/contrib/samples/helloworld/pom.xml index bff6b86a6..36d12eaf0 100644 --- a/contrib/samples/helloworld/pom.xml +++ b/contrib/samples/helloworld/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-samples - 0.7.1-SNAPSHOT + 0.8.1-SNAPSHOT .. diff --git a/contrib/samples/mcpfilesystem/pom.xml b/contrib/samples/mcpfilesystem/pom.xml index 3e221f5f6..935aa6531 100644 --- a/contrib/samples/mcpfilesystem/pom.xml +++ b/contrib/samples/mcpfilesystem/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-parent - 0.7.1-SNAPSHOT + 0.8.1-SNAPSHOT ../../.. diff --git a/contrib/samples/pom.xml b/contrib/samples/pom.xml index f3c8359b8..905f8e711 100644 --- a/contrib/samples/pom.xml +++ b/contrib/samples/pom.xml @@ -5,7 +5,7 @@ com.google.adk google-adk-parent - 0.7.1-SNAPSHOT + 0.8.1-SNAPSHOT ../.. diff --git a/contrib/spring-ai/pom.xml b/contrib/spring-ai/pom.xml index e07deec9e..f49c3faae 100644 --- a/contrib/spring-ai/pom.xml +++ b/contrib/spring-ai/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-parent - 0.7.1-SNAPSHOT + 0.8.1-SNAPSHOT ../../pom.xml diff --git a/core/pom.xml b/core/pom.xml index 93c72e745..a0f843f56 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-parent - 0.7.1-SNAPSHOT + 0.8.1-SNAPSHOT google-adk @@ -209,10 +209,29 @@ maven-surefire-plugin + basic test + + + false + + + + + vertex-ai-rag-retrieval + + test + + + + true + + + VertexAiRagRetrievalTest#processLlmRequest_gemini2Model_addVertexRagStoreToConfig, VertexAiRagRetrievalTest#processLlmRequest_otherModel_doNotAddVertexRagStoreToConfig + apigee-llm diff --git a/core/src/main/java/com/google/adk/Version.java b/core/src/main/java/com/google/adk/Version.java index 10219a31b..1dc0282c3 100644 --- a/core/src/main/java/com/google/adk/Version.java +++ b/core/src/main/java/com/google/adk/Version.java @@ -22,7 +22,7 @@ */ public final class Version { // Don't touch this, release-please should keep it up to date. - public static final String JAVA_ADK_VERSION = "0.7.0"; // x-release-please-released-version + public static final String JAVA_ADK_VERSION = "0.8.0"; // x-release-please-released-version private Version() {} } diff --git a/core/src/main/java/com/google/adk/agents/LiveRequest.java b/core/src/main/java/com/google/adk/agents/LiveRequest.java index 9c66f038b..df1ada6a5 100644 --- a/core/src/main/java/com/google/adk/agents/LiveRequest.java +++ b/core/src/main/java/com/google/adk/agents/LiveRequest.java @@ -77,18 +77,12 @@ public abstract static class Builder { @JsonProperty("content") public abstract Builder content(@Nullable Content content); - public abstract Builder content(Optional content); - @JsonProperty("blob") public abstract Builder blob(@Nullable Blob blob); - public abstract Builder blob(Optional blob); - @JsonProperty("close") public abstract Builder close(@Nullable Boolean close); - public abstract Builder close(Optional close); - abstract LiveRequest autoBuild(); public final LiveRequest build() { diff --git a/core/src/main/java/com/google/adk/models/VertexCredentials.java b/core/src/main/java/com/google/adk/models/VertexCredentials.java index 5e36cbe95..93dc05ec1 100644 --- a/core/src/main/java/com/google/adk/models/VertexCredentials.java +++ b/core/src/main/java/com/google/adk/models/VertexCredentials.java @@ -38,16 +38,11 @@ public static Builder builder() { /** Builder for {@link VertexCredentials}. */ @AutoValue.Builder public abstract static class Builder { - public abstract Builder setProject(Optional value); public abstract Builder setProject(@Nullable String value); - public abstract Builder setLocation(Optional value); - public abstract Builder setLocation(@Nullable String value); - public abstract Builder setCredentials(Optional value); - public abstract Builder setCredentials(@Nullable GoogleCredentials value); public abstract VertexCredentials build(); diff --git a/core/src/main/java/com/google/adk/runner/Runner.java b/core/src/main/java/com/google/adk/runner/Runner.java index 58fc27f47..f2cb5b9d5 100644 --- a/core/src/main/java/com/google/adk/runner/Runner.java +++ b/core/src/main/java/com/google/adk/runner/Runner.java @@ -591,9 +591,9 @@ private void copySessionStates(Session source, Session target) { * @return invocation context configured for a live run. */ private InvocationContext newInvocationContextForLive( - Session session, Optional liveRequestQueue, RunConfig runConfig) { + Session session, @Nullable LiveRequestQueue liveRequestQueue, RunConfig runConfig) { RunConfig.Builder runConfigBuilder = RunConfig.builder(runConfig); - if (liveRequestQueue.isPresent()) { + if (liveRequestQueue != null) { // Default to AUDIO modality if not specified. if (CollectionUtils.isNullOrEmpty(runConfig.responseModalities())) { runConfigBuilder.setResponseModalities( @@ -614,8 +614,9 @@ private InvocationContext newInvocationContextForLive( InvocationContext.Builder builder = newInvocationContextBuilder(session) .runConfig(runConfigBuilder.build()) - .userContent(Content.fromParts()); - liveRequestQueue.ifPresent(builder::liveRequestQueue); + .userContent(Content.fromParts()) + .liveRequestQueue(liveRequestQueue); + return builder.build(); } @@ -643,7 +644,7 @@ public Flowable runLive( return Flowable.defer( () -> { InvocationContext invocationContext = - newInvocationContextForLive(session, Optional.of(liveRequestQueue), runConfig); + newInvocationContextForLive(session, liveRequestQueue, runConfig); Single invocationContextSingle; if (invocationContext.agent() instanceof LlmAgent agent) { diff --git a/core/src/main/java/com/google/adk/tools/retrieval/VertexAiRagRetrieval.java b/core/src/main/java/com/google/adk/tools/retrieval/VertexAiRagRetrieval.java index b36a05d10..16f11a1f8 100644 --- a/core/src/main/java/com/google/adk/tools/retrieval/VertexAiRagRetrieval.java +++ b/core/src/main/java/com/google/adk/tools/retrieval/VertexAiRagRetrieval.java @@ -20,6 +20,7 @@ import com.google.adk.models.LlmRequest; import com.google.adk.tools.ToolContext; +import com.google.adk.utils.ModelNameUtils; import com.google.cloud.aiplatform.v1.RagContexts; import com.google.cloud.aiplatform.v1.RagQuery; import com.google.cloud.aiplatform.v1.RetrieveContextsRequest; @@ -105,10 +106,9 @@ public VertexAiRagRetrieval( public Completable processLlmRequest( LlmRequest.Builder llmRequestBuilder, ToolContext toolContext) { LlmRequest llmRequest = llmRequestBuilder.build(); - // Use Gemini built-in Vertex AI RAG tool for Gemini 2 models or when using Vertex AI API Model + // Use Gemini built-in Vertex AI RAG tool for Gemini models when using Vertex AI API Model boolean useVertexAi = Boolean.parseBoolean(System.getenv("GOOGLE_GENAI_USE_VERTEXAI")); - if (useVertexAi - && (llmRequest.model().isPresent() && llmRequest.model().get().startsWith("gemini-2"))) { + if (useVertexAi && llmRequest.model().filter(ModelNameUtils::isGeminiModel).isPresent()) { GenerateContentConfig config = llmRequest.config().orElseGet(() -> GenerateContentConfig.builder().build()); ImmutableList.Builder toolsBuilder = ImmutableList.builder(); diff --git a/core/src/test/java/com/google/adk/runner/InputAudioTranscriptionTest.java b/core/src/test/java/com/google/adk/runner/InputAudioTranscriptionTest.java index 55d41916f..95a016e34 100644 --- a/core/src/test/java/com/google/adk/runner/InputAudioTranscriptionTest.java +++ b/core/src/test/java/com/google/adk/runner/InputAudioTranscriptionTest.java @@ -33,7 +33,6 @@ import com.google.genai.types.Modality; import com.google.genai.types.Part; import java.lang.reflect.Method; -import java.util.Optional; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -50,10 +49,9 @@ private InvocationContext invokeNewInvocationContextForLive( throws Exception { Method method = Runner.class.getDeclaredMethod( - "newInvocationContextForLive", Session.class, Optional.class, RunConfig.class); + "newInvocationContextForLive", Session.class, LiveRequestQueue.class, RunConfig.class); method.setAccessible(true); - return (InvocationContext) - method.invoke(runner, session, Optional.of(liveRequestQueue), runConfig); + return (InvocationContext) method.invoke(runner, session, liveRequestQueue, runConfig); } @Test diff --git a/core/src/test/java/com/google/adk/tools/retrieval/VertexAiRagRetrievalTest.java b/core/src/test/java/com/google/adk/tools/retrieval/VertexAiRagRetrievalTest.java index 6f04a7ef8..8246751b9 100644 --- a/core/src/test/java/com/google/adk/tools/retrieval/VertexAiRagRetrievalTest.java +++ b/core/src/test/java/com/google/adk/tools/retrieval/VertexAiRagRetrievalTest.java @@ -208,7 +208,7 @@ public void processLlmRequest_otherModel_doNotAddVertexRagStoreToConfig() { "projects/test-project/locations/us-central1", ragResources, vectorDistanceThreshold); - LlmRequest.Builder llmRequestBuilder = LlmRequest.builder().model("gemini-1-pro"); + LlmRequest.Builder llmRequestBuilder = LlmRequest.builder().model("other-model"); ToolContext toolContext = buildToolContext(); GenerateContentConfig initialConfig = GenerateContentConfig.builder().build(); llmRequestBuilder.config(initialConfig); diff --git a/dev/pom.xml b/dev/pom.xml index 6a0a03467..57aa808c2 100644 --- a/dev/pom.xml +++ b/dev/pom.xml @@ -18,7 +18,7 @@ com.google.adk google-adk-parent - 0.7.1-SNAPSHOT + 0.8.1-SNAPSHOT google-adk-dev diff --git a/maven_plugin/examples/custom_tools/pom.xml b/maven_plugin/examples/custom_tools/pom.xml index 73e53e480..abd3c60f2 100644 --- a/maven_plugin/examples/custom_tools/pom.xml +++ b/maven_plugin/examples/custom_tools/pom.xml @@ -4,7 +4,7 @@ com.example custom-tools-example - 0.7.1-SNAPSHOT + 0.8.1-SNAPSHOT jar ADK Custom Tools Example diff --git a/maven_plugin/examples/simple-agent/pom.xml b/maven_plugin/examples/simple-agent/pom.xml index 8b6c14c26..309fe9364 100644 --- a/maven_plugin/examples/simple-agent/pom.xml +++ b/maven_plugin/examples/simple-agent/pom.xml @@ -4,7 +4,7 @@ com.example simple-adk-agent - 0.7.1-SNAPSHOT + 0.8.1-SNAPSHOT jar Simple ADK Agent Example diff --git a/maven_plugin/pom.xml b/maven_plugin/pom.xml index 373c6775d..6ff3404f3 100644 --- a/maven_plugin/pom.xml +++ b/maven_plugin/pom.xml @@ -5,7 +5,7 @@ com.google.adk google-adk-parent - 0.7.1-SNAPSHOT + 0.8.1-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 34bef7755..ffe904d74 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ com.google.adk google-adk-parent - 0.7.1-SNAPSHOT + 0.8.1-SNAPSHOT pom Google Agent Development Kit Maven Parent POM @@ -495,13 +495,13 @@ - + - - + + - + diff --git a/tutorials/city-time-weather/pom.xml b/tutorials/city-time-weather/pom.xml index ef0c8f991..f4e8bdb52 100644 --- a/tutorials/city-time-weather/pom.xml +++ b/tutorials/city-time-weather/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-parent - 0.7.1-SNAPSHOT + 0.8.1-SNAPSHOT ../../pom.xml diff --git a/tutorials/live-audio-single-agent/pom.xml b/tutorials/live-audio-single-agent/pom.xml index 77a3f3b55..b6e649222 100644 --- a/tutorials/live-audio-single-agent/pom.xml +++ b/tutorials/live-audio-single-agent/pom.xml @@ -20,7 +20,7 @@ com.google.adk google-adk-parent - 0.7.1-SNAPSHOT + 0.8.1-SNAPSHOT ../../pom.xml