Skip to content

Commit c0b8a0e

Browse files
committed
test(Gemini API): update tests for response parsing and structured output handling
Signed-off-by: liuhy <liuhongyu@apache.org>
1 parent ac585d9 commit c0b8a0e

8 files changed

Lines changed: 984 additions & 308 deletions

File tree

agentscope-core/src/test/java/io/agentscope/core/formatter/anthropic/AnthropicResponseParserTest.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
package io.agentscope.core.formatter.anthropic;
1717

1818
import static org.junit.jupiter.api.Assertions.assertEquals;
19-
import static org.junit.jupiter.api.Assertions.assertFalse;
2019
import static org.junit.jupiter.api.Assertions.assertNotNull;
2120
import static org.junit.jupiter.api.Assertions.assertNull;
2221
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -349,8 +348,7 @@ void testParseStreamEventUnknownType() throws Exception {
349348
ChatResponse response = invokeParseStreamEvent(event, startTime);
350349

351350
assertNotNull(response);
352-
assertNotNull(response.getId()); // Builder auto-generates UUID when id is null
353-
assertFalse(response.getId().isEmpty());
351+
assertNull(response.getId());
354352
assertTrue(response.getContent().isEmpty());
355353
assertNull(response.getUsage());
356354
}

agentscope-core/src/test/java/io/agentscope/core/formatter/gemini/GeminiMessageConverterTest.java

Lines changed: 0 additions & 243 deletions
Original file line numberDiff line numberDiff line change
@@ -16,33 +16,23 @@
1616
package io.agentscope.core.formatter.gemini;
1717

1818
import static org.junit.jupiter.api.Assertions.assertEquals;
19-
import static org.junit.jupiter.api.Assertions.assertFalse;
2019
import static org.junit.jupiter.api.Assertions.assertNotNull;
21-
import static org.junit.jupiter.api.Assertions.assertNull;
22-
import static org.junit.jupiter.api.Assertions.assertThrows;
2320
import static org.junit.jupiter.api.Assertions.assertTrue;
24-
import static org.mockito.Mockito.mock;
25-
import static org.mockito.Mockito.when;
2621

