| package semanticcache |
|
|
| import ( |
| "testing" |
|
|
| bifrost "github.com/maximhq/bifrost/core" |
| "github.com/maximhq/bifrost/core/schemas" |
| ) |
|
|
| |
| |
| func TestExtractTextForEmbedding_NilContent(t *testing.T) { |
| plugin := &Plugin{ |
| config: &Config{}, |
| } |
|
|
| tests := []struct { |
| name string |
| request *schemas.BifrostRequest |
| }{ |
| { |
| name: "ChatRequest with nil Content in assistant tool-call message", |
| request: &schemas.BifrostRequest{ |
| RequestType: schemas.ChatCompletionRequest, |
| ChatRequest: &schemas.BifrostChatRequest{ |
| Provider: schemas.OpenAI, |
| Model: "gpt-4o-mini", |
| Input: []schemas.ChatMessage{ |
| { |
| Role: schemas.ChatMessageRoleUser, |
| Content: &schemas.ChatMessageContent{ |
| ContentStr: bifrost.Ptr("Call the get_weather function"), |
| }, |
| }, |
| { |
| Role: schemas.ChatMessageRoleAssistant, |
| Content: nil, |
| ChatAssistantMessage: &schemas.ChatAssistantMessage{ |
| ToolCalls: []schemas.ChatAssistantMessageToolCall{ |
| { |
| ID: bifrost.Ptr("call_123"), |
| Type: bifrost.Ptr("function"), |
| Function: schemas.ChatAssistantMessageToolCallFunction{ |
| Name: bifrost.Ptr("get_weather"), |
| Arguments: `{"location": "San Francisco"}`, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| Params: &schemas.ChatParameters{ |
| Temperature: bifrost.Ptr(0.7), |
| MaxCompletionTokens: bifrost.Ptr(100), |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "ChatRequest where all messages have nil Content", |
| request: &schemas.BifrostRequest{ |
| RequestType: schemas.ChatCompletionRequest, |
| ChatRequest: &schemas.BifrostChatRequest{ |
| Provider: schemas.OpenAI, |
| Model: "gpt-4o-mini", |
| Input: []schemas.ChatMessage{ |
| { |
| Role: schemas.ChatMessageRoleAssistant, |
| Content: nil, |
| }, |
| }, |
| Params: &schemas.ChatParameters{ |
| Temperature: bifrost.Ptr(0.7), |
| MaxCompletionTokens: bifrost.Ptr(100), |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "ResponsesRequest with nil Content", |
| request: &schemas.BifrostRequest{ |
| RequestType: schemas.ResponsesRequest, |
| ResponsesRequest: createResponsesRequestWithNilContent(), |
| }, |
| }, |
| } |
|
|
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| |
| text, hash, err := plugin.extractTextForEmbedding(tt.request) |
| |
| t.Logf("text=%q, hash=%q, err=%v", text, hash, err) |
| }) |
| } |
| } |
|
|
| func TestPrepareDirectCacheLookup_ResponsesStreamRequest(t *testing.T) { |
| plugin := &Plugin{ |
| config: getDefaultTestConfig(), |
| logger: bifrost.NewDefaultLogger(schemas.LogLevelDebug), |
| } |
|
|
| req := &schemas.BifrostRequest{ |
| RequestType: schemas.ResponsesStreamRequest, |
| ResponsesRequest: CreateStreamingResponsesRequest("Explain cache invalidation", 0.2, 200), |
| } |
|
|
| ctx := CreateContextWithCacheKey("responses-stream-direct") |
| directID, err := plugin.prepareDirectCacheLookup(ctx, req, "responses-stream-direct") |
| if err != nil { |
| t.Fatalf("prepareDirectCacheLookup failed: %v", err) |
| } |
| if directID == "" { |
| t.Fatal("expected deterministic direct cache id") |
| } |
| if got, _ := ctx.Value(requestHashKey).(string); got == "" { |
| t.Fatal("expected request hash to be stored in context") |
| } |
| if got, _ := ctx.Value(requestParamsHashKey).(string); got == "" { |
| t.Fatal("expected params hash to be stored in context") |
| } |
| } |
|
|
| func TestPrepareDirectCacheLookup_UnsupportedRequestTypeFailsClosed(t *testing.T) { |
| plugin := &Plugin{ |
| config: getDefaultTestConfig(), |
| logger: bifrost.NewDefaultLogger(schemas.LogLevelDebug), |
| } |
|
|
| req := &schemas.BifrostRequest{ |
| RequestType: schemas.PassthroughRequest, |
| PassthroughRequest: &schemas.BifrostPassthroughRequest{ |
| Provider: schemas.OpenAI, |
| Model: "gpt-4o-mini", |
| Method: "GET", |
| Path: "/v1/models", |
| }, |
| } |
|
|
| ctx := CreateContextWithCacheKey("unsupported-direct") |
| directID, err := plugin.prepareDirectCacheLookup(ctx, req, "unsupported-direct") |
| if err == nil { |
| t.Fatal("expected prepareDirectCacheLookup to reject unsupported request type") |
| } |
| if directID != "" { |
| t.Fatalf("expected no direct cache id, got %q", directID) |
| } |
| if got, _ := ctx.Value(requestHashKey).(string); got != "" { |
| t.Fatalf("expected request hash to remain unset, got %q", got) |
| } |
| if got, _ := ctx.Value(requestParamsHashKey).(string); got != "" { |
| t.Fatalf("expected params hash to remain unset, got %q", got) |
| } |
| if got, _ := ctx.Value(requestStorageIDKey).(string); got != "" { |
| t.Fatalf("expected storage id to remain unset, got %q", got) |
| } |
| } |
|
|
| func TestPreLLMHookSkipsUnsupportedCountTokensRequest(t *testing.T) { |
| plugin := &Plugin{ |
| config: getDefaultTestConfig(), |
| logger: bifrost.NewDefaultLogger(schemas.LogLevelDebug), |
| } |
|
|
| req := &schemas.BifrostRequest{ |
| RequestType: schemas.CountTokensRequest, |
| CountTokensRequest: &schemas.BifrostResponsesRequest{ |
| Provider: schemas.Anthropic, |
| Model: "claude-sonnet-4-5", |
| Input: []schemas.ResponsesMessage{ |
| { |
| Role: bifrost.Ptr(schemas.ResponsesInputMessageRoleUser), |
| Content: &schemas.ResponsesMessageContent{ |
| ContentStr: bifrost.Ptr("How many tokens is this message?"), |
| }, |
| }, |
| }, |
| }, |
| } |
|
|
| ctx := CreateContextWithCacheKey("count-tokens-test") |
| ctx.SetValue(requestIDKey, "stale-request-id") |
| ctx.SetValue(requestStorageIDKey, "stale-storage-id") |
| ctx.SetValue(requestHashKey, "stale-request-hash") |
| ctx.SetValue(requestParamsHashKey, "stale-params-hash") |
| ctx.SetValue(requestModelKey, "stale-model") |
| ctx.SetValue(requestProviderKey, schemas.OpenAI) |
| ctx.SetValue(requestEmbeddingKey, []float32{1, 2, 3}) |
| ctx.SetValue(requestEmbeddingTokensKey, 99) |
| ctx.SetValue(isCacheHitKey, true) |
| ctx.SetValue(cacheHitTypeKey, CacheTypeDirect) |
|
|
| modifiedReq, shortCircuit, err := plugin.PreLLMHook(ctx, req) |
| if err != nil { |
| t.Fatalf("PreLLMHook failed: %v", err) |
| } |
| if modifiedReq != req { |
| t.Fatal("expected original request to be returned unchanged") |
| } |
| if shortCircuit != nil { |
| t.Fatal("expected no short-circuit for unsupported count tokens request") |
| } |
| if got, _ := ctx.Value(requestIDKey).(string); got != "" { |
| t.Fatalf("expected requestIDKey to remain unset, got %q", got) |
| } |
| if got, _ := ctx.Value(requestHashKey).(string); got != "" { |
| t.Fatalf("expected requestHashKey to remain unset, got %q", got) |
| } |
| if got, _ := ctx.Value(requestParamsHashKey).(string); got != "" { |
| t.Fatalf("expected requestParamsHashKey to remain unset, got %q", got) |
| } |
| if got, _ := ctx.Value(requestStorageIDKey).(string); got != "" { |
| t.Fatalf("expected requestStorageIDKey to remain unset, got %q", got) |
| } |
| if got, _ := ctx.Value(requestModelKey).(string); got != "" { |
| t.Fatalf("expected requestModelKey to remain unset, got %q", got) |
| } |
| if got, ok := ctx.Value(requestProviderKey).(schemas.ModelProvider); ok && got != "" { |
| t.Fatalf("expected requestProviderKey to remain unset, got %q", got) |
| } |
| if got := ctx.Value(requestEmbeddingKey); got != nil { |
| t.Fatalf("expected requestEmbeddingKey to remain unset, got %#v", got) |
| } |
| if got, ok := ctx.Value(requestEmbeddingTokensKey).(int); ok && got != 0 { |
| t.Fatalf("expected requestEmbeddingTokensKey to remain unset, got %d", got) |
| } |
| if got, ok := ctx.Value(isCacheHitKey).(bool); ok && got { |
| t.Fatal("expected isCacheHitKey to remain unset") |
| } |
| if got, ok := ctx.Value(cacheHitTypeKey).(CacheType); ok && got != "" { |
| t.Fatalf("expected cacheHitTypeKey to remain unset, got %q", got) |
| } |
| } |
|
|
| |
| |
| func TestGetNormalizedInputForCaching_NilContent(t *testing.T) { |
| plugin := &Plugin{ |
| config: &Config{}, |
| } |
|
|
| request := &schemas.BifrostRequest{ |
| RequestType: schemas.ChatCompletionRequest, |
| ChatRequest: &schemas.BifrostChatRequest{ |
| Provider: schemas.OpenAI, |
| Model: "gpt-4o-mini", |
| Input: []schemas.ChatMessage{ |
| { |
| Role: schemas.ChatMessageRoleUser, |
| Content: &schemas.ChatMessageContent{ |
| ContentStr: bifrost.Ptr("Call the get_weather function"), |
| }, |
| }, |
| { |
| Role: schemas.ChatMessageRoleAssistant, |
| Content: nil, |
| ChatAssistantMessage: &schemas.ChatAssistantMessage{ |
| ToolCalls: []schemas.ChatAssistantMessageToolCall{ |
| { |
| ID: bifrost.Ptr("call_123"), |
| Type: bifrost.Ptr("function"), |
| Function: schemas.ChatAssistantMessageToolCallFunction{ |
| Name: bifrost.Ptr("get_weather"), |
| Arguments: `{"location": "San Francisco"}`, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| Params: &schemas.ChatParameters{ |
| Temperature: bifrost.Ptr(0.7), |
| MaxCompletionTokens: bifrost.Ptr(100), |
| }, |
| }, |
| } |
|
|
| |
| result := plugin.getNormalizedInputForCaching(request) |
| t.Logf("result type: %T", result) |
| } |
|
|
| |
| func createResponsesRequestWithNilContent() *schemas.BifrostResponsesRequest { |
| return &schemas.BifrostResponsesRequest{ |
| Provider: schemas.OpenAI, |
| Model: "gpt-4o-mini", |
| Input: []schemas.ResponsesMessage{ |
| { |
| Role: bifrost.Ptr(schemas.ResponsesInputMessageRoleUser), |
| Content: &schemas.ResponsesMessageContent{ |
| ContentStr: bifrost.Ptr("Hello"), |
| }, |
| }, |
| { |
| Role: bifrost.Ptr(schemas.ResponsesInputMessageRoleAssistant), |
| Content: nil, |
| }, |
| }, |
| Params: &schemas.ResponsesParameters{ |
| Temperature: bifrost.Ptr(0.7), |
| MaxOutputTokens: bifrost.Ptr(100), |
| }, |
| } |
| } |
|
|