-
Notifications
You must be signed in to change notification settings - Fork 150
feat: support for image generation #980
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Radheshg04
wants to merge
22
commits into
maximhq:main
Choose a base branch
from
Radheshg04:feature/image-generation-950
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 13 commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
7219ffd
feat: Phase 1 — core schema + OpenAI/Azure provider support for image…
Radheshg04 b226723
docs: update image generation provider documentation
Radheshg04 0ef6a4c
fix: address typos and restore missing image generation metadata
Radheshg04 a05755f
feat: Phase 2 - HTTP transport and non-streaming endpoint + early str…
Radheshg04 84eb975
feat: Phase 3 - Streaming support and accumulator
Radheshg04 d69fbe1
fix: streaming routing + accumulator bug fixes
Radheshg04 e03e10e
feat: Phase 4 - Semantic cache integration + bug fixes
Radheshg04 7db0fac
feat: Phase 5 - UI components and documentation + Added Unit Tests
Radheshg04 8e3a14c
feat: Added integration tests and load tests
Radheshg04 d50deda
feat: Added support for image generations via chatcompletions api and…
Radheshg04 e86076c
fix: fixed request types in providers + minor bug fixes
Radheshg04 f50dccc
fix: Addressed reviews + image streaming bug fixes
Radheshg04 d41adc5
Merge branch 'main' into feature/image-generation-950
Radheshg04 3738f96
fix: Addressed reviews
Radheshg04 2d61123
fix: fixed azure and openai streaming inconsistencies + added support…
Radheshg04 e9d1aee
docs: update changelogs for image generation support
Radheshg04 3bfad4d
fix: added cases for image generation handlers in transport + fixed a…
Radheshg04 7e4f581
Merge branch 'main' into feature/image-generation-950
Radheshg04 96e06c0
docs: added image generation support to multimodal docs, provider mat…
Radheshg04 01deafd
docs: addressed reviews and updated docs
Radheshg04 8b69047
feat: added image gen request converter; updated image gen schema to …
Radheshg04 502f360
fix: added routing for image gen
Radheshg04 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| package testutil | ||
|
|
||
| import ( | ||
| "context" | ||
| "os" | ||
| "testing" | ||
|
|
||
| bifrost "github.com/maximhq/bifrost/core" | ||
| "github.com/maximhq/bifrost/core/schemas" | ||
| ) | ||
|
|
||
| // RunImageGenerationTest executes the end-to-end image generation test (non-streaming) | ||
| func RunImageGenerationTest(t *testing.T, client *bifrost.Bifrost, ctx context.Context, testConfig ComprehensiveTestConfig) { | ||
| if testConfig.ImageGenerationModel == "" { | ||
| t.Logf("Image generation not configured for provider %s", testConfig.Provider) | ||
| return | ||
| } | ||
|
|
||
| t.Run("ImageGeneration", func(t *testing.T) { | ||
| if os.Getenv("SKIP_PARALLEL_TESTS") != "true" { | ||
| t.Parallel() | ||
| } | ||
|
|
||
| retryConfig := GetTestRetryConfigForScenario("ImageGeneration", testConfig) | ||
| retryContext := TestRetryContext{ | ||
| ScenarioName: "ImageGeneration", | ||
| ExpectedBehavior: map[string]interface{}{}, | ||
| TestMetadata: map[string]interface{}{ | ||
| "provider": testConfig.Provider, | ||
| "model": testConfig.ImageGenerationModel, | ||
| }, | ||
| } | ||
|
|
||
| expectations := GetExpectationsForScenario("ImageGeneration", testConfig, map[string]interface{}{ | ||
| "min_images": 1, | ||
| "expected_size": "1024x1024", | ||
| }) | ||
|
|
||
| imageGenerationRetryConfig := ImageGenerationRetryConfig{ | ||
| MaxAttempts: retryConfig.MaxAttempts, | ||
| BaseDelay: retryConfig.BaseDelay, | ||
| MaxDelay: retryConfig.MaxDelay, | ||
| Conditions: []ImageGenerationRetryCondition{}, | ||
| OnRetry: retryConfig.OnRetry, | ||
| OnFinalFail: retryConfig.OnFinalFail, | ||
| } | ||
| // Test basic image generation | ||
| imageGenerationOperation := func() (*schemas.BifrostImageGenerationResponse, *schemas.BifrostError) { | ||
| request := &schemas.BifrostImageGenerationRequest{ | ||
| Provider: testConfig.Provider, | ||
| Model: testConfig.ImageGenerationModel, | ||
| Input: &schemas.ImageGenerationInput{ | ||
| Prompt: "A serene Japanese garden with cherry blossoms in spring", | ||
| }, | ||
| Params: &schemas.ImageGenerationParameters{ | ||
| Size: bifrost.Ptr("1024x1024"), | ||
| Quality: bifrost.Ptr("standard"), | ||
| ResponseFormat: bifrost.Ptr("b64_json"), | ||
| N: bifrost.Ptr(1), | ||
| }, | ||
| Fallbacks: testConfig.ImageGenerationFallbacks, | ||
| } | ||
|
|
||
| response, err := client.ImageGenerationRequest(ctx, request) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if response != nil { | ||
| return response, nil | ||
| } | ||
| return nil, &schemas.BifrostError{ | ||
| IsBifrostError: true, | ||
| Error: &schemas.ErrorField{ | ||
| Message: "No image generation response returned", | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| imageGenerationResponse, imageGenerationError := WithImageGenerationRetry(t, imageGenerationRetryConfig, retryContext, expectations, "ImageGeneration", imageGenerationOperation) | ||
|
|
||
| if imageGenerationError != nil { | ||
| t.Fatalf("❌ Image generation failed: %v", GetErrorMessage(imageGenerationError)) | ||
| } | ||
|
|
||
| // Validate response | ||
| if imageGenerationResponse == nil { | ||
| t.Fatal("❌ Image generation returned nil response") | ||
| } | ||
|
|
||
| if len(imageGenerationResponse.Data) == 0 { | ||
| t.Fatal("❌ Image generation returned no image data") | ||
| } | ||
|
|
||
| // Validate first image | ||
| imageData := imageGenerationResponse.Data[0] | ||
| if imageData.B64JSON == "" && imageData.URL == "" { | ||
| t.Fatal("❌ Image data missing both b64_json and URL") | ||
| } | ||
|
|
||
| // Validate base64 if present | ||
| if imageData.B64JSON != "" { | ||
| if len(imageData.B64JSON) < 100 { | ||
| t.Errorf("❌ Base64 image data too short: %d bytes", len(imageData.B64JSON)) | ||
| } | ||
| } | ||
|
|
||
| // Validate usage if present | ||
| if imageGenerationResponse.Usage != nil { | ||
| if imageGenerationResponse.Usage.TotalTokens == 0 { | ||
| t.Logf("⚠️ Usage total_tokens is 0 (may be provider-specific)") | ||
| } | ||
| } | ||
|
|
||
| // Validate extra fields | ||
| if imageGenerationResponse.ExtraFields.Provider == "" { | ||
| t.Error("❌ ExtraFields.Provider is empty") | ||
| } | ||
|
|
||
| if imageGenerationResponse.ExtraFields.ModelRequested == "" { | ||
| t.Error("❌ ExtraFields.ModelRequested is empty") | ||
| } | ||
|
|
||
| t.Logf("✅ Image generation successful: ID=%s, Provider=%s, Model=%s, Images=%d", | ||
| imageGenerationResponse.ID, imageGenerationResponse.ExtraFields.Provider, imageGenerationResponse.ExtraFields.ModelRequested, len(imageGenerationResponse.Data)) | ||
| }) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| package testutil | ||
|
|
||
| import ( | ||
| "context" | ||
| "os" | ||
| "testing" | ||
| "time" | ||
|
|
||
| bifrost "github.com/maximhq/bifrost/core" | ||
| "github.com/maximhq/bifrost/core/schemas" | ||
| ) | ||
|
|
||
| // RunImageGenerationCacheTest tests cache hit/miss scenarios | ||
| func RunImageGenerationCacheTest(t *testing.T, client *bifrost.Bifrost, ctx context.Context, testConfig ComprehensiveTestConfig) { | ||
| if testConfig.ImageGenerationModel == "" { | ||
| t.Logf("Image generation cache test skipped: not configured for provider %s", testConfig.Provider) | ||
| return | ||
| } | ||
|
|
||
| t.Run("ImageGenerationCache", func(t *testing.T) { | ||
| if os.Getenv("SKIP_PARALLEL_TESTS") != "true" { | ||
| t.Parallel() | ||
| } | ||
|
|
||
| // Use a unique prompt for cache testing | ||
| cacheTestPrompt := "A unique test image for cache validation - " + time.Now().Format("20060102150405") | ||
|
|
||
| request := &schemas.BifrostImageGenerationRequest{ | ||
| Provider: testConfig.Provider, | ||
| Model: testConfig.ImageGenerationModel, | ||
| Input: &schemas.ImageGenerationInput{ | ||
| Prompt: cacheTestPrompt, | ||
| }, | ||
| Params: &schemas.ImageGenerationParameters{ | ||
| Size: bifrost.Ptr("1024x1024"), | ||
| ResponseFormat: bifrost.Ptr("b64_json"), | ||
| }, | ||
| } | ||
|
|
||
| // First request - should be a cache miss | ||
| start1 := time.Now() | ||
| response1, err1 := client.ImageGenerationRequest(ctx, request) | ||
| duration1 := time.Since(start1) | ||
|
|
||
| if err1 != nil { | ||
| t.Fatalf("❌ First image generation request failed: %v", GetErrorMessage(err1)) | ||
| } | ||
|
|
||
| if response1 == nil || len(response1.Data) == 0 { | ||
| t.Fatal("❌ First request returned no image data") | ||
| } | ||
|
|
||
| // Check cache debug info if available | ||
| cacheHit1 := false | ||
| if response1.ExtraFields.CacheDebug != nil { | ||
| cacheHit1 = response1.ExtraFields.CacheDebug.CacheHit | ||
| } | ||
|
|
||
| if cacheHit1 { | ||
| t.Logf("⚠️ First request was a cache hit (unexpected, but may be valid)") | ||
| } else { | ||
| t.Logf("✅ First request was a cache miss (expected)") | ||
| } | ||
|
|
||
| // Second request with same prompt - should be a cache hit | ||
| start2 := time.Now() | ||
| response2, err2 := client.ImageGenerationRequest(ctx, request) | ||
| duration2 := time.Since(start2) | ||
|
|
||
| if err2 != nil { | ||
| t.Fatalf("❌ Second image generation request failed: %v", GetErrorMessage(err2)) | ||
| } | ||
|
|
||
| if response2 == nil || len(response2.Data) == 0 { | ||
| t.Fatal("❌ Second request returned no image data") | ||
| } | ||
|
|
||
| // Check cache debug info | ||
| cacheHit2 := false | ||
| if response2.ExtraFields.CacheDebug != nil { | ||
| cacheHit2 = response2.ExtraFields.CacheDebug.CacheHit | ||
| } | ||
|
|
||
| if cacheHit2 { | ||
| t.Logf("✅ Second request was a cache hit (expected)") | ||
|
|
||
| // Cache hit should be faster | ||
| if duration2 < duration1 { | ||
| t.Logf("✅ Cache hit was faster: %v vs %v", duration2, duration1) | ||
| } else { | ||
| t.Logf("⚠️ Cache hit was not faster: %v vs %v (may be due to network variance)", duration2, duration1) | ||
| } | ||
|
|
||
| // Validate cached response matches original | ||
| if len(response1.Data) == len(response2.Data) { | ||
| // Compare image data (should be identical for cache hit) | ||
| if response1.Data[0].B64JSON != "" && response2.Data[0].B64JSON != "" { | ||
| if response1.Data[0].B64JSON == response2.Data[0].B64JSON { | ||
| t.Logf("✅ Cached image data matches original") | ||
| } else { | ||
| t.Errorf("❌ Cached image data does not match original") | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| t.Logf("⚠️ Second request was a cache miss (cache may not be enabled or TTL expired)") | ||
| } | ||
|
|
||
| // Test with different prompt - should be cache miss | ||
| request2 := &schemas.BifrostImageGenerationRequest{ | ||
| Provider: testConfig.Provider, | ||
| Model: testConfig.ImageGenerationModel, | ||
| Input: &schemas.ImageGenerationInput{ | ||
| Prompt: "A different prompt for cache miss test", | ||
| }, | ||
| Params: &schemas.ImageGenerationParameters{ | ||
| Size: bifrost.Ptr("1024x1024"), | ||
| ResponseFormat: bifrost.Ptr("b64_json"), | ||
| }, | ||
| } | ||
|
|
||
| response3, err3 := client.ImageGenerationRequest(ctx, request2) | ||
| if err3 != nil { | ||
| t.Fatalf("❌ Third image generation request failed: %v", GetErrorMessage(err3)) | ||
| } | ||
|
|
||
| cacheHit3 := false | ||
| if response3.ExtraFields.CacheDebug != nil { | ||
| cacheHit3 = response3.ExtraFields.CacheDebug.CacheHit | ||
| } | ||
|
|
||
| if cacheHit3 { | ||
| t.Logf("⚠️ Different prompt was a cache hit (unexpected)") | ||
| } else { | ||
| t.Logf("✅ Different prompt was a cache miss (expected)") | ||
| } | ||
|
|
||
| t.Logf("✅ Cache test completed: First=%v, Second=%v, Different=%v", cacheHit1, cacheHit2, cacheHit3) | ||
| }) | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.