From 14b9ea6d3e43df14ab595d31b20821167428ccbd Mon Sep 17 00:00:00 2001 From: alexcastgonz <82875882+pcbeingused333@users.noreply.github.com> Date: Tue, 20 Oct 2026 06:41:57 -0400 Subject: [PATCH] =?UTF-8?q?fix(setup):=20=E6=9B=B4=E6=96=B0=20Google=20?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E6=A8=A1=E5=9E=8B=E5=B9=B6=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=20Gemini=203.8=20=E6=80=9D=E8=80=83=E7=BA=A7=E5=88=AB=20(#7109?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 * fix(llm): honor Gemini 3.8 minimum thinking level --------- Co-authored-by: Claude Sonnet 5.5 Co-authored-by: jxxghp --- app/agent/llm/helper.py | 11 ++++-- docs/cli.md | 4 +++ scripts/local_setup.py | 2 +- tests/test_llm_thinking_options.py | 36 +++++++++++++++++++ tests/test_local_setup_llm_provider_prompt.py | 32 +++++++++++++++++ 5 files changed, 82 insertions(+), 3 deletions(-) diff --git a/app/agent/llm/helper.py b/app/agent/llm/helper.py index cfcfc5cb6..4003390c1 100644 --- a/app/agent/llm/helper.py +++ b/app/agent/llm/helper.py @@ -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", diff --git a/docs/cli.md b/docs/cli.md index d033b146e..fea5640b1 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -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 diff --git a/scripts/local_setup.py b/scripts/local_setup.py index 13acf7216..12e183196 100644 --- a/scripts/local_setup.py +++ b/scripts/local_setup.py @@ -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": { diff --git a/tests/test_llm_thinking_options.py b/tests/test_llm_thinking_options.py index 3c655eb2b..e20ce1f78 100644 --- a/tests/test_llm_thinking_options.py +++ b/tests/test_llm_thinking_options.py @@ -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]) diff --git a/tests/test_local_setup_llm_provider_prompt.py b/tests/test_local_setup_llm_provider_prompt.py index 3d6d0dbc3..1662dd809 100644 --- a/tests/test_local_setup_llm_provider_prompt.py +++ b/tests/test_local_setup_llm_provider_prompt.py @@ -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")