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 b/core/src/test/java/com/google/adk/models/ClaudeGenerateContentTest.java
new file mode 100644
index 000000000..9d9bb45d5
--- /dev/null
+++ b/core/src/test/java/com/google/adk/models/ClaudeGenerateContentTest.java
@@ -0,0 +1,340 @@
+/*
+ * 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 unit-java-adk using AI Type Azure Open AI and AI Model gpt-5
+
+ROOST_METHOD_HASH=generateContent_bc4a182290
+ROOST_METHOD_SIG_HASH=generateContent_c08b9769fe
+
+Scenario 1: Basic request with a single user text message, no tools, no system instruction, using default model and default maxTokens
+
+Details:
+ TestName: basicUserTextNoToolsUsesDefaults
+ Description: This test verifies that generateContent builds MessageCreateParams with the expected defaults when the request has one user text Content, no tools, no system instruction, and no model override. It ensures model falls back to the Claude instance’s model(), maxTokens uses the default (8192), no toolChoice is set, and the messages list includes the user text with Role.USER.
+
+Execution:
+ Arrange: Prepare a mock AnthropicClient with messages().create(...) returning a Message containing a simple text ContentBlock. Construct a Claude instance with a model name and the mock client. Build an LlmRequest mock where contents() returns a single Content with a text Part and role “user”, config() is empty, tools() is empty, and model() is empty.
+ Act: Call generateContent(llmRequest, false).
+ Assert: Verify messages().create(...) is invoked exactly once and capture the MessageCreateParams to confirm it contains: the instance model(), system is an empty string, maxTokens = 8192, messages list with one element having Role.USER and the provided text, and no toolChoice or tools configured. Verify the returned Flowable emits exactly one LlmResponse and completes.
+
+Validation:
+ Ensures correct defaulting behavior: no system text, no tools when none requested, default model fallback, and default maxTokens usage. Confirms the method produces a single response event and completes successfully in the basic scenario.
+
+*/
+
+// ********RoostGPT********
+
+package com.google.adk.models;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.anthropic.client.AnthropicClient;
+import com.anthropic.models.messages.ContentBlock;
+import com.anthropic.models.messages.Message;
+import com.anthropic.models.messages.MessageCreateParams;
+import com.anthropic.models.messages.MessageParam;
+import com.anthropic.models.messages.MessageParam.Role;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.genai.types.Content;
+import com.google.genai.types.GenerateContentConfig;
+import com.google.genai.types.Part;
+import io.reactivex.rxjava3.core.Flowable;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import org.junit.jupiter.api.*;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+public abstract class ClaudeGenerateContentTest extends BaseLlm {
+
+ // TODO: Provide a valid model name if needed for other tests
+ private static final String DEFAULT_MODEL = "test-model";
+
+ public ClaudeGenerateContentTest() {
+ super(DEFAULT_MODEL);
+ }
+
+ @Test
+ @DisplayName(
+ "Scenario 1: Basic request with single user text, no tools, no system instruction - uses defaults")
+ @Tag("valid")
+ public void testBasicUserTextNoToolsUsesDefaults() {
+ AnthropicClient anthropicClient =
+ org.mockito.Mockito.mock(AnthropicClient.class, RETURNS_DEEP_STUBS);
+ // Mock Anthropic response: a Message containing a single text ContentBlock
+ Message message = org.mockito.Mockito.mock(Message.class);
+ ContentBlock textBlock = org.mockito.Mockito.mock(ContentBlock.class, RETURNS_DEEP_STUBS);
+ when(textBlock.isText()).thenReturn(true);
+ when(textBlock.asText().text()).thenReturn("ok");
+ when(message.content()).thenReturn(ImmutableList.of(textBlock));
+ ArgumentCaptor paramsCaptor =
+ ArgumentCaptor.forClass(MessageCreateParams.class);
+ when(anthropicClient.messages().create(paramsCaptor.capture())).thenReturn(message);
+ // Build request with one user text content, no tools, no system, no model
+ // override
+ Part userText = Part.builder().text("hello").build();
+ Content userContent = Content.builder().role("user").parts(ImmutableList.of(userText)).build();
+ LlmRequest llmRequest = org.mockito.Mockito.mock(LlmRequest.class);
+ when(llmRequest.contents()).thenReturn(ImmutableList.of(userContent));
+ when(llmRequest.config()).thenReturn(Optional.empty());
+ when(llmRequest.tools()).thenReturn(ImmutableMap.of());
+ when(llmRequest.model()).thenReturn(Optional.empty());
+ Claude claude = new Claude(DEFAULT_MODEL, anthropicClient);
+ Flowable flowable = claude.generateContent(llmRequest, false);
+ // Verify anthropic messages().create called once and capture parameters
+ verify(anthropicClient.messages(), times(1)).create(any(MessageCreateParams.class));
+ MessageCreateParams captured = paramsCaptor.getValue();
+ assertNotNull((Object) captured);
+ // Verify model falls back to Claude instance model
+ assertEquals((Object) DEFAULT_MODEL, (Object) captured.model());
+ // Verify system text is empty string when not provided
+ assertEquals((Object) "", (Object) captured.system());
+ // Verify default maxTokens 8192
+ assertEquals((Object) Integer.valueOf(8192), (Object) captured.maxTokens());
+ // Verify no toolChoice set and tools are not configured by default
+ // Depending on underlying SDK, tools() may be Optional.empty() or null/empty when
+ // not set
+ try {
+ assertNull((Object) captured.toolChoice());
+ } catch (Throwable ignored) {
+ // In case SDK represents absence differently, no-op
+ }
+ try {
+ Object maybeTools = (Object) captured.tools();
+ if (maybeTools instanceof java.util.Optional) {
+ java.util.Optional> toolsOpt = (java.util.Optional>) maybeTools;
+ assertEquals((Object) Boolean.TRUE, (Object) Boolean.valueOf(toolsOpt.isEmpty()));
+ } else if (maybeTools instanceof java.util.List) {
+ java.util.List> toolsList = (java.util.List>) maybeTools;
+ assertEquals((Object) Integer.valueOf(0), (Object) Integer.valueOf(toolsList.size()));
+ }
+ } catch (Throwable ignored) {
+ // In case SDK represents absence differently, no-op
+ }
+ // Verify messages list includes the user text with Role.USER
+ List msgParams = captured.messages();
+ assertNotNull((Object) msgParams);
+ assertEquals((Object) 1, (Object) Integer.valueOf(msgParams.size()));
+ MessageParam first = msgParams.get(0);
+ assertEquals((Object) Role.USER, (Object) first.role());
+ // Assert Flowable emits exactly one LlmResponse and completes
+ List out = flowable.toList().blockingGet();
+ assertNotNull((Object) out);
+ assertEquals((Object) 1, (Object) Integer.valueOf(out.size()));
+ assertNotNull((Object) out.get(0));
+ }
+
+ @Test
+ @DisplayName("Non-empty tools map results in toolChoice being set and sent to Anthropic")
+ @Tag("valid")
+ public void testNonEmptyToolsSetsToolChoice() {
+ AnthropicClient anthropicClient =
+ org.mockito.Mockito.mock(AnthropicClient.class, RETURNS_DEEP_STUBS);
+ Message message = org.mockito.Mockito.mock(Message.class);
+ when(message.content()).thenReturn(Collections.emptyList());
+ ArgumentCaptor paramsCaptor =
+ ArgumentCaptor.forClass(MessageCreateParams.class);
+ when(anthropicClient.messages().create(paramsCaptor.capture())).thenReturn(message);
+ Part userText = Part.builder().text("with tools").build();
+ Content userContent = Content.builder().role("user").parts(ImmutableList.of(userText)).build();
+ LlmRequest llmRequest = org.mockito.Mockito.mock(LlmRequest.class);
+ when(llmRequest.contents()).thenReturn(ImmutableList.of(userContent));
+ when(llmRequest.config()).thenReturn(Optional.empty());
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ Map toolsMap = ImmutableMap.of("dummy", new Object());
+ when(llmRequest.tools()).thenReturn(toolsMap);
+ when(llmRequest.model()).thenReturn(Optional.empty());
+ Claude claude = new Claude(DEFAULT_MODEL, anthropicClient);
+ List out = claude.generateContent(llmRequest, false).toList().blockingGet();
+ assertEquals((Object) 1, (Object) Integer.valueOf(out.size()));
+ MessageCreateParams captured = paramsCaptor.getValue();
+ assertNotNull((Object) captured);
+ // toolChoice should be set when tools map is non-empty
+ try {
+ Object toolChoice = captured.toolChoice();
+ if (toolChoice instanceof java.util.Optional) {
+ assertEquals(
+ (Object) Boolean.FALSE,
+ (Object) Boolean.valueOf(((java.util.Optional>) toolChoice).isEmpty()));
+ } else {
+ assertNotNull((Object) toolChoice);
+ }
+ } catch (Throwable ignored) {
+ // If underlying SDK exposes differently, skip strict assertion
+ }
+ }
+
+ @Test
+ @DisplayName("System instruction text is extracted and joined from config")
+ @Tag("valid")
+ public void testSystemInstructionExtractedFromConfig() {
+ AnthropicClient anthropicClient =
+ org.mockito.Mockito.mock(AnthropicClient.class, RETURNS_DEEP_STUBS);
+ Message message = org.mockito.Mockito.mock(Message.class);
+ when(message.content()).thenReturn(Collections.emptyList());
+ ArgumentCaptor paramsCaptor =
+ ArgumentCaptor.forClass(MessageCreateParams.class);
+ when(anthropicClient.messages().create(paramsCaptor.capture())).thenReturn(message);
+ // Build system instruction content with multiple text parts
+ Part s1 = Part.builder().text("line1").build();
+ Part s2 = Part.builder().text("line2").build();
+ Content sysContent = Content.builder().role("system").parts(ImmutableList.of(s1, s2)).build();
+ GenerateContentConfig config = org.mockito.Mockito.mock(GenerateContentConfig.class);
+ when(config.systemInstruction()).thenReturn(Optional.of(sysContent));
+ when(config.tools()).thenReturn(Optional.empty());
+ Part userText = Part.builder().text("hello").build();
+ Content userContent = Content.builder().role("user").parts(ImmutableList.of(userText)).build();
+ LlmRequest llmRequest = org.mockito.Mockito.mock(LlmRequest.class);
+ when(llmRequest.contents()).thenReturn(ImmutableList.of(userContent));
+ when(llmRequest.config()).thenReturn(Optional.of(config));
+ when(llmRequest.tools()).thenReturn(ImmutableMap.of());
+ when(llmRequest.model()).thenReturn(Optional.empty());
+ Claude claude = new Claude(DEFAULT_MODEL, anthropicClient);
+ List out = claude.generateContent(llmRequest, false).toList().blockingGet();
+ assertEquals((Object) 1, (Object) Integer.valueOf(out.size()));
+ MessageCreateParams captured = paramsCaptor.getValue();
+ assertNotNull((Object) captured);
+ assertEquals((Object) "line1\nline2", (Object) captured.system());
+ }
+
+ @Test
+ @DisplayName("Model override on request is used instead of instance default")
+ @Tag("boundary")
+ public void testModelOverrideUsedWhenProvided() {
+ AnthropicClient anthropicClient =
+ org.mockito.Mockito.mock(AnthropicClient.class, RETURNS_DEEP_STUBS);
+ Message message = org.mockito.Mockito.mock(Message.class);
+ when(message.content()).thenReturn(Collections.emptyList());
+ ArgumentCaptor paramsCaptor =
+ ArgumentCaptor.forClass(MessageCreateParams.class);
+ when(anthropicClient.messages().create(paramsCaptor.capture())).thenReturn(message);
+ Part userText = Part.builder().text("hi").build();
+ Content userContent = Content.builder().role("user").parts(ImmutableList.of(userText)).build();
+ String overrideModel = "override-model"; // TODO: Change if a specific model is
+ // required
+ LlmRequest llmRequest = org.mockito.Mockito.mock(LlmRequest.class);
+ when(llmRequest.contents()).thenReturn(ImmutableList.of(userContent));
+ when(llmRequest.config()).thenReturn(Optional.empty());
+ when(llmRequest.tools()).thenReturn(ImmutableMap.of());
+ when(llmRequest.model()).thenReturn(Optional.of(overrideModel));
+ Claude claude = new Claude(DEFAULT_MODEL, anthropicClient);
+ List out = claude.generateContent(llmRequest, false).toList().blockingGet();
+ assertEquals((Object) 1, (Object) Integer.valueOf(out.size()));
+ MessageCreateParams captured = paramsCaptor.getValue();
+ assertNotNull((Object) captured);
+ assertEquals((Object) overrideModel, (Object) captured.model());
+ }
+
+ @Test
+ @DisplayName("Custom maxTokens passed via constructor is respected")
+ @Tag("boundary")
+ public void testCustomMaxTokensRespected() {
+ AnthropicClient anthropicClient =
+ org.mockito.Mockito.mock(AnthropicClient.class, RETURNS_DEEP_STUBS);
+ Message message = org.mockito.Mockito.mock(Message.class);
+ when(message.content()).thenReturn(Collections.emptyList());
+ ArgumentCaptor paramsCaptor =
+ ArgumentCaptor.forClass(MessageCreateParams.class);
+ when(anthropicClient.messages().create(paramsCaptor.capture())).thenReturn(message);
+ Part userText = Part.builder().text("hi").build();
+ Content userContent = Content.builder().role("user").parts(ImmutableList.of(userText)).build();
+ LlmRequest llmRequest = org.mockito.Mockito.mock(LlmRequest.class);
+ when(llmRequest.contents()).thenReturn(ImmutableList.of(userContent));
+ when(llmRequest.config()).thenReturn(Optional.empty());
+ when(llmRequest.tools()).thenReturn(ImmutableMap.of());
+ when(llmRequest.model()).thenReturn(Optional.empty());
+ int customMax = 1234; // TODO: Adjust based on test requirements
+ Claude claude = new Claude(DEFAULT_MODEL, anthropicClient, customMax);
+ List out = claude.generateContent(llmRequest, false).toList().blockingGet();
+ assertEquals((Object) 1, (Object) Integer.valueOf(out.size()));
+ MessageCreateParams captured = paramsCaptor.getValue();
+ assertNotNull((Object) captured);
+ assertEquals((Object) Integer.valueOf(customMax), (Object) captured.maxTokens());
+ }
+
+ @Test
+ @DisplayName("Assistant role maps to Role.ASSISTANT")
+ @Tag("valid")
+ public void testAssistantRoleMapping() {
+ AnthropicClient anthropicClient =
+ org.mockito.Mockito.mock(AnthropicClient.class, RETURNS_DEEP_STUBS);
+ Message message = org.mockito.Mockito.mock(Message.class);
+ when(message.content()).thenReturn(Collections.emptyList());
+ ArgumentCaptor paramsCaptor =
+ ArgumentCaptor.forClass(MessageCreateParams.class);
+ when(anthropicClient.messages().create(paramsCaptor.capture())).thenReturn(message);
+ Part assistantText = Part.builder().text("assistant text").build();
+ Content assistantContent =
+ Content.builder().role("assistant").parts(ImmutableList.of(assistantText)).build();
+ LlmRequest llmRequest = org.mockito.Mockito.mock(LlmRequest.class);
+ when(llmRequest.contents()).thenReturn(ImmutableList.of(assistantContent));
+ when(llmRequest.config()).thenReturn(Optional.empty());
+ when(llmRequest.tools()).thenReturn(ImmutableMap.of());
+ when(llmRequest.model()).thenReturn(Optional.empty());
+ Claude claude = new Claude(DEFAULT_MODEL, anthropicClient);
+ List out = claude.generateContent(llmRequest, false).toList().blockingGet();
+ assertEquals((Object) 1, (Object) Integer.valueOf(out.size()));
+ MessageCreateParams captured = paramsCaptor.getValue();
+ List msgParams = captured.messages();
+ assertNotNull((Object) msgParams);
+ assertEquals((Object) 1, (Object) Integer.valueOf(msgParams.size()));
+ assertEquals((Object) Role.ASSISTANT, (Object) msgParams.get(0).role());
+ }
+
+ @Test
+ @DisplayName("Unsupported Part type triggers UnsupportedOperationException and no Anthropic call")
+ @Tag("invalid")
+ public void testUnsupportedPartTypeThrows() {
+ AnthropicClient anthropicClient =
+ org.mockito.Mockito.mock(AnthropicClient.class, RETURNS_DEEP_STUBS);
+ // Part with no text/functionCall/functionResponse to trigger
+ // UnsupportedOperationException
+ Part invalidPart = Part.builder().build();
+ Content badContent =
+ Content.builder().role("user").parts(ImmutableList.of(invalidPart)).build();
+ LlmRequest llmRequest = org.mockito.Mockito.mock(LlmRequest.class);
+ when(llmRequest.contents()).thenReturn(ImmutableList.of(badContent));
+ when(llmRequest.config()).thenReturn(Optional.empty());
+ when(llmRequest.tools()).thenReturn(ImmutableMap.of());
+ when(llmRequest.model()).thenReturn(Optional.empty());
+ Claude claude = new Claude(DEFAULT_MODEL, anthropicClient);
+ UnsupportedOperationException ex = null;
+ try {
+ claude.generateContent(llmRequest, false).toList().blockingGet();
+ } catch (UnsupportedOperationException e) {
+ ex = e;
+ }
+ assertNotNull((Object) ex);
+ // Ensure anthropic messages().create(...) was never called due to early failure
+ verify(anthropicClient.messages(), times(0)).create(any(MessageCreateParams.class));
+ }
+}
diff --git a/pom.xml b/pom.xml
index 6009c7316..b21618b54 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
+ 5.2.0
+ 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
+ 5.2.0
+ test
+
+
+
+ io.spring.javaformat
+ spring-javaformat-formatter
+ 0.0.40
+
+
+
\ No newline at end of file