2024-02-29 01:08:18 +08:00
package gemini
import (
2025-11-26 01:52:52 +08:00
"encoding/json"
2024-02-29 01:08:18 +08:00
"errors"
"fmt"
"io"
"net/http"
2025-11-29 00:44:12 +08:00
"slices"
2025-02-18 01:39:13 +08:00
"strings"
2025-10-11 15:30:09 +08:00
"github.com/QuantumNous/new-api/dto"
"github.com/QuantumNous/new-api/relay/channel"
"github.com/QuantumNous/new-api/relay/channel/openai"
relaycommon "github.com/QuantumNous/new-api/relay/common"
"github.com/QuantumNous/new-api/relay/constant"
"github.com/QuantumNous/new-api/setting/model_setting"
"github.com/QuantumNous/new-api/types"
2025-02-18 01:39:13 +08:00
"github.com/gin-gonic/gin"
2024-02-29 01:08:18 +08:00
)
type Adaptor struct {
}
2025-08-01 22:23:35 +08:00
func ( a * Adaptor ) ConvertGeminiRequest ( c * gin . Context , info * relaycommon . RelayInfo , request * dto . GeminiChatRequest ) ( any , error ) {
2025-08-02 12:53:58 +08:00
if len ( request . Contents ) > 0 {
for i , content := range request . Contents {
if i == 0 {
if request . Contents [ 0 ] . Role == "" {
request . Contents [ 0 ] . Role = "user"
}
}
for _ , part := range content . Parts {
if part . FileData != nil {
if part . FileData . MimeType == "" && strings . Contains ( part . FileData . FileUri , "www.youtube.com" ) {
part . FileData . MimeType = "video/webm"
}
}
}
}
}
2025-08-01 22:23:35 +08:00
return request , nil
}
2025-07-26 13:31:33 +08:00
func ( a * Adaptor ) ConvertClaudeRequest ( c * gin . Context , info * relaycommon . RelayInfo , req * dto . ClaudeRequest ) ( any , error ) {
adaptor := openai . Adaptor { }
oaiReq , err := adaptor . ConvertClaudeRequest ( c , info , req )
if err != nil {
return nil , err
}
return a . ConvertOpenAIRequest ( c , info , oaiReq . ( * dto . GeneralOpenAIRequest ) )
2025-03-12 21:31:46 +08:00
}
2024-07-16 22:07:10 +08:00
func ( a * Adaptor ) ConvertAudioRequest ( c * gin . Context , info * relaycommon . RelayInfo , request dto . AudioRequest ) ( io . Reader , error ) {
//TODO implement me
return nil , errors . New ( "not implemented" )
2024-07-06 17:09:22 +08:00
}
2025-11-26 01:52:52 +08:00
type ImageConfig struct {
AspectRatio string ` json:"aspectRatio,omitempty" `
ImageSize string ` json:"imageSize,omitempty" `
}
type SizeMapping struct {
AspectRatio string
ImageSize string
}
type QualityMapping struct {
Standard string
HD string
High string
FourK string
Auto string
}
func getImageSizeMapping ( ) QualityMapping {
return QualityMapping {
Standard : "1K" ,
HD : "2K" ,
High : "2K" ,
FourK : "4K" ,
Auto : "1K" ,
}
}
func getSizeMappings ( ) map [ string ] SizeMapping {
return map [ string ] SizeMapping {
"1536x1024" : { AspectRatio : "3:2" , ImageSize : "" } ,
"1024x1536" : { AspectRatio : "2:3" , ImageSize : "" } ,
"1024x1792" : { AspectRatio : "9:16" , ImageSize : "" } ,
"1792x1024" : { AspectRatio : "16:9" , ImageSize : "" } ,
2025-11-30 18:45:23 +08:00
"2048x2048" : { AspectRatio : "" , ImageSize : "2K" } ,
"4096x4096" : { AspectRatio : "" , ImageSize : "4K" } ,
2025-11-26 01:52:52 +08:00
}
}
func processSizeParameters ( size , quality string ) ImageConfig {
config := ImageConfig { } // 默认为空值
if size != "" {
if strings . Contains ( size , ":" ) {
config . AspectRatio = size // 直接设置,不与默认值比较
} else {
if mapping , exists := getSizeMappings ( ) [ size ] ; exists {
if mapping . AspectRatio != "" {
config . AspectRatio = mapping . AspectRatio
}
if mapping . ImageSize != "" {
config . ImageSize = mapping . ImageSize
}
}
}
}
if quality != "" {
qualityMapping := getImageSizeMapping ( )
switch strings . ToLower ( strings . TrimSpace ( quality ) ) {
case "hd" , "high" :
config . ImageSize = qualityMapping . HD
case "4k" :
config . ImageSize = qualityMapping . FourK
case "standard" , "medium" , "low" , "auto" , "1k" :
config . ImageSize = qualityMapping . Standard
}
}
return config
}
2024-07-16 22:07:10 +08:00
func ( a * Adaptor ) ConvertImageRequest ( c * gin . Context , info * relaycommon . RelayInfo , request dto . ImageRequest ) ( any , error ) {
2025-11-30 18:45:54 +08:00
if strings . HasPrefix ( info . UpstreamModelName , "gemini-3-pro-image" ) {
2025-11-26 00:29:13 +08:00
chatRequest := dto . GeneralOpenAIRequest {
Model : request . Model ,
Messages : [ ] dto . Message {
2025-11-30 18:45:54 +08:00
{ Role : "user" , Content : request . Prompt } ,
2025-11-26 00:29:13 +08:00
} ,
N : int ( request . N ) ,
}
2025-11-26 01:52:52 +08:00
config := processSizeParameters ( strings . TrimSpace ( request . Size ) , request . Quality )
2025-11-29 00:44:12 +08:00
// 兼容 nano-banana 传quality[imageSize]会报错 An internal error has occurred. Please retry or report in https://developers.generativeai.google/guide/troubleshooting
if slices . Contains ( [ ] string { "nano-banana" , "gemini-2.5-flash-image" } , info . UpstreamModelName ) {
config . ImageSize = ""
}
2025-11-26 01:52:52 +08:00
googleGenerationConfig := map [ string ] interface { } {
2025-11-30 18:45:23 +08:00
"response_modalities" : [ ] string { "TEXT" , "IMAGE" } ,
"image_config" : config ,
2025-11-26 01:52:52 +08:00
}
extraBody := map [ string ] interface { } {
"google" : map [ string ] interface { } {
2025-11-30 18:45:23 +08:00
"generation_config" : googleGenerationConfig ,
2025-11-26 01:52:52 +08:00
} ,
}
chatRequest . ExtraBody , _ = json . Marshal ( extraBody )
2025-11-26 00:29:13 +08:00
return a . ConvertOpenAIRequest ( c , info , & chatRequest )
2025-02-18 01:39:13 +08:00
}
2025-08-18 21:49:28 +08:00
// convert size to aspect ratio but allow user to specify aspect ratio
2025-02-18 01:39:13 +08:00
aspectRatio := "1:1" // default aspect ratio
2025-08-18 21:49:28 +08:00
size := strings . TrimSpace ( request . Size )
if size != "" {
if strings . Contains ( size , ":" ) {
aspectRatio = size
} else {
2025-11-26 01:52:52 +08:00
if mapping , exists := getSizeMappings ( ) [ size ] ; exists && mapping . AspectRatio != "" {
aspectRatio = mapping . AspectRatio
2025-08-18 21:49:28 +08:00
}
}
2025-02-18 01:39:13 +08:00
}
// build gemini imagen request
2025-08-01 22:23:35 +08:00
geminiRequest := dto . GeminiImageRequest {
Instances : [ ] dto . GeminiImageInstance {
2025-02-18 01:39:13 +08:00
{
Prompt : request . Prompt ,
} ,
} ,
2025-08-01 22:23:35 +08:00
Parameters : dto . GeminiImageParameters {
2025-08-14 20:05:06 +08:00
SampleCount : int ( request . N ) ,
2025-02-18 01:39:13 +08:00
AspectRatio : aspectRatio ,
PersonGeneration : "allow_adult" , // default allow adult
} ,
}
2025-10-09 01:16:04 +08:00
// Set imageSize when quality parameter is specified
// Map quality parameter to imageSize (only supported by Standard and Ultra models)
// quality values: auto, high, medium, low (for gpt-image-1), hd, standard (for dall-e-3)
// imageSize values: 1K (default), 2K
// https://ai.google.dev/gemini-api/docs/imagen
// https://platform.openai.com/docs/api-reference/images/create
if request . Quality != "" {
imageSize := "1K" // default
switch request . Quality {
case "hd" , "high" :
imageSize = "2K"
case "2K" :
imageSize = "2K"
case "standard" , "medium" , "low" , "auto" , "1K" :
imageSize = "1K"
default :
// unknown quality value, default to 1K
imageSize = "1K"
}
geminiRequest . Parameters . ImageSize = imageSize
}
2025-02-18 01:39:13 +08:00
return geminiRequest , nil
2024-07-16 22:07:10 +08:00
}
func ( a * Adaptor ) Init ( info * relaycommon . RelayInfo ) {
2024-08-02 17:23:59 +08:00
2024-04-15 14:19:19 +08:00
}
2024-02-29 01:08:18 +08:00
func ( a * Adaptor ) GetRequestURL ( info * relaycommon . RelayInfo ) ( string , error ) {
2025-04-18 19:36:18 +08:00
2025-11-07 17:43:33 +08:00
if model_setting . GetGeminiSettings ( ) . ThinkingAdapterEnabled &&
! model_setting . ShouldPreserveThinkingSuffix ( info . OriginModelName ) {
2025-06-15 21:12:56 +08:00
// 新增逻辑:处理 -thinking-<budget> 格式
2025-06-20 16:02:23 +08:00
if strings . Contains ( info . UpstreamModelName , "-thinking-" ) {
2025-06-15 21:12:56 +08:00
parts := strings . Split ( info . UpstreamModelName , "-thinking-" )
info . UpstreamModelName = parts [ 0 ]
2025-06-20 16:02:23 +08:00
} else if strings . HasSuffix ( info . UpstreamModelName , "-thinking" ) { // 旧的适配
2025-04-18 19:36:18 +08:00
info . UpstreamModelName = strings . TrimSuffix ( info . UpstreamModelName , "-thinking" )
2025-06-20 16:02:23 +08:00
} else if strings . HasSuffix ( info . UpstreamModelName , "-nothinking" ) {
2025-04-18 19:36:18 +08:00
info . UpstreamModelName = strings . TrimSuffix ( info . UpstreamModelName , "-nothinking" )
}
}
2025-02-26 18:19:09 +08:00
version := model_setting . GetGeminiVersionSetting ( info . UpstreamModelName )
2024-04-15 14:19:19 +08:00
2025-02-18 01:39:13 +08:00
if strings . HasPrefix ( info . UpstreamModelName , "imagen" ) {
2025-08-14 21:10:04 +08:00
return fmt . Sprintf ( "%s/%s/models/%s:predict" , info . ChannelBaseUrl , version , info . UpstreamModelName ) , nil
2025-02-18 01:39:13 +08:00
}
2025-03-10 23:32:06 +08:00
if strings . HasPrefix ( info . UpstreamModelName , "text-embedding" ) ||
strings . HasPrefix ( info . UpstreamModelName , "embedding" ) ||
strings . HasPrefix ( info . UpstreamModelName , "gemini-embedding" ) {
2025-08-09 00:27:33 +08:00
action := "embedContent"
2025-08-09 01:07:48 +08:00
if info . IsGeminiBatchEmbedding {
2025-08-09 00:27:33 +08:00
action = "batchEmbedContents"
}
2025-08-14 21:10:04 +08:00
return fmt . Sprintf ( "%s/%s/models/%s:%s" , info . ChannelBaseUrl , version , info . UpstreamModelName , action ) , nil
2025-03-10 23:32:06 +08:00
}
2024-06-27 00:16:39 +08:00
action := "generateContent"
if info . IsStream {
2024-07-18 20:28:47 +08:00
action = "streamGenerateContent?alt=sse"
2025-08-07 06:18:22 +08:00
if info . RelayMode == constant . RelayModeGemini {
info . DisablePing = true
}
2024-06-27 00:16:39 +08:00
}
2025-08-14 21:10:04 +08:00
return fmt . Sprintf ( "%s/%s/models/%s:%s" , info . ChannelBaseUrl , version , info . UpstreamModelName , action ) , nil
2024-02-29 01:08:18 +08:00
}
2024-10-04 16:08:18 +08:00
func ( a * Adaptor ) SetupRequestHeader ( c * gin . Context , req * http . Header , info * relaycommon . RelayInfo ) error {
2024-02-29 16:21:25 +08:00
channel . SetupApiRequestHeader ( info , c , req )
2024-10-04 16:08:18 +08:00
req . Set ( "x-goog-api-key" , info . ApiKey )
2024-02-29 01:08:18 +08:00
return nil
}
2025-03-13 19:32:08 +08:00
func ( a * Adaptor ) ConvertOpenAIRequest ( c * gin . Context , info * relaycommon . RelayInfo , request * dto . GeneralOpenAIRequest ) ( any , error ) {
2024-02-29 01:08:18 +08:00
if request == nil {
return nil , errors . New ( "request is nil" )
}
2025-04-18 19:36:18 +08:00
2025-11-20 15:54:33 +08:00
geminiRequest , err := CovertOpenAI2Gemini ( c , * request , info )
2024-12-20 21:50:58 +08:00
if err != nil {
return nil , err
}
2025-04-18 19:36:18 +08:00
return geminiRequest , nil
2024-02-29 01:08:18 +08:00
}
2024-07-06 17:09:22 +08:00
func ( a * Adaptor ) ConvertRerankRequest ( c * gin . Context , relayMode int , request dto . RerankRequest ) ( any , error ) {
return nil , nil
}
2025-01-23 05:54:39 +08:00
func ( a * Adaptor ) ConvertEmbeddingRequest ( c * gin . Context , info * relaycommon . RelayInfo , request dto . EmbeddingRequest ) ( any , error ) {
2025-03-10 23:32:06 +08:00
if request . Input == nil {
return nil , errors . New ( "input is required" )
}
inputs := request . ParseInput ( )
if len ( inputs ) == 0 {
return nil , errors . New ( "input is empty" )
}
2025-08-09 18:31:56 +08:00
// We always build a batch-style payload with `requests`, so ensure we call the
// batch endpoint upstream to avoid payload/endpoint mismatches.
info . IsGeminiBatchEmbedding = true
2025-08-04 13:02:57 +00:00
// process all inputs
geminiRequests := make ( [ ] map [ string ] interface { } , 0 , len ( inputs ) )
for _ , input := range inputs {
geminiRequest := map [ string ] interface { } {
"model" : fmt . Sprintf ( "models/%s" , info . UpstreamModelName ) ,
"content" : dto . GeminiChatContent {
Parts : [ ] dto . GeminiPart {
{
Text : input ,
} ,
2025-03-10 23:32:06 +08:00
} ,
} ,
2025-08-04 13:02:57 +00:00
}
2025-03-10 23:32:06 +08:00
2025-08-04 13:02:57 +00:00
// set specific parameters for different models
// https://ai.google.dev/api/embeddings?hl=zh-cn#method:-models.embedcontent
switch info . UpstreamModelName {
2025-08-09 18:05:11 +08:00
case "text-embedding-004" , "gemini-embedding-exp-03-07" , "gemini-embedding-001" :
2025-08-04 14:19:19 +00:00
// Only newer models introduced after 2024 support OutputDimensionality
2025-08-04 13:02:57 +00:00
if request . Dimensions > 0 {
geminiRequest [ "outputDimensionality" ] = request . Dimensions
}
2025-03-10 23:32:06 +08:00
}
2025-08-04 13:02:57 +00:00
geminiRequests = append ( geminiRequests , geminiRequest )
2025-03-10 23:32:06 +08:00
}
2025-08-04 13:02:57 +00:00
return map [ string ] interface { } {
"requests" : geminiRequests ,
} , nil
2025-01-23 05:54:39 +08:00
}
2025-05-02 13:59:46 +08:00
func ( a * Adaptor ) ConvertOpenAIResponsesRequest ( c * gin . Context , info * relaycommon . RelayInfo , request dto . OpenAIResponsesRequest ) ( any , error ) {
// TODO implement me
return nil , errors . New ( "not implemented" )
}
2024-10-04 16:08:18 +08:00
func ( a * Adaptor ) DoRequest ( c * gin . Context , info * relaycommon . RelayInfo , requestBody io . Reader ) ( any , error ) {
2024-02-29 16:21:25 +08:00
return channel . DoApiRequest ( a , c , info , requestBody )
2024-02-29 01:08:18 +08:00
}
2025-07-10 15:02:40 +08:00
func ( a * Adaptor ) DoResponse ( c * gin . Context , resp * http . Response , info * relaycommon . RelayInfo ) ( usage any , err * types . NewAPIError ) {
2025-05-26 13:34:41 +08:00
if info . RelayMode == constant . RelayModeGemini {
2025-09-19 00:24:01 +08:00
if strings . Contains ( info . RequestURLPath , ":embedContent" ) ||
strings . Contains ( info . RequestURLPath , ":batchEmbedContents" ) {
2025-08-09 00:27:33 +08:00
return NativeGeminiEmbeddingHandler ( c , resp , info )
}
2025-05-26 14:50:50 +08:00
if info . IsStream {
2025-07-10 15:02:40 +08:00
return GeminiTextGenerationStreamHandler ( c , info , resp )
2025-05-26 14:50:50 +08:00
} else {
2025-07-10 15:02:40 +08:00
return GeminiTextGenerationHandler ( c , info , resp )
2025-05-26 14:50:50 +08:00
}
2025-05-26 13:34:41 +08:00
}
2025-02-18 01:39:13 +08:00
if strings . HasPrefix ( info . UpstreamModelName , "imagen" ) {
2025-07-10 15:02:40 +08:00
return GeminiImageHandler ( c , info , resp )
2025-02-18 01:39:13 +08:00
}
2025-11-26 01:52:52 +08:00
if model_setting . IsGeminiModelSupportImagine ( info . UpstreamModelName ) {
return ChatImageHandler ( c , info , resp )
}
2025-03-10 23:32:06 +08:00
// check if the model is an embedding model
if strings . HasPrefix ( info . UpstreamModelName , "text-embedding" ) ||
strings . HasPrefix ( info . UpstreamModelName , "embedding" ) ||
strings . HasPrefix ( info . UpstreamModelName , "gemini-embedding" ) {
2025-07-10 15:02:40 +08:00
return GeminiEmbeddingHandler ( c , info , resp )
2025-03-10 23:32:06 +08:00
}
2024-02-29 01:08:18 +08:00
if info . IsStream {
2025-07-10 15:02:40 +08:00
return GeminiChatStreamHandler ( c , info , resp )
2024-02-29 01:08:18 +08:00
} else {
2025-07-10 15:02:40 +08:00
return GeminiChatHandler ( c , info , resp )
2024-02-29 01:08:18 +08:00
}
2025-04-18 19:36:18 +08:00
2024-02-29 01:08:18 +08:00
}
func ( a * Adaptor ) GetModelList ( ) [ ] string {
return ModelList
}
func ( a * Adaptor ) GetChannelName ( ) string {
return ChannelName
}