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,271 @@
/*
* 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 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.TextBlockParam;
import com.fasterxml.jackson.core.type.TypeReference;
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 io.reactivex.rxjava3.subscribers.TestSubscriber;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
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.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.*;
import com.anthropic.models.messages.ContentBlock;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.Tool;
import com.anthropic.models.messages.ToolChoice;
import com.anthropic.models.messages.ToolChoiceAuto;
import com.anthropic.models.messages.ToolResultBlockParam;
import com.anthropic.models.messages.ToolUnion;
import com.anthropic.models.messages.ToolUseBlockParam;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.google.genai.types.FunctionCall;
import com.google.genai.types.FunctionDeclaration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Tests for Claude.generateContent method.
*/
@ExtendWith(MockitoExtension.class)
public abstract class ClaudeGenerateContentTest extends BaseLlm {

@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private AnthropicClient anthropicClient;

@Captor
private ArgumentCaptor<MessageCreateParams> paramsCaptor;

private Claude claude;

public ClaudeGenerateContentTest() {
super("test-model"); // TODO: Update model name if needed for your environment
}

@BeforeEach
public void setUp() {
// Default maxTokens provided via constructor for predictability
this.claude = new Claude("test-model", anthropicClient, 1024);
}

@Test
@Tag("valid")
public void public void testBuildsMessageParamsFromTextOnlyContents() {
// Arrange
List<Part> parts1 = ImmutableList.of(
Part.builder().text("Hello").build(),
Part.builder().text("How are you?").build()
);
List<Part> parts2 = ImmutableList.of(
Part.builder().text("Second message").build()
);
Content content1 = Content.builder().role("user").parts(ImmutableList.copyOf(parts1)).build();
Content content2 = Content.builder().role("user").parts(ImmutableList.copyOf(parts2)).build();
LlmRequest llmRequest = LlmRequest.builder()
.model("test-model")
.contents(ImmutableList.of(content1, content2))
.build();
Message mockedMessage = mock(Message.class);
when(anthropicClient.messages().create(any(MessageCreateParams.class))).thenReturn(mockedMessage);
// Act
Flowable<LlmResponse> flowable = claude.generateContent(llmRequest, false);
// Assert
verify(anthropicClient.messages(), times(1)).create(paramsCaptor.capture());
MessageCreateParams capturedParams = paramsCaptor.getValue();
// Validate that the number of messages matches the number of contents
List<MessageParam> messageParams = capturedParams.messages();
assertEquals(2, (int) messageParams.size(), "Expected two message params to be created");
// Validate that each MessageParam contains corresponding text blocks
List<List<String>> expectedTexts = new ArrayList<>();
expectedTexts.add(parts1.stream().map(p -> p.text().orElse("")).collect(Collectors.toList()));
expectedTexts.add(parts2.stream().map(p -> p.text().orElse("")).collect(Collectors.toList()));
for (int i = 0; i < messageParams.size(); i++) {
MessageParam mp = messageParams.get(i);
// Role should map to USER for "user"
assertEquals(Role.USER, mp.role(), "Expected role USER for user content");
// Extract text values from content blocks
List<String> actualTexts = mp.content().stream()
.filter(cb -> cb.isText())
.map(cb -> cb.asText())
.map(TextBlockParam::text)
.collect(Collectors.toList());
assertEquals(expectedTexts.get(i), actualTexts, "Text blocks should match input parts");
}
// System text should be empty since not provided
assertEquals("", capturedParams.system(), "Expected empty system text when not provided");
// Flowable should emit exactly one LlmResponse and complete
TestSubscriber<LlmResponse> ts = new TestSubscriber<>();
flowable.subscribe(ts);
ts.assertNoErrors();
ts.assertValueCount(1);
ts.assertComplete();
// Additional sanity: emitted LlmResponse object is non-null
List<LlmResponse> emitted = ts.values();
assertNotNull(emitted.get(0), "Emitted LlmResponse should not be null");
}

@Test
@Tag("boundary")
public void public void testHandlesEmptyContentsAndCallsAnthropicOnce() {
// Arrange
LlmRequest llmRequest = LlmRequest.builder()
.model("test-model")
.contents(ImmutableList.of())
.build();
Message mockedMessage = mock(Message.class);
when(anthropicClient.messages().create(any(MessageCreateParams.class))).thenReturn(mockedMessage);
// Act
Flowable<LlmResponse> flowable = claude.generateContent(llmRequest, false);
// Assert
verify(anthropicClient.messages(), times(1)).create(paramsCaptor.capture());
MessageCreateParams capturedParams = paramsCaptor.getValue();
assertNotNull(capturedParams, "Captured params should not be null");
assertEquals(0, (int) capturedParams.messages().size(), "Expected zero message params for empty contents");
TestSubscriber<LlmResponse> ts = new TestSubscriber<>();
flowable.subscribe(ts);
ts.assertNoErrors();
ts.assertValueCount(1);
ts.assertComplete();
}

@Test
@Tag("valid")
public void public void testAppliesSystemInstructionWhenPresent() {
// Arrange
Content systemInstruction = Content.builder()
.parts(ImmutableList.of(
Part.builder().text("You are a helpful system").build(),
Part.builder().text("Follow the rules").build()
))
.build();
GenerateContentConfig config = GenerateContentConfig.builder()
.systemInstruction(systemInstruction)
.build();
Content userContent = Content.builder()
.role("user")
.parts(ImmutableList.of(Part.builder().text("Tell me a joke").build()))
.build();
LlmRequest llmRequest = LlmRequest.builder()
.model("test-model")
.config(config)
.contents(ImmutableList.of(userContent))
.build();
Message mockedMessage = mock(Message.class);
when(anthropicClient.messages().create(any(MessageCreateParams.class))).thenReturn(mockedMessage);
// Act
Flowable<LlmResponse> flowable = claude.generateContent(llmRequest, false);
// Assert
verify(anthropicClient.messages(), times(1)).create(paramsCaptor.capture());
MessageCreateParams capturedParams = paramsCaptor.getValue();
String expectedSystem = "You are a helpful system\nFollow the rules";
assertEquals(expectedSystem, capturedParams.system(), "System instruction should be correctly concatenated");
TestSubscriber<LlmResponse> ts = new TestSubscriber<>();
flowable.subscribe(ts);
ts.assertNoErrors();
ts.assertValueCount(1);
ts.assertComplete();
}

@Test
@Tag("valid")
public void public void testRoleMappingAssistantAndModelToAssistant() {
// Arrange
Content assistantContent = Content.builder()
.role("assistant")
.parts(ImmutableList.of(Part.builder().text("Assistant reply").build()))
.build();
Content modelContent = Content.builder()
.role("model")
.parts(ImmutableList.of(Part.builder().text("Model style reply").build()))
.build();
LlmRequest llmRequest = LlmRequest.builder()
.model("test-model")
.contents(ImmutableList.of(assistantContent, modelContent))
.build();
Message mockedMessage = mock(Message.class);
when(anthropicClient.messages().create(any(MessageCreateParams.class))).thenReturn(mockedMessage);
// Act
Flowable<LlmResponse> flowable = claude.generateContent(llmRequest, false);
// Assert
verify(anthropicClient.messages(), times(1)).create(paramsCaptor.capture());
MessageCreateParams capturedParams = paramsCaptor.getValue();
List<MessageParam> messageParams = capturedParams.messages();
assertEquals(2, (int) messageParams.size(), "Expected two message params");
for (MessageParam mp : messageParams) {
assertEquals(Role.ASSISTANT, mp.role(), "assistant/model roles should map to ASSISTANT");
}
TestSubscriber<LlmResponse> ts = new TestSubscriber<>();
flowable.subscribe(ts);
ts.assertNoErrors();
ts.assertValueCount(1);
ts.assertComplete();
}

}
Loading