362 lines
12 KiB
Go
362 lines
12 KiB
Go
package openai
|
|
|
|
import (
|
|
"fmt"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
providerUtils "github.com/maximhq/bifrost/core/providers/utils"
|
|
"github.com/maximhq/bifrost/core/schemas"
|
|
)
|
|
|
|
// ToOpenAIImageGenerationRequest converts a Bifrost Image Request to OpenAI format
|
|
func ToOpenAIImageGenerationRequest(bifrostReq *schemas.BifrostImageGenerationRequest) *OpenAIImageGenerationRequest {
|
|
if bifrostReq == nil || bifrostReq.Input == nil || bifrostReq.Input.Prompt == "" {
|
|
return nil
|
|
}
|
|
|
|
req := &OpenAIImageGenerationRequest{
|
|
Model: bifrostReq.Model,
|
|
Prompt: bifrostReq.Input.Prompt,
|
|
}
|
|
|
|
if bifrostReq.Params != nil {
|
|
req.ImageGenerationParameters = *bifrostReq.Params
|
|
}
|
|
|
|
switch bifrostReq.Provider {
|
|
case schemas.XAI:
|
|
filterXAISpecificParameters(req)
|
|
case schemas.OpenAI, schemas.Azure:
|
|
filterOpenAISpecificParameters(req)
|
|
}
|
|
if bifrostReq.Params != nil {
|
|
req.ExtraParams = bifrostReq.Params.ExtraParams
|
|
}
|
|
return req
|
|
}
|
|
|
|
func filterXAISpecificParameters(req *OpenAIImageGenerationRequest) {
|
|
req.ImageGenerationParameters.Quality = nil
|
|
req.ImageGenerationParameters.Style = nil
|
|
req.ImageGenerationParameters.Size = nil
|
|
req.ImageGenerationParameters.OutputCompression = nil
|
|
}
|
|
|
|
func filterOpenAISpecificParameters(req *OpenAIImageGenerationRequest) {
|
|
req.ImageGenerationParameters.Seed = nil
|
|
req.NumInferenceSteps = nil
|
|
req.NegativePrompt = nil
|
|
}
|
|
|
|
// ToBifrostImageGenerationRequest converts an OpenAI image generation request to Bifrost format
|
|
func (request *OpenAIImageGenerationRequest) ToBifrostImageGenerationRequest(ctx *schemas.BifrostContext) *schemas.BifrostImageGenerationRequest {
|
|
if request == nil {
|
|
return nil
|
|
}
|
|
|
|
provider, model := schemas.ParseModelString(request.Model, providerUtils.CheckAndSetDefaultProvider(ctx, schemas.OpenAI))
|
|
|
|
return &schemas.BifrostImageGenerationRequest{
|
|
Provider: provider,
|
|
Model: model,
|
|
Input: &schemas.ImageGenerationInput{
|
|
Prompt: request.Prompt,
|
|
},
|
|
Params: &request.ImageGenerationParameters,
|
|
Fallbacks: schemas.ParseFallbacks(request.Fallbacks),
|
|
}
|
|
}
|
|
|
|
func (request *OpenAIImageEditRequest) ToBifrostImageEditRequest(ctx *schemas.BifrostContext) *schemas.BifrostImageEditRequest {
|
|
if request == nil {
|
|
return nil
|
|
}
|
|
|
|
provider, model := schemas.ParseModelString(request.Model, providerUtils.CheckAndSetDefaultProvider(ctx, schemas.OpenAI))
|
|
|
|
return &schemas.BifrostImageEditRequest{
|
|
Provider: provider,
|
|
Model: model,
|
|
Input: request.Input,
|
|
Params: &request.ImageEditParameters,
|
|
Fallbacks: schemas.ParseFallbacks(request.Fallbacks),
|
|
}
|
|
}
|
|
|
|
func (request *OpenAIImageVariationRequest) ToBifrostImageVariationRequest(ctx *schemas.BifrostContext) *schemas.BifrostImageVariationRequest {
|
|
if request == nil {
|
|
return nil
|
|
}
|
|
|
|
provider, model := schemas.ParseModelString(request.Model, providerUtils.CheckAndSetDefaultProvider(ctx, schemas.OpenAI))
|
|
|
|
return &schemas.BifrostImageVariationRequest{
|
|
Provider: provider,
|
|
Model: model,
|
|
Input: request.Input,
|
|
Params: &request.ImageVariationParameters,
|
|
Fallbacks: schemas.ParseFallbacks(request.Fallbacks),
|
|
}
|
|
}
|
|
|
|
func ToOpenAIImageEditRequest(bifrostReq *schemas.BifrostImageEditRequest) *OpenAIImageEditRequest {
|
|
if bifrostReq == nil || bifrostReq.Input == nil || bifrostReq.Input.Images == nil || bifrostReq.Input.Prompt == "" {
|
|
return nil
|
|
}
|
|
|
|
req := &OpenAIImageEditRequest{
|
|
Model: bifrostReq.Model,
|
|
Input: bifrostReq.Input,
|
|
}
|
|
|
|
if bifrostReq.Params != nil {
|
|
req.ImageEditParameters = *bifrostReq.Params
|
|
}
|
|
|
|
if bifrostReq.Params != nil {
|
|
req.ExtraParams = bifrostReq.Params.ExtraParams
|
|
}
|
|
|
|
return req
|
|
}
|
|
|
|
func parseImageEditFormDataBodyFromRequest(writer *multipart.Writer, openaiReq *OpenAIImageEditRequest, providerName schemas.ModelProvider) *schemas.BifrostError {
|
|
// Add model field (required)
|
|
if err := writer.WriteField("model", openaiReq.Model); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to write model field", err)
|
|
}
|
|
|
|
// Add prompt field (required)
|
|
if err := writer.WriteField("prompt", openaiReq.Input.Prompt); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to write prompt field", err)
|
|
}
|
|
|
|
// Add stream field when requesting streaming
|
|
if openaiReq.Stream != nil && *openaiReq.Stream {
|
|
if err := writer.WriteField("stream", "true"); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to write stream field", err)
|
|
}
|
|
}
|
|
|
|
// Add optional parameters before file parts so routing metadata arrives first upstream.
|
|
if openaiReq.N != nil {
|
|
if err := writer.WriteField("n", strconv.Itoa(*openaiReq.N)); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to write n field", err)
|
|
}
|
|
}
|
|
|
|
if openaiReq.Size != nil {
|
|
if err := writer.WriteField("size", *openaiReq.Size); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to write size field", err)
|
|
}
|
|
}
|
|
|
|
if openaiReq.ResponseFormat != nil {
|
|
if err := writer.WriteField("response_format", *openaiReq.ResponseFormat); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to write response_format field", err)
|
|
}
|
|
}
|
|
|
|
if openaiReq.Quality != nil {
|
|
if err := writer.WriteField("quality", *openaiReq.Quality); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to write quality field", err)
|
|
}
|
|
}
|
|
|
|
if openaiReq.Background != nil {
|
|
if err := writer.WriteField("background", *openaiReq.Background); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to write background field", err)
|
|
}
|
|
}
|
|
|
|
if openaiReq.InputFidelity != nil {
|
|
if err := writer.WriteField("input_fidelity", *openaiReq.InputFidelity); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to write input_fidelity field", err)
|
|
}
|
|
}
|
|
|
|
if openaiReq.PartialImages != nil {
|
|
if err := writer.WriteField("partial_images", strconv.Itoa(*openaiReq.PartialImages)); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to write partial_images field", err)
|
|
}
|
|
}
|
|
|
|
if openaiReq.OutputFormat != nil {
|
|
if err := writer.WriteField("output_format", *openaiReq.OutputFormat); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to write output_format field", err)
|
|
}
|
|
}
|
|
|
|
if openaiReq.OutputCompression != nil {
|
|
if err := writer.WriteField("output_compression", strconv.Itoa(*openaiReq.OutputCompression)); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to write output_compression field", err)
|
|
}
|
|
}
|
|
|
|
if openaiReq.User != nil {
|
|
if err := writer.WriteField("user", *openaiReq.User); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to write user field", err)
|
|
}
|
|
}
|
|
|
|
// Add image[] fields (one for each image)
|
|
for i, imageInput := range openaiReq.Input.Images {
|
|
fieldName := "image[]"
|
|
|
|
// Detect and validate MIME type
|
|
mimeType := http.DetectContentType(imageInput.Image)
|
|
// Fallback to PNG if content type is undetectable or generic
|
|
if mimeType == "" || mimeType == "application/octet-stream" {
|
|
mimeType = "image/png"
|
|
}
|
|
|
|
// Determine filename based on MIME type
|
|
var filename string
|
|
switch mimeType {
|
|
case "image/jpeg":
|
|
filename = fmt.Sprintf("image%d.jpg", i)
|
|
case "image/webp":
|
|
filename = fmt.Sprintf("image%d.webp", i)
|
|
default:
|
|
filename = fmt.Sprintf("image%d.png", i)
|
|
}
|
|
|
|
// Create form part with proper Content-Type header (not CreateFormFile which defaults to application/octet-stream)
|
|
part, err := writer.CreatePart(map[string][]string{
|
|
"Content-Disposition": {fmt.Sprintf(`form-data; name="%s"; filename="%s"`, fieldName, filename)},
|
|
"Content-Type": {mimeType},
|
|
})
|
|
if err != nil {
|
|
return providerUtils.NewBifrostOperationError(fmt.Sprintf("failed to create form part for image %d", i), err)
|
|
}
|
|
if _, err := part.Write(imageInput.Image); err != nil {
|
|
return providerUtils.NewBifrostOperationError(fmt.Sprintf("failed to write image %d data", i), err)
|
|
}
|
|
}
|
|
|
|
// Add mask if present
|
|
if len(openaiReq.Mask) > 0 {
|
|
// Detect MIME type for mask
|
|
maskMimeType := http.DetectContentType(openaiReq.Mask)
|
|
if maskMimeType != "image/png" && maskMimeType != "image/jpeg" && maskMimeType != "image/webp" {
|
|
maskMimeType = "image/png"
|
|
}
|
|
|
|
var maskFilename string
|
|
switch maskMimeType {
|
|
case "image/jpeg":
|
|
maskFilename = "mask.jpg"
|
|
case "image/webp":
|
|
maskFilename = "mask.webp"
|
|
default:
|
|
maskFilename = "mask.png"
|
|
}
|
|
|
|
// Create form part with proper Content-Type header
|
|
maskPart, err := writer.CreatePart(map[string][]string{
|
|
"Content-Disposition": {`form-data; name="mask"; filename="` + maskFilename + `"`},
|
|
"Content-Type": {maskMimeType},
|
|
})
|
|
if err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to create mask form part", err)
|
|
}
|
|
if _, err := maskPart.Write(openaiReq.Mask); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to write mask data", err)
|
|
}
|
|
}
|
|
|
|
// Close the multipart writer
|
|
if err := writer.Close(); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to close multipart writer", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ToOpenAIImageVariationRequest(bifrostReq *schemas.BifrostImageVariationRequest) *OpenAIImageVariationRequest {
|
|
if bifrostReq == nil || bifrostReq.Input == nil || bifrostReq.Input.Image.Image == nil || len(bifrostReq.Input.Image.Image) == 0 {
|
|
return nil
|
|
}
|
|
|
|
req := &OpenAIImageVariationRequest{
|
|
Model: bifrostReq.Model,
|
|
Input: bifrostReq.Input,
|
|
}
|
|
|
|
if bifrostReq.Params != nil {
|
|
req.ImageVariationParameters = *bifrostReq.Params
|
|
}
|
|
|
|
if bifrostReq.Params != nil {
|
|
req.ExtraParams = bifrostReq.Params.ExtraParams
|
|
}
|
|
|
|
return req
|
|
}
|
|
|
|
func parseImageVariationFormDataBodyFromRequest(writer *multipart.Writer, openaiReq *OpenAIImageVariationRequest, providerName schemas.ModelProvider) *schemas.BifrostError {
|
|
// Add model field (required)
|
|
if err := writer.WriteField("model", openaiReq.Model); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to write model field", err)
|
|
}
|
|
|
|
// Add image file (required)
|
|
if openaiReq.Input == nil || openaiReq.Input.Image.Image == nil || len(openaiReq.Input.Image.Image) == 0 {
|
|
return providerUtils.NewBifrostOperationError("image is required", nil)
|
|
}
|
|
|
|
// Add optional parameters before the image part so metadata arrives first upstream.
|
|
if openaiReq.N != nil {
|
|
if err := writer.WriteField("n", strconv.Itoa(*openaiReq.N)); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to write n field", err)
|
|
}
|
|
}
|
|
|
|
if openaiReq.ResponseFormat != nil {
|
|
if err := writer.WriteField("response_format", *openaiReq.ResponseFormat); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to write response_format field", err)
|
|
}
|
|
}
|
|
|
|
if openaiReq.Size != nil {
|
|
if err := writer.WriteField("size", *openaiReq.Size); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to write size field", err)
|
|
}
|
|
}
|
|
|
|
if openaiReq.User != nil {
|
|
if err := writer.WriteField("user", *openaiReq.User); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to write user field", err)
|
|
}
|
|
}
|
|
|
|
// Detect MIME type
|
|
mimeType := http.DetectContentType(openaiReq.Input.Image.Image)
|
|
// If still not detected, default to PNG
|
|
if mimeType == "application/octet-stream" || mimeType == "" {
|
|
mimeType = "image/png"
|
|
}
|
|
|
|
filename := "image"
|
|
part, err := writer.CreatePart(map[string][]string{
|
|
"Content-Disposition": {fmt.Sprintf(`form-data; name="image"; filename="%s"`, filename)},
|
|
"Content-Type": {mimeType},
|
|
})
|
|
if err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to create image part", err)
|
|
}
|
|
|
|
if _, err := part.Write(openaiReq.Input.Image.Image); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to write image data", err)
|
|
}
|
|
|
|
// Close the multipart writer
|
|
if err := writer.Close(); err != nil {
|
|
return providerUtils.NewBifrostOperationError("failed to close multipart writer", err)
|
|
}
|
|
|
|
return nil
|
|
}
|