175 lines
5.1 KiB
Go
175 lines
5.1 KiB
Go
package llmtests
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
bifrost "github.com/maximhq/bifrost/core"
|
|
"github.com/maximhq/bifrost/core/providers/anthropic"
|
|
"github.com/maximhq/bifrost/core/schemas"
|
|
)
|
|
|
|
// RunCompactionTest tests that context_management with compaction is correctly
|
|
// forwarded through Bifrost via the Responses API.
|
|
//
|
|
// Because compaction requires a minimum trigger of 50,000 input tokens, this
|
|
// test does NOT trigger actual compaction. Instead it verifies:
|
|
// 1. The context_management field survives the Bifrost request round-trip
|
|
// 2. The compact-2026-01-12 beta header is properly sent
|
|
// 3. The API accepts the request without error (non-streaming + streaming)
|
|
func RunCompactionTest(t *testing.T, client *bifrost.Bifrost, ctx context.Context, testConfig ComprehensiveTestConfig) {
|
|
if !testConfig.Scenarios.Compaction {
|
|
t.Logf("Compaction not supported for provider %s", testConfig.Provider)
|
|
return
|
|
}
|
|
|
|
// Compaction is currently Anthropic-only
|
|
if testConfig.Provider != schemas.Anthropic {
|
|
t.Logf("Compaction test skipped: only supported for Anthropic provider")
|
|
return
|
|
}
|
|
|
|
t.Run("Compaction", func(t *testing.T) {
|
|
if os.Getenv("SKIP_PARALLEL_TESTS") != "true" {
|
|
t.Parallel()
|
|
}
|
|
|
|
// Build context_management with compaction config
|
|
contextManagement := &anthropic.ContextManagement{
|
|
Edits: []anthropic.ContextManagementEdit{
|
|
{
|
|
Type: anthropic.ContextManagementEditTypeCompact,
|
|
CompactManagementEditConfig: &anthropic.CompactManagementEditConfig{
|
|
// Use minimum trigger to avoid actual compaction on short input
|
|
Trigger: &anthropic.CompactManagementEditTypeAndValue{
|
|
TypeAndValueObject: &anthropic.CompactManagementEditTypeAndValueObject{
|
|
Type: "input_tokens",
|
|
Value: schemas.Ptr(50000),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
messages := []schemas.ResponsesMessage{
|
|
CreateBasicResponsesMessage("Hello! What is the capital of France? Answer in one word."),
|
|
}
|
|
|
|
// Compaction requires Claude Opus 4.6 or Claude Sonnet 4.6
|
|
compactionModel := testConfig.CompactionModel
|
|
if compactionModel == "" {
|
|
compactionModel = "claude-sonnet-4-6"
|
|
}
|
|
|
|
// --- Non-streaming test ---
|
|
t.Run("NonStreaming", func(t *testing.T) {
|
|
bfCtx := schemas.NewBifrostContext(ctx, schemas.NoDeadline)
|
|
|
|
request := &schemas.BifrostResponsesRequest{
|
|
Provider: testConfig.Provider,
|
|
Model: compactionModel,
|
|
Input: messages,
|
|
Params: &schemas.ResponsesParameters{
|
|
MaxOutputTokens: bifrost.Ptr(100),
|
|
ExtraParams: map[string]interface{}{
|
|
"context_management": contextManagement,
|
|
},
|
|
},
|
|
Fallbacks: testConfig.Fallbacks,
|
|
}
|
|
|
|
response, err := client.ResponsesRequest(bfCtx, request)
|
|
if err != nil {
|
|
t.Fatalf("Compaction non-streaming request failed: %s", GetErrorMessage(err))
|
|
}
|
|
if response == nil {
|
|
t.Fatal("Expected non-nil response")
|
|
}
|
|
|
|
content := GetResponsesContent(response)
|
|
if content == "" {
|
|
t.Error("Expected non-empty response content")
|
|
}
|
|
|
|
// Verify stop_reason is NOT "compaction" (input is too short to trigger)
|
|
if response.StopReason != nil && *response.StopReason == "compaction" {
|
|
t.Log("Compaction triggered unexpectedly on short input")
|
|
}
|
|
|
|
t.Logf("Compaction non-streaming passed: stop_reason=%v, content=%s",
|
|
response.StopReason, content)
|
|
})
|
|
|
|
// --- Streaming test ---
|
|
t.Run("Streaming", func(t *testing.T) {
|
|
bfCtx := schemas.NewBifrostContext(ctx, schemas.NoDeadline)
|
|
|
|
request := &schemas.BifrostResponsesRequest{
|
|
Provider: testConfig.Provider,
|
|
Model: compactionModel,
|
|
Input: messages,
|
|
Params: &schemas.ResponsesParameters{
|
|
MaxOutputTokens: bifrost.Ptr(100),
|
|
ExtraParams: map[string]interface{}{
|
|
"context_management": contextManagement,
|
|
},
|
|
},
|
|
Fallbacks: testConfig.Fallbacks,
|
|
}
|
|
|
|
responseChan, err := client.ResponsesStreamRequest(bfCtx, request)
|
|
if err != nil {
|
|
t.Fatalf("Compaction streaming request failed: %s", GetErrorMessage(err))
|
|
}
|
|
|
|
var fullContent strings.Builder
|
|
var chunkCount int
|
|
var hasCreated, hasCompleted bool
|
|
|
|
streamCtx, cancel := context.WithTimeout(ctx, 60*time.Second)
|
|
defer cancel()
|
|
|
|
for {
|
|
select {
|
|
case chunk, ok := <-responseChan:
|
|
if !ok {
|
|
goto done
|
|
}
|
|
chunkCount++
|
|
if chunk.BifrostResponsesStreamResponse != nil {
|
|
if chunk.BifrostResponsesStreamResponse.Type == schemas.ResponsesStreamResponseTypeCreated {
|
|
hasCreated = true
|
|
}
|
|
if chunk.BifrostResponsesStreamResponse.Type == schemas.ResponsesStreamResponseTypeCompleted {
|
|
hasCompleted = true
|
|
}
|
|
if chunk.BifrostResponsesStreamResponse.Delta != nil {
|
|
fullContent.WriteString(*chunk.BifrostResponsesStreamResponse.Delta)
|
|
}
|
|
}
|
|
case <-streamCtx.Done():
|
|
t.Fatal("Streaming timed out")
|
|
}
|
|
}
|
|
done:
|
|
|
|
if chunkCount == 0 {
|
|
t.Fatal("Expected at least one streaming chunk")
|
|
}
|
|
if !hasCreated {
|
|
t.Error("Missing response.created event")
|
|
}
|
|
if !hasCompleted {
|
|
t.Error("Missing response.completed event")
|
|
}
|
|
|
|
content := fullContent.String()
|
|
t.Logf("Compaction streaming passed: %d chunks, content=%s", chunkCount, content)
|
|
})
|
|
})
|
|
}
|