Files
bifrost/core/providers/vertex/vertex_caching_test.go
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

239 lines
8.3 KiB
Go

package vertex_test
import (
"encoding/json"
"strings"
"testing"
"github.com/maximhq/bifrost/core/providers/anthropic"
"github.com/maximhq/bifrost/core/schemas"
)
// TestVertex_AnthropicModel_CachingDeterminism verifies that Vertex's delegation
// to anthropic.ToAnthropicChatRequest() 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 TestVertex_AnthropicModel_CachingDeterminism(t *testing.T) {
makeReq := func(props *schemas.OrderedMap) *schemas.BifrostChatRequest {
return &schemas.BifrostChatRequest{
Provider: schemas.Vertex,
Model: "claude-sonnet-4-20250514",
Input: []schemas.ChatMessage{{
Role: schemas.ChatMessageRoleUser,
Content: &schemas.ChatMessageContent{ContentStr: schemas.Ptr("test")},
}},
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("chain_of_thought", schemas.NewOrderedMapFromPairs(
schemas.KV("type", "string"),
schemas.KV("description", "Reasoning"),
)),
schemas.KV("answer", schemas.NewOrderedMapFromPairs(
schemas.KV("type", "string"),
schemas.KV("description", "The answer"),
)),
)
// Version B: description before type (different structural order)
propsB := schemas.NewOrderedMapFromPairs(
schemas.KV("chain_of_thought", schemas.NewOrderedMapFromPairs(
schemas.KV("description", "Reasoning"),
schemas.KV("type", "string"),
)),
schemas.KV("answer", schemas.NewOrderedMapFromPairs(
schemas.KV("description", "The answer"),
schemas.KV("type", "string"),
)),
)
// Vertex delegates Anthropic models to anthropic.ToAnthropicChatRequest()
ctx, cancel := schemas.NewBifrostContextWithCancel(nil)
defer cancel()
resultA, err := anthropic.ToAnthropicChatRequest(ctx, makeReq(propsA))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
resultB, err := anthropic.ToAnthropicChatRequest(ctx, makeReq(propsB))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
jsonA, err := schemas.Marshal(resultA.Tools[0].InputSchema)
if err != nil {
t.Fatalf("failed to marshal params A: %v", err)
}
jsonB, err := schemas.Marshal(resultB.Tools[0].InputSchema)
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 Vertex→Anthropic path: same schema produced different JSON\nA: %s\nB: %s", jsonA, jsonB)
}
// CoT: property order preserved
keys := resultA.Tools[0].InputSchema.Properties.Keys()
if len(keys) != 2 || keys[0] != "chain_of_thought" || keys[1] != "answer" {
t.Errorf("expected property order [chain_of_thought, answer], got %v", keys)
}
}
// TestVertex_AnthropicModel_PreservesPropertyOrder verifies that the
// Vertex→Anthropic delegation path preserves user-defined property ordering.
func TestVertex_AnthropicModel_PreservesPropertyOrder(t *testing.T) {
bifrostReq := &schemas.BifrostChatRequest{
Provider: schemas.Vertex,
Model: "claude-sonnet-4-20250514",
Input: []schemas.ChatMessage{{
Role: schemas.ChatMessageRoleUser,
Content: &schemas.ChatMessageContent{ContentStr: schemas.Ptr("test")},
}},
Params: &schemas.ChatParameters{
Tools: []schemas.ChatTool{{
Type: schemas.ChatToolTypeFunction,
Function: &schemas.ChatToolFunction{
Name: "AnswerResponseModel",
Description: schemas.Ptr("Extract answer"),
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"))),
schemas.KV("is_unanswered", schemas.NewOrderedMapFromPairs(schemas.KV("type", "boolean"))),
),
Required: []string{"answer", "is_unanswered"},
},
},
}},
},
}
ctx, cancel := schemas.NewBifrostContextWithCancel(nil)
defer cancel()
result, err := anthropic.ToAnthropicChatRequest(ctx, bifrostReq)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
keys := result.Tools[0].InputSchema.Properties.Keys()
expected := []string{"chain_of_thought", "answer", "citations", "is_unanswered"}
if len(keys) != len(expected) {
t.Fatalf("expected %d properties, got %d: %v", len(expected), len(keys), keys)
}
for i, k := range expected {
if keys[i] != k {
t.Errorf("property %d: expected %q, got %q (full order: %v)", i, k, keys[i], keys)
}
}
}
// TestVertex_ToolInputKeyOrderPreservation verifies that tool call arguments
// preserve their original key ordering through the Vertex→Anthropic delegation path.
// TestVertex_ToolInputKeyOrderPreservation verifies that Vertex→Anthropic 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 TestVertex_ToolInputKeyOrderPreservation(t *testing.T) {
bifrostReq := &schemas.BifrostChatRequest{
Provider: schemas.Vertex,
Model: "claude-sonnet-4-20250514",
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_vrtx_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_vrtx_002"),
Function: schemas.ChatAssistantMessageToolCallFunction{
Name: schemas.Ptr("bash"),
Arguments: `{"command":"git diff main...HEAD --stat","description":"Show diff"}`,
},
},
{
Index: 2,
Type: schemas.Ptr("function"),
ID: schemas.Ptr("toolu_vrtx_003"),
Function: schemas.ChatAssistantMessageToolCallFunction{
Name: schemas.Ptr("bash"),
Arguments: `{"command":"git log main..HEAD","description":"Show commits"}`,
},
},
},
},
},
},
}
ctx, cancel := schemas.NewBifrostContextWithCancel(nil)
defer cancel()
result, err := anthropic.ToAnthropicChatRequest(ctx, bifrostReq)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Collect all tool_use content blocks
var toolUseBlocks []struct{ jsonStr string }
for _, msg := range result.Messages {
for _, block := range msg.Content.ContentBlocks {
if block.Type == "tool_use" {
jsonBytes, _ := json.Marshal(block.Input)
toolUseBlocks = append(toolUseBlocks, struct{ jsonStr string }{string(jsonBytes)})
}
}
}
if len(toolUseBlocks) != 3 {
t.Fatalf("expected 3 tool_use blocks, got %d", len(toolUseBlocks))
}
// Block 0: keys should be description, timeout, command (NOT alphabetical)
s0 := toolUseBlocks[0].jsonStr
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 (NOT alphabetical)
s1 := toolUseBlocks[1].jsonStr
if !(strings.Index(s1, "command") < strings.Index(s1, "description")) {
t.Errorf("block 1: key order not preserved, expected command < description in: %s", s1)
}
// Block 2: keys should be command, description
s2 := toolUseBlocks[2].jsonStr
if !(strings.Index(s2, "command") < strings.Index(s2, "description")) {
t.Errorf("block 2: key order not preserved, expected command < description in: %s", s2)
}
}