2722
import io.agentscope.core.formatter.gemini.dto.GeminiContent;
2823
import io.agentscope.core.formatter.gemini.dto.GeminiPart;
2924
import io.agentscope.core.message.AudioBlock;
3025
import io.agentscope.core.message.Base64Source;
31-
import io.agentscope.core.message.ContentBlock;
3226
import io.agentscope.core.message.ImageBlock;
3327
import io.agentscope.core.message.Msg;
3428
import io.agentscope.core.message.MsgRole;
35-
import io.agentscope.core.message.Source;
3629
import io.agentscope.core.message.TextBlock;
3730
import io.agentscope.core.message.ThinkingBlock;
3831
import io.agentscope.core.message.ToolResultBlock;
3932
import io.agentscope.core.message.ToolUseBlock;
4033
import io.agentscope.core.message.URLSource;
4134
import io.agentscope.core.message.VideoBlock;
42-
import java.lang.reflect.InvocationTargetException;
43-
import java.lang.reflect.Method;
4435
import java.util.ArrayList;
45-
import java.util.Arrays;
4636
import java.util.Base64;
4737
import java.util.HashMap;
4838
import java.util.List;
@@ -72,8 +62,6 @@ class GeminiMessageConverterTest {
7262

7363
private GeminiMessageConverter converter;
7464

75-
private static class DummySource extends Source {}
76-
7765
@BeforeEach
7866
void setUp() {
7967
converter = new GeminiMessageConverter();
@@ -953,235 +941,4 @@ void testToolCallFallbackToInputMapWhenContentInvalidJson() {
953941
assertEquals("Tokyo", args.get("city"));
954942
assertEquals("celsius", args.get("unit"));
955943
}
956-
957-
@Test
958-
@DisplayName("Should convert synthetic tool call to text part")
959-
void testConvertSyntheticToolUseBlockToText() {
960-
ToolUseBlock syntheticToolCall =
961-
ToolUseBlock.builder()
962-
.id("call_syn")
963-
.name("generate_response")
964-
.input(Map.of("k", "v"))
965-
.metadata(Map.of("synthetic", true))
966-
.build();
967-
968-
Msg msg =
969-
Msg.builder()
970-
.name("assistant")
971-
.role(MsgRole.ASSISTANT)
972-
.content(List.of(syntheticToolCall))
973-
.build();
974-
975-
List<GeminiContent> result = converter.convertMessages(List.of(msg));
976-
977-
assertEquals(1, result.size());
978-
GeminiPart part = result.get(0).getParts().get(0);
979-
assertNull(part.getFunctionCall());
980-
assertEquals("[Synthetic tool call: generate_response]", part.getText());
981-
}
982-
983-
@Test
984-
@DisplayName("Should carry thinking signature from ThinkingBlock to next tool call")
985-
void testToolCallUsesPrecedingThinkingSignature() {
986-
ThinkingBlock thinking =
987-
ThinkingBlock.builder().metadata(Map.of("thoughtSignature", "sig_pre")).build();
988-
ToolUseBlock toolUse =
989-
ToolUseBlock.builder()
990-
.id("call_pre_sig")
991-
.name("search")
992-
.input(Map.of("q", "hello"))
993-
.build();
994-
995-
Msg msg =
996-
Msg.builder()
997-
.name("assistant")
998-
.role(MsgRole.ASSISTANT)
999-
.content(List.of(thinking, toolUse))
1000-
.build();
1001-
1002-
List<GeminiContent> result = converter.convertMessages(List.of(msg));
1003-
1004-
assertEquals(1, result.size());
1005-
GeminiPart part = result.get(0).getParts().get(0);
1006-
assertNotNull(part.getFunctionCall());
1007-
assertEquals("sig_pre", part.getThoughtSignature());
1008-
}
1009-
1010-
@Test
1011-
@DisplayName("Should prefer metadata thought signature over preceding thinking signature")
1012-
void testToolCallMetadataSignatureOverridesThinkingSignature() {
1013-
ThinkingBlock thinking =
1014-
ThinkingBlock.builder()
1015-
.metadata(Map.of("thoughtSignature", "sig_from_thinking"))
1016-
.build();
1017-
ToolUseBlock toolUse =
1018-
ToolUseBlock.builder()
1019-
.id("call_meta_sig")
1020-
.name("search")
1021-
.input(Map.of("q", "hello"))
1022-
.metadata(Map.of(ToolUseBlock.METADATA_THOUGHT_SIGNATURE, "sig_from_tool"))
1023-
.build();
1024-
1025-
Msg msg =
1026-
Msg.builder()
1027-
.name("assistant")
1028-
.role(MsgRole.ASSISTANT)
1029-
.content(List.of(thinking, toolUse))
1030-
.build();
1031-
1032-
List<GeminiContent> result = converter.convertMessages(List.of(msg));
1033-
1034-
GeminiPart part = result.get(0).getParts().get(0);
1035-
assertEquals("sig_from_tool", part.getThoughtSignature());
1036-
}
1037-
1038-
@Test
1039-
@DisplayName("Should convert byte array thought signature metadata to base64")
1040-
void testToolCallByteArrayThoughtSignature() {
1041-
byte[] signatureBytes = new byte[] {1, 2, 3, 4};
1042-
ToolUseBlock toolUse =
1043-
ToolUseBlock.builder()
1044-
.id("call_byte_sig")
1045-
.name("search")
1046-
.input(Map.of("q", "hello"))
1047-
.metadata(Map.of(ToolUseBlock.METADATA_THOUGHT_SIGNATURE, signatureBytes))
1048-
.build();
1049-
1050-
Msg msg =
1051-
Msg.builder()
1052-
.name("assistant")
1053-
.role(MsgRole.ASSISTANT)
1054-
.content(List.of(toolUse))
1055-
.build();
1056-
1057-
List<GeminiContent> result = converter.convertMessages(List.of(msg));
1058-
GeminiPart part = result.get(0).getParts().get(0);
1059-
1060-
assertEquals(
1061-
Base64.getEncoder().encodeToString(signatureBytes), part.getThoughtSignature());
1062-
}
1063-
1064-
@Test
1065-
@DisplayName("Should not merge same-role contents when function call is present")
1066-
void testSameRoleNotMergedWhenFunctionExists() {
1067-
ToolUseBlock toolUse =
1068-
ToolUseBlock.builder()
1069-
.id("call_merge_guard")
1070-
.name("search")
1071-
.input(Map.of("q", "hello"))
1072-
.build();
1073-
1074-
Msg assistantToolMsg =
1075-
Msg.builder()
1076-
.name("assistant")
1077-
.role(MsgRole.ASSISTANT)
1078-
.content(List.of(toolUse))
1079-
.build();
1080-
Msg assistantTextMsg =
1081-
Msg.builder()
1082-
.name("assistant")
1083-
.role(MsgRole.ASSISTANT)
1084-
.content(List.of(TextBlock.builder().text("after tool").build()))
1085-
.build();
1086-
1087-
List<GeminiContent> result =
1088-
converter.convertMessages(List.of(assistantToolMsg, assistantTextMsg));
1089-
1090-
assertEquals(2, result.size());
1091-
assertFalse(result.get(0).getParts().isEmpty());
1092-
assertNotNull(result.get(0).getParts().get(0).getFunctionCall());
1093-
assertEquals("after tool", result.get(1).getParts().get(0).getText());
1094-
}
1095-
1096-
@Test
1097-
@DisplayName("Should fallback to input map when content parses to null")
1098-
void testToolCallFallbackToInputMapWhenContentParsesToNull() {
1099-
Map<String, Object> inputMap = new HashMap<>();
1100-
inputMap.put("city", "Shenzhen");
1101-
inputMap.put("unit", "celsius");
1102-
1103-
ToolUseBlock toolBlock =
1104-
ToolUseBlock.builder()
1105-
.id("call_null_json_test")
1106-
.name("get_weather")
1107-
.input(inputMap)
1108-
.content("null")
1109-
.build();
1110-
1111-
Msg msg =
1112-
Msg.builder()
1113-
.name("assistant")
1114-
.content(List.of(toolBlock))
1115-
.role(MsgRole.ASSISTANT)
1116-
.build();
1117-
1118-
List<GeminiContent> result = converter.convertMessages(List.of(msg));
1119-
GeminiPart part = result.get(0).getParts().get(0);
1120-
assertNotNull(part.getFunctionCall());
1121-
assertEquals("Shenzhen", part.getFunctionCall().getArgs().get("city"));
1122-
assertEquals("celsius", part.getFunctionCall().getArgs().get("unit"));
1123-
}
1124-
1125-
@Test
1126-
@DisplayName("Should return unsupported source placeholder for unknown source type")
1127-
void testToolResultWithUnsupportedSourceType() {
1128-
ImageBlock imageBlock = ImageBlock.builder().source(new DummySource()).build();
1129-
1130-
String output = converter.convertToolResultToString(List.of(imageBlock));
1131-
1132-
assertEquals("[image - unsupported source type]", output);
1133-
}
1134-
1135-
@Test
1136-
@DisplayName("Should support base64 media type without slash extension")
1137-
void testToolResultWithBase64MediaTypeWithoutSlash() {
1138-
String base64Data = Base64.getEncoder().encodeToString("fake image".getBytes());
1139-
ImageBlock imageBlock =
1140-
ImageBlock.builder()
1141-
.source(Base64Source.builder().mediaType("png").data(base64Data).build())
1142-
.build();
1143-
1144-
String output = converter.convertToolResultToString(List.of(imageBlock));
1145-
1146-
assertTrue(output.contains(".png"));
1147-
}
1148-
1149-
@Test
1150-
@DisplayName("Should return false for hasFunction helper with null values")
1151-
void testHasFunctionHelperWithNullValues() throws Exception {
1152-
Method method =
1153-
GeminiMessageConverter.class.getDeclaredMethod("hasFunction", GeminiContent.class);
1154-
method.setAccessible(true);
1155-
1156-
assertEquals(false, method.invoke(converter, new Object[] {null}));
1157-
assertEquals(false, method.invoke(converter, new GeminiContent("user", null)));
1158-
}
1159-
1160-
@Test
1161-
@DisplayName("Should throw for extractSourceFromBlock with unsupported block type")
1162-
void testExtractSourceFromBlockUnsupportedType() throws Exception {
1163-
Method method =
1164-
GeminiMessageConverter.class.getDeclaredMethod(
1165-
"extractSourceFromBlock", ContentBlock.class);
1166-
method.setAccessible(true);
1167-
1168-
try {
1169-
method.invoke(converter, TextBlock.builder().text("not media").build());
1170-
} catch (InvocationTargetException e) {
1171-
assertTrue(e.getCause() instanceof IllegalArgumentException);
1172-
assertTrue(e.getCause().getMessage().contains("Unsupported block type"));
1173-
return;
1174-
}
1175-
throw new AssertionError("Expected IllegalArgumentException");
1176-
}
1177-
1178-
@Test
1179-
@DisplayName("Should throw when mocked message returns null content block")
1180-
void testConvertMessageWithMockedNullContentBlockThrows() {
1181-
Msg mocked = mock(Msg.class);
1182-
when(mocked.getRole()).thenReturn(MsgRole.USER);
1183-
when(mocked.getContent()).thenReturn(Arrays.asList((ContentBlock) null));
1184-
1185-
assertThrows(NullPointerException.class, () -> converter.convertMessages(List.of(mocked)));
1186-
}
1187944
}

0 commit comments

Comments
 (0)