diff --git a/core/pom.xml b/core/pom.xml
index fe65715f3..7d2032c7e 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -1,4 +1,4 @@
-
+
4.0.0
-
com.google.adk
google-adk-parent
- 0.4.1-SNAPSHOT
+ 0.4.1-SNAPSHOT
+
-
google-adk
Agent Development Kit
Agent Development Kit: an open-source, code-first toolkit designed to simplify building, evaluating, and deploying advanced AI agents anywhere.
-
-
-
com.anthropic
@@ -201,6 +197,15 @@
maven-compiler-plugin
+
+ io.spring.javaformat
+ spring-javaformat-maven-plugin
+ 0.0.40
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/core/src/test/java/com/google/adk/models/ClaudeGenerateContentTest.java.invalid b/core/src/test/java/com/google/adk/models/ClaudeGenerateContentTest.java.invalid
new file mode 100644
index 000000000..09330fac4
--- /dev/null
+++ b/core/src/test/java/com/google/adk/models/ClaudeGenerateContentTest.java.invalid
@@ -0,0 +1,826 @@
+//This test file is marked invalid as it contains compilation errors. Change the extension to of this file to .java, to manually edit its contents
+/*
+ * 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
+ *
+ * http://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.
+ */
+// ********RoostGPT********
+/*
+Test generated by RoostGPT for test adk-java-demo using AI Type Azure Open AI and AI Model gpt-4.1
+
+ROOST_METHOD_HASH=generateContent_bc4a182290
+ROOST_METHOD_SIG_HASH=generateContent_c08b9769fe
+
+Here are your existing test cases which we found out and are not considered for test generation:
+
+File Path: /var/tmp/Roost/RoostGPT/adk-java-demo/2d03a3bd-e91c-428d-9cd7-034af8f4f796/source/adk-java/core/src/test/java/com/google/adk/models/ApigeeLlmTest.java
+Tests:
+ "@Test
+public void generateContent_stripsApigeePrefixAndSendsToDelegate() {
+ when(mockGeminiDelegate.generateContent(any(), anyBoolean())).thenReturn(Flowable.empty());
+ ApigeeLlm llm = new ApigeeLlm("apigee/gemini/v1/gemini-1.5-flash", mockGeminiDelegate);
+ LlmRequest request = LlmRequest.builder().model("apigee/gemini/v1/gemini-1.5-flash").contents(ImmutableList.of(Content.builder().parts(Part.fromText("hi")).build())).build();
+ llm.generateContent(request, true).test().assertNoErrors();
+ ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(LlmRequest.class);
+ verify(mockGeminiDelegate).generateContent(requestCaptor.capture(), eq(true));
+ assertThat(requestCaptor.getValue().model()).hasValue("gemini-1.5-flash");
+}
+"
+ "@Test
+public void build_withTrailingSlashInModel_parsesVersionAndModelId() {
+ when(mockGeminiDelegate.generateContent(any(), anyBoolean())).thenReturn(Flowable.empty());
+ ApigeeLlm llm = new ApigeeLlm("apigee/gemini/v1/", mockGeminiDelegate);
+ LlmRequest request = LlmRequest.builder().contents(ImmutableList.of(Content.builder().parts(Part.fromText("hi")).build())).build();
+ assertThrows(IllegalArgumentException.class, () -> llm.generateContent(request, false));
+ verify(mockGeminiDelegate, never()).generateContent(any(), anyBoolean());
+}
+"Scenario 1: Basic content generation with minimal request
+
+Details:
+ TestName: generateContentWithMinimalValidRequest
+ Description: This test checks that the generateContent method returns a valid Flowable when given a minimal LlmRequest containing only mandatory fields and a simple text Content, with no config or tools specified.
+Execution:
+ Arrange: Prepare an LlmRequest with only one Content (e.g., text "Hello"), no config, no tools, and provide a mock AnthropicClient that returns a valid Message.
+ Act: Call generateContent with the LlmRequest and stream set to false.
+ Assert: Confirm that the Flowable emits a correctly mapped LlmResponse containing the expected content.
+Validation:
+ The assertion verifies that Claude correctly processes the minimal input scenario and invokes AnthropicClient with correct parameters. It ensures fundamental request handling works and downstream conversions do not fail.
+
+Scenario 2: Content generation with model fallback
+
+Details:
+ TestName: generateContentUsesFallbackModelWhenRequestModelMissing
+ Description: This test validates that if LlmRequest.model() is empty, generateContent uses the Claude instance’s default model instead.
+Execution:
+ Arrange: Create an LlmRequest without a model (model() returns Optional.empty()) and a mock AnthropicClient set up to capture the model parameter.
+ Act: Invoke generateContent with this LlmRequest.
+ Assert: Check that the MessageCreateParams passed to the AnthropicClient uses the Claude’s own model as the fallback.
+Validation:
+ This confirms compliance with expected model selection logic and prevents failures due to missing model specification, ensuring reliability in client API usage.
+
+Scenario 3: Handling request with system instructions
+
+Details:
+ TestName: generateContentWithSystemInstructionExtractsTextProperly
+ Description: This test assesses whether generateContent correctly extracts and concatenates system instruction text from LlmRequest.config().systemInstruction() parts and passes it on to the system parameter.
+Execution:
+ Arrange: Mock a system instruction Content containing multiple Part with text fields, create a GenerateContentConfig with this Content, and attach it to LlmRequest.
+ Act: Call generateContent as usual.
+ Assert: Verify that the MessageCreateParams‘ system string is correctly formed (concatenating available texts with “\n”).
+Validation:
+ It checks proper processing and extraction of instructions, which is critical for system prompt handling and model output controls.
+
+Scenario 4: No system instruction present
+
+Details:
+ TestName: generateContentWithNoSystemInstructionLeavesSystemEmpty
+ Description: This scenario inspects the outcome when LlmRequest’s config lacks a system instruction. Claude should set system to an empty string.
+Execution:
+ Arrange: Construct a LlmRequest with a config but with systemInstruction() as Optional.empty().
+ Act: Call generateContent.
+ Assert: Ensure that the “system” field passed to MessageCreateParams is an empty string.
+Validation:
+ The assertion confirms robustness in the absence of system instructions, preventing unintended prompt leakages.
+
+Scenario 5: Handling tool configuration presence with function declarations
+
+Details:
+ TestName: generateContentWithFunctionDeclarationsMapsToolsProperly
+ Description: This scenario verifies the logic that maps function declarations into Anthropic tools and ToolUnion, and includes them in the MessageCreateParams.
+Execution:
+ Arrange: Provide LlmRequest.config() with Tools containing a non-empty functionDeclarations() list, mock the conversion and validate AnthropicClient receives ToolUnion in parameters.
+ Act: Call generateContent.
+ Assert: Confirm that tools are correctly constructed and included, and that toolChoice is also properly set.
+Validation:
+ Ensures integration of model tool functionality, allowing for advanced agent capabilities, and verifies correct mapping and transmission of function tool constructs.
+
+Scenario 6: Tools specified in request but no functionDeclarations
+
+Details:
+ TestName: generateContentWithToolsButNoFunctionDeclarationsSetsToolsToEmpty
+ Description: Checks that when tools are present but functionDeclarations() is absent or empty, generateContent doesn’t send any tools to the API.
+Execution:
+ Arrange: Prepare config with tools present but functionDeclarations() returns empty Optional or an empty list.
+ Act: Call generateContent.
+ Assert: Assert that the tools parameter is ImmutableList.of(), and toolChoice is set if tools() in LlmRequest is not empty.
+Validation:
+ This prevents accidental attempts to invoke tools and maintains command-and-control integrity when tools are not properly defined.
+
+Scenario 7: Null or malformed contents in request
+
+Details:
+ TestName: generateContentWithEmptyOrMalformedContentsThrowsException
+ Description: Validates that if LlmRequest.contents() is empty or contains parts that cannot be converted, generateContent throws an exception or handles gracefully.
+Execution:
+ Arrange: Provide an LlmRequest with empty contents or contents with unsupported Part types.
+ Act: Call generateContent.
+ Assert: Confirm exception is thrown, or error is propagated as expected.
+Validation:
+ This verifies robustness against malformed requests and the method’s error handling, crucial for input validation and preventing runtime errors.
+
+Scenario 8: Handling empty tools in LlmRequest
+
+Details:
+ TestName: generateContentWithEmptyToolsDoesNotSetToolChoice
+ Description: Checks that when LlmRequest.tools() is empty, generateContent does not set toolChoice, and MessageCreateParams does not receive toolChoice or tools parameters.
+Execution:
+ Arrange: Construct LlmRequest with empty tools and simulate an AnthropicClient.
+ Act: Call generateContent.
+ Assert: Ensure toolChoice is null and neither tools nor toolChoice is set in params.
+Validation:
+ It ensures toolChoice logic is strictly followed and prevents repetitive, unnecessary data transmission when tools are not involved.
+
+Scenario 9: Multiple contents conversion
+
+Details:
+ TestName: generateContentWithMultipleContentsConvertsAllProperly
+ Description: Verifies that when several Content entries are included in LlmRequest.contents(), each is correctly mapped to MessageParam and aggregated for the request.
+Execution:
+ Arrange: Prepare LlmRequest with several distinct Content elements, mock contentToAnthropicMessageParam.
+ Act: Execute generateContent.
+ Assert: Confirm that all MessageParams produced match the input Contents and are passed in messages list.
+Validation:
+ This ensures batch processing of user/model prompts and improves reliability when multi-turn or multi-instruction requests are made.
+
+Scenario 10: Max tokens setting
+
+Details:
+ TestName: generateContentWithCustomMaxTokensPropagatesValue
+ Description: Ensures that the maxTokens field (either default or custom on construction) is propagated into MessageCreateParams as intended.
+Execution:
+ Arrange: Instantiate Claude with a custom maxTokens setting and build a normal LlmRequest.
+ Act: Call generateContent.
+ Assert: Check that MessageCreateParams.maxTokens equals Claude’s maxTokens value.
+Validation:
+ This checks that response length and resource constraints are handled correctly according to instance configuration.
+
+Scenario 11: Return value mapping correctness
+
+Details:
+ TestName: generateContentReturnMappingProducesValidLlmResponse
+ Description: Validates that the returned Flowable emits an LlmResponse correctly mapped from the AnthropicClient Message, using convertAnthropicResponseToLlmResponse.
+Execution:
+ Arrange: Mock the Claude method convertAnthropicResponseToLlmResponse and setup AnthropicClient to return a Message with predictable content.
+ Act: Call generateContent.
+ Assert: Verify that the Flowable contains a valid LlmResponse with content as expected.
+Validation:
+ This assures that end-to-end conversion is functioning reliably, producing expected application-level LlmResponse outputs for all valid input combinations.
+
+Scenario 12: Logging of API responses
+
+Details:
+ TestName: generateContentLogsResponseSuccessfully
+ Description: Checks that a debug log statement is recorded when a response is received from AnthropicClient.
+Execution:
+ Arrange: Set up logger to capture output, mock AnthropicClient to return a Message.
+ Act: Call generateContent.
+ Assert: Validate that a log entry is made with level debug including the Message.
+Validation:
+ Successful logging is crucial for traceability and debugging in production scenarios, and this test confirms correct log behavior.
+
+Scenario 13: Handling contents with functionCall and functionResponse Parts
+
+Details:
+ TestName: generateContentHandlesFunctionCallAndResponseParts
+ Description: Ensures that Contents using Parts with functionCall or functionResponse types are correctly mapped to corresponding Anthropic message block structures.
+Execution:
+ Arrange: Provide LlmRequest.contents() with Content whose parts include functionCall and functionResponse, mock necessary conversions.
+ Act: Execute generateContent.
+ Assert: Confirm that MessageParams and ContentBlockParams are correctly constructed for each case.
+Validation:
+ This scenario tests advanced AI features such as tools invocation and returns, ensuring system and agent workflow integration.
+
+Scenario 14: Streaming mode parameter
+
+Details:
+ TestName: generateContentWithStreamingModeDoesNotAlterBehavior
+ Description: Verifies that the stream boolean passed to generateContent does not affect output since streaming is not yet implemented.
+Execution:
+ Arrange: Build two identical LlmRequests and call generateContent with stream true and false.
+ Act: Call generateContent in both configurations.
+ Assert: Assert that output is identical for both invocations.
+Validation:
+ This ensures that the method is future-proof and stable until streaming support is added.
+
+Scenario 15: Edge case – missing or empty Part texts in systemInstruction
+
+Details:
+ TestName: generateContentIgnoresEmptySystemInstructionParts
+ Description: Tests that if systemInstruction Parts exist but none have text present, the system parameter remains empty.
+Execution:
+ Arrange: Construct a system instruction Content where all Part elements have text as Optional.empty().
+ Act: Call generateContent with this LlmRequest.
+ Assert: Ensure system field is sent as empty.
+Validation:
+ Important to avoid passing garbage or empty instructions to the model, ensuring clarity and correctness for the AI backend.
+
+Scenario 16: Exception handling in conversion methods
+
+Details:
+ TestName: generateContentThrowsForUnsupportedPartTypes
+ Description: Validates that the method throws UnsupportedOperationException when Part types are not text, functionCall, or functionResponse.
+Execution:
+ Arrange: Prepare Content with a custom, unsupported Part type, and provide as LlmRequest.contents().
+ Act: Call generateContent and expect exception.
+ Assert: Verify method throws UnsupportedOperationException accordingly.
+Validation:
+ Critical to prevent silent failures or incorrect message formatting, this test ensures conversion logic is explicit and safe.
+
+Scenario 17: Tools with disableParallelToolUse
+
+Details:
+ TestName: generateContentSetsToolChoiceWithDisableParallelToolUse
+ Description: Ensures that when LlmRequest.tools() is non-empty, toolChoice is set using ToolChoiceAuto with disableParallelToolUse set to true.
+Execution:
+ Arrange: Provide LlmRequest.tools() with at least one tool.
+ Act: Call generateContent.
+ Assert: Confirm that toolChoice parameter in MessageCreateParams is set and that disableParallelToolUse is true.
+Validation:
+ This maintains important compliance for how tool usage is controlled and tested, especially for multi-step tool calls.
+
+Scenario 18: Verify correct building of MessageCreateParams
+
+Details:
+ TestName: generateContentBuildsMessageCreateParamsWithAllFields
+ Description: Validates that MessageCreateParams are built with model, system, messages, maxTokens, tools and toolChoice when all options are present.
+Execution:
+ Arrange: Compose LlmRequest with all fields specified: model, systemInstruction, tools, functionDeclarations, etc.
+ Act: Call generateContent.
+ Assert: Check that all relevant fields are correctly passed to AnthropicClient.
+Validation:
+ Comprehensive test ensuring complete and correct request construction for full-featured scenarios.
+
+Scenario 19: Return type is always Flowable
+
+Details:
+ TestName: generateContentAlwaysReturnsFlowable
+ Description: Ensures that regardless of input, generateContent returns a Flowable, not a single object or null.
+Execution:
+ Arrange: Provide diverse variants of LlmRequest.
+ Act: Call generateContent multiple times.
+ Assert: Confirm output is always a Flowable instance.
+Validation:
+ Guarantees API contract and integration reliability for downstream consumers.
+
+Scenario 20: Empty config option
+
+Details:
+ TestName: generateContentWithEmptyConfigDoesNotFail
+ Description: Verifies that when LlmRequest.config() is absent, method functions and does not throw exception.
+Execution:
+ Arrange: LlmRequest with config Optional.empty().
+ Act: Call generateContent.
+ Assert: Confirm successful execution and valid response.
+Validation:
+ Application behaves safely and predictably even without configuration, increasing flexibility for client code.
+
+
+*/
+
+// ********RoostGPT********
+
+package com.google.adk.models;
+import com.anthropic.client.AnthropicClient;
+import com.anthropic.models.messages.ContentBlock;
+import com.anthropic.models.messages.MessageCreateParams;
+import com.anthropic.models.messages.MessageParam;
+import com.anthropic.models.messages.MessageParam.Role;
+import com.anthropic.models.messages.Message;
+import com.anthropic.models.messages.ToolChoice;
+import com.anthropic.models.messages.ToolChoiceAuto;
+import com.anthropic.models.messages.ToolUnion;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.genai.types.Content;
+import com.google.genai.types.FunctionCall;
+import com.google.genai.types.FunctionDeclaration;
+import com.google.genai.types.GenerateContentConfig;
+import com.google.genai.types.Part;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import io.reactivex.rxjava3.core.Flowable;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.Mockito;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.util.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+import org.junit.jupiter.api.*;
+import com.anthropic.models.messages.ContentBlockParam;
+import com.anthropic.models.messages.TextBlockParam;
+import com.anthropic.models.messages.Tool;
+import com.anthropic.models.messages.ToolResultBlockParam;
+import com.anthropic.models.messages.ToolUseBlockParam;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+// NOTE: AnthropicClient.Messages does not exist as a nested or inner class. Instead, the API usage should be changed to use proper stubbing on the interface itself and remove all references to 'AnthropicClient.Messages'.
+// Do not use:
+// @Mock
+// private AnthropicClient.Messages messagesApi;
+// Instead, we invoke methods directly on the `anthropicClient` mock that will be configured to return a mock for the relevant methods.
+@ExtendWith(MockitoExtension.class)
+class ClaudeGenerateContentTest extends BaseLlm {
+ @Mock
+ private AnthropicClient anthropicClient;
+ @Mock
+ private Logger mockLogger;
+ private Claude claude;
+ private static final String DEFAULT_MODEL = "claude-3-opus";
+ private static final int DEFAULT_MAX_TOKENS = 8192;
+ @BeforeEach
+ void setUp() {
+ claude = new Claude(DEFAULT_MODEL, anthropicClient, DEFAULT_MAX_TOKENS);
+ }
+ private Content createSimpleContent(String text) {
+ Part textPart = Part.ofText(text);
+ return Content.builder().role("user").parts(Optional.of(ImmutableList.of(textPart))).build();
+ }
+ private LlmRequest createMinimalLlmRequest(Content content) {
+ return LlmRequest.builder()
+ .contents(ImmutableList.of(content))
+ .tools(ImmutableMap.of())
+ .liveConnectConfig(LiveConnectConfig.builder().build())
+ .build();
+ }
+ @Test
+ @Tag("valid")
+ @DisplayName("generateContentWithMinimalValidRequest")
+ public void testGenerateContentWithMinimalValidRequest() {
+ Content textContent = createSimpleContent("Hello");
+ LlmRequest llmRequest = createMinimalLlmRequest(textContent);
+ Message message = mock(Message.class);
+ AnthropicClient.Messages messagesApi = mock(AnthropicClient.Messages.class);
+ when(anthropicClient.messages()).thenReturn(messagesApi);
+ when(messagesApi.create(any())).thenReturn(message);
+ MessageParam expectedMsgParam = MessageParam.builder().role(Role.USER).contentBlocks(ImmutableList.of()).build();
+ Claude spyClaude = Mockito.spy(claude);
+ doReturn(expectedMsgParam).when(spyClaude).contentToAnthropicMessageParam(any());
+ LlmResponse expectedResponse = LlmResponse.builder().content(Content.builder().role("model").parts(ImmutableList.of()).build()).build();
+ doReturn(expectedResponse).when(spyClaude).convertAnthropicResponseToLlmResponse(message);
+ Flowable flowable = spyClaude.generateContent(llmRequest, false);
+ LlmResponse actualResponse = flowable.blockingFirst();
+ assertEquals((Object) expectedResponse, actualResponse);
+ }
+ @Test
+ @Tag("boundary")
+ @DisplayName("generateContentUsesFallbackModelWhenRequestModelMissing")
+ public void testGenerateContentUsesFallbackModelWhenRequestModelMissing() {
+ Content textContent = createSimpleContent("Hi, fallback model.");
+ LlmRequest llmRequest = LlmRequest.builder()
+ .contents(ImmutableList.of(textContent))
+ .tools(ImmutableMap.of())
+ .liveConnectConfig(LiveConnectConfig.builder().build())
+ .build();
+ LlmRequest spyLlmRequest = Mockito.spy(llmRequest);
+ when(spyLlmRequest.model()).thenReturn(Optional.empty());
+ Message message = mock(Message.class);
+ AnthropicClient.Messages messagesApi = mock(AnthropicClient.Messages.class);
+ when(anthropicClient.messages()).thenReturn(messagesApi);
+ when(messagesApi.create(any())).thenReturn(message);
+ Claude spyClaude = Mockito.spy(claude);
+ doReturn(MessageParam.builder().role(Role.USER).contentBlocks(ImmutableList.of()).build())
+ .when(spyClaude).contentToAnthropicMessageParam(any());
+ doReturn(LlmResponse.builder().build()).when(spyClaude).convertAnthropicResponseToLlmResponse(any());
+ spyClaude.generateContent(spyLlmRequest, false);
+ ArgumentCaptor captor = ArgumentCaptor.forClass(MessageCreateParams.class);
+ verify(messagesApi).create(captor.capture());
+ MessageCreateParams params = captor.getValue();
+ assertEquals((Object) DEFAULT_MODEL, params.model());
+ }
+ @Test
+ @Tag("valid")
+ @DisplayName("generateContentWithSystemInstructionExtractsTextProperly")
+ public void testGenerateContentWithSystemInstructionExtractsTextProperly() {
+ Part part1 = Part.ofText("System A");
+ Part part2 = Part.ofText("System B");
+ Content sysContent = Content.builder().role("system").parts(Optional.of(ImmutableList.of(part1, part2))).build();
+ GenerateContentConfig config = GenerateContentConfig.builder().systemInstruction(Optional.of(sysContent)).build();
+ LlmRequest llmRequest = LlmRequest.builder()
+ .contents(ImmutableList.of(createSimpleContent("Hi")))
+ .config(Optional.of(config))
+ .tools(ImmutableMap.of())
+ .liveConnectConfig(LiveConnectConfig.builder().build())
+ .build();
+ Message message = mock(Message.class);
+ AnthropicClient.Messages messagesApi = mock(AnthropicClient.Messages.class);
+ when(anthropicClient.messages()).thenReturn(messagesApi);
+ when(messagesApi.create(any())).thenReturn(message);
+ Claude spyClaude = Mockito.spy(claude);
+ doReturn(MessageParam.builder().role(Role.USER).contentBlocks(ImmutableList.of()).build())
+ .when(spyClaude).contentToAnthropicMessageParam(any());
+ doReturn(LlmResponse.builder().build()).when(spyClaude).convertAnthropicResponseToLlmResponse(any());
+ spyClaude.generateContent(llmRequest, false);
+ ArgumentCaptor captor = ArgumentCaptor.forClass(MessageCreateParams.class);
+ verify(messagesApi).create(captor.capture());
+ MessageCreateParams params = captor.getValue();
+ assertEquals((Object) "System A\nSystem B", params.system());
+ }
+ @Test
+ @Tag("boundary")
+ @DisplayName("generateContentWithNoSystemInstructionLeavesSystemEmpty")
+ public void testGenerateContentWithNoSystemInstructionLeavesSystemEmpty() {
+ GenerateContentConfig config = GenerateContentConfig.builder().systemInstruction(Optional.empty()).build();
+ LlmRequest llmRequest = LlmRequest.builder()
+ .contents(ImmutableList.of(createSimpleContent("Hello")))
+ .config(Optional.of(config))
+ .tools(ImmutableMap.of())
+ .liveConnectConfig(LiveConnectConfig.builder().build())
+ .build();
+ Message message = mock(Message.class);
+ AnthropicClient.Messages messagesApi = mock(AnthropicClient.Messages.class);
+ when(anthropicClient.messages()).thenReturn(messagesApi);
+ when(messagesApi.create(any())).thenReturn(message);
+ Claude spyClaude = Mockito.spy(claude);
+ doReturn(MessageParam.builder().role(Role.USER).contentBlocks(ImmutableList.of()).build())
+ .when(spyClaude).contentToAnthropicMessageParam(any());
+ doReturn(LlmResponse.builder().build()).when(spyClaude).convertAnthropicResponseToLlmResponse(any());
+ spyClaude.generateContent(llmRequest, false);
+ ArgumentCaptor captor = ArgumentCaptor.forClass(MessageCreateParams.class);
+ verify(messagesApi).create(captor.capture());
+ MessageCreateParams params = captor.getValue();
+ assertEquals((Object) "", params.system());
+ }
+ @Test
+ @Tag("valid")
+ @DisplayName("generateContentWithFunctionDeclarationsMapsToolsProperly")
+ public void testGenerateContentWithFunctionDeclarationsMapsToolsProperly() {
+ FunctionDeclaration functionDecl = FunctionDeclaration.builder().name("my_func").build();
+ Tools toolsWrapper = Tools.builder().functionDeclarations(Optional.of(ImmutableList.of(functionDecl))).build();
+ GenerateContentConfig config = GenerateContentConfig.builder().tools(Optional.of(ImmutableList.of(toolsWrapper))).build();
+ LlmRequest llmRequest = LlmRequest.builder()
+ .contents(ImmutableList.of(createSimpleContent("Tool test")))
+ .config(Optional.of(config))
+ .tools(ImmutableMap.of())
+ .liveConnectConfig(LiveConnectConfig.builder().build())
+ .build();
+ Message message = mock(Message.class);
+ AnthropicClient.Messages messagesApi = mock(AnthropicClient.Messages.class);
+ when(anthropicClient.messages()).thenReturn(messagesApi);
+ when(messagesApi.create(any())).thenReturn(message);
+ Claude spyClaude = Mockito.spy(claude);
+ doReturn(MessageParam.builder().role(Role.USER).contentBlocks(ImmutableList.of()).build())
+ .when(spyClaude).contentToAnthropicMessageParam(any());
+ doReturn(ToolUnion.ofTool(new Tool())).when(spyClaude).functionDeclarationToAnthropicTool(any());
+ doReturn(LlmResponse.builder().build()).when(spyClaude).convertAnthropicResponseToLlmResponse(any());
+ spyClaude.generateContent(llmRequest, false);
+ ArgumentCaptor captor = ArgumentCaptor.forClass(MessageCreateParams.class);
+ verify(messagesApi).create(captor.capture());
+ MessageCreateParams params = captor.getValue();
+ assertTrue((boolean) (params.tools() != null && !params.tools().isEmpty()));
+ assertNotNull(params.toolChoice());
+ }
+ @Test
+ @Tag("boundary")
+ @DisplayName("generateContentWithToolsButNoFunctionDeclarationsSetsToolsToEmpty")
+ public void testGenerateContentWithToolsButNoFunctionDeclarationsSetsToolsToEmpty() {
+ Tools toolsWrapper = Tools.builder().functionDeclarations(Optional.empty()).build();
+ GenerateContentConfig config = GenerateContentConfig.builder().tools(Optional.of(ImmutableList.of(toolsWrapper))).build();
+ LlmRequest llmRequest = LlmRequest.builder()
+ .contents(ImmutableList.of(createSimpleContent("Tool test")))
+ .config(Optional.of(config))
+ .tools(ImmutableMap.of("toolA", mock(BaseTool.class)))
+ .liveConnectConfig(LiveConnectConfig.builder().build())
+ .build();
+ Message message = mock(Message.class);
+ AnthropicClient.Messages messagesApi = mock(AnthropicClient.Messages.class);
+ when(anthropicClient.messages()).thenReturn(messagesApi);
+ when(messagesApi.create(any())).thenReturn(message);
+ Claude spyClaude = Mockito.spy(claude);
+ doReturn(MessageParam.builder().role(Role.USER).contentBlocks(ImmutableList.of()).build())
+ .when(spyClaude).contentToAnthropicMessageParam(any());
+ doReturn(LlmResponse.builder().build()).when(spyClaude).convertAnthropicResponseToLlmResponse(any());
+ spyClaude.generateContent(llmRequest, false);
+ ArgumentCaptor captor = ArgumentCaptor.forClass(MessageCreateParams.class);
+ verify(messagesApi).create(captor.capture());
+ MessageCreateParams params = captor.getValue();
+ assertTrue((boolean) params.tools().isEmpty());
+ assertNotNull(params.toolChoice());
+ }
+ @Test
+ @Tag("invalid")
+ @DisplayName("generateContentWithEmptyOrMalformedContentsThrowsException")
+ public void testGenerateContentWithEmptyOrMalformedContentsThrowsException() {
+ LlmRequest llmRequest = LlmRequest.builder()
+ .contents(ImmutableList.of())
+ .tools(ImmutableMap.of())
+ .liveConnectConfig(LiveConnectConfig.builder().build())
+ .build();
+ Claude spyClaude = Mockito.spy(claude);
+ doThrow(new UnsupportedOperationException("Unsupported type")).when(spyClaude).contentToAnthropicMessageParam(any());
+ assertThrows(UnsupportedOperationException.class, () -> spyClaude.generateContent(llmRequest, false).blockingFirst());
+ }
+ @Test
+ @Tag("boundary")
+ @DisplayName("generateContentWithEmptyToolsDoesNotSetToolChoice")
+ public void testGenerateContentWithEmptyToolsDoesNotSetToolChoice() {
+ LlmRequest llmRequest = LlmRequest.builder()
+ .contents(ImmutableList.of(createSimpleContent("No tool choice")))
+ .tools(ImmutableMap.of())
+ .liveConnectConfig(LiveConnectConfig.builder().build())
+ .build();
+ Message message = mock(Message.class);
+ AnthropicClient.Messages messagesApi = mock(AnthropicClient.Messages.class);
+ when(anthropicClient.messages()).thenReturn(messagesApi);
+ when(messagesApi.create(any())).thenReturn(message);
+ Claude spyClaude = Mockito.spy(claude);
+ doReturn(MessageParam.builder().role(Role.USER).contentBlocks(ImmutableList.of()).build())
+ .when(spyClaude).contentToAnthropicMessageParam(any());
+ doReturn(LlmResponse.builder().build()).when(spyClaude).convertAnthropicResponseToLlmResponse(any());
+ spyClaude.generateContent(llmRequest, false);
+ ArgumentCaptor captor = ArgumentCaptor.forClass(MessageCreateParams.class);
+ verify(messagesApi).create(captor.capture());
+ MessageCreateParams params = captor.getValue();
+ assertNull(params.toolChoice());
+ assertTrue((boolean) params.tools().isEmpty());
+ }
+ @Test
+ @Tag("valid")
+ @DisplayName("generateContentWithMultipleContentsConvertsAllProperly")
+ public void testGenerateContentWithMultipleContentsConvertsAllProperly() {
+ Content c1 = createSimpleContent("A");
+ Content c2 = createSimpleContent("B");
+ Content c3 = createSimpleContent("C");
+ LlmRequest llmRequest = LlmRequest.builder()
+ .contents(ImmutableList.of(c1, c2, c3))
+ .tools(ImmutableMap.of())
+ .liveConnectConfig(LiveConnectConfig.builder().build())
+ .build();
+ Message message = mock(Message.class);
+ AnthropicClient.Messages messagesApi = mock(AnthropicClient.Messages.class);
+ when(anthropicClient.messages()).thenReturn(messagesApi);
+ when(messagesApi.create(any())).thenReturn(message);
+ Claude spyClaude = Mockito.spy(claude);
+ doReturn(MessageParam.builder().role(Role.USER).contentBlocks(ImmutableList.of()).build())
+ .when(spyClaude).contentToAnthropicMessageParam(any());
+ doReturn(LlmResponse.builder().build()).when(spyClaude).convertAnthropicResponseToLlmResponse(any());
+ spyClaude.generateContent(llmRequest, false);
+ ArgumentCaptor captor = ArgumentCaptor.forClass(MessageCreateParams.class);
+ verify(messagesApi).create(captor.capture());
+ MessageCreateParams params = captor.getValue();
+ assertEquals((int) 3, params.messages().size());
+ }
+ @Test
+ @Tag("boundary")
+ @DisplayName("generateContentWithCustomMaxTokensPropagatesValue")
+ public void testGenerateContentWithCustomMaxTokensPropagatesValue() {
+ int customTokens = 2345;
+ Claude customClaude = new Claude("custom-model", anthropicClient, customTokens);
+ Content textContent = createSimpleContent("Tokens please");
+ LlmRequest llmRequest = createMinimalLlmRequest(textContent);
+ Message message = mock(Message.class);
+ AnthropicClient.Messages messagesApi = mock(AnthropicClient.Messages.class);
+ when(anthropicClient.messages()).thenReturn(messagesApi);
+ when(messagesApi.create(any())).thenReturn(message);
+ Claude spyClaude = Mockito.spy(customClaude);
+ doReturn(MessageParam.builder().role(Role.USER).contentBlocks(ImmutableList.of()).build())
+ .when(spyClaude).contentToAnthropicMessageParam(any());
+ doReturn(LlmResponse.builder().build()).when(spyClaude).convertAnthropicResponseToLlmResponse(any());
+ spyClaude.generateContent(llmRequest, false);
+ ArgumentCaptor captor = ArgumentCaptor.forClass(MessageCreateParams.class);
+ verify(messagesApi).create(captor.capture());
+ MessageCreateParams params = captor.getValue();
+ assertEquals((int) customTokens, params.maxTokens());
+ }
+ @Test
+ @Tag("valid")
+ @DisplayName("generateContentReturnMappingProducesValidLlmResponse")
+ public void testGenerateContentReturnMappingProducesValidLlmResponse() {
+ Content textContent = createSimpleContent("Mapping check.");
+ LlmRequest llmRequest = createMinimalLlmRequest(textContent);
+ Message message = mock(Message.class);
+ AnthropicClient.Messages messagesApi = mock(AnthropicClient.Messages.class);
+ when(anthropicClient.messages()).thenReturn(messagesApi);
+ when(messagesApi.create(any())).thenReturn(message);
+ Claude spyClaude = Mockito.spy(claude);
+ doReturn(MessageParam.builder().role(Role.USER).contentBlocks(ImmutableList.of()).build())
+ .when(spyClaude).contentToAnthropicMessageParam(any());
+ LlmResponse expectedResponse = LlmResponse.builder().content(Content.builder().role("model").parts(ImmutableList.of()).build()).build();
+ doReturn(expectedResponse).when(spyClaude).convertAnthropicResponseToLlmResponse(message);
+ Flowable flowable = spyClaude.generateContent(llmRequest, false);
+ assertEquals((Object) expectedResponse, flowable.blockingFirst());
+ }
+ @Test
+ @Tag("integration")
+ @DisplayName("generateContentLogsResponseSuccessfully")
+ public void testGenerateContentLogsResponseSuccessfully() {
+ Content textContent = createSimpleContent("Log test.");
+ LlmRequest llmRequest = createMinimalLlmRequest(textContent);
+ Message message = mock(Message.class);
+ AnthropicClient.Messages messagesApi = mock(AnthropicClient.Messages.class);
+ when(anthropicClient.messages()).thenReturn(messagesApi);
+ when(messagesApi.create(any())).thenReturn(message);
+ Claude spyClaude = Mockito.spy(claude);
+ doReturn(MessageParam.builder().role(Role.USER).contentBlocks(ImmutableList.of()).build())
+ .when(spyClaude).contentToAnthropicMessageParam(any());
+ doReturn(LlmResponse.builder().build()).when(spyClaude).convertAnthropicResponseToLlmResponse(any());
+ spyClaude.logger = mockLogger;
+ spyClaude.generateContent(llmRequest, false);
+ verify(mockLogger, times(1)).debug(anyString(), eq(message));
+ }
+ @Test
+ @Tag("valid")
+ @DisplayName("generateContentHandlesFunctionCallAndResponseParts")
+ public void testGenerateContentHandlesFunctionCallAndResponseParts() {
+ Part call = Part.ofFunctionCall(FunctionCall.builder().name("myFunc").build());
+ Part response = Part.ofFunctionResponse("result", "payload");
+ Content content = Content.builder().role("user").parts(Optional.of(ImmutableList.of(call, response))).build();
+ LlmRequest llmRequest = createMinimalLlmRequest(content);
+ Message message = mock(Message.class);
+ AnthropicClient.Messages messagesApi = mock(AnthropicClient.Messages.class);
+ when(anthropicClient.messages()).thenReturn(messagesApi);
+ when(messagesApi.create(any())).thenReturn(message);
+ Claude spyClaude = Mockito.spy(claude);
+ doReturn(MessageParam.builder().role(Role.USER).contentBlocks(ImmutableList.of()).build())
+ .when(spyClaude).contentToAnthropicMessageParam(any());
+ doReturn(LlmResponse.builder().build()).when(spyClaude).convertAnthropicResponseToLlmResponse(any());
+ Flowable flowable = spyClaude.generateContent(llmRequest, false);
+ assertNotNull((Object) flowable.blockingFirst());
+ }
+ @Test
+ @Tag("boundary")
+ @DisplayName("generateContentWithStreamingModeDoesNotAlterBehavior")
+ public void testGenerateContentWithStreamingModeDoesNotAlterBehavior() {
+ Content content = createSimpleContent("Stream test");
+ LlmRequest llmRequest = createMinimalLlmRequest(content);
+ Message message = mock(Message.class);
+ AnthropicClient.Messages messagesApi = mock(AnthropicClient.Messages.class);
+ when(anthropicClient.messages()).thenReturn(messagesApi);
+ when(messagesApi.create(any())).thenReturn(message);
+ Claude spyClaude = Mockito.spy(claude);
+ doReturn(MessageParam.builder().role(Role.USER).contentBlocks(ImmutableList.of()).build())
+ .when(spyClaude).contentToAnthropicMessageParam(any());
+ LlmResponse expectedResponse = LlmResponse.builder().build();
+ doReturn(expectedResponse).when(spyClaude).convertAnthropicResponseToLlmResponse(message);
+ Flowable flowable1 = spyClaude.generateContent(llmRequest, true);
+ Flowable flowable2 = spyClaude.generateContent(llmRequest, false);
+ assertEquals((Object) flowable1.blockingFirst(), flowable2.blockingFirst());
+ }
+ @Test
+ @Tag("boundary")
+ @DisplayName("generateContentIgnoresEmptySystemInstructionParts")
+ public void testGenerateContentIgnoresEmptySystemInstructionParts() {
+ Part emptyPart1 = Part.ofText("");
+ Part emptyPart2 = Part.ofFunctionCall(FunctionCall.builder().name("empty").build());
+ List parts = Arrays.asList(emptyPart1, emptyPart2);
+ Content systemContent = Content.builder().role("system").parts(Optional.of(ImmutableList.copyOf(parts))).build();
+ GenerateContentConfig config = GenerateContentConfig.builder().systemInstruction(Optional.of(systemContent)).build();
+ LlmRequest llmRequest = LlmRequest.builder()
+ .contents(ImmutableList.of(createSimpleContent("Empty system parts")))
+ .config(Optional.of(config))
+ .tools(ImmutableMap.of())
+ .liveConnectConfig(LiveConnectConfig.builder().build())
+ .build();
+ Message message = mock(Message.class);
+ AnthropicClient.Messages messagesApi = mock(AnthropicClient.Messages.class);
+ when(anthropicClient.messages()).thenReturn(messagesApi);
+ when(messagesApi.create(any())).thenReturn(message);
+ Claude spyClaude = Mockito.spy(claude);
+ doReturn(MessageParam.builder().role(Role.USER).contentBlocks(ImmutableList.of()).build())
+ .when(spyClaude).contentToAnthropicMessageParam(any());
+ doReturn(LlmResponse.builder().build()).when(spyClaude).convertAnthropicResponseToLlmResponse(any());
+ spyClaude.generateContent(llmRequest, false);
+ ArgumentCaptor captor = ArgumentCaptor.forClass(MessageCreateParams.class);
+ verify(messagesApi).create(captor.capture());
+ MessageCreateParams params = captor.getValue();
+ assertEquals((Object) "", params.system());
+ }
+ @Test
+ @Tag("invalid")
+ @DisplayName("generateContentThrowsForUnsupportedPartTypes")
+ public void testGenerateContentThrowsForUnsupportedPartTypes() {
+ Part unsupportedPart = Part.ofCustomType("unsupported", Optional.empty());
+ Content content = Content.builder().role("user").parts(Optional.of(ImmutableList.of(unsupportedPart))).build();
+ LlmRequest llmRequest = createMinimalLlmRequest(content);
+ Claude spyClaude = Mockito.spy(claude);
+ doThrow(new UnsupportedOperationException()).when(spyClaude).contentToAnthropicMessageParam(any());
+ assertThrows(UnsupportedOperationException.class, () -> spyClaude.generateContent(llmRequest, false).blockingFirst());
+ }
+ @Test
+ @Tag("valid")
+ @DisplayName("generateContentSetsToolChoiceWithDisableParallelToolUse")
+ public void testGenerateContentSetsToolChoiceWithDisableParallelToolUse() {
+ LlmRequest llmRequest = LlmRequest.builder()
+ .contents(ImmutableList.of(createSimpleContent("Parallel tool disable")))
+ .tools(ImmutableMap.of("toolA", mock(BaseTool.class)))
+ .liveConnectConfig(LiveConnectConfig.builder().build())
+ .build();
+ Message message = mock(Message.class);
+ AnthropicClient.Messages messagesApi = mock(AnthropicClient.Messages.class);
+ when(anthropicClient.messages()).thenReturn(messagesApi);
+ when(messagesApi.create(any())).thenReturn(message);
+ Claude spyClaude = Mockito.spy(claude);
+ doReturn(MessageParam.builder().role(Role.USER).contentBlocks(ImmutableList.of()).build())
+ .when(spyClaude).contentToAnthropicMessageParam(any());
+ doReturn(LlmResponse.builder().build()).when(spyClaude).convertAnthropicResponseToLlmResponse(any());
+ spyClaude.generateContent(llmRequest, false);
+ ArgumentCaptor captor = ArgumentCaptor.forClass(MessageCreateParams.class);
+ verify(messagesApi).create(captor.capture());
+ MessageCreateParams params = captor.getValue();
+ assertNotNull(params.toolChoice());
+ ToolChoiceAuto autoChoice = (ToolChoiceAuto) params.toolChoice().choice();
+ assertTrue((boolean) autoChoice.disableParallelToolUse());
+ }
+ @Test
+ @Tag("integration")
+ @DisplayName("generateContentBuildsMessageCreateParamsWithAllFields")
+ public void testGenerateContentBuildsMessageCreateParamsWithAllFields() {
+ FunctionDeclaration functionDecl = FunctionDeclaration.builder().name("fullfunc").build();
+ Tools toolsWrapper = Tools.builder().functionDeclarations(Optional.of(ImmutableList.of(functionDecl))).build();
+ Part sysPart = Part.ofText("System Full");
+ Content sysContent = Content.builder().role("system").parts(Optional.of(ImmutableList.of(sysPart))).build();
+ GenerateContentConfig config = GenerateContentConfig.builder().systemInstruction(Optional.of(sysContent))
+ .tools(Optional.of(ImmutableList.of(toolsWrapper)))
+ .build();
+ Content content = createSimpleContent("Full-featured");
+ LlmRequest llmRequest = LlmRequest.builder()
+ .contents(ImmutableList.of(content))
+ .config(Optional.of(config))
+ .tools(ImmutableMap.of("toolA", mock(BaseTool.class)))
+ .liveConnectConfig(LiveConnectConfig.builder().build())
+ .build();
+ Message message = mock(Message.class);
+ AnthropicClient.Messages messagesApi = mock(AnthropicClient.Messages.class);
+ when(anthropicClient.messages()).thenReturn(messagesApi);
+ when(messagesApi.create(any())).thenReturn(message);
+ Claude spyClaude = Mockito.spy(claude);
+ doReturn(MessageParam.builder().role(Role.USER).contentBlocks(ImmutableList.of()).build())
+ .when(spyClaude).contentToAnthropicMessageParam(any());
+ doReturn(ToolUnion.ofTool(new Tool())).when(spyClaude).functionDeclarationToAnthropicTool(any());
+ doReturn(LlmResponse.builder().build()).when(spyClaude).convertAnthropicResponseToLlmResponse(any());
+ spyClaude.generateContent(llmRequest, false);
+ ArgumentCaptor captor = ArgumentCaptor.forClass(MessageCreateParams.class);
+ verify(messagesApi).create(captor.capture());
+ MessageCreateParams params = captor.getValue();
+ assertEquals((Object) DEFAULT_MODEL, params.model());
+ assertEquals((Object) "System Full", params.system());
+ assertEquals((int) 1, params.messages().size());
+ assertTrue((boolean) params.maxTokens() > 0);
+ assertTrue((boolean) params.tools().size() > 0);
+ assertNotNull(params.toolChoice());
+ }
+ @Test
+ @Tag("valid")
+ @DisplayName("generateContentAlwaysReturnsFlowable")
+ public void testGenerateContentAlwaysReturnsFlowable() {
+ Content content = createSimpleContent("Flowable test");
+ LlmRequest llmRequest = createMinimalLlmRequest(content);
+ Message message = mock(Message.class);
+ AnthropicClient.Messages messagesApi = mock(AnthropicClient.Messages.class);
+ when(anthropicClient.messages()).thenReturn(messagesApi);
+ when(messagesApi.create(any())).thenReturn(message);
+ Claude spyClaude = Mockito.spy(claude);
+ doReturn(MessageParam.builder().role(Role.USER).contentBlocks(ImmutableList.of()).build())
+ .when(spyClaude).contentToAnthropicMessageParam(any());
+ doReturn(LlmResponse.builder().build()).when(spyClaude).convertAnthropicResponseToLlmResponse(any());
+ Flowable flowable = spyClaude.generateContent(llmRequest, false);
+ assertTrue(flowable instanceof Flowable);
+ }
+ @Test
+ @Tag("boundary")
+ @DisplayName("generateContentWithEmptyConfigDoesNotFail")
+ public void testGenerateContentWithEmptyConfigDoesNotFail() {
+ Content content = createSimpleContent("No config");
+ LlmRequest llmRequest = LlmRequest.builder()
+ .contents(ImmutableList.of(content))
+ .config(Optional.empty())
+ .tools(ImmutableMap.of())
+ .liveConnectConfig(LiveConnectConfig.builder().build())
+ .build();
+ Message message = mock(Message.class);
+ AnthropicClient.Messages messagesApi = mock(AnthropicClient.Messages.class);
+ when(anthropicClient.messages()).thenReturn(messagesApi);
+ when(messagesApi.create(any())).thenReturn(message);
+ Claude spyClaude = Mockito.spy(claude);
+ doReturn(MessageParam.builder().role(Role.USER).contentBlocks(ImmutableList.of()).build())
+ .when(spyClaude).contentToAnthropicMessageParam(any());
+ doReturn(LlmResponse.builder().build()).when(spyClaude).convertAnthropicResponseToLlmResponse(any());
+ Flowable flowable = spyClaude.generateContent(llmRequest, false);
+ assertNotNull((Object) flowable.blockingFirst());
+ }
+}
\ No newline at end of file
diff --git a/core/src/test/java/com/google/adk/models/LlmRegistryRegisterLlmTest.java b/core/src/test/java/com/google/adk/models/LlmRegistryRegisterLlmTest.java
new file mode 100644
index 000000000..837208007
--- /dev/null
+++ b/core/src/test/java/com/google/adk/models/LlmRegistryRegisterLlmTest.java
@@ -0,0 +1,365 @@
+/*
+ * 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
+ *
+ * http://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.
+ */
+// ********RoostGPT********
+/*
+Test generated by RoostGPT for test adk-java-demo using AI Type Azure Open AI and AI Model gpt-4.1
+
+ROOST_METHOD_HASH=registerLlm_39033090dc
+ROOST_METHOD_SIG_HASH=registerLlm_6d1828529e
+
+Scenario 1: Registering a New Model Name Pattern and Factory
+
+Details:
+ TestName: registerNewPatternAndFactory
+ Description: This test case verifies that when the registerLlm method is invoked with a new, previously unregistered modelNamePattern and a valid factory, the mapping is correctly inserted into the llmFactories map.
+
+Execution:
+ Arrange: Prepare a unique model name pattern string and a mock or fake implementation of LlmFactory.
+ Act: Call registerLlm with the unique pattern and the factory.
+ Assert: Check that llmFactories contains the new pattern with its associated factory.
+
+Validation:
+ The assertion confirms that a new pattern-factory association is registered. This ensures extendability and correctness in dynamically registering new model providers.
+
+
+Scenario 2: Overwriting an Existing Factory for an Existing Pattern
+
+Details:
+ TestName: overwriteExistingPatternFactory
+ Description: This test checks whether calling registerLlm with an already-registered modelNamePattern will successfully overwrite the existing factory associated with that pattern in llmFactories.
+
+Execution:
+ Arrange: Add a pattern with an initial factory to llmFactories or via registerLlm; create a second factory.
+ Act: Call registerLlm for the same pattern using the new factory.
+ Assert: Confirm that llmFactories maps the pattern to the new factory, replacing the old one.
+
+Validation:
+ The assertion validates the expected overwrite behavior of the map, which is crucial to support factory updates or hot-swapping in runtime.
+
+
+Scenario 3: Registering a Null Pattern
+
+Details:
+ TestName: registerNullPattern
+ Description: This test checks what happens if registerLlm is invoked with a null modelNamePattern. It's intended to verify whether the ConcurrentHashMap accepts null keys and how the method handles this invalid input.
+
+Execution:
+ Arrange: Create a valid instance of LlmFactory.
+ Act: Call registerLlm with null as the pattern and the valid factory.
+ Assert: Validate the resulting behavior (expecting a NullPointerException or equivalent behavior due to ConcurrentHashMap policy).
+
+Validation:
+ The assertion demonstrates how the registry enforces or fails to enforce non-null keys, preventing accidental configuration errors and runtime faults.
+
+
+Scenario 4: Registering a Null Factory
+
+Details:
+ TestName: registerNullFactory
+ Description: This test ensures the method’s behavior when a valid modelNamePattern is registered but the factory argument is null.
+
+Execution:
+ Arrange: Prepare a valid model name pattern.
+ Act: Call registerLlm with the valid pattern and a null factory.
+ Assert: Check for the presence of the pattern in llmFactories with a null value or an exception if thrown.
+
+Validation:
+ The assertion is necessary to clarify if the implementation supports null factories, which could cause runtime failures when creating LLM instances.
+
+
+Scenario 5: Registering Duplicate Patterns Concurrently
+
+Details:
+ TestName: registerPatternConcurrently
+ Description: This test ensures that concurrent invocations of registerLlm for the same pattern but with different factories work correctly without causing data races or inconsistent state in llmFactories.
+
+Execution:
+ Arrange: Create multiple threads, each with the same pattern and different factory instances.
+ Act: Start all threads to invoke registerLlm concurrently.
+ Assert: After all threads finish, verify that llmFactories contains the pattern and one of the registered factories.
+
+Validation:
+ Verifies thread-safety and correct atomic map updating, essential in a concurrent environment such as a registry for LLM providers.
+
+
+Scenario 6: Registering a Pattern that Matches Built-in Factories
+
+Details:
+ TestName: registerConflictingBuiltinPattern
+ Description: This test assesses the result of registering a modelNamePattern that is identical to or overlaps with one of the patterns registered in the static initializer block (such as "gemini-.*").
+
+Execution:
+ Arrange: Prepare a pattern (like "gemini-.*") used in static registration and a test factory.
+ Act: Call registerLlm with this pattern and the custom factory.
+ Assert: Check that llmFactories now maps this pattern to the latest factory, overriding the built-in one.
+
+Validation:
+ This checks if user-supplied registrations can override pre-registered (default) factories, which could affect base functionality and configurability.
+
+
+Scenario 7: Registering an Empty String as Pattern
+
+Details:
+ TestName: registerEmptyStringPattern
+ Description: This test determines if an empty string pattern can be registered and properly stored in llmFactories.
+
+Execution:
+ Arrange: Create a valid LlmFactory.
+ Act: Call registerLlm with an empty string and the factory.
+ Assert: Verify that llmFactories has an entry with the empty string as the key and the provided factory as the value.
+
+Validation:
+ Needed to see how the system handles potentially invalid or catch-all configurations, as empty patterns might be applied broadly or lead to ambiguous matching.
+
+
+Scenario 8: Registering Multiple Distinct Patterns
+
+Details:
+ TestName: registerMultipleDistinctPatterns
+ Description: This test checks whether registerLlm can successfully register several different modelNamePatterns (all unique) with distinct factories, ensuring the map is additive and not destructive to previous entries.
+
+Execution:
+ Arrange: Prepare several unique patterns and their associated factories.
+ Act: Invoke registerLlm multiple times (one per pattern/factory pair).
+ Assert: Confirm that all patterns are present in llmFactories, each mapped to the correct factory.
+
+Validation:
+ Demonstrates the ability to support multiple providers, which is central to the extensibility of the registry.
+
+
+Scenario 9: Registering a Pattern with Special Regex Characters
+
+Details:
+ TestName: registerRegexSpecialCharsPattern
+ Description: This test validates that complex or special-regex-character patterns are correctly registered and retrievable from llmFactories.
+
+Execution:
+ Arrange: Define a pattern with special regex symbols (e.g., "^apigee-\\d{3}$") and a suitable factory.
+ Act: Call registerLlm with this regex and the factory.
+ Assert: Confirm the pattern exists as entered in llmFactories, with the expected factory.
+
+Validation:
+ Important for supporting advanced matching capabilities, ensuring users can employ precise or complex regex patterns without data loss or corruption.
+
+
+Scenario 10: Registering a Factory with a Pattern Already Having Null Value
+
+Details:
+ TestName: registerFactoryWithPatternHavingNullValue
+ Description: This test simulates a case where the llmFactories map (perhaps due to a previous faulty registration) has a pattern mapped to null and verifies that re-registering with a valid factory replaces the null.
+
+Execution:
+ Arrange: Insert a null value into llmFactories for a given pattern (using registerLlm or direct insertion if possible).
+ Act: Call registerLlm with the pattern and a valid factory.
+ Assert: Check that the pattern now maps to the new, non-null factory.
+
+Validation:
+ Ensures resiliency when recovering from unintentionally broken map entries, a plausible scenario in mutable registries.
+
+*/
+
+// ********RoostGPT********
+
+package com.google.adk.models;
+
+import java.util.Map;
+import org.junit.jupiter.api.*;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+// Mocking dependencies according to instructions.
+public class LlmRegistryRegisterLlmTest {
+
+ // Using test setup to clean static map before each test
+ @BeforeEach
+ public void setUp() {
+ // Clear static map to avoid cross-test pollution
+ Map llmFactories = getLlmFactories();
+ llmFactories.clear();
+ }
+
+ private Map getLlmFactories() {
+ // Reflective access to static field for validation, according to instructions
+ try {
+ java.lang.reflect.Field f = LlmRegistry.class.getDeclaredField("llmFactories");
+ f.setAccessible(true);
+ return (Map) f.get(null);
+ } catch (Exception ex) {
+ throw new RuntimeException("Reflection failed", ex);
+ }
+ }
+
+ @Test
+ @Tag("valid")
+ @DisplayName("Scenario 1: Registering a New Model Name Pattern and Factory")
+ public void testRegisterNewPatternAndFactory() {
+ String uniquePattern = "test-model-abc";
+ LlmRegistry.LlmFactory mockFactory = Mockito.mock(LlmRegistry.LlmFactory.class);
+ LlmRegistry.registerLlm(uniquePattern, mockFactory);
+ Assertions.assertEquals(
+ mockFactory,
+ getLlmFactories().get(uniquePattern),
+ "Factory should be registered for new pattern");
+ }
+
+ @Test
+ @Tag("valid")
+ @DisplayName("Scenario 2: Overwriting an Existing Factory for an Existing Pattern")
+ public void testOverwriteExistingPatternFactory() {
+ String pattern = "overwritable-pattern";
+ LlmRegistry.LlmFactory firstFactory = Mockito.mock(LlmRegistry.LlmFactory.class);
+ LlmRegistry.LlmFactory secondFactory = Mockito.mock(LlmRegistry.LlmFactory.class);
+ LlmRegistry.registerLlm(pattern, firstFactory);
+ LlmRegistry.registerLlm(pattern, secondFactory);
+ Assertions.assertEquals(
+ secondFactory, getLlmFactories().get(pattern), "Factory should be overwritten with latest");
+ }
+
+ @Test
+ @Tag("invalid")
+ @DisplayName("Scenario 3: Registering a Null Pattern")
+ public void testRegisterNullPattern() {
+ LlmRegistry.LlmFactory factory = Mockito.mock(LlmRegistry.LlmFactory.class);
+ Assertions.assertThrows(
+ NullPointerException.class,
+ () -> {
+ LlmRegistry.registerLlm(null, factory);
+ },
+ "ConcurrentHashMap should throw NullPointerException for null key");
+ }
+
+ @Test
+ @Tag("invalid")
+ @DisplayName("Scenario 4: Registering a Null Factory")
+ public void testRegisterNullFactory() {
+ String pattern = "null-factory-pattern";
+ LlmRegistry.registerLlm(pattern, null);
+ Assertions.assertNull(
+ getLlmFactories().get(pattern), "Factory should be null for valid pattern");
+ }
+
+ @Test
+ @Tag("integration")
+ @DisplayName("Scenario 5: Registering Duplicate Patterns Concurrently")
+ public void testRegisterPatternConcurrently() throws InterruptedException {
+ String pattern = "concurrent-pattern";
+ LlmRegistry.LlmFactory factory1 = Mockito.mock(LlmRegistry.LlmFactory.class);
+ LlmRegistry.LlmFactory factory2 = Mockito.mock(LlmRegistry.LlmFactory.class);
+ LlmRegistry.LlmFactory factory3 = Mockito.mock(LlmRegistry.LlmFactory.class);
+ Thread t1 = new Thread(() -> LlmRegistry.registerLlm(pattern, factory1));
+ Thread t2 = new Thread(() -> LlmRegistry.registerLlm(pattern, factory2));
+ Thread t3 = new Thread(() -> LlmRegistry.registerLlm(pattern, factory3));
+ t1.start();
+ t2.start();
+ t3.start();
+ t1.join();
+ t2.join();
+ t3.join();
+ // One of the factories (last write wins) should be associated
+ LlmRegistry.LlmFactory actualFactory = getLlmFactories().get(pattern);
+ Assertions.assertTrue(
+ actualFactory == factory1 || actualFactory == factory2 || actualFactory == factory3,
+ "One of the concurrently registered factories should be mapped");
+ }
+
+ @Test
+ @Tag("boundary")
+ @DisplayName("Scenario 6: Registering a Pattern that Matches Built-in Factories")
+ public void testRegisterConflictingBuiltinPattern() {
+ String builtinPattern = "gemini-.*"; // TODO: Adjust if builtin static initializer
+ // uses a different pattern
+ LlmRegistry.LlmFactory customFactory = Mockito.mock(LlmRegistry.LlmFactory.class);
+ LlmRegistry.registerLlm(builtinPattern, customFactory);
+ Assertions.assertEquals(
+ customFactory,
+ getLlmFactories().get(builtinPattern),
+ "Custom factory should override builtin factory");
+ }
+
+ @Test
+ @Tag("boundary")
+ @DisplayName("Scenario 7: Registering an Empty String as Pattern")
+ public void testRegisterEmptyStringPattern() {
+ String emptyPattern = "";
+ LlmRegistry.LlmFactory emptyFactory = Mockito.mock(LlmRegistry.LlmFactory.class);
+ LlmRegistry.registerLlm(emptyPattern, emptyFactory);
+ Assertions.assertEquals(
+ emptyFactory,
+ getLlmFactories().get(emptyPattern),
+ "Empty pattern should be allowed and mapped");
+ }
+
+ @Test
+ @Tag("valid")
+ @DisplayName("Scenario 8: Registering Multiple Distinct Patterns")
+ public void testRegisterMultipleDistinctPatterns() {
+ LlmRegistry.LlmFactory factoryOne = Mockito.mock(LlmRegistry.LlmFactory.class);
+ LlmRegistry.LlmFactory factoryTwo = Mockito.mock(LlmRegistry.LlmFactory.class);
+ LlmRegistry.LlmFactory factoryThree = Mockito.mock(LlmRegistry.LlmFactory.class);
+ String patternOne = "unique-pattern-1";
+ String patternTwo = "unique-pattern-2";
+ String patternThree = "unique-pattern-3";
+ LlmRegistry.registerLlm(patternOne, factoryOne);
+ LlmRegistry.registerLlm(patternTwo, factoryTwo);
+ LlmRegistry.registerLlm(patternThree, factoryThree);
+ Assertions.assertEquals(
+ factoryOne,
+ getLlmFactories().get(patternOne),
+ "First unique pattern should match its factory");
+ Assertions.assertEquals(
+ factoryTwo,
+ getLlmFactories().get(patternTwo),
+ "Second unique pattern should match its factory");
+ Assertions.assertEquals(
+ factoryThree,
+ getLlmFactories().get(patternThree),
+ "Third unique pattern should match its factory");
+ }
+
+ @Test
+ @Tag("boundary")
+ @DisplayName("Scenario 9: Registering a Pattern with Special Regex Characters")
+ public void testRegisterRegexSpecialCharsPattern() {
+ String specialPattern = "^apigee-\\d{3}$";
+ LlmRegistry.LlmFactory regexFactory = Mockito.mock(LlmRegistry.LlmFactory.class);
+ LlmRegistry.registerLlm(specialPattern, regexFactory);
+ Assertions.assertEquals(
+ regexFactory,
+ getLlmFactories().get(specialPattern),
+ "Special regex pattern should be mapped correctly");
+ }
+
+ @Test
+ @Tag("boundary")
+ @DisplayName("Scenario 10: Registering a Factory with a Pattern Already Having Null Value")
+ public void testRegisterFactoryWithPatternHavingNullValue() {
+ String nullPattern = "null-pattern";
+ // Register with null factory first
+ LlmRegistry.registerLlm(nullPattern, null);
+ Assertions.assertNull(getLlmFactories().get(nullPattern), "Should initially be null");
+ // Register again with a valid factory
+ LlmRegistry.LlmFactory recoveryFactory = Mockito.mock(LlmRegistry.LlmFactory.class);
+ LlmRegistry.registerLlm(nullPattern, recoveryFactory);
+ Assertions.assertEquals(
+ recoveryFactory,
+ getLlmFactories().get(nullPattern),
+ "Null should be replaced with valid factory");
+ }
+}
diff --git a/pom.xml b/pom.xml
index 6009c7316..24fd6ece6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,4 +1,4 @@
-
+
-
+
4.0.0
-
com.google.adk
google-adk-parent
- 0.4.1-SNAPSHOT
+ 0.4.1-SNAPSHOT
+
pom
-
Google Agent Development Kit Maven Parent POM
https://github.com/google/adk-java
Google Agent Development Kit (ADK) for Java
-
core
dev
@@ -39,12 +35,10 @@
a2a
a2a/webservice
-
17
${java.version}
UTF-8
-
1.11.0
3.4.1
1.49.0
@@ -73,7 +67,6 @@
3.9.0
5.4.3
-
@@ -112,7 +105,6 @@
pom
import
-
com.anthropic
@@ -274,9 +266,21 @@
assertj-core
${assertj.version}
+
+ org.mockito
+ mockito-junit-jupiter
+ 2.23.4
+ test
+
+
+
+ io.spring.javaformat
+ spring-javaformat-formatter
+ 0.0.40
+
+
-
@@ -324,8 +328,7 @@
plain
-
+
**/*Test.java
@@ -469,6 +472,36 @@
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.2.5
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-report-plugin
+ 3.2.5
+
+ testReport
+
+
+
+
+ org.apache.maven.plugins
+ maven-site-plugin
+ 2.1
+
+ testReport
+
+
+
+
+ io.spring.javaformat
+ spring-javaformat-maven-plugin
+ 0.0.40
+
+
@@ -528,7 +561,6 @@
-
The Apache License, Version 2.0
@@ -558,4 +590,19 @@
https://central.sonatype.com/repository/maven-snapshots/
+
+
+ org.mockito
+ mockito-junit-jupiter
+ 2.23.4
+ test
+
+
+
+ io.spring.javaformat
+ spring-javaformat-formatter
+ 0.0.40
+
+
+
\ No newline at end of file