first commit
This commit is contained in:
702
core/schemas/mux_test.go
Normal file
702
core/schemas/mux_test.go
Normal file
@@ -0,0 +1,702 @@
|
||||
package schemas
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestToChatMessages_PreservesDeveloperRole(t *testing.T) {
|
||||
messages := []ResponsesMessage{
|
||||
{
|
||||
Role: Ptr(ResponsesInputMessageRoleDeveloper),
|
||||
Content: &ResponsesMessageContent{
|
||||
ContentBlocks: []ResponsesMessageContentBlock{
|
||||
{
|
||||
Type: ResponsesInputMessageContentBlockTypeText,
|
||||
Text: Ptr("You are helpful"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
chatMessages := ToChatMessages(messages)
|
||||
if len(chatMessages) != 1 {
|
||||
t.Fatalf("expected 1 message, got %d", len(chatMessages))
|
||||
}
|
||||
if chatMessages[0].Role != ChatMessageRoleDeveloper {
|
||||
t.Fatalf("expected role %q, got %q", ChatMessageRoleDeveloper, chatMessages[0].Role)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToChatRequest_NormalizesDeveloperRoleToSystemForFallback(t *testing.T) {
|
||||
req := &BifrostResponsesRequest{
|
||||
Input: []ResponsesMessage{
|
||||
{
|
||||
Role: Ptr(ResponsesInputMessageRoleDeveloper),
|
||||
Content: &ResponsesMessageContent{
|
||||
ContentBlocks: []ResponsesMessageContentBlock{
|
||||
{
|
||||
Type: ResponsesInputMessageContentBlockTypeText,
|
||||
Text: Ptr("You are helpful"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Params: &ResponsesParameters{},
|
||||
}
|
||||
|
||||
chatReq := req.ToChatRequest()
|
||||
if chatReq == nil {
|
||||
t.Fatal("expected non-nil chat request")
|
||||
}
|
||||
if len(chatReq.Input) != 1 {
|
||||
t.Fatalf("expected 1 chat message, got %d", len(chatReq.Input))
|
||||
}
|
||||
if chatReq.Input[0].Role != ChatMessageRoleSystem {
|
||||
t.Fatalf("expected role %q in fallback conversion, got %q", ChatMessageRoleSystem, chatReq.Input[0].Role)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToChatMessages_LeavesExistingSupportedRolesUnchanged(t *testing.T) {
|
||||
messages := []ResponsesMessage{
|
||||
{Role: Ptr(ResponsesInputMessageRoleSystem)},
|
||||
{Role: Ptr(ResponsesInputMessageRoleUser)},
|
||||
{Role: Ptr(ResponsesInputMessageRoleAssistant)},
|
||||
}
|
||||
|
||||
chatMessages := ToChatMessages(messages)
|
||||
if len(chatMessages) != len(messages) {
|
||||
t.Fatalf("expected %d messages, got %d", len(messages), len(chatMessages))
|
||||
}
|
||||
|
||||
if chatMessages[0].Role != ChatMessageRoleSystem {
|
||||
t.Fatalf("expected system role, got %q", chatMessages[0].Role)
|
||||
}
|
||||
if chatMessages[1].Role != ChatMessageRoleUser {
|
||||
t.Fatalf("expected user role, got %q", chatMessages[1].Role)
|
||||
}
|
||||
if chatMessages[2].Role != ChatMessageRoleAssistant {
|
||||
t.Fatalf("expected assistant role, got %q", chatMessages[2].Role)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToChatRequest_FiltersUnsupportedResponsesToolsForFallback(t *testing.T) {
|
||||
validName := "valid_tool"
|
||||
invalidName := " "
|
||||
req := &BifrostResponsesRequest{
|
||||
Params: &ResponsesParameters{
|
||||
Tools: []ResponsesTool{
|
||||
{
|
||||
Type: ResponsesToolTypeFunction,
|
||||
Name: &validName,
|
||||
ResponsesToolFunction: &ResponsesToolFunction{
|
||||
Parameters: &ToolFunctionParameters{
|
||||
Type: "object",
|
||||
Properties: &OrderedMap{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: ResponsesToolTypeFunction,
|
||||
Name: &invalidName,
|
||||
},
|
||||
{
|
||||
Type: ResponsesToolTypeMCP,
|
||||
Name: Ptr("mcp_tool"),
|
||||
},
|
||||
{
|
||||
Type: ResponsesToolTypeWebSearch,
|
||||
Name: Ptr("web_search"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
chatReq := req.ToChatRequest()
|
||||
if chatReq == nil || chatReq.Params == nil {
|
||||
t.Fatal("expected non-nil chat request params")
|
||||
}
|
||||
if len(chatReq.Params.Tools) != 1 {
|
||||
t.Fatalf("expected 1 valid fallback tool, got %d", len(chatReq.Params.Tools))
|
||||
}
|
||||
if chatReq.Params.Tools[0].Type != ChatToolTypeFunction {
|
||||
t.Fatalf("expected tool type %q, got %q", ChatToolTypeFunction, chatReq.Params.Tools[0].Type)
|
||||
}
|
||||
if chatReq.Params.Tools[0].Function == nil || chatReq.Params.Tools[0].Function.Name != validName {
|
||||
t.Fatalf("expected function tool %q to be preserved", validName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToChatRequest_DropsInvalidToolChoiceForFallback(t *testing.T) {
|
||||
validName := "valid_tool"
|
||||
invalidChoiceName := "missing_tool"
|
||||
req := &BifrostResponsesRequest{
|
||||
Params: &ResponsesParameters{
|
||||
Tools: []ResponsesTool{
|
||||
{
|
||||
Type: ResponsesToolTypeFunction,
|
||||
Name: &validName,
|
||||
},
|
||||
},
|
||||
ToolChoice: &ResponsesToolChoice{
|
||||
ResponsesToolChoiceStruct: &ResponsesToolChoiceStruct{
|
||||
Type: ResponsesToolChoiceTypeFunction,
|
||||
Name: &invalidChoiceName,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
chatReq := req.ToChatRequest()
|
||||
if chatReq == nil || chatReq.Params == nil {
|
||||
t.Fatal("expected non-nil chat request params")
|
||||
}
|
||||
if chatReq.Params.ToolChoice != nil {
|
||||
t.Fatal("expected incompatible tool choice to be removed for fallback")
|
||||
}
|
||||
}
|
||||
|
||||
func TestToChatRequest_AllNonFunctionToolsDropsToolsAndToolChoice(t *testing.T) {
|
||||
auto := string(ChatToolChoiceTypeAuto)
|
||||
req := &BifrostResponsesRequest{
|
||||
Params: &ResponsesParameters{
|
||||
Tools: []ResponsesTool{
|
||||
{Type: ResponsesToolTypeMCP, Name: Ptr("mcp")},
|
||||
{Type: ResponsesToolTypeWebSearch, Name: Ptr("search")},
|
||||
},
|
||||
ToolChoice: &ResponsesToolChoice{
|
||||
ResponsesToolChoiceStr: &auto,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
chatReq := req.ToChatRequest()
|
||||
if chatReq == nil || chatReq.Params == nil {
|
||||
t.Fatal("expected non-nil chat request params")
|
||||
}
|
||||
if chatReq.Params.Tools != nil {
|
||||
t.Fatalf("expected nil tools when all tools are unsupported, got %d", len(chatReq.Params.Tools))
|
||||
}
|
||||
if chatReq.Params.ToolChoice != nil {
|
||||
t.Fatal("expected tool choice to be dropped when no valid tools remain")
|
||||
}
|
||||
}
|
||||
|
||||
func TestToChatRequest_DropsAllowedToolsAndCustomToolChoiceForFallback(t *testing.T) {
|
||||
validName := "valid_tool"
|
||||
tests := []ResponsesToolChoiceType{
|
||||
ResponsesToolChoiceTypeAllowedTools,
|
||||
ResponsesToolChoiceTypeCustom,
|
||||
}
|
||||
|
||||
for _, choiceType := range tests {
|
||||
t.Run(string(choiceType), func(t *testing.T) {
|
||||
req := &BifrostResponsesRequest{
|
||||
Params: &ResponsesParameters{
|
||||
Tools: []ResponsesTool{
|
||||
{
|
||||
Type: ResponsesToolTypeFunction,
|
||||
Name: &validName,
|
||||
},
|
||||
},
|
||||
ToolChoice: &ResponsesToolChoice{
|
||||
ResponsesToolChoiceStruct: &ResponsesToolChoiceStruct{
|
||||
Type: choiceType,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
chatReq := req.ToChatRequest()
|
||||
if chatReq == nil || chatReq.Params == nil {
|
||||
t.Fatal("expected non-nil chat request params")
|
||||
}
|
||||
if chatReq.Params.ToolChoice != nil {
|
||||
t.Fatalf("expected %q tool choice to be dropped for fallback", choiceType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToChatRequest_PreservesStringToolChoiceAutoAndNone(t *testing.T) {
|
||||
validName := "valid_tool"
|
||||
tests := []string{
|
||||
string(ChatToolChoiceTypeAuto),
|
||||
string(ChatToolChoiceTypeNone),
|
||||
}
|
||||
|
||||
for _, choice := range tests {
|
||||
t.Run(choice, func(t *testing.T) {
|
||||
req := &BifrostResponsesRequest{
|
||||
Params: &ResponsesParameters{
|
||||
Tools: []ResponsesTool{
|
||||
{
|
||||
Type: ResponsesToolTypeFunction,
|
||||
Name: &validName,
|
||||
},
|
||||
},
|
||||
ToolChoice: &ResponsesToolChoice{
|
||||
ResponsesToolChoiceStr: &choice,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
chatReq := req.ToChatRequest()
|
||||
if chatReq == nil || chatReq.Params == nil {
|
||||
t.Fatal("expected non-nil chat request params")
|
||||
}
|
||||
if chatReq.Params.ToolChoice == nil || chatReq.Params.ToolChoice.ChatToolChoiceStr == nil {
|
||||
t.Fatal("expected string tool choice to be preserved")
|
||||
}
|
||||
if *chatReq.Params.ToolChoice.ChatToolChoiceStr != choice {
|
||||
t.Fatalf("expected tool choice %q, got %q", choice, *chatReq.Params.ToolChoice.ChatToolChoiceStr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToBifrostResponsesStreamResponse_PopulatesFinalDoneTextAndCompletedOutput(t *testing.T) {
|
||||
state := AcquireChatToResponsesStreamState()
|
||||
defer ReleaseChatToResponsesStreamState(state)
|
||||
|
||||
makeChunk := func(role *string, content *string, finishReason *string) *BifrostChatResponse {
|
||||
return &BifrostChatResponse{
|
||||
ID: "chatcmpl-test",
|
||||
Model: "test-model",
|
||||
Choices: []BifrostResponseChoice{
|
||||
{
|
||||
FinishReason: finishReason,
|
||||
ChatStreamResponseChoice: &ChatStreamResponseChoice{
|
||||
Delta: &ChatStreamResponseChoiceDelta{
|
||||
Role: role,
|
||||
Content: content,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
role := string(ChatMessageRoleAssistant)
|
||||
part1 := "Hello"
|
||||
part2 := " world"
|
||||
stop := string(BifrostFinishReasonStop)
|
||||
|
||||
var all []*BifrostResponsesStreamResponse
|
||||
all = append(all, makeChunk(&role, nil, nil).ToBifrostResponsesStreamResponse(state)...)
|
||||
all = append(all, makeChunk(nil, &part1, nil).ToBifrostResponsesStreamResponse(state)...)
|
||||
all = append(all, makeChunk(nil, &part2, nil).ToBifrostResponsesStreamResponse(state)...)
|
||||
all = append(all, makeChunk(nil, nil, &stop).ToBifrostResponsesStreamResponse(state)...)
|
||||
|
||||
var outputTextDone *BifrostResponsesStreamResponse
|
||||
var completed *BifrostResponsesStreamResponse
|
||||
for _, evt := range all {
|
||||
if evt == nil {
|
||||
continue
|
||||
}
|
||||
if evt.Type == ResponsesStreamResponseTypeOutputTextDone {
|
||||
outputTextDone = evt
|
||||
}
|
||||
if evt.Type == ResponsesStreamResponseTypeCompleted {
|
||||
completed = evt
|
||||
}
|
||||
}
|
||||
|
||||
if outputTextDone == nil || outputTextDone.Text == nil {
|
||||
t.Fatal("expected response.output_text.done with text")
|
||||
}
|
||||
if *outputTextDone.Text != "Hello world" {
|
||||
t.Fatalf("expected output_text.done text %q, got %q", "Hello world", *outputTextDone.Text)
|
||||
}
|
||||
|
||||
if completed == nil || completed.Response == nil || len(completed.Response.Output) != 1 {
|
||||
t.Fatal("expected response.completed with one output message")
|
||||
}
|
||||
msg := completed.Response.Output[0]
|
||||
if msg.Content == nil || len(msg.Content.ContentBlocks) == 0 || msg.Content.ContentBlocks[0].Text == nil {
|
||||
t.Fatal("expected completed output message to include text content block")
|
||||
}
|
||||
if *msg.Content.ContentBlocks[0].Text != "Hello world" {
|
||||
t.Fatalf("expected completed output text %q, got %q", "Hello world", *msg.Content.ContentBlocks[0].Text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToBifrostResponsesResponse_MapsLengthToIncomplete(t *testing.T) {
|
||||
length := string(BifrostFinishReasonLength)
|
||||
resp := (&BifrostChatResponse{
|
||||
Choices: []BifrostResponseChoice{
|
||||
{FinishReason: &length},
|
||||
},
|
||||
}).ToBifrostResponsesResponse()
|
||||
|
||||
if resp == nil || resp.Status == nil {
|
||||
t.Fatal("expected status to be set")
|
||||
}
|
||||
if *resp.Status != "incomplete" {
|
||||
t.Fatalf("expected status %q, got %q", "incomplete", *resp.Status)
|
||||
}
|
||||
if resp.IncompleteDetails == nil {
|
||||
t.Fatal("expected incomplete_details to be set")
|
||||
}
|
||||
if resp.IncompleteDetails.Reason != "max_output_tokens" {
|
||||
t.Fatalf("expected incomplete_details.reason %q, got %q", "max_output_tokens", resp.IncompleteDetails.Reason)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToBifrostResponsesResponse_MapsToolCallsToCompleted(t *testing.T) {
|
||||
toolCalls := string(BifrostFinishReasonToolCalls)
|
||||
resp := (&BifrostChatResponse{
|
||||
Choices: []BifrostResponseChoice{
|
||||
{FinishReason: &toolCalls},
|
||||
},
|
||||
}).ToBifrostResponsesResponse()
|
||||
|
||||
if resp == nil || resp.Status == nil {
|
||||
t.Fatal("expected status to be set")
|
||||
}
|
||||
if *resp.Status != "completed" {
|
||||
t.Fatalf("expected status %q, got %q", "completed", *resp.Status)
|
||||
}
|
||||
if resp.IncompleteDetails != nil {
|
||||
t.Fatal("expected incomplete_details to be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestToBifrostResponsesResponse_PrioritizesLengthAcrossChoices(t *testing.T) {
|
||||
stop := string(BifrostFinishReasonStop)
|
||||
length := string(BifrostFinishReasonLength)
|
||||
resp := (&BifrostChatResponse{
|
||||
Choices: []BifrostResponseChoice{
|
||||
{FinishReason: &stop},
|
||||
{FinishReason: &length},
|
||||
},
|
||||
}).ToBifrostResponsesResponse()
|
||||
|
||||
if resp == nil || resp.Status == nil {
|
||||
t.Fatal("expected status to be set")
|
||||
}
|
||||
if *resp.Status != "incomplete" {
|
||||
t.Fatalf("expected status %q, got %q", "incomplete", *resp.Status)
|
||||
}
|
||||
if resp.IncompleteDetails == nil || resp.IncompleteDetails.Reason != "max_output_tokens" {
|
||||
t.Fatal("expected max_output_tokens incomplete_details")
|
||||
}
|
||||
}
|
||||
|
||||
func TestToBifrostResponsesResponse_UnknownFinishReasonLeavesStatusUnset(t *testing.T) {
|
||||
unknown := "content_filter"
|
||||
resp := (&BifrostChatResponse{
|
||||
Choices: []BifrostResponseChoice{
|
||||
{FinishReason: &unknown},
|
||||
},
|
||||
}).ToBifrostResponsesResponse()
|
||||
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response")
|
||||
}
|
||||
if resp.Status != nil {
|
||||
t.Fatalf("expected status to be nil, got %q", *resp.Status)
|
||||
}
|
||||
if resp.IncompleteDetails != nil {
|
||||
t.Fatal("expected incomplete_details to be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestToBifrostResponsesStreamResponse_IncludesFunctionCallsInCompletedOutput(t *testing.T) {
|
||||
state := AcquireChatToResponsesStreamState()
|
||||
defer ReleaseChatToResponsesStreamState(state)
|
||||
|
||||
role := string(ChatMessageRoleAssistant)
|
||||
part1 := "Let me help"
|
||||
toolCallsFinish := string(BifrostFinishReasonToolCalls)
|
||||
funcName := "get_weather"
|
||||
toolCallID := "call_abc123"
|
||||
|
||||
var all []*BifrostResponsesStreamResponse
|
||||
|
||||
// Role chunk
|
||||
all = append(all, (&BifrostChatResponse{
|
||||
ID: "chatcmpl-test",
|
||||
Model: "test-model",
|
||||
Choices: []BifrostResponseChoice{
|
||||
{
|
||||
ChatStreamResponseChoice: &ChatStreamResponseChoice{
|
||||
Delta: &ChatStreamResponseChoiceDelta{
|
||||
Role: &role,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}).ToBifrostResponsesStreamResponse(state)...)
|
||||
|
||||
// Text content chunk
|
||||
all = append(all, (&BifrostChatResponse{
|
||||
ID: "chatcmpl-test",
|
||||
Model: "test-model",
|
||||
Choices: []BifrostResponseChoice{
|
||||
{
|
||||
ChatStreamResponseChoice: &ChatStreamResponseChoice{
|
||||
Delta: &ChatStreamResponseChoiceDelta{
|
||||
Content: &part1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}).ToBifrostResponsesStreamResponse(state)...)
|
||||
|
||||
// Tool call chunk with function name
|
||||
all = append(all, (&BifrostChatResponse{
|
||||
ID: "chatcmpl-test",
|
||||
Model: "test-model",
|
||||
Choices: []BifrostResponseChoice{
|
||||
{
|
||||
ChatStreamResponseChoice: &ChatStreamResponseChoice{
|
||||
Delta: &ChatStreamResponseChoiceDelta{
|
||||
ToolCalls: []ChatAssistantMessageToolCall{
|
||||
{
|
||||
Index: 0,
|
||||
ID: &toolCallID,
|
||||
Function: ChatAssistantMessageToolCallFunction{Name: &funcName, Arguments: `{"city":`},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}).ToBifrostResponsesStreamResponse(state)...)
|
||||
|
||||
// Tool call argument continuation
|
||||
all = append(all, (&BifrostChatResponse{
|
||||
ID: "chatcmpl-test",
|
||||
Model: "test-model",
|
||||
Choices: []BifrostResponseChoice{
|
||||
{
|
||||
ChatStreamResponseChoice: &ChatStreamResponseChoice{
|
||||
Delta: &ChatStreamResponseChoiceDelta{
|
||||
ToolCalls: []ChatAssistantMessageToolCall{
|
||||
{
|
||||
Index: 0,
|
||||
Function: ChatAssistantMessageToolCallFunction{Arguments: `"Paris"}`},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}).ToBifrostResponsesStreamResponse(state)...)
|
||||
|
||||
// Finish with tool_calls
|
||||
all = append(all, (&BifrostChatResponse{
|
||||
ID: "chatcmpl-test",
|
||||
Model: "test-model",
|
||||
Choices: []BifrostResponseChoice{
|
||||
{
|
||||
FinishReason: &toolCallsFinish,
|
||||
ChatStreamResponseChoice: &ChatStreamResponseChoice{
|
||||
Delta: &ChatStreamResponseChoiceDelta{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}).ToBifrostResponsesStreamResponse(state)...)
|
||||
|
||||
var completed *BifrostResponsesStreamResponse
|
||||
for _, evt := range all {
|
||||
if evt != nil && evt.Type == ResponsesStreamResponseTypeCompleted {
|
||||
completed = evt
|
||||
}
|
||||
}
|
||||
|
||||
if completed == nil || completed.Response == nil {
|
||||
t.Fatal("expected response.completed event")
|
||||
}
|
||||
|
||||
output := completed.Response.Output
|
||||
if len(output) < 2 {
|
||||
t.Fatalf("expected at least 2 output items (text + function_call), got %d", len(output))
|
||||
}
|
||||
|
||||
var hasText, hasFunctionCall bool
|
||||
for _, item := range output {
|
||||
if item.Type != nil && *item.Type == ResponsesMessageTypeMessage {
|
||||
hasText = true
|
||||
if item.Content == nil || len(item.Content.ContentBlocks) == 0 || item.Content.ContentBlocks[0].Text == nil {
|
||||
t.Fatal("text message missing content")
|
||||
}
|
||||
if *item.Content.ContentBlocks[0].Text != "Let me help" {
|
||||
t.Fatalf("expected text %q, got %q", "Let me help", *item.Content.ContentBlocks[0].Text)
|
||||
}
|
||||
}
|
||||
if item.Type != nil && *item.Type == ResponsesMessageTypeFunctionCall {
|
||||
hasFunctionCall = true
|
||||
if item.ResponsesToolMessage == nil {
|
||||
t.Fatal("function_call item missing ResponsesToolMessage")
|
||||
}
|
||||
if item.Name == nil || *item.Name != "get_weather" {
|
||||
t.Fatalf("expected function name %q, got %v", "get_weather", item.Name)
|
||||
}
|
||||
if item.Arguments == nil || *item.Arguments != `{"city":"Paris"}` {
|
||||
t.Fatalf("expected arguments %q, got %v", `{"city":"Paris"}`, item.Arguments)
|
||||
}
|
||||
if item.CallID == nil || *item.CallID != toolCallID {
|
||||
t.Fatalf("expected call_id %q, got %v", toolCallID, item.CallID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasText {
|
||||
t.Fatal("expected text message in completed output")
|
||||
}
|
||||
if !hasFunctionCall {
|
||||
t.Fatal("expected function_call item in completed output")
|
||||
}
|
||||
}
|
||||
|
||||
func TestToBifrostResponsesStreamResponse_ToolCallsOnlyInCompletedOutput(t *testing.T) {
|
||||
state := AcquireChatToResponsesStreamState()
|
||||
defer ReleaseChatToResponsesStreamState(state)
|
||||
|
||||
role := string(ChatMessageRoleAssistant)
|
||||
toolCallsFinish := string(BifrostFinishReasonToolCalls)
|
||||
funcName := "get_weather"
|
||||
toolCallID := "call_xyz789"
|
||||
|
||||
var all []*BifrostResponsesStreamResponse
|
||||
|
||||
// Role chunk
|
||||
all = append(all, (&BifrostChatResponse{
|
||||
ID: "chatcmpl-test",
|
||||
Model: "test-model",
|
||||
Choices: []BifrostResponseChoice{
|
||||
{
|
||||
ChatStreamResponseChoice: &ChatStreamResponseChoice{
|
||||
Delta: &ChatStreamResponseChoiceDelta{
|
||||
Role: &role,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}).ToBifrostResponsesStreamResponse(state)...)
|
||||
|
||||
// Tool call chunk (no text content at all)
|
||||
all = append(all, (&BifrostChatResponse{
|
||||
ID: "chatcmpl-test",
|
||||
Model: "test-model",
|
||||
Choices: []BifrostResponseChoice{
|
||||
{
|
||||
ChatStreamResponseChoice: &ChatStreamResponseChoice{
|
||||
Delta: &ChatStreamResponseChoiceDelta{
|
||||
ToolCalls: []ChatAssistantMessageToolCall{
|
||||
{
|
||||
Index: 0,
|
||||
ID: &toolCallID,
|
||||
Function: ChatAssistantMessageToolCallFunction{Name: &funcName, Arguments: `{"q":"test"}`},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}).ToBifrostResponsesStreamResponse(state)...)
|
||||
|
||||
// Finish with tool_calls
|
||||
all = append(all, (&BifrostChatResponse{
|
||||
ID: "chatcmpl-test",
|
||||
Model: "test-model",
|
||||
Choices: []BifrostResponseChoice{
|
||||
{
|
||||
FinishReason: &toolCallsFinish,
|
||||
ChatStreamResponseChoice: &ChatStreamResponseChoice{
|
||||
Delta: &ChatStreamResponseChoiceDelta{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}).ToBifrostResponsesStreamResponse(state)...)
|
||||
|
||||
var completed *BifrostResponsesStreamResponse
|
||||
for _, evt := range all {
|
||||
if evt != nil && evt.Type == ResponsesStreamResponseTypeCompleted {
|
||||
completed = evt
|
||||
}
|
||||
}
|
||||
|
||||
if completed == nil || completed.Response == nil {
|
||||
t.Fatal("expected response.completed event")
|
||||
}
|
||||
|
||||
output := completed.Response.Output
|
||||
if len(output) != 1 {
|
||||
t.Fatalf("expected 1 output item (function_call only), got %d", len(output))
|
||||
}
|
||||
|
||||
item := output[0]
|
||||
if item.Type == nil || *item.Type != ResponsesMessageTypeFunctionCall {
|
||||
t.Fatal("expected function_call type")
|
||||
}
|
||||
if item.ResponsesToolMessage == nil {
|
||||
t.Fatal("function_call item missing ResponsesToolMessage")
|
||||
}
|
||||
if item.Name == nil || *item.Name != "get_weather" {
|
||||
t.Fatalf("expected function name %q, got %v", "get_weather", item.Name)
|
||||
}
|
||||
if item.Arguments == nil || *item.Arguments != `{"q":"test"}` {
|
||||
t.Fatalf("expected arguments %q, got %v", `{"q":"test"}`, item.Arguments)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToBifrostResponsesStreamResponse_MapsLengthToIncompleteEvent(t *testing.T) {
|
||||
state := AcquireChatToResponsesStreamState()
|
||||
defer ReleaseChatToResponsesStreamState(state)
|
||||
|
||||
makeChunk := func(role *string, content *string, finishReason *string) *BifrostChatResponse {
|
||||
return &BifrostChatResponse{
|
||||
ID: "chatcmpl-test",
|
||||
Model: "test-model",
|
||||
Choices: []BifrostResponseChoice{
|
||||
{
|
||||
FinishReason: finishReason,
|
||||
ChatStreamResponseChoice: &ChatStreamResponseChoice{
|
||||
Delta: &ChatStreamResponseChoiceDelta{
|
||||
Role: role,
|
||||
Content: content,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
role := string(ChatMessageRoleAssistant)
|
||||
part := "Hello"
|
||||
length := string(BifrostFinishReasonLength)
|
||||
|
||||
var all []*BifrostResponsesStreamResponse
|
||||
all = append(all, makeChunk(&role, nil, nil).ToBifrostResponsesStreamResponse(state)...)
|
||||
all = append(all, makeChunk(nil, &part, nil).ToBifrostResponsesStreamResponse(state)...)
|
||||
all = append(all, makeChunk(nil, nil, &length).ToBifrostResponsesStreamResponse(state)...)
|
||||
|
||||
var completed *BifrostResponsesStreamResponse
|
||||
var incomplete *BifrostResponsesStreamResponse
|
||||
for _, evt := range all {
|
||||
if evt == nil {
|
||||
continue
|
||||
}
|
||||
if evt.Type == ResponsesStreamResponseTypeCompleted {
|
||||
completed = evt
|
||||
}
|
||||
if evt.Type == ResponsesStreamResponseTypeIncomplete {
|
||||
incomplete = evt
|
||||
}
|
||||
}
|
||||
|
||||
if completed != nil {
|
||||
t.Fatal("did not expect response.completed for finish_reason=length")
|
||||
}
|
||||
if incomplete == nil || incomplete.Response == nil {
|
||||
t.Fatal("expected response.incomplete with response payload")
|
||||
}
|
||||
if incomplete.Response.Status == nil || *incomplete.Response.Status != "incomplete" {
|
||||
t.Fatal("expected terminal response status to be incomplete")
|
||||
}
|
||||
if incomplete.Response.IncompleteDetails == nil || incomplete.Response.IncompleteDetails.Reason != "max_output_tokens" {
|
||||
t.Fatal("expected incomplete_details.reason to be max_output_tokens")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user