2024-02-29 01:08:18 +08:00
package gemini
2023-12-18 23:45:08 +08:00
import (
"encoding/json"
2025-07-10 15:02:40 +08:00
"errors"
2023-12-18 23:45:08 +08:00
"fmt"
"io"
"net/http"
"one-api/common"
2024-12-06 14:31:27 +08:00
"one-api/constant"
2024-02-29 01:08:18 +08:00
"one-api/dto"
relaycommon "one-api/relay/common"
2025-03-05 19:47:41 +08:00
"one-api/relay/helper"
2024-02-29 01:08:18 +08:00
"one-api/service"
2025-02-26 18:19:09 +08:00
"one-api/setting/model_setting"
2025-07-10 15:02:40 +08:00
"one-api/types"
2025-06-15 21:12:56 +08:00
"strconv"
2023-12-18 23:45:08 +08:00
"strings"
2024-12-29 03:58:21 +08:00
"unicode/utf8"
2024-12-20 13:20:07 +08:00
"github.com/gin-gonic/gin"
2023-12-18 23:45:08 +08:00
)
2025-06-02 19:00:55 +08:00
var geminiSupportedMimeTypes = map [ string ] bool {
"application/pdf" : true ,
"audio/mpeg" : true ,
"audio/mp3" : true ,
"audio/wav" : true ,
"image/png" : true ,
"image/jpeg" : true ,
"text/plain" : true ,
"video/mov" : true ,
"video/mpeg" : true ,
"video/mp4" : true ,
"video/mpg" : true ,
"video/avi" : true ,
"video/wmv" : true ,
"video/mpegps" : true ,
"video/flv" : true ,
}
2025-06-15 21:12:56 +08:00
// Gemini 允许的思考预算范围
const (
2025-06-18 00:49:35 +08:00
pro25MinBudget = 128
pro25MaxBudget = 32768
flash25MaxBudget = 24576
flash25LiteMinBudget = 512
flash25LiteMaxBudget = 24576
2025-06-15 21:12:56 +08:00
)
2025-06-18 00:49:35 +08:00
// clampThinkingBudget 根据模型名称将预算限制在允许的范围内
func clampThinkingBudget ( modelName string , budget int ) int {
isNew25Pro := strings . HasPrefix ( modelName , "gemini-2.5-pro" ) &&
! strings . HasPrefix ( modelName , "gemini-2.5-pro-preview-05-06" ) &&
! strings . HasPrefix ( modelName , "gemini-2.5-pro-preview-03-25" )
is25FlashLite := strings . HasPrefix ( modelName , "gemini-2.5-flash-lite" )
if is25FlashLite {
if budget < flash25LiteMinBudget {
return flash25LiteMinBudget
}
if budget > flash25LiteMaxBudget {
return flash25LiteMaxBudget
}
} else if isNew25Pro {
if budget < pro25MinBudget {
return pro25MinBudget
}
if budget > pro25MaxBudget {
return pro25MaxBudget
}
} else { // 其他模型
if budget < 0 {
return 0
}
if budget > flash25MaxBudget {
return flash25MaxBudget
}
}
return budget
}
2025-06-21 21:50:03 +08:00
func ThinkingAdaptor ( geminiRequest * GeminiChatRequest , info * relaycommon . RelayInfo ) {
2025-04-18 19:36:18 +08:00
if model_setting . GetGeminiSettings ( ) . ThinkingAdapterEnabled {
2025-06-20 16:02:23 +08:00
modelName := info . UpstreamModelName
2025-06-18 00:49:35 +08:00
isNew25Pro := strings . HasPrefix ( modelName , "gemini-2.5-pro" ) &&
! strings . HasPrefix ( modelName , "gemini-2.5-pro-preview-05-06" ) &&
! strings . HasPrefix ( modelName , "gemini-2.5-pro-preview-03-25" )
if strings . Contains ( modelName , "-thinking-" ) {
parts := strings . SplitN ( modelName , "-thinking-" , 2 )
2025-06-15 21:12:56 +08:00
if len ( parts ) == 2 && parts [ 1 ] != "" {
if budgetTokens , err := strconv . Atoi ( parts [ 1 ] ) ; err == nil {
2025-06-18 00:49:35 +08:00
clampedBudget := clampThinkingBudget ( modelName , budgetTokens )
2025-06-15 21:12:56 +08:00
geminiRequest . GenerationConfig . ThinkingConfig = & GeminiThinkingConfig {
2025-06-18 00:49:35 +08:00
ThinkingBudget : common . GetPointer ( clampedBudget ) ,
2025-06-15 21:12:56 +08:00
IncludeThoughts : true ,
}
}
}
2025-06-18 00:49:35 +08:00
} else if strings . HasSuffix ( modelName , "-thinking" ) {
2025-06-06 01:29:06 +08:00
unsupportedModels := [ ] string {
"gemini-2.5-pro-preview-05-06" ,
"gemini-2.5-pro-preview-03-25" ,
}
isUnsupported := false
for _ , unsupportedModel := range unsupportedModels {
2025-06-18 00:49:35 +08:00
if strings . HasPrefix ( modelName , unsupportedModel ) {
2025-06-06 01:29:06 +08:00
isUnsupported = true
break
}
}
if isUnsupported {
2025-06-06 00:56:38 +08:00
geminiRequest . GenerationConfig . ThinkingConfig = & GeminiThinkingConfig {
IncludeThoughts : true ,
}
} else {
geminiRequest . GenerationConfig . ThinkingConfig = & GeminiThinkingConfig {
IncludeThoughts : true ,
}
2025-06-21 17:51:13 +08:00
if geminiRequest . GenerationConfig . MaxOutputTokens > 0 {
budgetTokens := model_setting . GetGeminiSettings ( ) . ThinkingAdapterBudgetTokensPercentage * float64 ( geminiRequest . GenerationConfig . MaxOutputTokens )
clampedBudget := clampThinkingBudget ( modelName , int ( budgetTokens ) )
geminiRequest . GenerationConfig . ThinkingConfig . ThinkingBudget = common . GetPointer ( clampedBudget )
}
2025-06-06 00:56:38 +08:00
}
2025-06-18 00:49:35 +08:00
} else if strings . HasSuffix ( modelName , "-nothinking" ) {
2025-06-18 03:25:59 +08:00
if ! isNew25Pro {
2025-06-06 01:58:02 +08:00
geminiRequest . GenerationConfig . ThinkingConfig = & GeminiThinkingConfig {
ThinkingBudget : common . GetPointer ( 0 ) ,
}
2025-04-18 23:13:28 +08:00
}
2025-04-18 19:36:18 +08:00
}
}
2025-06-21 21:50:03 +08:00
}
// Setting safety to the lowest possible values since Gemini is already powerless enough
func CovertGemini2OpenAI ( textRequest dto . GeneralOpenAIRequest , info * relaycommon . RelayInfo ) ( * GeminiChatRequest , error ) {
geminiRequest := GeminiChatRequest {
Contents : make ( [ ] GeminiChatContent , 0 , len ( textRequest . Messages ) ) ,
GenerationConfig : GeminiChatGenerationConfig {
Temperature : textRequest . Temperature ,
TopP : textRequest . TopP ,
MaxOutputTokens : textRequest . MaxTokens ,
Seed : int64 ( textRequest . Seed ) ,
} ,
}
if model_setting . IsGeminiModelSupportImagine ( info . UpstreamModelName ) {
geminiRequest . GenerationConfig . ResponseModalities = [ ] string {
"TEXT" ,
"IMAGE" ,
}
}
ThinkingAdaptor ( & geminiRequest , info )
2025-04-18 19:36:18 +08:00
2025-02-26 16:54:43 +08:00
safetySettings := make ( [ ] GeminiChatSafetySettings , 0 , len ( SafetySettingList ) )
for _ , category := range SafetySettingList {
safetySettings = append ( safetySettings , GeminiChatSafetySettings {
Category : category ,
2025-02-26 18:19:09 +08:00
Threshold : model_setting . GetGeminiSafetySetting ( category ) ,
2025-02-26 16:54:43 +08:00
} )
}
geminiRequest . SafetySettings = safetySettings
2024-12-24 20:46:02 +08:00
// openaiContent.FuncToToolCalls()
2024-07-18 20:28:47 +08:00
if textRequest . Tools != nil {
2025-02-26 23:56:10 +08:00
functions := make ( [ ] dto . FunctionRequest , 0 , len ( textRequest . Tools ) )
2024-12-12 17:58:25 +08:00
googleSearch := false
2024-12-24 20:46:02 +08:00
codeExecution := false
2024-07-18 20:28:47 +08:00
for _ , tool := range textRequest . Tools {
2024-12-12 17:58:25 +08:00
if tool . Function . Name == "googleSearch" {
googleSearch = true
continue
}
2024-12-24 20:46:02 +08:00
if tool . Function . Name == "codeExecution" {
codeExecution = true
continue
}
2024-12-22 14:29:14 +08:00
if tool . Function . Parameters != nil {
2025-04-10 22:35:03 +08:00
2024-12-22 14:29:14 +08:00
params , ok := tool . Function . Parameters . ( map [ string ] interface { } )
if ok {
if props , hasProps := params [ "properties" ] . ( map [ string ] interface { } ) ; hasProps {
if len ( props ) == 0 {
2024-12-22 14:35:21 +08:00
tool . Function . Parameters = nil
2024-12-22 14:29:14 +08:00
}
}
}
}
2025-04-10 22:35:03 +08:00
// Clean the parameters before appending
cleanedParams := cleanFunctionParameters ( tool . Function . Parameters )
tool . Function . Parameters = cleanedParams
2024-07-18 20:28:47 +08:00
functions = append ( functions , tool . Function )
}
2024-12-24 20:46:02 +08:00
if codeExecution {
geminiRequest . Tools = append ( geminiRequest . Tools , GeminiChatTool {
CodeExecution : make ( map [ string ] string ) ,
} )
2024-12-12 17:58:25 +08:00
}
if googleSearch {
2024-12-24 20:46:02 +08:00
geminiRequest . Tools = append ( geminiRequest . Tools , GeminiChatTool {
2024-12-12 17:58:25 +08:00
GoogleSearch : make ( map [ string ] string ) ,
} )
2024-07-18 20:28:47 +08:00
}
2024-12-24 20:46:02 +08:00
if len ( functions ) > 0 {
geminiRequest . Tools = append ( geminiRequest . Tools , GeminiChatTool {
FunctionDeclarations : functions ,
} )
}
// common.SysLog("tools: " + fmt.Sprintf("%+v", geminiRequest.Tools))
// json_data, _ := json.Marshal(geminiRequest.Tools)
// common.SysLog("tools_json: " + string(json_data))
2023-12-18 23:45:08 +08:00
}
2024-12-24 20:46:02 +08:00
2024-12-21 16:01:17 +08:00
if textRequest . ResponseFormat != nil && ( textRequest . ResponseFormat . Type == "json_schema" || textRequest . ResponseFormat . Type == "json_object" ) {
geminiRequest . GenerationConfig . ResponseMimeType = "application/json"
if textRequest . ResponseFormat . JsonSchema != nil && textRequest . ResponseFormat . JsonSchema . Schema != nil {
cleanedSchema := removeAdditionalPropertiesWithDepth ( textRequest . ResponseFormat . JsonSchema . Schema , 0 )
geminiRequest . GenerationConfig . ResponseSchema = cleanedSchema
}
}
2024-12-23 01:26:14 +08:00
tool_call_ids := make ( map [ string ] string )
2024-12-24 20:46:02 +08:00
var system_content [ ] string
2024-12-16 20:19:29 +08:00
//shouldAddDummyModelMessage := false
2023-12-18 23:45:08 +08:00
for _ , message := range textRequest . Messages {
2024-12-16 20:19:29 +08:00
if message . Role == "system" {
2024-12-24 20:46:02 +08:00
system_content = append ( system_content , message . StringContent ( ) )
2024-12-16 20:19:29 +08:00
continue
2024-12-24 20:46:02 +08:00
} else if message . Role == "tool" || message . Role == "function" {
if len ( geminiRequest . Contents ) == 0 || geminiRequest . Contents [ len ( geminiRequest . Contents ) - 1 ] . Role == "model" {
2024-12-23 01:26:14 +08:00
geminiRequest . Contents = append ( geminiRequest . Contents , GeminiChatContent {
Role : "user" ,
} )
}
var parts = & geminiRequest . Contents [ len ( geminiRequest . Contents ) - 1 ] . Parts
name := ""
if message . Name != nil {
name = * message . Name
} else if val , exists := tool_call_ids [ message . ToolCallId ] ; exists {
name = val
}
2025-06-08 14:35:56 +08:00
var contentMap map [ string ] interface { }
contentStr := message . StringContent ( )
// 1. 尝试解析为 JSON 对象
if err := json . Unmarshal ( [ ] byte ( contentStr ) , & contentMap ) ; err != nil {
// 2. 如果失败,尝试解析为 JSON 数组
var contentSlice [ ] interface { }
if err := json . Unmarshal ( [ ] byte ( contentStr ) , & contentSlice ) ; err == nil {
// 如果是数组,包装成对象
contentMap = map [ string ] interface { } { "result" : contentSlice }
} else {
// 3. 如果再次失败,作为纯文本处理
contentMap = map [ string ] interface { } { "content" : contentStr }
}
}
2024-12-23 01:26:14 +08:00
functionResp := & FunctionResponse {
2025-06-06 00:56:38 +08:00
Name : name ,
Response : contentMap ,
2024-12-23 01:26:14 +08:00
}
2025-06-06 00:56:38 +08:00
2024-12-23 01:26:14 +08:00
* parts = append ( * parts , GeminiPart {
FunctionResponse : functionResp ,
} )
continue
2024-12-16 20:19:29 +08:00
}
2024-12-22 16:20:30 +08:00
var parts [ ] GeminiPart
2023-12-18 23:45:08 +08:00
content := GeminiChatContent {
Role : message . Role ,
}
2024-12-24 20:46:02 +08:00
// isToolCall := false
2024-12-22 16:20:30 +08:00
if message . ToolCalls != nil {
2024-12-24 20:46:02 +08:00
// message.Role = "model"
// isToolCall = true
2024-12-22 16:20:30 +08:00
for _ , call := range message . ParseToolCalls ( ) {
2024-12-24 20:46:02 +08:00
args := map [ string ] interface { } { }
if call . Function . Arguments != "" {
if json . Unmarshal ( [ ] byte ( call . Function . Arguments ) , & args ) != nil {
return nil , fmt . Errorf ( "invalid arguments for function %s, args: %s" , call . Function . Name , call . Function . Arguments )
}
}
2024-12-22 16:20:30 +08:00
toolCall := GeminiPart {
FunctionCall : & FunctionCall {
FunctionName : call . Function . Name ,
2024-12-24 20:46:02 +08:00
Arguments : args ,
2024-12-22 16:20:30 +08:00
} ,
2024-12-20 21:36:23 +08:00
}
2024-12-22 16:20:30 +08:00
parts = append ( parts , toolCall )
2024-12-23 01:26:14 +08:00
tool_call_ids [ call . ID ] = call . Function . Name
2024-12-22 16:20:30 +08:00
}
}
2024-12-24 20:46:02 +08:00
openaiContent := message . ParseContent ( )
imageNum := 0
for _ , part := range openaiContent {
if part . Type == dto . ContentTypeText {
if part . Text == "" {
continue
}
parts = append ( parts , GeminiPart {
Text : part . Text ,
} )
} else if part . Type == dto . ContentTypeImageURL {
imageNum += 1
if constant . GeminiVisionMaxImageNum != - 1 && imageNum > constant . GeminiVisionMaxImageNum {
return nil , fmt . Errorf ( "too many images in the message, max allowed is %d" , constant . GeminiVisionMaxImageNum )
}
// 判断是否是url
2025-03-15 19:43:37 +08:00
if strings . HasPrefix ( part . GetImageMedia ( ) . Url , "http" ) {
2025-06-02 19:00:55 +08:00
// 是url, 获取文件的类型和base64编码的数据
2025-03-15 19:43:37 +08:00
fileData , err := service . GetFileBase64FromUrl ( part . GetImageMedia ( ) . Url )
2024-12-29 00:00:24 +08:00
if err != nil {
2025-06-02 19:00:55 +08:00
return nil , fmt . Errorf ( "get file base64 from url '%s' failed: %w" , part . GetImageMedia ( ) . Url , err )
}
// 校验 MimeType 是否在 Gemini 支持的白名单中
if _ , ok := geminiSupportedMimeTypes [ strings . ToLower ( fileData . MimeType ) ] ; ! ok {
2025-06-17 22:40:41 +08:00
url := part . GetImageMedia ( ) . Url
2025-06-17 22:44:57 +08:00
return nil , fmt . Errorf ( "mime type is not supported by Gemini: '%s', url: '%s', supported types are: %v" , fileData . MimeType , url , getSupportedMimeTypesList ( ) )
2024-12-29 00:00:24 +08:00
}
2025-06-02 19:00:55 +08:00
2024-12-23 01:26:14 +08:00
parts = append ( parts , GeminiPart {
2024-12-24 20:46:02 +08:00
InlineData : & GeminiInlineData {
2025-06-02 19:00:55 +08:00
MimeType : fileData . MimeType , // 使用原始的 MimeType, 因为大小写可能对API有意义
2024-12-29 00:00:24 +08:00
Data : fileData . Base64Data ,
2024-12-24 20:46:02 +08:00
} ,
2024-12-23 01:26:14 +08:00
} )
2024-12-24 20:46:02 +08:00
} else {
2025-03-15 19:43:37 +08:00
format , base64String , err := service . DecodeBase64FileData ( part . GetImageMedia ( ) . Url )
2024-12-24 20:46:02 +08:00
if err != nil {
return nil , fmt . Errorf ( "decode base64 image data failed: %s" , err . Error ( ) )
2024-07-22 21:20:23 +08:00
}
2024-12-24 20:46:02 +08:00
parts = append ( parts , GeminiPart {
InlineData : & GeminiInlineData {
2024-12-29 10:11:39 +08:00
MimeType : format ,
2024-12-24 20:46:02 +08:00
Data : base64String ,
} ,
} )
2024-07-22 21:20:23 +08:00
}
2025-04-11 16:23:54 +08:00
} else if part . Type == dto . ContentTypeFile {
if part . GetFile ( ) . FileId != "" {
return nil , fmt . Errorf ( "only base64 file is supported in gemini" )
}
format , base64String , err := service . DecodeBase64FileData ( part . GetFile ( ) . FileData )
if err != nil {
return nil , fmt . Errorf ( "decode base64 file data failed: %s" , err . Error ( ) )
}
parts = append ( parts , GeminiPart {
InlineData : & GeminiInlineData {
MimeType : format ,
Data : base64String ,
} ,
} )
} else if part . Type == dto . ContentTypeInputAudio {
if part . GetInputAudio ( ) . Data == "" {
return nil , fmt . Errorf ( "only base64 audio is supported in gemini" )
}
2025-06-07 12:26:23 +08:00
base64String , err := service . DecodeBase64AudioData ( part . GetInputAudio ( ) . Data )
2025-04-11 16:23:54 +08:00
if err != nil {
return nil , fmt . Errorf ( "decode base64 audio data failed: %s" , err . Error ( ) )
}
parts = append ( parts , GeminiPart {
InlineData : & GeminiInlineData {
2025-06-07 12:26:23 +08:00
MimeType : "audio/" + part . GetInputAudio ( ) . Format ,
2025-04-11 16:23:54 +08:00
Data : base64String ,
} ,
} )
2023-12-27 16:32:54 +08:00
}
}
2024-12-23 01:26:14 +08:00
2023-12-27 16:32:54 +08:00
content . Parts = parts
2023-12-18 23:45:08 +08:00
// there's no assistant role in gemini and API shall vomit if Role is not user or model
if content . Role == "assistant" {
content . Role = "model"
}
2025-06-19 11:25:59 +08:00
if len ( content . Parts ) > 0 {
geminiRequest . Contents = append ( geminiRequest . Contents , content )
}
2023-12-18 23:45:08 +08:00
}
2024-12-24 20:46:02 +08:00
if len ( system_content ) > 0 {
geminiRequest . SystemInstructions = & GeminiChatContent {
Parts : [ ] GeminiPart {
{
Text : strings . Join ( system_content , "\n" ) ,
} ,
} ,
}
}
2024-12-20 21:50:58 +08:00
return & geminiRequest , nil
2023-12-18 23:45:08 +08:00
}
2025-06-02 19:00:55 +08:00
// Helper function to get a list of supported MIME types for error messages
func getSupportedMimeTypesList ( ) [ ] string {
keys := make ( [ ] string , 0 , len ( geminiSupportedMimeTypes ) )
for k := range geminiSupportedMimeTypes {
keys = append ( keys , k )
}
return keys
}
2025-04-10 22:35:03 +08:00
// cleanFunctionParameters recursively removes unsupported fields from Gemini function parameters.
func cleanFunctionParameters ( params interface { } ) interface { } {
if params == nil {
return nil
}
2025-05-23 20:02:50 +08:00
switch v := params . ( type ) {
case map [ string ] interface { } :
// Create a copy to avoid modifying the original
cleanedMap := make ( map [ string ] interface { } )
for k , val := range v {
cleanedMap [ k ] = val
}
2025-04-10 22:35:03 +08:00
2025-05-23 20:02:50 +08:00
// Remove unsupported root-level fields
delete ( cleanedMap , "default" )
delete ( cleanedMap , "exclusiveMaximum" )
delete ( cleanedMap , "exclusiveMinimum" )
delete ( cleanedMap , "$schema" )
delete ( cleanedMap , "additionalProperties" )
// Check and clean 'format' for string types
if propType , typeExists := cleanedMap [ "type" ] . ( string ) ; typeExists && propType == "string" {
if formatValue , formatExists := cleanedMap [ "format" ] . ( string ) ; formatExists {
if formatValue != "enum" && formatValue != "date-time" {
delete ( cleanedMap , "format" )
}
}
}
2025-04-10 22:35:03 +08:00
2025-05-23 20:02:50 +08:00
// Clean properties
if props , ok := cleanedMap [ "properties" ] . ( map [ string ] interface { } ) ; ok && props != nil {
cleanedProps := make ( map [ string ] interface { } )
for propName , propValue := range props {
cleanedProps [ propName ] = cleanFunctionParameters ( propValue )
2025-04-10 22:35:03 +08:00
}
2025-05-23 20:02:50 +08:00
cleanedMap [ "properties" ] = cleanedProps
}
2025-04-10 22:35:03 +08:00
2025-05-23 20:02:50 +08:00
// Recursively clean items in arrays
if items , ok := cleanedMap [ "items" ] . ( map [ string ] interface { } ) ; ok && items != nil {
cleanedMap [ "items" ] = cleanFunctionParameters ( items )
}
// Also handle items if it's an array of schemas
if itemsArray , ok := cleanedMap [ "items" ] . ( [ ] interface { } ) ; ok {
cleanedItemsArray := make ( [ ] interface { } , len ( itemsArray ) )
for i , item := range itemsArray {
cleanedItemsArray [ i ] = cleanFunctionParameters ( item )
2025-04-10 22:35:03 +08:00
}
2025-05-23 20:02:50 +08:00
cleanedMap [ "items" ] = cleanedItemsArray
}
2025-04-10 22:35:03 +08:00
2025-05-23 20:02:50 +08:00
// Recursively clean other schema composition keywords
for _ , field := range [ ] string { "allOf" , "anyOf" , "oneOf" } {
if nested , ok := cleanedMap [ field ] . ( [ ] interface { } ) ; ok {
cleanedNested := make ( [ ] interface { } , len ( nested ) )
for i , item := range nested {
cleanedNested [ i ] = cleanFunctionParameters ( item )
2025-04-10 22:35:03 +08:00
}
2025-05-23 20:02:50 +08:00
cleanedMap [ field ] = cleanedNested
2025-04-10 22:35:03 +08:00
}
2025-05-23 20:02:50 +08:00
}
2025-04-10 22:35:03 +08:00
2025-05-23 20:02:50 +08:00
// Recursively clean patternProperties
if patternProps , ok := cleanedMap [ "patternProperties" ] . ( map [ string ] interface { } ) ; ok {
cleanedPatternProps := make ( map [ string ] interface { } )
for pattern , schema := range patternProps {
cleanedPatternProps [ pattern ] = cleanFunctionParameters ( schema )
2025-04-10 22:35:03 +08:00
}
2025-05-23 20:02:50 +08:00
cleanedMap [ "patternProperties" ] = cleanedPatternProps
}
2025-04-10 22:35:03 +08:00
2025-05-23 20:02:50 +08:00
// Recursively clean definitions
if definitions , ok := cleanedMap [ "definitions" ] . ( map [ string ] interface { } ) ; ok {
cleanedDefinitions := make ( map [ string ] interface { } )
for defName , defSchema := range definitions {
cleanedDefinitions [ defName ] = cleanFunctionParameters ( defSchema )
}
cleanedMap [ "definitions" ] = cleanedDefinitions
2025-04-10 22:35:03 +08:00
}
2025-05-23 20:02:50 +08:00
// Recursively clean $defs (newer JSON Schema draft)
if defs , ok := cleanedMap [ "$defs" ] . ( map [ string ] interface { } ) ; ok {
cleanedDefs := make ( map [ string ] interface { } )
for defName , defSchema := range defs {
cleanedDefs [ defName ] = cleanFunctionParameters ( defSchema )
}
cleanedMap [ "$defs" ] = cleanedDefs
2025-04-10 22:35:03 +08:00
}
2025-05-23 20:02:50 +08:00
// Clean conditional keywords
for _ , field := range [ ] string { "if" , "then" , "else" , "not" } {
if nested , ok := cleanedMap [ field ] ; ok {
cleanedMap [ field ] = cleanFunctionParameters ( nested )
2025-04-10 22:35:03 +08:00
}
}
2025-05-23 20:02:50 +08:00
return cleanedMap
case [ ] interface { } :
// Handle arrays of schemas
cleanedArray := make ( [ ] interface { } , len ( v ) )
for i , item := range v {
cleanedArray [ i ] = cleanFunctionParameters ( item )
}
return cleanedArray
default :
// Not a map or array, return as is (e.g., could be a primitive)
return params
}
2025-04-10 22:35:03 +08:00
}
2024-12-21 16:01:17 +08:00
func removeAdditionalPropertiesWithDepth ( schema interface { } , depth int ) interface { } {
if depth >= 5 {
return schema
}
v , ok := schema . ( map [ string ] interface { } )
if ! ok || len ( v ) == 0 {
return schema
}
2024-12-26 00:24:45 +08:00
// 删除所有的title字段
delete ( v , "title" )
2025-05-07 18:08:56 +08:00
delete ( v , "$schema" )
2024-12-21 16:01:17 +08:00
// 如果type不为object和array, 则直接返回
if typeVal , exists := v [ "type" ] ; ! exists || ( typeVal != "object" && typeVal != "array" ) {
return schema
}
switch v [ "type" ] {
case "object" :
delete ( v , "additionalProperties" )
// 处理 properties
if properties , ok := v [ "properties" ] . ( map [ string ] interface { } ) ; ok {
for key , value := range properties {
properties [ key ] = removeAdditionalPropertiesWithDepth ( value , depth + 1 )
}
}
for _ , field := range [ ] string { "allOf" , "anyOf" , "oneOf" } {
if nested , ok := v [ field ] . ( [ ] interface { } ) ; ok {
for i , item := range nested {
nested [ i ] = removeAdditionalPropertiesWithDepth ( item , depth + 1 )
}
}
}
case "array" :
if items , ok := v [ "items" ] . ( map [ string ] interface { } ) ; ok {
v [ "items" ] = removeAdditionalPropertiesWithDepth ( items , depth + 1 )
}
}
return v
}
2024-12-29 03:58:21 +08:00
func unescapeString ( s string ) ( string , error ) {
var result [ ] rune
escaped := false
i := 0
for i < len ( s ) {
r , size := utf8 . DecodeRuneInString ( s [ i : ] ) // 正确解码UTF-8字符
if r == utf8 . RuneError {
return "" , fmt . Errorf ( "invalid UTF-8 encoding" )
}
if escaped {
// 如果是转义符后的字符,检查其类型
switch r {
case '"' :
result = append ( result , '"' )
case '\\' :
result = append ( result , '\\' )
case '/' :
result = append ( result , '/' )
case 'b' :
result = append ( result , '\b' )
case 'f' :
result = append ( result , '\f' )
case 'n' :
result = append ( result , '\n' )
case 'r' :
result = append ( result , '\r' )
case 't' :
result = append ( result , '\t' )
case '\'' :
result = append ( result , '\'' )
default :
// 如果遇到一个非法的转义字符,直接按原样输出
result = append ( result , '\\' , r )
}
escaped = false
} else {
if r == '\\' {
escaped = true // 记录反斜杠作为转义符
} else {
result = append ( result , r )
}
}
i += size // 移动到下一个字符
}
return string ( result ) , nil
}
func unescapeMapOrSlice ( data interface { } ) interface { } {
switch v := data . ( type ) {
case map [ string ] interface { } :
for k , val := range v {
v [ k ] = unescapeMapOrSlice ( val )
}
case [ ] interface { } :
for i , val := range v {
v [ i ] = unescapeMapOrSlice ( val )
}
case string :
if unescaped , err := unescapeString ( v ) ; err != nil {
return v
} else {
return unescaped
}
}
return data
}
2023-12-18 23:45:08 +08:00
2025-02-26 23:56:10 +08:00
func getResponseToolCall ( item * GeminiPart ) * dto . ToolCallResponse {
2024-12-29 03:58:21 +08:00
var argsBytes [ ] byte
var err error
if result , ok := item . FunctionCall . Arguments . ( map [ string ] interface { } ) ; ok {
argsBytes , err = json . Marshal ( unescapeMapOrSlice ( result ) )
} else {
argsBytes , err = json . Marshal ( item . FunctionCall . Arguments )
}
2024-07-18 20:28:47 +08:00
if err != nil {
2024-12-23 01:26:14 +08:00
return nil
2024-07-18 20:28:47 +08:00
}
2025-02-26 23:56:10 +08:00
return & dto . ToolCallResponse {
2024-07-18 20:28:47 +08:00
ID : fmt . Sprintf ( "call_%s" , common . GetUUID ( ) ) ,
Type : "function" ,
2025-02-26 23:56:10 +08:00
Function : dto . FunctionResponse {
2024-12-29 03:58:21 +08:00
Arguments : string ( argsBytes ) ,
2024-07-18 20:28:47 +08:00
Name : item . FunctionCall . FunctionName ,
} ,
}
}
2025-06-16 21:02:27 +08:00
func responseGeminiChat2OpenAI ( c * gin . Context , response * GeminiChatResponse ) * dto . OpenAITextResponse {
2024-02-29 01:08:18 +08:00
fullTextResponse := dto . OpenAITextResponse {
2025-06-16 21:02:27 +08:00
Id : helper . GetResponseID ( c ) ,
2023-12-18 23:45:08 +08:00
Object : "chat.completion" ,
Created : common . GetTimestamp ( ) ,
2024-02-29 01:08:18 +08:00
Choices : make ( [ ] dto . OpenAITextResponseChoice , 0 , len ( response . Candidates ) ) ,
2023-12-18 23:45:08 +08:00
}
2025-02-26 23:40:16 +08:00
isToolCall := false
2024-12-24 20:46:02 +08:00
for _ , candidate := range response . Candidates {
2024-02-29 01:08:18 +08:00
choice := dto . OpenAITextResponseChoice {
2024-12-24 20:46:02 +08:00
Index : int ( candidate . Index ) ,
2024-02-29 01:08:18 +08:00
Message : dto . Message {
2023-12-18 23:45:08 +08:00
Role : "assistant" ,
2025-06-07 23:05:01 +08:00
Content : "" ,
2023-12-18 23:45:08 +08:00
} ,
2024-12-06 14:31:27 +08:00
FinishReason : constant . FinishReasonStop ,
2023-12-18 23:45:08 +08:00
}
if len ( candidate . Content . Parts ) > 0 {
2024-12-23 01:26:14 +08:00
var texts [ ] string
2025-02-26 23:56:10 +08:00
var toolCalls [ ] dto . ToolCallResponse
2024-12-23 01:26:14 +08:00
for _ , part := range candidate . Content . Parts {
if part . FunctionCall != nil {
choice . FinishReason = constant . FinishReasonToolCalls
2025-02-26 23:56:10 +08:00
if call := getResponseToolCall ( & part ) ; call != nil {
2025-02-26 23:40:16 +08:00
toolCalls = append ( toolCalls , * call )
2024-12-23 01:26:14 +08:00
}
2025-05-22 16:11:50 +08:00
} else if part . Thought {
choice . Message . ReasoningContent = part . Text
2024-12-23 01:26:14 +08:00
} else {
2024-12-24 20:46:02 +08:00
if part . ExecutableCode != nil {
texts = append ( texts , "```" + part . ExecutableCode . Language + "\n" + part . ExecutableCode . Code + "\n```" )
} else if part . CodeExecutionResult != nil {
texts = append ( texts , "```output\n" + part . CodeExecutionResult . Output + "\n```" )
} else {
// 过滤掉空行
if part . Text != "\n" {
texts = append ( texts , part . Text )
}
}
2024-12-20 13:20:07 +08:00
}
2024-07-18 20:28:47 +08:00
}
2025-02-26 23:40:16 +08:00
if len ( toolCalls ) > 0 {
choice . Message . SetToolCalls ( toolCalls )
isToolCall = true
2024-12-24 20:46:02 +08:00
}
2024-12-23 01:26:14 +08:00
choice . Message . SetStringContent ( strings . Join ( texts , "\n" ) )
2024-12-24 20:46:02 +08:00
2023-12-18 23:45:08 +08:00
}
2024-12-24 20:46:02 +08:00
if candidate . FinishReason != nil {
switch * candidate . FinishReason {
case "STOP" :
choice . FinishReason = constant . FinishReasonStop
case "MAX_TOKENS" :
choice . FinishReason = constant . FinishReasonLength
default :
choice . FinishReason = constant . FinishReasonContentFilter
}
}
2025-02-26 23:40:16 +08:00
if isToolCall {
2024-12-24 20:46:02 +08:00
choice . FinishReason = constant . FinishReasonToolCalls
}
2023-12-18 23:45:08 +08:00
fullTextResponse . Choices = append ( fullTextResponse . Choices , choice )
}
return & fullTextResponse
}
2025-04-15 02:32:51 +08:00
func streamResponseGeminiChat2OpenAI ( geminiResponse * GeminiChatResponse ) ( * dto . ChatCompletionsStreamResponse , bool , bool ) {
2024-12-24 20:46:02 +08:00
choices := make ( [ ] dto . ChatCompletionsStreamResponseChoice , 0 , len ( geminiResponse . Candidates ) )
2025-03-05 19:47:41 +08:00
isStop := false
2025-04-15 02:32:51 +08:00
hasImage := false
2024-12-24 20:46:02 +08:00
for _ , candidate := range geminiResponse . Candidates {
if candidate . FinishReason != nil && * candidate . FinishReason == "STOP" {
2025-03-05 19:47:41 +08:00
isStop = true
2024-12-24 20:46:02 +08:00
candidate . FinishReason = nil
}
choice := dto . ChatCompletionsStreamResponseChoice {
Index : int ( candidate . Index ) ,
Delta : dto . ChatCompletionsStreamResponseChoiceDelta {
Role : "assistant" ,
} ,
}
2024-12-23 01:26:14 +08:00
var texts [ ] string
2024-12-24 20:46:02 +08:00
isTools := false
2025-05-22 15:52:23 +08:00
isThought := false
2024-12-24 20:46:02 +08:00
if candidate . FinishReason != nil {
// p := GeminiConvertFinishReason(*candidate.FinishReason)
switch * candidate . FinishReason {
case "STOP" :
choice . FinishReason = & constant . FinishReasonStop
case "MAX_TOKENS" :
choice . FinishReason = & constant . FinishReasonLength
default :
choice . FinishReason = & constant . FinishReasonContentFilter
}
}
for _ , part := range candidate . Content . Parts {
2025-04-15 02:32:51 +08:00
if part . InlineData != nil {
if strings . HasPrefix ( part . InlineData . MimeType , "image" ) {
imgText := ""
texts = append ( texts , imgText )
hasImage = true
}
} else if part . FunctionCall != nil {
2024-12-24 20:46:02 +08:00
isTools = true
2025-02-26 23:56:10 +08:00
if call := getResponseToolCall ( & part ) ; call != nil {
2024-12-28 17:46:56 +08:00
call . SetIndex ( len ( choice . Delta . ToolCalls ) )
2024-12-24 20:46:02 +08:00
choice . Delta . ToolCalls = append ( choice . Delta . ToolCalls , * call )
2024-12-23 01:26:14 +08:00
}
2025-05-22 15:52:23 +08:00
} else if part . Thought {
isThought = true
texts = append ( texts , part . Text )
2024-12-23 01:26:14 +08:00
} else {
2024-12-24 20:46:02 +08:00
if part . ExecutableCode != nil {
texts = append ( texts , "```" + part . ExecutableCode . Language + "\n" + part . ExecutableCode . Code + "\n```\n" )
} else if part . CodeExecutionResult != nil {
texts = append ( texts , "```output\n" + part . CodeExecutionResult . Output + "\n```\n" )
} else {
if part . Text != "\n" {
texts = append ( texts , part . Text )
}
}
2024-12-20 20:24:49 +08:00
}
2024-12-23 01:26:14 +08:00
}
2025-05-22 15:52:23 +08:00
if isThought {
choice . Delta . SetReasoningContent ( strings . Join ( texts , "\n" ) )
} else {
choice . Delta . SetContentString ( strings . Join ( texts , "\n" ) )
}
2024-12-24 20:46:02 +08:00
if isTools {
choice . FinishReason = & constant . FinishReasonToolCalls
2024-12-23 01:26:14 +08:00
}
2024-12-24 20:46:02 +08:00
choices = append ( choices , choice )
2024-07-18 20:28:47 +08:00
}
2024-12-24 20:46:02 +08:00
2024-02-29 01:08:18 +08:00
var response dto . ChatCompletionsStreamResponse
2023-12-18 23:45:08 +08:00
response . Object = "chat.completion.chunk"
2024-12-24 20:46:02 +08:00
response . Choices = choices
2025-04-15 02:32:51 +08:00
return & response , isStop , hasImage
2023-12-18 23:45:08 +08:00
}
2025-07-10 15:02:40 +08:00
func GeminiChatStreamHandler ( c * gin . Context , info * relaycommon . RelayInfo , resp * http . Response ) ( * dto . Usage , * types . NewAPIError ) {
2024-12-24 20:46:02 +08:00
// responseText := ""
2025-06-16 21:02:27 +08:00
id := helper . GetResponseID ( c )
2024-07-10 16:01:09 +08:00
createAt := common . GetTimestamp ( )
var usage = & dto . Usage { }
2025-04-15 02:32:51 +08:00
var imageCount int
2025-03-05 19:47:41 +08:00
helper . StreamScannerHandler ( c , resp , info , func ( data string ) bool {
2024-07-18 20:28:47 +08:00
var geminiResponse GeminiChatResponse
2025-06-28 00:02:07 +08:00
err := common . UnmarshalJsonStr ( data , & geminiResponse )
2024-07-18 20:28:47 +08:00
if err != nil {
common . LogError ( c , "error unmarshalling stream response: " + err . Error ( ) )
2025-03-05 19:47:41 +08:00
return false
2023-12-18 23:45:08 +08:00
}
2024-07-18 20:28:47 +08:00
2025-04-15 02:32:51 +08:00
response , isStop , hasImage := streamResponseGeminiChat2OpenAI ( & geminiResponse )
if hasImage {
imageCount ++
}
2024-07-18 20:28:47 +08:00
response . Id = id
response . Created = createAt
2024-12-23 00:02:15 +08:00
response . Model = info . UpstreamModelName
2024-07-18 20:28:47 +08:00
if geminiResponse . UsageMetadata . TotalTokenCount != 0 {
usage . PromptTokens = geminiResponse . UsageMetadata . PromptTokenCount
usage . CompletionTokens = geminiResponse . UsageMetadata . CandidatesTokenCount
2025-04-18 19:36:18 +08:00
usage . CompletionTokenDetails . ReasoningTokens = geminiResponse . UsageMetadata . ThoughtsTokenCount
2025-04-29 16:21:20 +08:00
usage . TotalTokens = geminiResponse . UsageMetadata . TotalTokenCount
2025-06-07 12:26:23 +08:00
for _ , detail := range geminiResponse . UsageMetadata . PromptTokensDetails {
if detail . Modality == "AUDIO" {
usage . PromptTokensDetails . AudioTokens = detail . TokenCount
} else if detail . Modality == "TEXT" {
usage . PromptTokensDetails . TextTokens = detail . TokenCount
}
}
2023-12-18 23:45:08 +08:00
}
2025-03-05 19:47:41 +08:00
err = helper . ObjectData ( c , response )
2024-07-18 20:28:47 +08:00
if err != nil {
common . LogError ( c , err . Error ( ) )
2024-07-10 16:01:09 +08:00
}
2025-03-05 19:47:41 +08:00
if isStop {
response := helper . GenerateStopResponse ( id , createAt , info . UpstreamModelName , constant . FinishReasonStop )
helper . ObjectData ( c , response )
2024-12-24 20:46:02 +08:00
}
2025-03-05 19:47:41 +08:00
return true
} )
2024-07-19 17:16:20 +08:00
2024-12-24 20:46:02 +08:00
var response * dto . ChatCompletionsStreamResponse
2024-07-19 17:16:20 +08:00
2025-04-15 02:32:51 +08:00
if imageCount != 0 {
if usage . CompletionTokens == 0 {
usage . CompletionTokens = imageCount * 258
}
}
2024-12-24 20:46:02 +08:00
usage . PromptTokensDetails . TextTokens = usage . PromptTokens
2025-04-29 16:21:20 +08:00
usage . CompletionTokens = usage . TotalTokens - usage . PromptTokens
2024-07-18 20:28:47 +08:00
2024-07-10 16:01:09 +08:00
if info . ShouldIncludeUsage {
2025-03-05 19:47:41 +08:00
response = helper . GenerateFinalUsageResponse ( id , createAt , info . UpstreamModelName , * usage )
err := helper . ObjectData ( c , response )
2024-07-10 16:01:09 +08:00
if err != nil {
common . SysError ( "send final response failed: " + err . Error ( ) )
}
}
2025-03-05 19:47:41 +08:00
helper . Done ( c )
2025-03-05 19:51:22 +08:00
//resp.Body.Close()
2025-07-10 15:02:40 +08:00
return usage , nil
2023-12-18 23:45:08 +08:00
}
2025-07-10 15:02:40 +08:00
func GeminiChatHandler ( c * gin . Context , info * relaycommon . RelayInfo , resp * http . Response ) ( * dto . Usage , * types . NewAPIError ) {
2023-12-18 23:45:08 +08:00
responseBody , err := io . ReadAll ( resp . Body )
if err != nil {
2025-07-10 15:02:40 +08:00
return nil , types . NewError ( err , types . ErrorCodeBadResponseBody )
2023-12-18 23:45:08 +08:00
}
2025-06-27 21:37:13 +08:00
common . CloseResponseBodyGracefully ( resp )
2025-05-22 16:11:50 +08:00
if common . DebugEnabled {
println ( string ( responseBody ) )
}
2023-12-18 23:45:08 +08:00
var geminiResponse GeminiChatResponse
2025-07-10 15:02:40 +08:00
err = common . Unmarshal ( responseBody , & geminiResponse )
2023-12-18 23:45:08 +08:00
if err != nil {
2025-07-10 15:02:40 +08:00
return nil , types . NewError ( err , types . ErrorCodeBadResponseBody )
2023-12-18 23:45:08 +08:00
}
if len ( geminiResponse . Candidates ) == 0 {
2025-07-10 15:02:40 +08:00
return nil , types . NewError ( errors . New ( "no candidates returned" ) , types . ErrorCodeBadResponseBody )
2023-12-18 23:45:08 +08:00
}
2025-06-16 21:02:27 +08:00
fullTextResponse := responseGeminiChat2OpenAI ( c , & geminiResponse )
2024-12-23 00:02:15 +08:00
fullTextResponse . Model = info . UpstreamModelName
2024-02-29 01:08:18 +08:00
usage := dto . Usage {
2024-07-10 16:01:09 +08:00
PromptTokens : geminiResponse . UsageMetadata . PromptTokenCount ,
CompletionTokens : geminiResponse . UsageMetadata . CandidatesTokenCount ,
TotalTokens : geminiResponse . UsageMetadata . TotalTokenCount ,
2023-12-18 23:45:08 +08:00
}
2025-04-18 19:36:18 +08:00
usage . CompletionTokenDetails . ReasoningTokens = geminiResponse . UsageMetadata . ThoughtsTokenCount
2025-04-29 16:21:20 +08:00
usage . CompletionTokens = usage . TotalTokens - usage . PromptTokens
2025-04-18 19:36:18 +08:00
2025-06-07 12:26:23 +08:00
for _ , detail := range geminiResponse . UsageMetadata . PromptTokensDetails {
if detail . Modality == "AUDIO" {
usage . PromptTokensDetails . AudioTokens = detail . TokenCount
} else if detail . Modality == "TEXT" {
usage . PromptTokensDetails . TextTokens = detail . TokenCount
}
}
2023-12-18 23:45:08 +08:00
fullTextResponse . Usage = usage
jsonResponse , err := json . Marshal ( fullTextResponse )
if err != nil {
2025-07-10 15:02:40 +08:00
return nil , types . NewError ( err , types . ErrorCodeBadResponseBody )
2023-12-18 23:45:08 +08:00
}
c . Writer . Header ( ) . Set ( "Content-Type" , "application/json" )
c . Writer . WriteHeader ( resp . StatusCode )
2025-07-10 15:02:40 +08:00
c . Writer . Write ( jsonResponse )
return & usage , nil
2023-12-18 23:45:08 +08:00
}
2025-03-10 23:32:06 +08:00
2025-07-10 15:02:40 +08:00
func GeminiEmbeddingHandler ( c * gin . Context , info * relaycommon . RelayInfo , resp * http . Response ) ( * dto . Usage , * types . NewAPIError ) {
2025-06-28 00:02:07 +08:00
defer common . CloseResponseBodyGracefully ( resp )
2025-03-10 23:32:06 +08:00
responseBody , readErr := io . ReadAll ( resp . Body )
if readErr != nil {
2025-07-10 15:02:40 +08:00
return nil , types . NewError ( readErr , types . ErrorCodeBadResponseBody )
2025-03-10 23:32:06 +08:00
}
var geminiResponse GeminiEmbeddingResponse
2025-07-10 15:02:40 +08:00
if jsonErr := common . Unmarshal ( responseBody , & geminiResponse ) ; jsonErr != nil {
return nil , types . NewError ( jsonErr , types . ErrorCodeBadResponseBody )
2025-03-10 23:32:06 +08:00
}
// convert to openai format response
openAIResponse := dto . OpenAIEmbeddingResponse {
Object : "list" ,
Data : [ ] dto . OpenAIEmbeddingResponseItem {
{
Object : "embedding" ,
Embedding : geminiResponse . Embedding . Values ,
Index : 0 ,
} ,
} ,
Model : info . UpstreamModelName ,
}
// calculate usage
// https://ai.google.dev/gemini-api/docs/pricing?hl=zh-cn#text-embedding-004
// Google has not yet clarified how embedding models will be billed
// refer to openai billing method to use input tokens billing
// https://platform.openai.com/docs/guides/embeddings#what-are-embeddings
2025-07-10 15:02:40 +08:00
usage := & dto . Usage {
2025-03-10 23:32:06 +08:00
PromptTokens : info . PromptTokens ,
CompletionTokens : 0 ,
TotalTokens : info . PromptTokens ,
}
2025-07-10 15:02:40 +08:00
openAIResponse . Usage = * usage
2025-03-10 23:32:06 +08:00
2025-07-10 15:02:40 +08:00
jsonResponse , jsonErr := common . Marshal ( openAIResponse )
2025-03-10 23:32:06 +08:00
if jsonErr != nil {
2025-07-10 15:02:40 +08:00
return nil , types . NewError ( jsonErr , types . ErrorCodeBadResponseBody )
2025-03-10 23:32:06 +08:00
}
2025-06-28 00:02:07 +08:00
common . IOCopyBytesGracefully ( c , resp , jsonResponse )
2025-03-10 23:32:06 +08:00
return usage , nil
}