diff --git a/astrbot/core/provider/sources/gemini_source.py b/astrbot/core/provider/sources/gemini_source.py index 388ab5c91f..489ca358b8 100644 --- a/astrbot/core/provider/sources/gemini_source.py +++ b/astrbot/core/provider/sources/gemini_source.py @@ -241,15 +241,10 @@ async def _prepare_query_config( thinking_config = types.ThinkingConfig( thinking_budget=thinking_budget, ) - elif model_name in [ - "gemini-3-pro", - "gemini-3-pro-preview", - "gemini-3-flash", - "gemini-3-flash-preview", - "gemini-3-flash-lite", - "gemini-3-flash-lite-preview", - ]: - # The thinkingLevel parameter, recommended for Gemini 3 models and onwards + elif any(model_name.startswith(p) for p in ("gemini-3-", "gemini-3.")): + # The thinkingLevel parameter, recommended for Gemini 3 models and onwards. + # Use prefix match so new variants (3.1, 3-flash-lite-preview, etc.) are + # covered without needing to keep an exhaustive list up to date. # Gemini 2.5 series models don't support thinkingLevel; use thinkingBudget instead. thinking_level = self.provider_config.get("gm_thinking_config", {}).get( "level", "HIGH" @@ -517,7 +512,11 @@ def _process_content_parts( ): chain.append(Comp.Plain("这是图片")) for part in result_parts: - if part.text: + # Skip thinking parts — their text is already captured via + # _extract_reasoning_content above. Including them here would + # leak the model's internal reasoning into the user-facing message, + # which also causes duplicate/triple replies on some platforms. + if part.text and not part.thought: chain.append(Comp.Plain(part.text)) if (