Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions core/pom.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0"?>
<!--
Copyright 2025 Google LLC

Expand All @@ -16,19 +16,15 @@
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.google.adk</groupId>
<artifactId>google-adk-parent</artifactId>
<version>0.4.1-SNAPSHOT</version><!-- {x-version-update:google-adk:current} -->
<version>0.4.1-SNAPSHOT</version>
<!-- {x-version-update:google-adk:current} -->
</parent>

<artifactId>google-adk</artifactId>
<name>Agent Development Kit</name>
<description>Agent Development Kit: an open-source, code-first toolkit designed to simplify building, evaluating, and deploying advanced AI agents anywhere.</description>



<dependencies>
<dependency>
<groupId>com.anthropic</groupId>
Expand Down Expand Up @@ -201,6 +197,15 @@
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>io.spring.javaformat</groupId>
<artifactId>spring-javaformat-maven-plugin</artifactId>
<version>0.0.40</version>
<!--Plugin added by RoostGPT-->
</plugin>
</plugins>
<pluginManagement>
<plugins/>
</pluginManagement>
</build>
</project>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
/*
* 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: Builds messages from text-only contents and invokes Anthropic once

Details:
TestName: buildsMessageParamsFromTextOnlyContents
Description: Verifies that when LlmRequest contains Content instances with only text Part(s), the method maps them into Anthropic MessageParam(s), builds MessageCreateParams correctly, calls anthropicClient.messages().create exactly once, and returns a Flowable that emits exactly one LlmResponse.

Execution:
Arrange: Create an LlmRequest with a non-empty contents() list where each Content has role "user" and parts containing text only. Mock anthropicClient.messages().create to return a valid Message with a simple text ContentBlock.
Act: Call generateContent with stream=false.
Assert: Capture the MessageCreateParams passed to anthropicClient.messages().create and assert that:
- messages contains entries equal in count to LlmRequest.contents().
- each MessageParam contains text blocks reflecting the corresponding input text parts.
- the Flowable emits exactly one LlmResponse and completes.
Validation:
Confirms that text parts are correctly propagated to Anthropic as text message blocks and that the method performs a single, synchronous Anthropic call, then wraps the converted response in a single-emission Flowable.


*/

