703 lines
20 KiB
Go
703 lines
20 KiB
Go
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")
|
|
}
|
|
}
|