Explorar el Código

feat: Fill thoughtSignature only for Gemini/Vertex channels using the OpenAI format

Seefs hace 3 meses
padre
commit
50c04a62f9

+ 1 - 1
relay/channel/gemini/adaptor.go

@@ -177,7 +177,7 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn
 		return nil, errors.New("request is nil")
 	}
 
-	geminiRequest, err := CovertGemini2OpenAI(c, *request, info)
+	geminiRequest, err := CovertOpenAI2Gemini(c, *request, info)
 	if err != nil {
 		return nil, err
 	}

+ 35 - 1
relay/channel/gemini/relay-gemini.go

@@ -44,6 +44,8 @@ var geminiSupportedMimeTypes = map[string]bool{
 	"video/flv":       true,
 }
 
+const thoughtSignatureBypassValue = "context_engineering_is_the_way_to_go"
+
 // Gemini 允许的思考预算范围
 const (
 	pro25MinBudget       = 128
@@ -181,7 +183,7 @@ func ThinkingAdaptor(geminiRequest *dto.GeminiChatRequest, info *relaycommon.Rel
 }
 
 // Setting safety to the lowest possible values since Gemini is already powerless enough
-func CovertGemini2OpenAI(c *gin.Context, textRequest dto.GeneralOpenAIRequest, info *relaycommon.RelayInfo) (*dto.GeminiChatRequest, error) {
+func CovertOpenAI2Gemini(c *gin.Context, textRequest dto.GeneralOpenAIRequest, info *relaycommon.RelayInfo) (*dto.GeminiChatRequest, error) {
 
 	geminiRequest := dto.GeminiChatRequest{
 		Contents: make([]dto.GeminiChatContent, 0, len(textRequest.Messages)),
@@ -193,6 +195,10 @@ func CovertGemini2OpenAI(c *gin.Context, textRequest dto.GeneralOpenAIRequest, i
 		},
 	}
 
+	attachThoughtSignature := (info.ChannelType == constant.ChannelTypeGemini ||
+		info.ChannelType == constant.ChannelTypeVertexAi) &&
+		model_setting.GetGeminiSettings().FunctionCallThoughtSignatureEnabled
+
 	if model_setting.IsGeminiModelSupportImagine(info.UpstreamModelName) {
 		geminiRequest.GenerationConfig.ResponseModalities = []string{
 			"TEXT",
@@ -371,6 +377,8 @@ func CovertGemini2OpenAI(c *gin.Context, textRequest dto.GeneralOpenAIRequest, i
 		content := dto.GeminiChatContent{
 			Role: message.Role,
 		}
+		shouldAttachThoughtSignature := attachThoughtSignature && (message.Role == "assistant" || message.Role == "model")
+		signatureAttached := false
 		// isToolCall := false
 		if message.ToolCalls != nil {
 			// message.Role = "model"
@@ -388,6 +396,10 @@ func CovertGemini2OpenAI(c *gin.Context, textRequest dto.GeneralOpenAIRequest, i
 						Arguments:    args,
 					},
 				}
+				if shouldAttachThoughtSignature && !signatureAttached && hasFunctionCallContent(toolCall.FunctionCall) && len(toolCall.ThoughtSignature) == 0 {
+					toolCall.ThoughtSignature = json.RawMessage(strconv.Quote(thoughtSignatureBypassValue))
+					signatureAttached = true
+				}
 				parts = append(parts, toolCall)
 				tool_call_ids[call.ID] = call.Function.Name
 			}
@@ -496,6 +508,28 @@ func CovertGemini2OpenAI(c *gin.Context, textRequest dto.GeneralOpenAIRequest, i
 	return &geminiRequest, nil
 }
 
+func hasFunctionCallContent(call *dto.FunctionCall) bool {
+	if call == nil {
+		return false
+	}
+	if strings.TrimSpace(call.FunctionName) != "" {
+		return true
+	}
+
+	switch v := call.Arguments.(type) {
+	case nil:
+		return false
+	case string:
+		return strings.TrimSpace(v) != ""
+	case map[string]interface{}:
+		return len(v) > 0
+	case []interface{}:
+		return len(v) > 0
+	default:
+		return true
+	}
+}
+
 // Helper function to get a list of supported MIME types for error messages
 func getSupportedMimeTypesList() []string {
 	keys := make([]string, 0, len(geminiSupportedMimeTypes))

+ 1 - 1
relay/channel/vertex/adaptor.go

@@ -296,7 +296,7 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn
 		info.UpstreamModelName = claudeReq.Model
 		return vertexClaudeReq, nil
 	} else if a.RequestMode == RequestModeGemini {
-		geminiRequest, err := gemini.CovertGemini2OpenAI(c, *request, info)
+		geminiRequest, err := gemini.CovertOpenAI2Gemini(c, *request, info)
 		if err != nil {
 			return nil, err
 		}

+ 2 - 0
setting/model_setting/gemini.go

@@ -11,6 +11,7 @@ type GeminiSettings struct {
 	SupportedImagineModels                []string          `json:"supported_imagine_models"`
 	ThinkingAdapterEnabled                bool              `json:"thinking_adapter_enabled"`
 	ThinkingAdapterBudgetTokensPercentage float64           `json:"thinking_adapter_budget_tokens_percentage"`
+	FunctionCallThoughtSignatureEnabled   bool              `json:"function_call_thought_signature_enabled"`
 }
 
 // 默认配置
@@ -29,6 +30,7 @@ var defaultGeminiSettings = GeminiSettings{
 	},
 	ThinkingAdapterEnabled:                false,
 	ThinkingAdapterBudgetTokensPercentage: 0.6,
+	FunctionCallThoughtSignatureEnabled:   true,
 }
 
 // 全局实例

+ 1 - 0
web/bun.lock

@@ -1,5 +1,6 @@
 {
   "lockfileVersion": 1,
+  "configVersion": 0,
   "workspaces": {
     "": {
       "name": "react-template",

+ 2 - 0
web/src/i18n/locales/en.json

@@ -69,6 +69,8 @@
     "Gemini思考适配设置": "Gemini thinking adaptation settings",
     "Gemini版本设置": "Gemini version settings",
     "Gemini设置": "Gemini settings",
+    "启用FunctionCall思维签名填充": "Enable FunctionCall thoughtSignature fill",
+    "仅为使用OpenAI格式的Gemini/Vertex渠道填充thoughtSignature": "Fill thoughtSignature only for Gemini/Vertex channels using the OpenAI format",
     "GitHub": "GitHub",
     "GitHub Client ID": "GitHub Client ID",
     "GitHub Client Secret": "GitHub Client Secret",

+ 2 - 0
web/src/i18n/locales/fr.json

@@ -71,6 +71,8 @@
     "Gemini思考适配设置": "Paramètres d'adaptation de la pensée Gemini",
     "Gemini版本设置": "Paramètres de version Gemini",
     "Gemini设置": "Paramètres Gemini",
+    "启用FunctionCall思维签名填充": "Activer le remplissage de thoughtSignature pour FunctionCall",
+    "仅为使用OpenAI格式的Gemini/Vertex渠道填充thoughtSignature": "Remplit thoughtSignature uniquement pour les canaux Gemini/Vertex utilisant le format OpenAI",
     "GitHub": "GitHub",
     "GitHub Client ID": "ID client GitHub",
     "GitHub Client Secret": "Secret client GitHub",

+ 2 - 0
web/src/i18n/locales/ja.json

@@ -69,6 +69,8 @@
     "Gemini思考适配设置": "Gemini思考モード設定",
     "Gemini版本设置": "Geminiバージョン設定",
     "Gemini设置": "Gemini設定",
+    "启用FunctionCall思维签名填充": "FunctionCall用のthoughtSignature自動付与を有効化",
+    "仅为使用OpenAI格式的Gemini/Vertex渠道填充thoughtSignature": "OpenAI形式を利用するGemini/VertexチャネルにのみthoughtSignatureを付与します",
     "GitHub": "GitHub",
     "GitHub Client ID": "GitHub Client ID",
     "GitHub Client Secret": "GitHub Client Secret",

+ 2 - 0
web/src/i18n/locales/ru.json

@@ -73,6 +73,8 @@
     "Gemini思考适配设置": "Настройки адаптации мышления Gemini",
     "Gemini版本设置": "Настройки версии Gemini",
     "Gemini设置": "Настройки Gemini",
+    "启用FunctionCall思维签名填充": "Включить автозаполнение thoughtSignature для FunctionCall",
+    "仅为使用OpenAI格式的Gemini/Vertex渠道填充thoughtSignature": "Заполнять thoughtSignature только для каналов Gemini/Vertex, использующих формат OpenAI",
     "GitHub": "GitHub",
     "GitHub Client ID": "ID клиента GitHub",
     "GitHub Client Secret": "Секрет клиента GitHub",

+ 2 - 0
web/src/i18n/locales/zh.json

@@ -67,6 +67,8 @@
     "Gemini思考适配设置": "Gemini思考适配设置",
     "Gemini版本设置": "Gemini版本设置",
     "Gemini设置": "Gemini设置",
+    "启用FunctionCall思维签名填充": "启用FunctionCall思维签名填充",
+    "仅为使用OpenAI格式的Gemini/Vertex渠道填充thoughtSignature": "仅为使用OpenAI格式的Gemini/Vertex渠道填充thoughtSignature",
     "GitHub": "GitHub",
     "GitHub Client ID": "GitHub Client ID",
     "GitHub Client Secret": "GitHub Client Secret",

+ 30 - 10
web/src/pages/Setting/Model/SettingGeminiModel.jsx

@@ -39,19 +39,22 @@ const GEMINI_VERSION_EXAMPLE = {
   default: 'v1beta',
 };
 
+const DEFAULT_GEMINI_INPUTS = {
+  'gemini.safety_settings': '',
+  'gemini.version_settings': '',
+  'gemini.supported_imagine_models': '',
+  'gemini.thinking_adapter_enabled': false,
+  'gemini.thinking_adapter_budget_tokens_percentage': 0.6,
+  'gemini.function_call_thought_signature_enabled': true,
+};
+
 export default function SettingGeminiModel(props) {
   const { t } = useTranslation();
 
   const [loading, setLoading] = useState(false);
-  const [inputs, setInputs] = useState({
-    'gemini.safety_settings': '',
-    'gemini.version_settings': '',
-    'gemini.supported_imagine_models': '',
-    'gemini.thinking_adapter_enabled': false,
-    'gemini.thinking_adapter_budget_tokens_percentage': 0.6,
-  });
+  const [inputs, setInputs] = useState(DEFAULT_GEMINI_INPUTS);
   const refForm = useRef();
-  const [inputsRow, setInputsRow] = useState(inputs);
+  const [inputsRow, setInputsRow] = useState(DEFAULT_GEMINI_INPUTS);
 
   async function onSubmit() {
     await refForm.current
@@ -92,9 +95,9 @@ export default function SettingGeminiModel(props) {
   }
 
   useEffect(() => {
-    const currentInputs = {};
+    const currentInputs = { ...DEFAULT_GEMINI_INPUTS };
     for (let key in props.options) {
-      if (Object.keys(inputs).includes(key)) {
+      if (Object.prototype.hasOwnProperty.call(DEFAULT_GEMINI_INPUTS, key)) {
         currentInputs[key] = props.options[key];
       }
     }
@@ -166,6 +169,23 @@ export default function SettingGeminiModel(props) {
                 />
               </Col>
             </Row>
+            <Row>
+              <Col span={16}>
+                <Form.Switch
+                  label={t('启用FunctionCall思维签名填充')}
+                  field={'gemini.function_call_thought_signature_enabled'}
+                  extraText={t(
+                    '仅为使用OpenAI格式的Gemini/Vertex渠道填充thoughtSignature',
+                  )}
+                  onChange={(value) =>
+                    setInputs({
+                      ...inputs,
+                      'gemini.function_call_thought_signature_enabled': value,
+                    })
+                  }
+                />
+              </Col>
+            </Row>
             <Row>
               <Col xs={24} sm={12} md={8} lg={8} xl={8}>
                 <Form.TextArea