fix(setup): 更新 Google 默认模型并兼容 Gemini 3.8 思考级别 (#7109)

* fix(setup): 更新 Google 默认模型为 gemini-3.8-flash

安装向导中 Google 的预设模型 gemini-2.5-flash 对新建的 Gemini API 密钥
已返回 404,在模型选择处直接回车会写入无法使用的 LLM_MODEL。改为
Google 文档中推荐给新项目的稳定模型 gemini-3.8-flash。

新增用例校验预设值不再是 Gemini 2.5 系列。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(llm): honor Gemini 3.8 minimum thinking level

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: jxxghp <jxxghp@gmail.com>
This commit is contained in:
alexcastgonz
2026-10-20 06:41:57 -04:00
committed by GitHub
parent d57af7979e
commit 14b9ea6d3e
5 changed files with 82 additions and 3 deletions

View File

@@ -984,6 +984,12 @@ class LLMHelper:
) -> dict[str, Any]:
"""
Gemini 3 使用 thinking_level;Gemini 2.5 使用 thinking_budget。
Gemini 3.8 Flash 不接受 minimal,也不能完全关闭思考,因此将关闭和最小映射到 low。
:param model_name: 已规范化的模型标识
:param thinking_level: 用户选择的统一思考级别
:return: 当前模型支持的 Google 原生思考参数
"""
if not model_name or thinking_level == "auto":
return {}
@@ -1020,9 +1026,10 @@ class LLMHelper:
)
if "gemini-3" in model_name:
minimum_level = "low" if "gemini-3.8-flash" in model_name else "minimal"
level_map = {
"off": "minimal",
"minimal": "minimal",
"off": minimum_level,
"minimal": minimum_level,
"low": "low",
"medium": "medium",
"high": "high",

View File

@@ -281,6 +281,7 @@ moviepilot setup --config-dir /path/to/moviepilot-config
与 `LLM_WEB_SEARCH_MODE`。联网搜索支持 MoviePilot 本地搜索、模型服务端搜索、
服务端优先自动回退与完全关闭;服务端模式仅在当前模型目录声明支持时生效。
向导会尝试读取当前提供商的模型目录;目录服务不可用时仍可手动输入模型 ID。
Google 新配置默认使用 `gemini-3.8-flash`,已有的 `LLM_MODEL` 配置继续保留。
当前可识别 OpenAI、Anthropic Claude、Google Gemini、xAI Grok 与 DeepSeek
官方端点已公布的服务端联网搜索能力,第三方兼容端点不会被自动误判。
- 下载目录按下载器内部路径原样保存,可用于远程下载器和路径映射;向导不会在本机解析或创建它。
@@ -298,6 +299,9 @@ moviepilot setup --config-dir /path/to/moviepilot-config
显式填写 `0`、`1` 等数值仍按填写值发送。
清空后保存、重新进入设置页及重启均保留这一语义。
Gemini 3.8 Flash 不支持完全关闭思考,也不接受 `minimal`。选择“关闭思考”或“最小”时,
使用该模型支持的最低级别 `low`;自动及其他思考级别按原设置处理。
如果希望在自动化安装时直接预设超级管理员,也可以在一键安装脚本中透传:
```shell

View File

@@ -76,7 +76,7 @@ LLM_PROVIDER_DEFAULTS = {
"base_url": "https://api.openai.com/v1",
},
"google": {
"model": "gemini-2.5-flash",
"model": "gemini-3.8-flash",
"base_url": "",
},
"anthropic": {

View File

@@ -114,9 +114,15 @@ def offline_runtime(monkeypatch):
clients = []
def reject_request(_request):
"""
拒绝任何意外的提供商网络调用。
"""
raise AssertionError("思考级别序列化测试不得发起外部请求")
def make_client(_proxy, *, async_client=False):
"""
创建仅使用离线传输器的客户端,并登记退出时的释放责任。
"""
client_type = httpx.AsyncClient if async_client else httpx.Client
client = client_type(transport=httpx.MockTransport(reject_request))
clients.append(client)
@@ -131,6 +137,36 @@ def offline_runtime(monkeypatch):
client.close()
@pytest.mark.parametrize("model_name", ["gemini-3.8-flash", "models/gemini-3.8-flash", "gemini-3-flash-preview"])
@pytest.mark.parametrize("level", ["off", "minimal", "low", "medium", "high", "max", "xhigh", "auto"])
def test_google_thinking_payload_respects_model_minimum(monkeypatch, offline_runtime, model_name, level) -> None:
"""
原生 Google 请求对 Gemini 3.8 Flash 使用最低可用的 low,并保留旧模型的 minimal。
"""
from langchain_google_genai import chat_models
offline_runtime.resolve_runtime.return_value.update(
provider_id="google", runtime="google", model_id=model_name, base_url=None,
)
monkeypatch.setattr(helper, "_patch_gemini_thought_signature", lambda: None)
monkeypatch.setattr(chat_models, "Client", Mock(return_value=Mock(aio=None)))
model = asyncio.run(LLMHelper.get_llm(
provider="google", model=model_name, api_key="offline-key", use_proxy=False,
web_search_mode="disabled", thinking_level=level, provider_runtime=offline_runtime,
))
config = model._prepare_params(None).thinking_config
if level == "auto":
assert config is None or config.thinking_level is None
return
expected = {"off": "minimal", "max": "high", "xhigh": "high"}.get(level, level)
if "gemini-3.8-flash" in model_name and expected == "minimal":
expected = "low"
assert config.thinking_level.value.lower() == expected
assert config.include_thoughts is False
@pytest.mark.parametrize("protocol", ["chat_completions", "responses"])
@pytest.mark.parametrize("effort", ["max", "xhigh"])
@pytest.mark.parametrize("has_catalog", [False, True])

View File

@@ -9,6 +9,8 @@ import uuid
from pathlib import Path
from unittest.mock import patch
import pytest
MODULE_PATH = Path(__file__).resolve().parents[1] / "scripts" / "local_setup.py"
@@ -296,3 +298,33 @@ def test_collect_agent_config_prompts_for_duplicate_base_url_presets() -> None:
base_url_preset="minimax-cn-coding",
runtime_python=None,
)
@pytest.mark.parametrize("saved_model", [None, "gemini-2.5-flash"])
def test_collect_agent_config_accepts_google_default_and_keeps_saved_model(saved_model) -> None:
"""
Google 向导回车使用新项目可访问的默认模型,同时保留用户已保存的模型。
"""
module = load_local_setup_module()
with (
patch.object(module, "print_step"),
patch.object(module, "_prompt_yes_no", side_effect=[True, False, True]),
patch.object(module, "_load_llm_provider_definitions", return_value=[]),
patch.object(module, "_prompt_provider_choice", return_value="google"),
patch.object(module, "_prompt_text", side_effect=lambda _prompt, **kwargs: kwargs["default"]),
patch.object(module, "_prompt_secret_text", return_value="offline-key"),
patch.object(module, "_load_llm_models", return_value=[]),
patch.object(module, "read_env_value", return_value=None),
patch.object(
module, "_env_default",
side_effect=lambda key, default="": saved_model if key == "LLM_MODEL" and saved_model else default,
),
patch.object(module, "_env_bool", side_effect=lambda _key, default: default),
patch.object(module, "_env_llm_thinking_level_default", return_value="auto"),
patch.object(module, "_prompt_choice", side_effect=lambda _prompt, **kwargs: kwargs["default"]),
):
config = module._collect_agent_config()
assert config["LLM_PROVIDER"] == "google"
assert config["LLM_MODEL"] == (saved_model or "gemini-3.8-flash")