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

708 lines
23 KiB
Go

package openai
import (
"encoding/json"
"strings"
"testing"
"github.com/bytedance/sonic"
providerUtils "github.com/maximhq/bifrost/core/providers/utils"
"github.com/maximhq/bifrost/core/schemas"
"github.com/stretchr/testify/require"
)
func TestToOpenAIChatRequest_ToolNormalization(t *testing.T) {
// Create tool parameters with keys in non-alphabetical order:
// "required" before "properties" before "type" — Normalized() should reorder to
// type → description → properties → required, then alphabetical.
unsortedParams := &schemas.ToolFunctionParameters{
Type: "object",
Properties: schemas.NewOrderedMapFromPairs(
schemas.KV("zebra", map[string]interface{}{"type": "string"}),
schemas.KV("alpha", map[string]interface{}{"type": "number"}),
),
Required: []string{"zebra"},
}
bifrostReq := &schemas.BifrostChatRequest{
Provider: schemas.OpenAI,
Model: "gpt-4o",
Input: []schemas.ChatMessage{{Role: schemas.ChatMessageRoleUser}},
Params: &schemas.ChatParameters{
Tools: []schemas.ChatTool{
{
Type: "function",
Function: &schemas.ChatToolFunction{
Name: "test_func",
Parameters: unsortedParams,
},
},
{
Type: "function",
Function: &schemas.ChatToolFunction{Name: "no_params_func"},
},
},
},
}
ctx, cancel := schemas.NewBifrostContextWithCancel(nil)
defer cancel()
result := ToOpenAIChatRequest(ctx, bifrostReq)
if result == nil {
t.Fatal("expected non-nil result")
}
// Verify parameters are normalized: Properties keys should preserve original order
// (user-defined property names are kept in client order for LLM generation quality)
normalizedParams := result.ChatParameters.Tools[0].Function.Parameters
if normalizedParams == nil {
t.Fatal("expected normalized parameters to be non-nil")
}
keys := normalizedParams.Properties.Keys()
if len(keys) != 2 || keys[0] != "zebra" || keys[1] != "alpha" {
t.Errorf("expected Properties keys preserved as [zebra, alpha], got %v", keys)
}
// Verify tool without parameters is unaffected
if result.ChatParameters.Tools[1].Function.Parameters != nil {
t.Error("expected nil parameters for tool without parameters")
}
// Verify original bifrostReq.Params.Tools was NOT mutated
origKeys := bifrostReq.Params.Tools[0].Function.Parameters.Properties.Keys()
if len(origKeys) != 2 || origKeys[0] != "zebra" || origKeys[1] != "alpha" {
t.Errorf("original parameters were mutated: expected [zebra, alpha], got %v", origKeys)
}
// Verify the Function pointer is a different object (deep copy)
if result.ChatParameters.Tools[0].Function == bifrostReq.Params.Tools[0].Function {
t.Error("expected Function pointer to be a copy, not the original")
}
}
func TestToOpenAIChatRequest_PreservesN(t *testing.T) {
req := &schemas.BifrostChatRequest{
Provider: schemas.OpenAI,
Model: "gpt-4.1",
Input: []schemas.ChatMessage{
{
Role: schemas.ChatMessageRoleUser,
Content: &schemas.ChatMessageContent{
ContentStr: schemas.Ptr("hello"),
},
},
},
Params: &schemas.ChatParameters{
N: schemas.Ptr(2),
},
}
out := ToOpenAIChatRequest(schemas.NewBifrostContext(nil, schemas.NoDeadline), req)
if out == nil {
t.Fatal("expected request")
}
if out.N == nil || *out.N != 2 {
t.Fatalf("expected n=2, got %#v", out.N)
}
}
func TestToOpenAIChatRequest_PreservesPropertyOrder(t *testing.T) {
params := &schemas.ToolFunctionParameters{
Type: "object",
Properties: schemas.NewOrderedMapFromPairs(
schemas.KV("reasoning", map[string]interface{}{"type": "string", "description": "Step by step"}),
schemas.KV("answer", map[string]interface{}{"type": "string", "description": "Final answer"}),
schemas.KV("confidence", map[string]interface{}{"type": "number", "description": "Score"}),
),
Required: []string{"reasoning", "answer"},
}
bifrostReq := &schemas.BifrostChatRequest{
Provider: schemas.OpenAI,
Model: "gpt-4o",
Input: []schemas.ChatMessage{{Role: schemas.ChatMessageRoleUser}},
Params: &schemas.ChatParameters{
Tools: []schemas.ChatTool{{
Type: "function",
Function: &schemas.ChatToolFunction{Name: "test_func", Parameters: params},
}},
},
}
ctx, cancel := schemas.NewBifrostContextWithCancel(nil)
defer cancel()
result := ToOpenAIChatRequest(ctx, bifrostReq)
// CoT: property order preserved
normalizedParams := result.ChatParameters.Tools[0].Function.Parameters
keys := normalizedParams.Properties.Keys()
if len(keys) != 3 || keys[0] != "reasoning" || keys[1] != "answer" || keys[2] != "confidence" {
t.Errorf("expected property order [reasoning, answer, confidence], got %v", keys)
}
}
func TestToOpenAIChatRequest_PreservesExplicitEmptyToolParameters(t *testing.T) {
var tool schemas.ChatTool
err := json.Unmarshal([]byte(`{"type":"function","function":{"name":"empty_schema","parameters":{},"strict":false}}`), &tool)
if err != nil {
t.Fatalf("failed to unmarshal tool: %v", err)
}
bifrostReq := &schemas.BifrostChatRequest{
Provider: schemas.OpenAI,
Model: "gpt-4o",
Input: []schemas.ChatMessage{{Role: schemas.ChatMessageRoleUser}},
Params: &schemas.ChatParameters{
Tools: []schemas.ChatTool{tool},
},
}
ctx, cancel := schemas.NewBifrostContextWithCancel(nil)
defer cancel()
result := ToOpenAIChatRequest(ctx, bifrostReq)
if result == nil {
t.Fatal("expected non-nil result")
}
params := result.ChatParameters.Tools[0].Function.Parameters
if params == nil {
t.Fatal("expected tool parameters to be preserved")
}
marshaled, err := schemas.Marshal(params)
if err != nil {
t.Fatalf("failed to marshal parameters: %v", err)
}
if string(marshaled) != `{}` {
t.Fatalf("expected parameters to remain {}, got %s", marshaled)
}
}
func TestToOpenAIChatRequest_CachingDeterminism(t *testing.T) {
// Same properties, different structural key orders within property definitions
makeReq := func(props *schemas.OrderedMap) *schemas.BifrostChatRequest {
return &schemas.BifrostChatRequest{
Provider: schemas.OpenAI,
Model: "gpt-4o",
Input: []schemas.ChatMessage{{Role: schemas.ChatMessageRoleUser}},
Params: &schemas.ChatParameters{
Tools: []schemas.ChatTool{{
Type: "function",
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"),
)),
)
ctx, cancel := schemas.NewBifrostContextWithCancel(nil)
defer cancel()
resultA := ToOpenAIChatRequest(ctx, makeReq(propsA))
resultB := 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)
}
if string(jsonA) != string(jsonB) {
t.Errorf("caching broken: same schema produced different JSON\nA: %s\nB: %s", jsonA, jsonB)
}
}
func TestToOpenAIChatRequest_FireworksPreservesReasoningAndCacheIsolation(t *testing.T) {
ctx, cancel := schemas.NewBifrostContextWithCancel(nil)
defer cancel()
cacheKey := "cache-key-1"
reasoning := "step by step"
predictionContent := "fireworks ok"
userContent := "Reply with exactly: fireworks ok"
bifrostReq := &schemas.BifrostChatRequest{
Provider: schemas.Fireworks,
Model: "accounts/fireworks/models/deepseek-v3p2",
Input: []schemas.ChatMessage{
{
Role: schemas.ChatMessageRoleUser,
Content: &schemas.ChatMessageContent{
ContentStr: &userContent,
},
},
{
Role: schemas.ChatMessageRoleAssistant,
Content: &schemas.ChatMessageContent{
ContentStr: &predictionContent,
},
ChatAssistantMessage: &schemas.ChatAssistantMessage{
Reasoning: &reasoning,
},
},
},
Params: &schemas.ChatParameters{
PromptCacheKey: &cacheKey,
Prediction: &schemas.ChatPrediction{
Type: "content",
Content: predictionContent,
},
},
}
result := ToOpenAIChatRequest(ctx, bifrostReq)
if result == nil {
t.Fatal("expected non-nil result")
}
if result.PromptCacheIsolationKey == nil || *result.PromptCacheIsolationKey != cacheKey {
t.Fatalf("expected prompt_cache_isolation_key %q, got %v", cacheKey, result.PromptCacheIsolationKey)
}
if result.PromptCacheKey != nil {
t.Fatalf("expected prompt_cache_key to be stripped, got %v", *result.PromptCacheKey)
}
if result.Prediction == nil || result.Prediction.Content != predictionContent {
t.Fatalf("expected prediction to be preserved, got %#v", result.Prediction)
}
if len(result.Messages) != 2 || result.Messages[1].OpenAIChatAssistantMessage == nil {
t.Fatalf("expected assistant message with OpenAI assistant payload, got %#v", result.Messages)
}
if result.Messages[1].OpenAIChatAssistantMessage.Reasoning == nil || *result.Messages[1].OpenAIChatAssistantMessage.Reasoning != reasoning {
t.Fatalf("expected assistant reasoning_content %q, got %#v", reasoning, result.Messages[1].OpenAIChatAssistantMessage)
}
ctx.SetValue(schemas.BifrostContextKeyPassthroughExtraParams, true)
wireBody, bifrostErr := providerUtils.CheckContextAndGetRequestBody(
ctx,
bifrostReq,
func() (providerUtils.RequestBodyWithExtraParams, error) {
return ToOpenAIChatRequest(ctx, bifrostReq), nil
},
)
if bifrostErr != nil {
t.Fatalf("failed to build request body: %v", bifrostErr.Error.Message)
}
var jsonMap map[string]interface{}
if err := sonic.Unmarshal(wireBody, &jsonMap); err != nil {
t.Fatalf("failed to parse marshaled request body: %v", err)
}
if got, ok := jsonMap["prompt_cache_isolation_key"].(string); !ok || got != cacheKey {
t.Fatalf("expected prompt_cache_isolation_key %q in wire payload, got %#v", cacheKey, jsonMap["prompt_cache_isolation_key"])
}
if _, ok := jsonMap["prompt_cache_key"]; ok {
t.Fatalf("expected prompt_cache_key to be absent from wire payload, got %#v", jsonMap["prompt_cache_key"])
}
messages, ok := jsonMap["messages"].([]interface{})
if !ok || len(messages) != 2 {
t.Fatalf("expected 2 messages in wire payload, got %#v", jsonMap["messages"])
}
assistantMessage, ok := messages[1].(map[string]interface{})
if !ok {
t.Fatalf("expected assistant message object, got %#v", messages[1])
}
if got, ok := assistantMessage["reasoning_content"].(string); !ok || got != reasoning {
t.Fatalf("expected reasoning_content %q in assistant payload, got %#v", reasoning, assistantMessage["reasoning_content"])
}
}
// TestToOpenAIChatRequest_AnnotationsNotInWirePayload verifies that MCPToolAnnotations
// (stored on ChatTool with json:"-") are never included in the JSON body sent to OpenAI.
func TestToOpenAIChatRequest_AnnotationsNotInWirePayload(t *testing.T) {
readOnly := true
bifrostReq := &schemas.BifrostChatRequest{
Provider: schemas.OpenAI,
Model: "gpt-4o",
Input: []schemas.ChatMessage{
{Role: schemas.ChatMessageRoleUser, Content: &schemas.ChatMessageContent{ContentStr: schemas.Ptr("hello")}},
},
Params: &schemas.ChatParameters{
Tools: []schemas.ChatTool{
{
Type: schemas.ChatToolTypeFunction,
Function: &schemas.ChatToolFunction{
Name: "read_file",
Description: schemas.Ptr("Read a file"),
Parameters: &schemas.ToolFunctionParameters{
Type: "object",
Properties: schemas.NewOrderedMapFromPairs(
schemas.KV("path", map[string]interface{}{"type": "string"}),
),
Required: []string{"path"},
},
},
Annotations: &schemas.MCPToolAnnotations{
Title: "File Reader",
ReadOnlyHint: &readOnly,
},
},
},
},
}
ctx, cancel := schemas.NewBifrostContextWithCancel(nil)
defer cancel()
result := ToOpenAIChatRequest(ctx, bifrostReq)
require.NotNil(t, result)
wireBody, err := json.Marshal(result)
require.NoError(t, err)
s := string(wireBody)
// Annotations must be absent from the wire payload
if strings.Contains(s, "annotations") {
t.Errorf("annotations field leaked into OpenAI wire payload: %s", s)
}
if strings.Contains(s, "readOnlyHint") {
t.Errorf("readOnlyHint leaked into OpenAI wire payload: %s", s)
}
if strings.Contains(s, "File Reader") {
t.Errorf("annotation title leaked into OpenAI wire payload: %s", s)
}
// The function definition must still be intact
if !strings.Contains(s, "read_file") {
t.Errorf("function name missing from OpenAI wire payload: %s", s)
}
}
func TestApplyXAICompatibility(t *testing.T) {
tests := []struct {
name string
model string
request *OpenAIChatRequest
validate func(t *testing.T, req *OpenAIChatRequest)
}{
{
name: "grok-3: preserves frequency_penalty and stop, clears presence_penalty and reasoning_effort",
model: "grok-3",
request: &OpenAIChatRequest{
Model: "grok-3",
Messages: []OpenAIMessage{},
ChatParameters: schemas.ChatParameters{
FrequencyPenalty: schemas.Ptr(0.5),
PresencePenalty: schemas.Ptr(0.3),
Stop: []string{"STOP"},
Reasoning: &schemas.ChatReasoning{
Effort: schemas.Ptr("high"),
},
},
},
validate: func(t *testing.T, req *OpenAIChatRequest) {
// frequency_penalty should be preserved
if req.FrequencyPenalty == nil || *req.FrequencyPenalty != 0.5 {
t.Errorf("Expected FrequencyPenalty to be preserved at 0.5, got %v", req.FrequencyPenalty)
}
// stop should be preserved
if len(req.Stop) != 1 || req.Stop[0] != "STOP" {
t.Errorf("Expected Stop to be preserved as ['STOP'], got %v", req.Stop)
}
// presence_penalty should be cleared
if req.PresencePenalty != nil {
t.Errorf("Expected PresencePenalty to be cleared (nil), got %v", *req.PresencePenalty)
}
// reasoning_effort should be cleared for non-mini grok-3
if req.Reasoning == nil {
t.Fatal("Expected Reasoning to remain non-nil")
}
if req.Reasoning.Effort != nil {
t.Errorf("Expected Reasoning.Effort to be cleared (nil) for grok-3, got %v", *req.Reasoning.Effort)
}
},
},
{
name: "grok-3-mini: clears all penalties and stop, preserves reasoning_effort",
model: "grok-3-mini",
request: &OpenAIChatRequest{
Model: "grok-3-mini",
Messages: []OpenAIMessage{},
ChatParameters: schemas.ChatParameters{
FrequencyPenalty: schemas.Ptr(0.5),
PresencePenalty: schemas.Ptr(0.3),
Stop: []string{"STOP"},
Reasoning: &schemas.ChatReasoning{
Effort: schemas.Ptr("medium"),
},
},
},
validate: func(t *testing.T, req *OpenAIChatRequest) {
// presence_penalty should be cleared
if req.PresencePenalty != nil {
t.Errorf("Expected PresencePenalty to be cleared (nil), got %v", *req.PresencePenalty)
}
// frequency_penalty should be cleared for grok-3-mini
if req.FrequencyPenalty != nil {
t.Errorf("Expected FrequencyPenalty to be cleared (nil) for grok-3-mini, got %v", *req.FrequencyPenalty)
}
// stop should be cleared for grok-3-mini
if req.Stop != nil {
t.Errorf("Expected Stop to be cleared (nil) for grok-3-mini, got %v", req.Stop)
}
// reasoning_effort should be preserved for grok-3-mini
if req.Reasoning == nil || req.Reasoning.Effort == nil {
t.Fatal("Expected Reasoning.Effort to be preserved for grok-3-mini")
}
if *req.Reasoning.Effort != "medium" {
t.Errorf("Expected Reasoning.Effort to be 'medium', got %v", *req.Reasoning.Effort)
}
},
},
{
name: "grok-4: clears all penalties, stop, and reasoning_effort",
model: "grok-4",
request: &OpenAIChatRequest{
Model: "grok-4",
Messages: []OpenAIMessage{},
ChatParameters: schemas.ChatParameters{
FrequencyPenalty: schemas.Ptr(0.5),
PresencePenalty: schemas.Ptr(0.3),
Stop: []string{"STOP"},
Reasoning: &schemas.ChatReasoning{
Effort: schemas.Ptr("high"),
},
},
},
validate: func(t *testing.T, req *OpenAIChatRequest) {
// presence_penalty should be cleared
if req.PresencePenalty != nil {
t.Errorf("Expected PresencePenalty to be cleared (nil), got %v", *req.PresencePenalty)
}
// frequency_penalty should be cleared for grok-4
if req.FrequencyPenalty != nil {
t.Errorf("Expected FrequencyPenalty to be cleared (nil) for grok-4, got %v", *req.FrequencyPenalty)
}
// stop should be cleared for grok-4
if req.Stop != nil {
t.Errorf("Expected Stop to be cleared (nil) for grok-4, got %v", req.Stop)
}
// reasoning_effort should be cleared for grok-4
if req.Reasoning == nil {
t.Fatal("Expected Reasoning to remain non-nil")
}
if req.Reasoning.Effort != nil {
t.Errorf("Expected Reasoning.Effort to be cleared (nil) for grok-4, got %v", *req.Reasoning.Effort)
}
},
},
{
name: "grok-4-fast-reasoning: clears all penalties, stop, and reasoning_effort",
model: "grok-4-fast-reasoning",
request: &OpenAIChatRequest{
Model: "grok-4-fast-reasoning",
Messages: []OpenAIMessage{},
ChatParameters: schemas.ChatParameters{
FrequencyPenalty: schemas.Ptr(0.5),
PresencePenalty: schemas.Ptr(0.3),
Stop: []string{"STOP", "END"},
Reasoning: &schemas.ChatReasoning{
Effort: schemas.Ptr("high"),
},
},
},
validate: func(t *testing.T, req *OpenAIChatRequest) {
// presence_penalty should be cleared
if req.PresencePenalty != nil {
t.Errorf("Expected PresencePenalty to be cleared (nil), got %v", *req.PresencePenalty)
}
// frequency_penalty should be cleared
if req.FrequencyPenalty != nil {
t.Errorf("Expected FrequencyPenalty to be cleared (nil), got %v", *req.FrequencyPenalty)
}
// stop should be cleared
if req.Stop != nil {
t.Errorf("Expected Stop to be cleared (nil), got %v", req.Stop)
}
// reasoning_effort should be cleared
if req.Reasoning == nil {
t.Fatal("Expected Reasoning to remain non-nil")
}
if req.Reasoning.Effort != nil {
t.Errorf("Expected Reasoning.Effort to be cleared (nil), got %v", *req.Reasoning.Effort)
}
},
},
{
name: "grok-code-fast-1: clears all penalties, stop, and reasoning_effort",
model: "grok-code-fast-1",
request: &OpenAIChatRequest{
Model: "grok-code-fast-1",
Messages: []OpenAIMessage{},
ChatParameters: schemas.ChatParameters{
FrequencyPenalty: schemas.Ptr(0.2),
PresencePenalty: schemas.Ptr(0.1),
Stop: []string{"END"},
Reasoning: &schemas.ChatReasoning{
Effort: schemas.Ptr("low"),
},
},
},
validate: func(t *testing.T, req *OpenAIChatRequest) {
// presence_penalty should be cleared
if req.PresencePenalty != nil {
t.Errorf("Expected PresencePenalty to be cleared (nil), got %v", *req.PresencePenalty)
}
// frequency_penalty should be cleared
if req.FrequencyPenalty != nil {
t.Errorf("Expected FrequencyPenalty to be cleared (nil), got %v", *req.FrequencyPenalty)
}
// stop should be cleared
if req.Stop != nil {
t.Errorf("Expected Stop to be cleared (nil), got %v", req.Stop)
}
// reasoning_effort should be cleared
if req.Reasoning == nil {
t.Fatal("Expected Reasoning to remain non-nil")
}
if req.Reasoning.Effort != nil {
t.Errorf("Expected Reasoning.Effort to be cleared (nil), got %v", *req.Reasoning.Effort)
}
},
},
{
name: "non-reasoning grok model: no changes applied",
model: "grok-2-latest",
request: &OpenAIChatRequest{
Model: "grok-2-latest",
Messages: []OpenAIMessage{},
ChatParameters: schemas.ChatParameters{
FrequencyPenalty: schemas.Ptr(0.5),
PresencePenalty: schemas.Ptr(0.3),
Stop: []string{"STOP"},
Reasoning: &schemas.ChatReasoning{
Effort: schemas.Ptr("high"),
},
},
},
validate: func(t *testing.T, req *OpenAIChatRequest) {
// All parameters should be preserved for non-reasoning models
if req.FrequencyPenalty == nil || *req.FrequencyPenalty != 0.5 {
t.Errorf("Expected FrequencyPenalty to be preserved at 0.5, got %v", req.FrequencyPenalty)
}
if req.PresencePenalty == nil || *req.PresencePenalty != 0.3 {
t.Errorf("Expected PresencePenalty to be preserved at 0.3, got %v", req.PresencePenalty)
}
if len(req.Stop) != 1 || req.Stop[0] != "STOP" {
t.Errorf("Expected Stop to be preserved as ['STOP'], got %v", req.Stop)
}
if req.Reasoning == nil || req.Reasoning.Effort == nil {
t.Fatal("Expected Reasoning.Effort to be preserved")
}
if *req.Reasoning.Effort != "high" {
t.Errorf("Expected Reasoning.Effort to be 'high', got %v", *req.Reasoning.Effort)
}
},
},
{
name: "grok-3: handles nil reasoning gracefully",
model: "grok-3",
request: &OpenAIChatRequest{
Model: "grok-3",
Messages: []OpenAIMessage{},
ChatParameters: schemas.ChatParameters{
FrequencyPenalty: schemas.Ptr(0.5),
PresencePenalty: schemas.Ptr(0.3),
Stop: []string{"STOP"},
Reasoning: nil,
},
},
validate: func(t *testing.T, req *OpenAIChatRequest) {
// Should handle nil reasoning without panicking
if req.Reasoning != nil {
t.Errorf("Expected Reasoning to remain nil, got %v", req.Reasoning)
}
// Other parameters should still be processed
if req.PresencePenalty != nil {
t.Errorf("Expected PresencePenalty to be cleared (nil), got %v", *req.PresencePenalty)
}
if req.FrequencyPenalty == nil || *req.FrequencyPenalty != 0.5 {
t.Errorf("Expected FrequencyPenalty to be preserved at 0.5, got %v", req.FrequencyPenalty)
}
},
},
{
name: "grok-3: preserves other parameters like temperature",
model: "grok-3",
request: &OpenAIChatRequest{
Model: "grok-3",
Messages: []OpenAIMessage{},
ChatParameters: schemas.ChatParameters{
Temperature: schemas.Ptr(0.8),
TopP: schemas.Ptr(0.9),
FrequencyPenalty: schemas.Ptr(0.5),
PresencePenalty: schemas.Ptr(0.3),
},
},
validate: func(t *testing.T, req *OpenAIChatRequest) {
// Unrelated parameters should be preserved
if req.Temperature == nil || *req.Temperature != 0.8 {
t.Errorf("Expected Temperature to be preserved at 0.8, got %v", req.Temperature)
}
if req.TopP == nil || *req.TopP != 0.9 {
t.Errorf("Expected TopP to be preserved at 0.9, got %v", req.TopP)
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Apply the compatibility function
tt.request.applyXAICompatibility(tt.model)
// Validate the results
tt.validate(t, tt.request)
})
}
}