diff --git a/relay/channel/gemini/adaptor.go b/relay/channel/gemini/adaptor.go
index b1067bc20..b522ca1be 100644
--- a/relay/channel/gemini/adaptor.go
+++ b/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
}
diff --git a/relay/channel/gemini/relay-gemini.go b/relay/channel/gemini/relay-gemini.go
index 51a0d615d..7ecf40fa9 100644
--- a/relay/channel/gemini/relay-gemini.go
+++ b/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))
diff --git a/relay/channel/vertex/adaptor.go b/relay/channel/vertex/adaptor.go
index a4b303e26..d117ef70e 100644
--- a/relay/channel/vertex/adaptor.go
+++ b/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
}
diff --git a/setting/model_setting/gemini.go b/setting/model_setting/gemini.go
index 4856482ed..6b68e6b20 100644
--- a/setting/model_setting/gemini.go
+++ b/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,
}
// 全局实例
diff --git a/web/bun.lock b/web/bun.lock
index e349685c2..fdec073ec 100644
--- a/web/bun.lock
+++ b/web/bun.lock
@@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
+ "configVersion": 0,
"workspaces": {
"": {
"name": "react-template",
diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json
index 0d4614ecf..7295914f4 100644
--- a/web/src/i18n/locales/en.json
+++ b/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",
diff --git a/web/src/i18n/locales/fr.json b/web/src/i18n/locales/fr.json
index fd310d259..ded4de6d0 100644
--- a/web/src/i18n/locales/fr.json
+++ b/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",
diff --git a/web/src/i18n/locales/ja.json b/web/src/i18n/locales/ja.json
index 28590bb62..a9ea3d0d8 100644
--- a/web/src/i18n/locales/ja.json
+++ b/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",
diff --git a/web/src/i18n/locales/ru.json b/web/src/i18n/locales/ru.json
index 67d4663f8..df9e52a38 100644
--- a/web/src/i18n/locales/ru.json
+++ b/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",
diff --git a/web/src/i18n/locales/zh.json b/web/src/i18n/locales/zh.json
index 63772d8d7..29c1c7f40 100644
--- a/web/src/i18n/locales/zh.json
+++ b/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",
diff --git a/web/src/pages/Setting/Model/SettingGeminiModel.jsx b/web/src/pages/Setting/Model/SettingGeminiModel.jsx
index 0ba7e2927..e75a4ca91 100644
--- a/web/src/pages/Setting/Model/SettingGeminiModel.jsx
+++ b/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) {
/>
+
+
+
+ setInputs({
+ ...inputs,
+ 'gemini.function_call_thought_signature_enabled': value,
+ })
+ }
+ />
+
+