199 lines
6.9 KiB
Go
199 lines
6.9 KiB
Go
package azure_test
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/maximhq/bifrost/core/providers/openai"
|
|
"github.com/maximhq/bifrost/core/schemas"
|
|
)
|
|
|
|
// TestAzure_OpenAIModel_CachingDeterminism verifies that Azure's delegation to
|
|
// openai.ToOpenAIChatRequest() produces deterministic JSON for prompt caching.
|
|
// Two schemas with the same properties but different structural key order within
|
|
// property definitions must produce byte-identical JSON after normalization.
|
|
func TestAzure_OpenAIModel_CachingDeterminism(t *testing.T) {
|
|
makeReq := func(props *schemas.OrderedMap) *schemas.BifrostChatRequest {
|
|
return &schemas.BifrostChatRequest{
|
|
Provider: schemas.Azure,
|
|
Model: "gpt-4o",
|
|
Input: []schemas.ChatMessage{{Role: schemas.ChatMessageRoleUser}},
|
|
Params: &schemas.ChatParameters{
|
|
Tools: []schemas.ChatTool{{
|
|
Type: schemas.ChatToolTypeFunction,
|
|
Function: &schemas.ChatToolFunction{
|
|
Name: "test",
|
|
Parameters: &schemas.ToolFunctionParameters{
|
|
Type: "object",
|
|
Properties: props,
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
}
|
|
}
|
|
|
|
// Version A: type before description
|
|
propsA := schemas.NewOrderedMapFromPairs(
|
|
schemas.KV("reasoning", schemas.NewOrderedMapFromPairs(
|
|
schemas.KV("type", "string"),
|
|
schemas.KV("description", "Step by step"),
|
|
)),
|
|
schemas.KV("answer", schemas.NewOrderedMapFromPairs(
|
|
schemas.KV("type", "string"),
|
|
schemas.KV("description", "Final answer"),
|
|
)),
|
|
)
|
|
|
|
// Version B: description before type (different structural order)
|
|
propsB := schemas.NewOrderedMapFromPairs(
|
|
schemas.KV("reasoning", schemas.NewOrderedMapFromPairs(
|
|
schemas.KV("description", "Step by step"),
|
|
schemas.KV("type", "string"),
|
|
)),
|
|
schemas.KV("answer", schemas.NewOrderedMapFromPairs(
|
|
schemas.KV("description", "Final answer"),
|
|
schemas.KV("type", "string"),
|
|
)),
|
|
)
|
|
|
|
// Azure delegates OpenAI models to openai.ToOpenAIChatRequest()
|
|
ctx, cancel := schemas.NewBifrostContextWithCancel(nil)
|
|
defer cancel()
|
|
resultA := openai.ToOpenAIChatRequest(ctx, makeReq(propsA))
|
|
resultB := openai.ToOpenAIChatRequest(ctx, makeReq(propsB))
|
|
|
|
jsonA, err := schemas.Marshal(resultA.ChatParameters.Tools[0].Function.Parameters)
|
|
if err != nil {
|
|
t.Fatalf("failed to marshal params A: %v", err)
|
|
}
|
|
jsonB, err := schemas.Marshal(resultB.ChatParameters.Tools[0].Function.Parameters)
|
|
if err != nil {
|
|
t.Fatalf("failed to marshal params B: %v", err)
|
|
}
|
|
|
|
// Caching: byte-identical JSON
|
|
if string(jsonA) != string(jsonB) {
|
|
t.Errorf("caching broken via Azure→OpenAI path: same schema produced different JSON\nA: %s\nB: %s", jsonA, jsonB)
|
|
}
|
|
|
|
// CoT: property order preserved
|
|
keys := resultA.ChatParameters.Tools[0].Function.Parameters.Properties.Keys()
|
|
if len(keys) != 2 || keys[0] != "reasoning" || keys[1] != "answer" {
|
|
t.Errorf("expected property order [reasoning, answer], got %v", keys)
|
|
}
|
|
}
|
|
|
|
// TestAzure_OpenAIModel_PreservesPropertyOrder verifies that the Azure→OpenAI
|
|
// delegation path preserves user-defined property ordering.
|
|
func TestAzure_OpenAIModel_PreservesPropertyOrder(t *testing.T) {
|
|
bifrostReq := &schemas.BifrostChatRequest{
|
|
Provider: schemas.Azure,
|
|
Model: "gpt-4o",
|
|
Input: []schemas.ChatMessage{{Role: schemas.ChatMessageRoleUser}},
|
|
Params: &schemas.ChatParameters{
|
|
Tools: []schemas.ChatTool{{
|
|
Type: schemas.ChatToolTypeFunction,
|
|
Function: &schemas.ChatToolFunction{
|
|
Name: "AnswerResponseModel",
|
|
Parameters: &schemas.ToolFunctionParameters{
|
|
Type: "object",
|
|
Properties: schemas.NewOrderedMapFromPairs(
|
|
schemas.KV("chain_of_thought", schemas.NewOrderedMapFromPairs(schemas.KV("type", "string"))),
|
|
schemas.KV("answer", schemas.NewOrderedMapFromPairs(schemas.KV("type", "string"))),
|
|
schemas.KV("citations", schemas.NewOrderedMapFromPairs(schemas.KV("type", "array"))),
|
|
),
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
}
|
|
|
|
ctx, cancel := schemas.NewBifrostContextWithCancel(nil)
|
|
defer cancel()
|
|
result := openai.ToOpenAIChatRequest(ctx, bifrostReq)
|
|
|
|
keys := result.ChatParameters.Tools[0].Function.Parameters.Properties.Keys()
|
|
if len(keys) != 3 || keys[0] != "chain_of_thought" || keys[1] != "answer" || keys[2] != "citations" {
|
|
t.Errorf("expected property order [chain_of_thought, answer, citations], got %v", keys)
|
|
}
|
|
}
|
|
|
|
// TestAzure_ToolInputKeyOrderPreservation verifies that tool call arguments
|
|
// preserve their original key ordering through the Azure→OpenAI delegation path.
|
|
// TestAzure_ToolInputKeyOrderPreservation verifies that Azure→OpenAI delegation
|
|
// preserves the original key ordering of tool call arguments for prompt caching.
|
|
// Tests multiple parallel tool calls with different key orderings per block.
|
|
func TestAzure_ToolInputKeyOrderPreservation(t *testing.T) {
|
|
bifrostReq := &schemas.BifrostChatRequest{
|
|
Provider: schemas.Azure,
|
|
Model: "gpt-4o",
|
|
Input: []schemas.ChatMessage{
|
|
{
|
|
Role: schemas.ChatMessageRoleUser,
|
|
Content: &schemas.ChatMessageContent{ContentStr: schemas.Ptr("test")},
|
|
},
|
|
{
|
|
Role: schemas.ChatMessageRoleAssistant,
|
|
ChatAssistantMessage: &schemas.ChatAssistantMessage{
|
|
ToolCalls: []schemas.ChatAssistantMessageToolCall{
|
|
{
|
|
Index: 0,
|
|
Type: schemas.Ptr("function"),
|
|
ID: schemas.Ptr("toolu_001"),
|
|
Function: schemas.ChatAssistantMessageToolCallFunction{
|
|
Name: schemas.Ptr("bash"),
|
|
Arguments: `{"description":"Find references quickly","timeout":30000,"command":"grep -r auth_injector ."}`,
|
|
},
|
|
},
|
|
{
|
|
Index: 1,
|
|
Type: schemas.Ptr("function"),
|
|
ID: schemas.Ptr("toolu_002"),
|
|
Function: schemas.ChatAssistantMessageToolCallFunction{
|
|
Name: schemas.Ptr("bash"),
|
|
Arguments: `{"command":"git diff main...HEAD --stat","description":"Show diff"}`,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
ctx, cancel := schemas.NewBifrostContextWithCancel(nil)
|
|
defer cancel()
|
|
result := openai.ToOpenAIChatRequest(ctx, bifrostReq)
|
|
if result == nil {
|
|
t.Fatal("expected non-nil result")
|
|
}
|
|
|
|
// Collect tool call arguments from assistant message
|
|
var argsList []string
|
|
for _, msg := range result.Messages {
|
|
if msg.OpenAIChatAssistantMessage != nil {
|
|
for _, tc := range msg.OpenAIChatAssistantMessage.ToolCalls {
|
|
argsList = append(argsList, tc.Function.Arguments)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(argsList) != 2 {
|
|
t.Fatalf("expected 2 tool call arguments, got %d", len(argsList))
|
|
}
|
|
|
|
// OpenAI path passes Arguments through as strings — verify key order is preserved
|
|
// Block 0: keys should be description, timeout, command
|
|
s0 := argsList[0]
|
|
if !(strings.Index(s0, "description") < strings.Index(s0, "timeout") &&
|
|
strings.Index(s0, "timeout") < strings.Index(s0, "command")) {
|
|
t.Errorf("block 0: key order not preserved, expected description < timeout < command in: %s", s0)
|
|
}
|
|
|
|
// Block 1: keys should be command, description
|
|
s1 := argsList[1]
|
|
if !(strings.Index(s1, "command") < strings.Index(s1, "description")) {
|
|
t.Errorf("block 1: key order not preserved, expected command < description in: %s", s1)
|
|
}
|
|
}
|