Skip to content

Commit 77ef93a

Browse files
committed
test(openai): cover streaming tool call edge cases
1 parent ba83f93 commit 77ef93a

1 file changed

Lines changed: 106 additions & 0 deletions

File tree

agentscope-core/src/test/java/io/agentscope/core/formatter/openai/OpenAIStreamingToolCallTest.java

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,4 +371,110 @@ void testToolCallWithNullArguments() {
371371
assertEquals("call_null", toolUse.getId());
372372
assertEquals("null_args_tool", toolUse.getName());
373373
}
374+
375+
@Test
376+
@DisplayName("Should generate streaming ID when first chunk has no ID")
377+
void testStreamingToolCallGeneratesIdWhenMissing() {
378+
OpenAIResponse response = new OpenAIResponse();
379+
response.setId("chatcmpl-tool");
380+
response.setObject("chat.completion.chunk");
381+
382+
OpenAIChoice choice = new OpenAIChoice();
383+
choice.setIndex(0);
384+
385+
OpenAIMessage delta = new OpenAIMessage();
386+
OpenAIToolCall toolCall = new OpenAIToolCall();
387+
toolCall.setId(null);
388+
toolCall.setIndex(0);
389+
toolCall.setType("function");
390+
391+
OpenAIFunction function = new OpenAIFunction();
392+
function.setName("lookup_weather");
393+
function.setArguments("");
394+
toolCall.setFunction(function);
395+
396+
delta.setToolCalls(List.of(toolCall));
397+
choice.setDelta(delta);
398+
response.setChoices(List.of(choice));
399+
400+
ChatResponse chatResponse = parser.parseResponse(response, Instant.now());
401+
402+
assertNotNull(chatResponse);
403+
ToolUseBlock toolUse = (ToolUseBlock) chatResponse.getContent().get(0);
404+
assertTrue(toolUse.getId().startsWith("streaming_"));
405+
assertEquals("lookup_weather", toolUse.getName());
406+
assertTrue(toolUse.getInput().isEmpty());
407+
}
408+
409+
@Test
410+
@DisplayName("Should keep raw content when complete streaming JSON is malformed")
411+
void testStreamingToolCallMalformedCompleteJsonKeepsRawContent() {
412+
OpenAIResponse response = new OpenAIResponse();
413+
response.setId("chatcmpl-tool");
414+
response.setObject("chat.completion.chunk");
415+
416+
OpenAIChoice choice = new OpenAIChoice();
417+
choice.setIndex(0);
418+
419+
OpenAIMessage delta = new OpenAIMessage();
420+
OpenAIToolCall toolCall = new OpenAIToolCall();
421+
toolCall.setId("call_bad_json");
422+
toolCall.setIndex(0);
423+
toolCall.setType("function");
424+
425+
OpenAIFunction function = new OpenAIFunction();
426+
function.setName("broken_tool");
427+
function.setArguments("{\"city\":}");
428+
toolCall.setFunction(function);
429+
430+
delta.setToolCalls(List.of(toolCall));
431+
choice.setDelta(delta);
432+
response.setChoices(List.of(choice));
433+
434+
ChatResponse chatResponse = parser.parseResponse(response, Instant.now());
435+
436+
assertNotNull(chatResponse);
437+
ToolUseBlock toolUse = (ToolUseBlock) chatResponse.getContent().get(0);
438+
assertEquals("call_bad_json", toolUse.getId());
439+
assertEquals("broken_tool", toolUse.getName());
440+
assertEquals("{\"city\":}", toolUse.getContent());
441+
assertTrue(toolUse.getInput().isEmpty());
442+
}
443+
444+
@Test
445+
@DisplayName("Should emit placeholder fragment when chunk only carries thought signature")
446+
void testStreamingToolCallThoughtSignatureOnlyFragment() {
447+
OpenAIResponse response = new OpenAIResponse();
448+
response.setId("chatcmpl-tool");
449+
response.setObject("chat.completion.chunk");
450+
451+
OpenAIChoice choice = new OpenAIChoice();
452+
choice.setIndex(0);
453+
454+
OpenAIMessage delta = new OpenAIMessage();
455+
OpenAIToolCall toolCall = new OpenAIToolCall();
456+
toolCall.setId("call_sig_only");
457+
toolCall.setIndex(0);
458+
toolCall.setType("function");
459+
toolCall.setThoughtSignature("signature_only");
460+
461+
OpenAIFunction function = new OpenAIFunction();
462+
function.setName(null);
463+
function.setArguments("");
464+
toolCall.setFunction(function);
465+
466+
delta.setToolCalls(List.of(toolCall));
467+
choice.setDelta(delta);
468+
response.setChoices(List.of(choice));
469+
470+
ChatResponse chatResponse = parser.parseResponse(response, Instant.now());
471+
472+
assertNotNull(chatResponse);
473+
ToolUseBlock toolUse = (ToolUseBlock) chatResponse.getContent().get(0);
474+
assertEquals(OpenAIResponseParser.FRAGMENT_PLACEHOLDER, toolUse.getName());
475+
assertEquals("", toolUse.getId());
476+
assertEquals(
477+
"signature_only",
478+
toolUse.getMetadata().get(ToolUseBlock.METADATA_THOUGHT_SIGNATURE));
479+
}
374480
}

0 commit comments

Comments
 (0)