Skip to content

Commit a7ded26

Browse files
committed
add tests for anthropic stream events
Signed-off-by: Dan Sun <dsun20@bloomberg.net>
1 parent a989de9 commit a7ded26

File tree

1 file changed

+105
-0
lines changed

1 file changed

+105
-0
lines changed

internal/extproc/translator/openai_gcpanthropic_stream_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,111 @@ data: {"type":"message_stop"}
296296
require.NotNil(t, bm)
297297
bodyStr := string(bm.GetBody())
298298

299+
// Parse all streaming events to verify the event flow
300+
var chunks []openai.ChatCompletionResponseChunk
301+
var textChunks []string
302+
var toolCallStarted bool
303+
var hasRole bool
304+
var toolCallCompleted bool
305+
var finalFinishReason openai.ChatCompletionChoicesFinishReason
306+
var finalUsageChunk *openai.ChatCompletionResponseChunk
307+
var toolCallChunks []string // Track partial JSON chunks
308+
309+
lines := strings.SplitSeq(strings.TrimSpace(bodyStr), "\n\n")
310+
for line := range lines {
311+
if !strings.HasPrefix(line, "data: ") || strings.Contains(line, "[DONE]") {
312+
continue
313+
}
314+
jsonBody := strings.TrimPrefix(line, "data: ")
315+
316+
var chunk openai.ChatCompletionResponseChunk
317+
err = json.Unmarshal([]byte(jsonBody), &chunk)
318+
require.NoError(t, err, "Failed to unmarshal chunk: %s", jsonBody)
319+
chunks = append(chunks, chunk)
320+
321+
// Check if this is the final usage chunk
322+
if strings.Contains(jsonBody, `"usage"`) {
323+
finalUsageChunk = &chunk
324+
}
325+
326+
if len(chunk.Choices) > 0 {
327+
choice := chunk.Choices[0]
328+
// Check for role in first content chunk
329+
if choice.Delta != nil && choice.Delta.Content != nil && *choice.Delta.Content != "" && !hasRole {
330+
require.NotNil(t, choice.Delta.Role, "Role should be present on first content chunk")
331+
require.Equal(t, openai.ChatMessageRoleAssistant, choice.Delta.Role)
332+
hasRole = true
333+
}
334+
335+
// Collect text content
336+
if choice.Delta != nil && choice.Delta.Content != nil {
337+
textChunks = append(textChunks, *choice.Delta.Content)
338+
}
339+
340+
// Check tool calls - start and accumulate partial JSON
341+
if choice.Delta != nil && len(choice.Delta.ToolCalls) > 0 {
342+
toolCall := choice.Delta.ToolCalls[0]
343+
344+
// Check tool call initiation
345+
if toolCall.Function.Name == "get_weather" && !toolCallStarted {
346+
require.Equal(t, "get_weather", toolCall.Function.Name)
347+
require.NotNil(t, toolCall.ID)
348+
require.Equal(t, "toolu_01T1x1fJ34qAmk2tNTrN7Up6", *toolCall.ID)
349+
require.Equal(t, int64(0), toolCall.Index, "Tool call should be at index 1 (after text content at index 0)")
350+
toolCallStarted = true
351+
}
352+
353+
// Accumulate partial JSON arguments - these should also be at index 1
354+
if toolCall.Function.Arguments != "" {
355+
toolCallChunks = append(toolCallChunks, toolCall.Function.Arguments)
356+
357+
// Verify the index remains consistent at 1 for all tool call chunks
358+
require.Equal(t, int64(0), toolCall.Index, "Tool call argument chunks should be at index 1")
359+
}
360+
}
361+
362+
// Track finish reason
363+
if choice.FinishReason != "" {
364+
finalFinishReason = choice.FinishReason
365+
if finalFinishReason == "tool_calls" {
366+
toolCallCompleted = true
367+
}
368+
}
369+
}
370+
}
371+
372+
// Check the final usage chunk for accumulated tool call arguments
373+
if finalUsageChunk != nil {
374+
require.Equal(t, 472, finalUsageChunk.Usage.PromptTokens)
375+
require.Equal(t, 89, finalUsageChunk.Usage.CompletionTokens)
376+
}
377+
378+
// Verify partial JSON accumulation in streaming chunks
379+
if len(toolCallChunks) > 0 {
380+
// Verify we got multiple partial JSON chunks during streaming
381+
require.True(t, len(toolCallChunks) >= 2, "Should receive multiple partial JSON chunks for tool arguments")
382+
383+
// Verify some expected partial content appears in the chunks
384+
fullPartialJSON := strings.Join(toolCallChunks, "")
385+
require.Contains(t, fullPartialJSON, `"location":`, "Partial JSON should contain location field")
386+
require.Contains(t, fullPartialJSON, `"unit":`, "Partial JSON should contain unit field")
387+
require.Contains(t, fullPartialJSON, "San Francisco", "Partial JSON should contain location value")
388+
require.Contains(t, fullPartialJSON, "fahrenheit", "Partial JSON should contain unit value")
389+
}
390+
391+
// Verify streaming event assertions
392+
require.True(t, len(chunks) >= 5, "Should have multiple streaming chunks")
393+
require.True(t, hasRole, "Should have role in first content chunk")
394+
require.True(t, toolCallStarted, "Tool call should have been initiated")
395+
require.True(t, toolCallCompleted, "Tool call should have complete arguments in final chunk")
396+
require.Equal(t, openai.ChatCompletionChoicesFinishReasonToolCalls, finalFinishReason, "Final finish reason should be tool_calls")
397+
398+
// Verify text content was streamed correctly
399+
fullText := strings.Join(textChunks, "")
400+
require.Contains(t, fullText, "Okay, let's check the weather for San Francisco, CA:")
401+
require.True(t, len(textChunks) >= 3, "Text should be streamed in multiple chunks")
402+
403+
// Original aggregate response assertions
299404
require.Contains(t, bodyStr, `"content":"Okay"`)
300405
require.Contains(t, bodyStr, `"name":"get_weather"`)
301406
require.Contains(t, bodyStr, "\"arguments\":\"{\\\"location\\\":")

0 commit comments

Comments
 (0)