diff --git a/core/changelog.md b/core/changelog.md index d3f9c52eb2..ab9118190f 100644 --- a/core/changelog.md +++ b/core/changelog.md @@ -1,4 +1,5 @@ - fix: allow setting authorization header through extra headers and in allow list and deny list +- feat: added image generation support for bedrock - feat: added support for gemini google search tool - fix: function response part handling in gemini - fix: call enrich error in vertex to return raw request and response in bifrost errors diff --git a/core/providers/bedrock/bedrock.go b/core/providers/bedrock/bedrock.go index 4c3fab1146..e5706a1c0c 100644 --- a/core/providers/bedrock/bedrock.go +++ b/core/providers/bedrock/bedrock.go @@ -1536,9 +1536,82 @@ func (provider *BedrockProvider) TranscriptionStream(ctx *schemas.BifrostContext return nil, providerUtils.NewUnsupportedOperationError(schemas.TranscriptionStreamRequest, schemas.Bedrock) } -// ImageGeneration is not supported by the Bedrock provider. +// ImageGeneration generates images using Amazon Bedrock. +// Supports Titan Image Generator v1, Nova Canvas v1, and Titan Image Generator v2. +// Returns a BifrostImageGenerationResponse containing the generated images and any error that occurred. func (provider *BedrockProvider) ImageGeneration(ctx *schemas.BifrostContext, key schemas.Key, request *schemas.BifrostImageGenerationRequest) (*schemas.BifrostImageGenerationResponse, *schemas.BifrostError) { - return nil, providerUtils.NewUnsupportedOperationError(schemas.ImageGenerationRequest, schemas.Bedrock) + if err := providerUtils.CheckOperationAllowed(schemas.Bedrock, provider.customProviderConfig, schemas.ImageGenerationRequest); err != nil { + return nil, err + } + + providerName := provider.GetProviderKey() + if key.BedrockKeyConfig == nil { + return nil, providerUtils.NewConfigurationError("bedrock key config is not provided", providerName) + } + + modelType := DetermineImageGenModelType(request.Model) + var rawResponse []byte + var jsonData []byte + var bifrostError *schemas.BifrostError + var latency time.Duration + var path string + var deployment string + switch modelType { + + case "titan-image-generator-v1", "nova-canvas-v1:0", "titan-image-generator-v2:0": + jsonData, bifrostError = providerUtils.CheckContextAndGetRequestBody( + ctx, + request, + func() (any, error) { return ToBedrockImageGenerationRequest(request) }, + provider.GetProviderKey()) + if bifrostError != nil { + return nil, bifrostError + } + path, deployment = provider.getModelPath("invoke", request.Model, key) + rawResponse, latency, bifrostError = provider.completeRequest(ctx, jsonData, path, key) + default: + return nil, providerUtils.NewConfigurationError("unsupported image generation model type", providerName) + } + if bifrostError != nil { + return nil, providerUtils.EnrichError(ctx, bifrostError, jsonData, nil, provider.sendBackRawRequest, provider.sendBackRawResponse) + } + + // Parse response based on model type + var bifrostResponse *schemas.BifrostImageGenerationResponse + switch modelType { + case "titan-image-generator-v1", "nova-canvas-v1:0", "titan-image-generator-v2:0": + var imageResp BedrockImageGenerationResponse + if err := sonic.Unmarshal(rawResponse, &imageResp); err != nil { + return nil, providerUtils.EnrichError(ctx, providerUtils.NewBifrostOperationError("error parsing image generation response", err, providerName), jsonData, rawResponse, provider.sendBackRawRequest, provider.sendBackRawResponse) + } + + if imageResp.Error != "" { + return nil, providerUtils.EnrichError(ctx, providerUtils.NewBifrostOperationError(imageResp.Error, nil, providerName), jsonData, rawResponse, provider.sendBackRawRequest, provider.sendBackRawResponse) + } + + bifrostResponse = ToBifrostImageGenerationResponse(&imageResp) + bifrostResponse.Model = request.Model + bifrostResponse.ExtraFields.RequestType = schemas.ImageGenerationRequest + bifrostResponse.ExtraFields.Provider = providerName + bifrostResponse.ExtraFields.ModelRequested = request.Model + bifrostResponse.ExtraFields.ModelDeployment = deployment + bifrostResponse.ExtraFields.Latency = latency.Milliseconds() + + // Set raw request if enabled + if providerUtils.ShouldSendBackRawRequest(ctx, provider.sendBackRawRequest) { + providerUtils.ParseAndSetRawRequest(&bifrostResponse.ExtraFields, jsonData) + } + + if providerUtils.ShouldSendBackRawResponse(ctx, provider.sendBackRawResponse) { + var rawResponseData interface{} + if err := sonic.Unmarshal(rawResponse, &rawResponseData); err == nil { + bifrostResponse.ExtraFields.RawResponse = rawResponseData + } + } + + return bifrostResponse, nil + } + return nil, providerUtils.NewConfigurationError("unsupported image generation model type", providerName) } // ImageGenerationStream is not supported by the Bedrock provider. diff --git a/core/providers/bedrock/images.go b/core/providers/bedrock/images.go new file mode 100644 index 0000000000..a7398845cf --- /dev/null +++ b/core/providers/bedrock/images.go @@ -0,0 +1,104 @@ +package bedrock + +import ( + "fmt" + "strconv" + "strings" + + "github.com/maximhq/bifrost/core/schemas" +) + +// ToBedrockImageGenerationRequest converts a Bifrost image generation request to a Bedrock image generation request +func ToBedrockImageGenerationRequest(request *schemas.BifrostImageGenerationRequest) (*BedrockImageGenerationRequest, error) { + if request == nil { + return nil, fmt.Errorf("request is nil") + } + + if request.Input == nil { + return nil, fmt.Errorf("request.Input is required") + } + + bedrockReq := &BedrockImageGenerationRequest{ + TaskType: schemas.Ptr(TaskTypeTextImage), + TextToImageParams: &BedrockTextToImageParams{ + Text: request.Input.Prompt, + }, + ImageGenerationConfig: &ImageGenerationConfig{}, + } + + if request.Params != nil { + if request.Params.N != nil { + bedrockReq.ImageGenerationConfig.NumberOfImages = request.Params.N + } + if request.Params.NegativePrompt != nil { + bedrockReq.TextToImageParams.NegativeText = request.Params.NegativePrompt + } + if request.Params.Seed != nil { + bedrockReq.ImageGenerationConfig.Seed = request.Params.Seed + } + if request.Params.Quality != nil { + bedrockReq.ImageGenerationConfig.Quality = request.Params.Quality + } + if request.Params.Style != nil { + bedrockReq.TextToImageParams.Style = request.Params.Style + } + if request.Params.Size != nil && strings.TrimSpace(strings.ToLower(*request.Params.Size)) != "auto" { + + size := strings.Split(strings.TrimSpace(strings.ToLower(*request.Params.Size)), "x") + if len(size) != 2 { + return nil, fmt.Errorf("invalid size format: expected 'WIDTHxHEIGHT', got %q", *request.Params.Size) + } + + width, err := strconv.Atoi(size[0]) + if err != nil { + return nil, fmt.Errorf("invalid width in size %q: %w", *request.Params.Size, err) + } + + height, err := strconv.Atoi(size[1]) + if err != nil { + return nil, fmt.Errorf("invalid height in size %q: %w", *request.Params.Size, err) + } + + bedrockReq.ImageGenerationConfig.Width = schemas.Ptr(width) + bedrockReq.ImageGenerationConfig.Height = schemas.Ptr(height) + } + if request.Params.ExtraParams != nil { + if cfgScale, ok := schemas.SafeExtractFloat64Pointer(request.Params.ExtraParams["cfgScale"]); ok { + bedrockReq.ImageGenerationConfig.CfgScale = cfgScale + } + } + } + + return bedrockReq, nil + +} + +// ToBifrostImageGenerationResponse converts a Bedrock image generation response to a Bifrost image generation response +func ToBifrostImageGenerationResponse(response *BedrockImageGenerationResponse) *schemas.BifrostImageGenerationResponse { + if response == nil { + return nil + } + + bifrostResponse := &schemas.BifrostImageGenerationResponse{} + + for index, image := range response.Images { + bifrostResponse.Data = append(bifrostResponse.Data, schemas.ImageData{ + B64JSON: image, + Index: index, + }) + } + + return bifrostResponse +} + +// DetermineImageGenModelType determines the image generation model type from the model name +func DetermineImageGenModelType(model string) string { + if strings.Contains(model, "nova-canvas-v1:0") { + return "nova-canvas-v1:0" + } else if strings.Contains(model, "titan-image-generator-v2:0") { + return "titan-image-generator-v2:0" + } else if strings.Contains(model, "titan-image-generator-v1") { + return "titan-image-generator-v1" + } + return model +} diff --git a/core/providers/bedrock/types.go b/core/providers/bedrock/types.go index 37e19e6d1d..ef01c7df98 100644 --- a/core/providers/bedrock/types.go +++ b/core/providers/bedrock/types.go @@ -569,6 +569,37 @@ type BedrockTitanEmbeddingResponse struct { InputTextTokenCount int `json:"inputTextTokenCount"` // Number of tokens in input } +const TaskTypeTextImage = "TEXT_IMAGE" + +// BedrockImageGenerationRequest represents a Bedrock image generation request +type BedrockImageGenerationRequest struct { + TaskType *string `json:"taskType"` // Should be "TEXT_IMAGE" + TextToImageParams *BedrockTextToImageParams `json:"textToImageParams"` // Parameters for text-to-image + ImageGenerationConfig *ImageGenerationConfig `json:"imageGenerationConfig"` // Image generation config +} + +type BedrockTextToImageParams struct { + Text string `json:"text"` // Prompt for image generation + NegativeText *string `json:"negativeText,omitempty"` // Negative prompt for image generation + Style *string `json:"style,omitempty"` // Style for image generation +} + +type ImageGenerationConfig struct { + NumberOfImages *int `json:"numberOfImages,omitempty"` + Height *int `json:"height,omitempty"` + Width *int `json:"width,omitempty"` + CfgScale *float64 `json:"cfgScale,omitempty"` + Quality *string `json:"quality,omitempty"` + Seed *int `json:"seed,omitempty"` +} + +// BedrockImageGenerationResponse represents a Bedrock image generation response +type BedrockImageGenerationResponse struct { + Images []string `json:"images"` // list of Base64 encoded images + MaskImage string `json:"maskImage"` // Base64 encoded mask image (optional) + Error string `json:"error"` // error message (if present) +} + // ==================== MODELS TYPES ==================== type BedrockModelLifecycle struct { Status string `json:"status"` diff --git a/docs/providers/supported-providers/bedrock.mdx b/docs/providers/supported-providers/bedrock.mdx index 282ad8a6e6..3bf954567c 100644 --- a/docs/providers/supported-providers/bedrock.mdx +++ b/docs/providers/supported-providers/bedrock.mdx @@ -18,14 +18,14 @@ AWS Bedrock supports multiple model families (Claude, Nova, Mistral, Llama, Cohe ### Model Family Support -| Family | Chat | Responses | Text | Embeddings | -|--------|------|-----------|------|------------| -| **Claude (Anthropic)** | ✅ | ✅ | ✅ | ❌ | -| **Nova (Anthropic)** | ✅ | ✅ | ❌ | ❌ | -| **Mistral** | ✅ | ✅ | ✅ | ❌ | -| **Llama** | ✅ | ✅ | ❌ | ❌ | -| **Cohere** | ✅ | ✅ | ❌ | ✅ | -| **Titan** | ✅ | ✅ | ❌ | ✅ | +| Family | Chat | Responses | Text | Embeddings | Image Generation | +|--------|------|-----------|------|------------|------------| +| **Claude (Anthropic)** | ✅ | ✅ | ✅ | ❌ | ❌ | +| **Nova (Anthropic)** | ✅ | ✅ | ❌ | ❌ | ✅ | +| **Mistral** | ✅ | ✅ | ✅ | ❌ | ❌ | +| **Llama** | ✅ | ✅ | ❌ | ❌ | ❌ | +| **Cohere** | ✅ | ✅ | ❌ | ✅ | ❌ | +| **Titan** | ✅ | ✅ | ❌ | ✅ | ✅ | ### Supported Operations @@ -38,7 +38,7 @@ AWS Bedrock supports multiple model families (Claude, Nova, Mistral, Llama, Cohe | Files | ✅ | - | S3 (via SDK) | | Batch | ✅ | - | `batch` | | List Models | ✅ | - | `listFoundationModels` | -| Image Generation | ❌ | ❌ | - | +| Image Generation | ✅ | ❌ | `invoke` | | Speech (TTS) | ❌ | ❌ | - | | Transcriptions (STT) | ❌ | ❌ | - | @@ -519,7 +519,71 @@ Supported embedding models: **Titan**, **Cohere** --- -# 5. Batch API +# 5. Image Generation + +Supported image generation models: **Titan Image Generator v1**, **Titan Image Generator v2**, **Nova Canvas v1** + +## Request Conversion + +| Parameter(Bifrost) | Transformation (Bedrock) | +|---------------------|----------------| +| `prompt` | `textToImageParams.text` | +| `n` | `imageGenerationConfig.numberOfImages` | +| `negativePrompt` | `textToImageParams.negativeText` | +| `seed` | `imageGenerationConfig.seed` | +| `quality` | `imageGenerationConfig.quality` | +| `style` | `textToImageParams.style` | +| `size` | `imageGenerationConfig.width` & `imageGenerationConfig.height` | + +## Response Conversion + +| Parameter(Bedrock) | Transformation (Bifrost) | +|---------------------|--------------------------| +| `images` | `data.b64_json` | + +### Example Request + + + + +```bash +curl -X POST http://localhost:8080/v1/images/generations \ + -H "Content-Type: application/json" \ + -d '{ + "model": "bedrock/amazon.nova-canvas-v1:0", + "prompt": "A futuristic cityscape with a flying car", + "size": "1024x1024", + "seed": 123, + "negative_prompt": "bikes", + "n": 2 + }' +``` + + + + +```go +resp, err := client.ImageGenerationRequest(ctx, &schemas.BifrostImageGenerationRequest{ + Provider: schemas.Bedrock, + Model: "amazon.nova-canvas-v1:0", + Input: &schemas.ImageGenerationInput{ + Prompt: "A futuristic cityscape with a flying car", + }, + Params: &schemas.ImageGenerationParameters{ + N: schemas.Ptr(2), + Seed: schemas.Ptr(123), + NegativePrompt: schemas.Ptr("bikes"), + Quality: schemas.Ptr("auto"), + Style: schemas.Ptr("natural"), + Size: schemas.Ptr("1024x1024"), + }, +}) +``` + + + + +# 6. Batch API **Request formats**: `requests` array (CustomID + Params) or `input_file_id` @@ -548,7 +612,7 @@ Supported embedding models: **Titan**, **Cohere** --- -# 6. Files API +# 7. Files API S3-backed file operations. Files are stored in S3 buckets integrated with Bedrock. @@ -574,7 +638,7 @@ S3-backed file operations. Files are stored in S3 buckets integrated with Bedroc --- -# 7. List Models +# 8. List Models **Request**: GET `/v1/models` (no body) @@ -594,7 +658,7 @@ S3-backed file operations. Files are stored in S3 buckets integrated with Bedroc --- -# 8. AWS Authentication & Configuration +# 9. AWS Authentication & Configuration Bifrost automatically handles AWS Bedrock authentication via multiple methods including explicit credentials, IAM roles, and bearer tokens with automatic Signature Version 4 (SigV4) signing. @@ -623,7 +687,7 @@ See **[Provider-Specific Authentication - AWS Bedrock](../../quickstart/go-sdk/p --- -# 9. Error Handling +# 10. Error Handling **HTTP Status Mapping**: diff --git a/docs/providers/supported-providers/overview.mdx b/docs/providers/supported-providers/overview.mdx index e8bd427e0d..1caa8ec19b 100644 --- a/docs/providers/supported-providers/overview.mdx +++ b/docs/providers/supported-providers/overview.mdx @@ -19,7 +19,7 @@ The following table summarizes which operations are supported by each provider v |----------|--------|------|----------------|------|---------------|-----------|--------------------|--------|-----------------|------------|-----|-------------|-----|--------------|-------|-------|--------------| | Anthropic (`anthropic/`) | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | | Azure (`azure/`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | -| Bedrock (`bedrock/`) | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | +| Bedrock (`bedrock/`) | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | | Cerebras (`cerebras/`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | Cohere (`cohere/`) | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | | Elevenlabs (`elevenlabs/`) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |