Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions relay/channel/gemini/constant.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ var ModelList = []string{
"embedding-001",
}

// Abilities and limitations
// See: https://ai.google.dev/gemini-api/docs/thinking#supported-models
var ModelsWithThoughtsSummarySupport = []string{
"gemini-2.5-pro",
"gemini-2.5-flash",
"gemini-2.5-flash-lite",
}

// Models with minimum thinking budget limits (thinking cannot be completely disabled due to the model's design)
var ModelsWithMinimumThinkingBudgetLimits = []string{
"gemini-2.5-pro",
}

var SafetySettingList = []string{
"HARM_CATEGORY_HARASSMENT",
"HARM_CATEGORY_HATE_SPEECH",
Expand Down
25 changes: 25 additions & 0 deletions relay/channel/gemini/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package gemini

import (
"strings"
)

func IsModelSupportThoughtsSummary(modelName string) bool {
for _, supportedModel := range ModelsWithThoughtsSummarySupport {
if strings.HasPrefix(modelName, supportedModel) {
return true
}
}

return false
}

func IsModelWithMinimumThinkingBudgetLimits(modelName string) bool {
for _, specialModel := range ModelsWithMinimumThinkingBudgetLimits {
if strings.HasPrefix(modelName, specialModel) {
return true
}
}

return false
}
106 changes: 62 additions & 44 deletions relay/channel/gemini/relay-gemini.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,56 +79,74 @@ func clampThinkingBudget(modelName string, budget int) int {
}

func ThinkingAdaptor(geminiRequest *GeminiChatRequest, info *relaycommon.RelayInfo) {
if model_setting.GetGeminiSettings().ThinkingAdapterEnabled {
modelName := info.UpstreamModelName
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)
if len(parts) == 2 && parts[1] != "" {
if budgetTokens, err := strconv.Atoi(parts[1]); err == nil {
clampedBudget := clampThinkingBudget(modelName, budgetTokens)
geminiRequest.GenerationConfig.ThinkingConfig = &GeminiThinkingConfig{
ThinkingBudget: common.GetPointer(clampedBudget),
IncludeThoughts: true,
}
if !model_setting.GetGeminiSettings().ThinkingAdapterEnabled {
return
}

modelName := info.UpstreamModelName

// 1. Process thinking budget settings
if strings.Contains(modelName, "-thinking-") {
parts := strings.SplitN(modelName, "-thinking-", 2)
if len(parts) == 2 && parts[1] != "" {
if budgetTokens, err := strconv.Atoi(parts[1]); err == nil {
clampedBudget := clampThinkingBudget(modelName, budgetTokens)
geminiRequest.GenerationConfig.ThinkingConfig = &GeminiThinkingConfig{
ThinkingBudget: common.GetPointer(clampedBudget),
}
}
} else if strings.HasSuffix(modelName, "-thinking") {
unsupportedModels := []string{
"gemini-2.5-pro-preview-05-06",
"gemini-2.5-pro-preview-03-25",
}
isUnsupported := false
for _, unsupportedModel := range unsupportedModels {
if strings.HasPrefix(modelName, unsupportedModel) {
isUnsupported = true
break
}
}
} else if strings.HasSuffix(modelName, "-thinking") {
unsupportedModels := []string{
"gemini-2.5-pro-preview-05-06",
"gemini-2.5-pro-preview-03-25",
}
isSupported := true
for _, unsupportedModel := range unsupportedModels {
if strings.HasPrefix(modelName, unsupportedModel) {
isSupported = false
break
}
}

if isUnsupported {
geminiRequest.GenerationConfig.ThinkingConfig = &GeminiThinkingConfig{
IncludeThoughts: true,
}
} else {
geminiRequest.GenerationConfig.ThinkingConfig = &GeminiThinkingConfig{
IncludeThoughts: true,
}
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)
}
if isSupported && geminiRequest.GenerationConfig.MaxOutputTokens > 0 {
budgetTokens := model_setting.GetGeminiSettings().ThinkingAdapterBudgetTokensPercentage * float64(geminiRequest.GenerationConfig.MaxOutputTokens)
clampedBudget := clampThinkingBudget(modelName, int(budgetTokens))
geminiRequest.GenerationConfig.ThinkingConfig = &GeminiThinkingConfig{
ThinkingBudget: common.GetPointer(clampedBudget),
}
} else if strings.HasSuffix(modelName, "-nothinking") {
if !isNew25Pro {
geminiRequest.GenerationConfig.ThinkingConfig = &GeminiThinkingConfig{
ThinkingBudget: common.GetPointer(0),
}
}
} else if strings.HasSuffix(modelName, "-nothinking") {
thinkingBudget := 0

// Reset to minimum allowed value if the model has minimum thinking budget limits
if IsModelWithMinimumThinkingBudgetLimits(modelName) {
thinkingBudget = clampThinkingBudget(modelName, 0)
}

geminiRequest.GenerationConfig.ThinkingConfig = &GeminiThinkingConfig{
ThinkingBudget: common.GetPointer(thinkingBudget),
}
}

// 2. Process thoughts summary parameter settings
if model_setting.GetGeminiSettings().IncludeThoughtsSummaryEnabled && IsModelSupportThoughtsSummary(modelName) {
applyIncludeThoughts := true
allowZeroThinkingBudget := !IsModelWithMinimumThinkingBudgetLimits(modelName)

// Skip only if the model has -nothinking suffix and no minimum thinking budget limits.
// Keep applying if the model has minimum thinking budget limits since there still a little pieces
// of thoughts content in the response
if strings.HasSuffix(modelName, "-nothinking") && allowZeroThinkingBudget {
applyIncludeThoughts = false
}

if applyIncludeThoughts {
if geminiRequest.GenerationConfig.ThinkingConfig == nil {
geminiRequest.GenerationConfig.ThinkingConfig = &GeminiThinkingConfig{}
}

geminiRequest.GenerationConfig.ThinkingConfig.IncludeThoughts = applyIncludeThoughts
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions setting/model_setting/gemini.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
IncludeThoughtsSummaryEnabled bool `json:"include_thoughts_summary_enabled"`
}

// 默认配置
Expand All @@ -29,6 +30,7 @@ var defaultGeminiSettings = GeminiSettings{
},
ThinkingAdapterEnabled: false,
ThinkingAdapterBudgetTokensPercentage: 0.6,
IncludeThoughtsSummaryEnabled: true,
}

// 全局实例
Expand Down
1 change: 1 addition & 0 deletions web/src/components/settings/ModelSetting.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const ModelSetting = () => {
'general_setting.ping_interval_seconds': 60,
'gemini.thinking_adapter_enabled': false,
'gemini.thinking_adapter_budget_tokens_percentage': 0.6,
'gemini.include_thoughts_summary_enabled': true,
});

let [loading, setLoading] = useState(false);
Expand Down
2 changes: 2 additions & 0 deletions web/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1691,6 +1691,8 @@
"请先选择同步渠道": "Please select the synchronization channel first",
"与本地相同": "Same as local",
"未找到匹配的模型": "No matching model found",
"包含思考摘要": "Include thinking summaries",
"开启思考过程摘要并将收到的摘要包含在回复中。仅在模型支持和和思考预算不为0时生效。": "Enable thinking summaries and include the received summaries in the reply. Only effective when the model supports it and thinking budget is not 0.",
"暴露倍率接口": "Expose ratio API",
"支付设置": "Payment Settings",
"(当前仅支持易支付接口,默认使用上方服务器地址作为回调地址!)": "(Currently only supports Epay interface, the default callback address is the server address above!)",
Expand Down
16 changes: 16 additions & 0 deletions web/src/pages/Setting/Model/SettingGeminiModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export default function SettingGeminiModel(props) {
'gemini.supported_imagine_models': '',
'gemini.thinking_adapter_enabled': false,
'gemini.thinking_adapter_budget_tokens_percentage': 0.6,
'gemini.include_thoughts_summary_enabled': true,
});
const refForm = useRef();
const [inputsRow, setInputsRow] = useState(inputs);
Expand Down Expand Up @@ -221,6 +222,21 @@ export default function SettingGeminiModel(props) {
/>
</Col>
</Row>
<Row>
<Col span={16}>
<Form.Switch
label={t('包含思考摘要')}
field={'gemini.include_thoughts_summary_enabled'}
extraText={t('开启思考过程摘要并将收到的摘要包含在回复中。仅在模型支持和思考预算不为0时生效。')}
onChange={(value) =>
setInputs({
...inputs,
'gemini.include_thoughts_summary_enabled': value,
})
}
/>
</Col>
</Row>
</Form.Section>

<Row>
Expand Down