// ********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.assertThrows;
import static org.mockito.ArgumentMatchers.any;
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.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.MessageParam;
import com.anthropic.models.messages.MessageParam.Role;
import com.anthropic.models.messages.ToolChoice;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.genai.types.Content;
import com.google.genai.types.FunctionCall;
import com.google.genai.types.GenerateContentConfig;
import com.google.genai.types.Part;
import io.reactivex.rxjava3.core.Flowable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.BeforeEach;
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.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
public abstract class ClaudeGenerateContentTest extends BaseLlm {

@Mock private AnthropicClient anthropicClient;

private Claude claude;

public ClaudeGenerateContentTest() {
super("test-base-llm"); // TODO: Change if a specific BaseLlm model name is
// required
}

@BeforeEach
public void setup() {
claude = new Claude("claude-3", anthropicClient); // TODO: Change model name if
// needed
}

@Test
@Tag("valid")
public void buildsMessageParamsFromTextOnlyContents() {
Part p1 = Part.builder().text("Hello").build();
Part p2 = Part.builder().text("World").build();
Content c1 = Content.builder().role("user").parts(ImmutableList.of(p1, p2)).build();
List<Content> inputContents = ImmutableList.of(c1);
LlmRequest llmRequest = org.mockito.Mockito.mock(LlmRequest.class);
when(llmRequest.contents()).thenReturn(inputContents);
when(llmRequest.config()).thenReturn(Optional.empty());
when(llmRequest.tools()).thenReturn(ImmutableMap.of());
when(llmRequest.model()).thenReturn(Optional.of("my-model")); // TODO: Change if
// needed
Message mockMessage = org.mockito.Mockito.mock(Message.class);
when(mockMessage.content()).thenReturn(null);
when(anthropicClient.messages().create(any(MessageCreateParams.class))).thenReturn(mockMessage);
Flowable<LlmResponse> flowable = claude.generateContent(llmRequest, false);
ArgumentCaptor<MessageCreateParams> paramsCaptor =
ArgumentCaptor.forClass(MessageCreateParams.class);
verify(anthropicClient.messages(), times(1)).create(paramsCaptor.capture());
MessageCreateParams params = paramsCaptor.getValue();
List<MessageParam> builtMessages = params.messages();
assertEquals((int) inputContents.size(), (int) builtMessages.size());
// Adjusted to use the actual return type of content() which is a union wrapper,
// not a List
MessageParam.Content content = builtMessages.get(0).content();
assertNotNull(content);
assertEquals(Role.USER, builtMessages.get(0).role());
assertEquals("my-model", params.model());
assertEquals("", params.system());
Optional<ToolChoice> toolChoice = params.toolChoice();
assertEquals(Optional.empty(), toolChoice);
List<LlmResponse> responses = flowable.toList().blockingGet();
assertEquals(1, (int) responses.size());
assertNotNull(responses.get(0));
}

@Test
@Tag("valid")
public void usesDefaultModelWhenRequestModelAbsent() {
Part p1 = Part.builder().text("Sample").build();
Content c1 = Content.builder().role("user").parts(ImmutableList.of(p1)).build();
List<Content> inputContents = ImmutableList.of(c1);
LlmRequest llmRequest = org.mockito.Mockito.mock(LlmRequest.class);
when(llmRequest.contents()).thenReturn(inputContents);
when(llmRequest.config()).thenReturn(Optional.empty());
when(llmRequest.tools()).thenReturn(ImmutableMap.of());
when(llmRequest.model()).thenReturn(Optional.empty());
Message mockMessage = org.mockito.Mockito.mock(Message.class);
when(mockMessage.content()).thenReturn(null);
when(anthropicClient.messages().create(any(MessageCreateParams.class))).thenReturn(mockMessage);
claude.generateContent(llmRequest, false);
ArgumentCaptor<MessageCreateParams> paramsCaptor =
ArgumentCaptor.forClass(MessageCreateParams.class);
verify(anthropicClient.messages(), times(1)).create(paramsCaptor.capture());
MessageCreateParams params = paramsCaptor.getValue();
assertEquals(claude.model(), params.model());
}

@Test
@Tag("valid")
public void extractsSystemInstructionFromConfigParts() {
Part sys1 = Part.builder().text("first line").build();
Part sys2 = Part.builder().text("second line").build();
Content sysContent =
Content.builder().role("system").parts(ImmutableList.of(sys1, sys2)).build();
GenerateContentConfig config = org.mockito.Mockito.mock(GenerateContentConfig.class);
when(config.systemInstruction()).thenReturn(Optional.of(sysContent));
when(config.tools()).thenReturn(Optional.empty());
Part p = Part.builder().text("hello").build();
Content c = Content.builder().role("user").parts(ImmutableList.of(p)).build();
LlmRequest llmRequest = org.mockito.Mockito.mock(LlmRequest.class);
when(llmRequest.contents()).thenReturn(ImmutableList.of(c));
when(llmRequest.config()).thenReturn(Optional.of(config));
when(llmRequest.tools()).thenReturn(ImmutableMap.of());
when(llmRequest.model()).thenReturn(Optional.of("model-x")); // TODO: Change if
// required
Message mockMessage = org.mockito.Mockito.mock(Message.class);
when(mockMessage.content()).thenReturn(null);
when(anthropicClient.messages().create(any(MessageCreateParams.class))).thenReturn(mockMessage);
claude.generateContent(llmRequest, false);
ArgumentCaptor<MessageCreateParams> paramsCaptor =
ArgumentCaptor.forClass(MessageCreateParams.class);
verify(anthropicClient.messages(), times(1)).create(paramsCaptor.capture());
MessageCreateParams params = paramsCaptor.getValue();
assertEquals("first line\nsecond line", params.system());
}

@Test
@Tag("valid")
public void setsToolChoiceWhenToolsProvided() {
Part p = Part.builder().text("hello").build();
Content c = Content.builder().role("user").parts(ImmutableList.of(p)).build();
@SuppressWarnings("unchecked")
Map tools = (Map) ImmutableMap.of("toolA", new Object());
LlmRequest llmRequest = org.mockito.Mockito.mock(LlmRequest.class);
when(llmRequest.contents()).thenReturn(ImmutableList.of(c));
when(llmRequest.config()).thenReturn(Optional.empty());
when(llmRequest.tools()).thenReturn(tools);
when(llmRequest.model()).thenReturn(Optional.of("model-z")); // TODO: Change if
// required
Message mockMessage = org.mockito.Mockito.mock(Message.class);
when(mockMessage.content()).thenReturn(null);
when(anthropicClient.messages().create(any(MessageCreateParams.class))).thenReturn(mockMessage);
claude.generateContent(llmRequest, false);
ArgumentCaptor<MessageCreateParams> paramsCaptor =
ArgumentCaptor.forClass(MessageCreateParams.class);
verify(anthropicClient.messages(), times(1)).create(paramsCaptor.capture());
MessageCreateParams params = paramsCaptor.getValue();
assertEquals(true, (boolean) params.toolChoice().isPresent());
}

@Test
@Tag("boundary")
public void handlesEmptyContentsGracefully() {
LlmRequest llmRequest = org.mockito.Mockito.mock(LlmRequest.class);
when(llmRequest.contents()).thenReturn(ImmutableList.of());
when(llmRequest.config()).thenReturn(Optional.empty());
when(llmRequest.tools()).thenReturn(ImmutableMap.of());
when(llmRequest.model()).thenReturn(Optional.of("model-empty")); // TODO: Change
// if required
Message mockMessage = org.mockito.Mockito.mock(Message.class);
when(mockMessage.content()).thenReturn(null);
when(anthropicClient.messages().create(any(MessageCreateParams.class))).thenReturn(mockMessage);
Flowable<LlmResponse> flowable = claude.generateContent(llmRequest, false);
ArgumentCaptor<MessageCreateParams> paramsCaptor =
ArgumentCaptor.forClass(MessageCreateParams.class);
verify(anthropicClient.messages(), times(1)).create(paramsCaptor.capture());
MessageCreateParams params = paramsCaptor.getValue();
assertEquals(0, (int) params.messages().size());
List<LlmResponse> responses = flowable.toList().blockingGet();
assertEquals(1, (int) responses.size());
assertNotNull(responses.get(0));
}

@Test
@Tag("invalid")
public void propagatesAnthropicClientException() {
Part p = Part.builder().text("error path").build();
Content c = Content.builder().role("user").parts(ImmutableList.of(p)).build();
LlmRequest llmRequest = org.mockito.Mockito.mock(LlmRequest.class);
when(llmRequest.contents()).thenReturn(ImmutableList.of(c));
when(llmRequest.config()).thenReturn(Optional.empty());
when(llmRequest.tools()).thenReturn(ImmutableMap.of());
when(llmRequest.model()).thenReturn(Optional.of("model-ex")); // TODO: Change if
// required
when(anthropicClient.messages().create(any(MessageCreateParams.class)))
.thenThrow(new RuntimeException("Downstream failure"));
assertThrows(RuntimeException.class, () -> claude.generateContent(llmRequest, false));
}

@Test
@Tag("valid")
public void ignoresNonTextSystemPartsWhileExtractingSystemText() {
Part sysText = Part.builder().text("sys-line").build();
FunctionCall nonTextFn = FunctionCall.builder().name("noop").build();
Part sysFnPart = Part.builder().functionCall(nonTextFn).build();
Content sysContent =
Content.builder().role("system").parts(ImmutableList.of(sysFnPart, sysText)).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("user input").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.of("model-sys")); // TODO: Change if
// required
Message mockMessage = org.mockito.Mockito.mock(Message.class);
when(mockMessage.content()).thenReturn(null);
when(anthropicClient.messages().create(any(MessageCreateParams.class))).thenReturn(mockMessage);
claude.generateContent(llmRequest, false);
ArgumentCaptor<MessageCreateParams> paramsCaptor =
ArgumentCaptor.forClass(MessageCreateParams.class);
verify(anthropicClient.messages(), times(1)).create(paramsCaptor.capture());
MessageCreateParams params = paramsCaptor.getValue();
assertEquals("sys-line", params.system());
}
}
Loading