diff --git a/app/api/endpoints/system.py b/app/api/endpoints/system.py index f0cccfe5..570ddf1c 100644 --- a/app/api/endpoints/system.py +++ b/app/api/endpoints/system.py @@ -57,7 +57,9 @@ class LlmTestRequest(BaseModel): enabled: Optional[bool] = None provider: Optional[str] = None model: Optional[str] = None + thinking_level: Optional[str] = None disable_thinking: Optional[bool] = None + reasoning_effort: Optional[str] = None api_key: Optional[str] = None base_url: Optional[str] = None @@ -308,7 +310,13 @@ def _build_llm_test_snapshot(payload: Optional[LlmTestRequest] = None) -> dict[s """ provider = settings.LLM_PROVIDER model = settings.LLM_MODEL + thinking_level = _normalize_llm_test_value( + getattr(settings, "LLM_THINKING_LEVEL", None), empty_as_none=True + ) disable_thinking = bool(getattr(settings, "LLM_DISABLE_THINKING", False)) + reasoning_effort = _normalize_llm_test_value( + getattr(settings, "LLM_REASONING_EFFORT", None), empty_as_none=True + ) api_key = settings.LLM_API_KEY base_url = settings.LLM_BASE_URL enabled = bool(settings.AI_AGENT_ENABLE) @@ -320,18 +328,32 @@ def _build_llm_test_snapshot(payload: Optional[LlmTestRequest] = None) -> dict[s provider = _normalize_llm_test_value(payload.provider) or "" if payload.model is not None: model = _normalize_llm_test_value(payload.model) or "" + if payload.thinking_level is not None: + thinking_level = _normalize_llm_test_value( + payload.thinking_level, empty_as_none=True + ) if payload.disable_thinking is not None: disable_thinking = bool(payload.disable_thinking) + if payload.reasoning_effort is not None: + reasoning_effort = _normalize_llm_test_value( + payload.reasoning_effort, empty_as_none=True + ) if payload.api_key is not None: api_key = _normalize_llm_test_value(payload.api_key, empty_as_none=True) if payload.base_url is not None: base_url = _normalize_llm_test_value(payload.base_url, empty_as_none=True) + if thinking_level is not None: + disable_thinking = None + reasoning_effort = None + return { "enabled": enabled, "provider": provider, "model": model, + "thinking_level": thinking_level, "disable_thinking": disable_thinking, + "reasoning_effort": reasoning_effort, "api_key": api_key, "base_url": base_url, } @@ -760,7 +782,9 @@ async def llm_test( result = await LLMHelper.test_current_settings( provider=snapshot["provider"], model=snapshot["model"], + thinking_level=snapshot["thinking_level"], disable_thinking=snapshot["disable_thinking"], + reasoning_effort=snapshot["reasoning_effort"], api_key=snapshot["api_key"], base_url=snapshot["base_url"], ) diff --git a/app/core/config.py b/app/core/config.py index bb8f42e0..76576ade 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -505,8 +505,12 @@ class ConfigModel(BaseModel): LLM_PROVIDER: str = "deepseek" # LLM模型名称 LLM_MODEL: str = "deepseek-chat" - # 是否尽量关闭模型的思考/推理能力(按各 provider/model 支持情况自动适配) + # 统一思考模式/深度配置:off/auto/minimal/low/medium/high/max/xhigh + LLM_THINKING_LEVEL: Optional[str] = None + # 兼容旧配置:是否尽量关闭模型的思考/推理能力(新配置优先) LLM_DISABLE_THINKING: bool = True + # 兼容旧配置:思考强度(新配置优先) + LLM_REASONING_EFFORT: Optional[str] = None # LLM是否支持图片输入,开启后消息图片会按多模态输入发送给模型 LLM_SUPPORT_IMAGE_INPUT: bool = True # LLM API密钥 diff --git a/app/helper/llm.py b/app/helper/llm.py index 9e337bef..d5edf0e6 100644 --- a/app/helper/llm.py +++ b/app/helper/llm.py @@ -77,6 +77,10 @@ def _get_httpx_proxy_key() -> str: class LLMHelper: """LLM模型相关辅助功能""" + _SUPPORTED_THINKING_LEVELS = frozenset( + {"off", "auto", "minimal", "low", "medium", "high", "max", "xhigh"} + ) + @staticmethod def _should_disable_thinking(disable_thinking: bool | None = None) -> bool: """ @@ -94,48 +98,276 @@ class LLMHelper: return (model_name or "").strip().lower() @classmethod - def _build_disabled_thinking_kwargs( + def _normalize_thinking_level_value(cls, value: str | None) -> str | None: + """ + 统一清理思考级别/强度值,并兼容常见别名。 + """ + if value is None: + return None + + normalized = str(value).strip().lower() + if not normalized: + return None + + alias_map = { + "none": "off", + "disabled": "off", + "disable": "off", + "enabled": "auto", + "enable": "auto", + "default": "auto", + "dynamic": "auto", + } + return alias_map.get(normalized, normalized) + + @classmethod + def _normalize_thinking_level( + cls, thinking_level: str | None = None + ) -> str | None: + """ + 统一清理 thinking_level 配置。 + """ + value = ( + thinking_level + if thinking_level is not None + else getattr(settings, "LLM_THINKING_LEVEL", None) + ) + normalized = cls._normalize_thinking_level_value(value) + if not normalized: + return None + + if normalized not in cls._SUPPORTED_THINKING_LEVELS: + logger.warning(f"忽略不支持的 thinking_level 配置: {normalized}") + return None + return normalized + + @classmethod + def _normalize_reasoning_effort( + cls, reasoning_effort: str | None = None + ) -> str | None: + """ + 统一清理 legacy reasoning_effort 配置。 + """ + value = ( + reasoning_effort + if reasoning_effort is not None + else getattr(settings, "LLM_REASONING_EFFORT", None) + ) + return cls._normalize_thinking_level(value) + + @classmethod + def _resolve_thinking_level( + cls, + thinking_level: str | None = None, + disable_thinking: bool | None = None, + reasoning_effort: str | None = None, + ) -> str: + """ + 统一解析本次调用的思考配置。 + + 优先级: + 1. 新字段 `thinking_level` + 2. 本次调用传入的 legacy 字段 + 3. 已保存的新字段 `LLM_THINKING_LEVEL` + 4. 已保存的 legacy 字段 + """ + explicit_level = cls._normalize_thinking_level(thinking_level) + if explicit_level: + return explicit_level + + explicit_effort = ( + cls._normalize_reasoning_effort(reasoning_effort) + if reasoning_effort is not None + else None + ) + if disable_thinking is not None or reasoning_effort is not None: + if disable_thinking is not None and bool(disable_thinking): + return "off" + return explicit_effort or "auto" + + configured_level = cls._normalize_thinking_level( + getattr(settings, "LLM_THINKING_LEVEL", None) + ) + if configured_level: + return configured_level + + legacy_disable = getattr(settings, "LLM_DISABLE_THINKING", None) + legacy_effort = cls._normalize_reasoning_effort( + getattr(settings, "LLM_REASONING_EFFORT", None) + ) + if legacy_disable is not None: + return "off" if bool(legacy_disable) else (legacy_effort or "auto") + + return legacy_effort or "off" + + @classmethod + def _normalize_deepseek_reasoning_effort( + cls, thinking_level: str | None = None + ) -> str | None: + """ + DeepSeek 文档当前建议使用 high/max;兼容常见 effort 别名。 + """ + normalized = cls._normalize_thinking_level(thinking_level) + if not normalized or normalized in {"off", "auto"}: + return None + + if normalized in {"minimal", "low", "medium", "high"}: + return "high" + if normalized in {"max", "xhigh"}: + return "max" + + logger.warning(f"忽略不支持的 DeepSeek reasoning_effort 配置: {normalized}") + return None + + @classmethod + def _normalize_openai_reasoning_effort( + cls, thinking_level: str | None = None + ) -> str | None: + """ + OpenAI reasoning_effort 支持更细粒度的 effort,统一做最近似映射。 + """ + normalized = cls._normalize_thinking_level(thinking_level) + if not normalized or normalized == "auto": + return None + if normalized == "off": + return "none" + if normalized == "max": + return "xhigh" + return normalized + + @classmethod + def _build_google_thinking_kwargs( + cls, model_name: str, thinking_level: str + ) -> dict[str, Any]: + """ + Gemini 3 使用 thinking_level;Gemini 2.5 使用 thinking_budget。 + """ + if not model_name or thinking_level == "auto": + return {} + + if "gemini-2.5" in model_name: + if thinking_level == "off": + if "pro" in model_name: + # Gemini 2.5 Pro 官方不支持完全关闭思考,回退到最小预算。 + return { + "thinking_budget": 128, + "include_thoughts": False, + } + return { + "thinking_budget": 0, + "include_thoughts": False, + } + + budget_map = { + "minimal": 512, + "low": 1024, + "medium": 4096, + "high": 8192, + "max": 24576, + "xhigh": 24576, + } + budget = budget_map.get(thinking_level) + return ( + { + "thinking_budget": budget, + "include_thoughts": False, + } + if budget is not None + else {} + ) + + if "gemini-3" in model_name: + level_map = { + "off": "minimal", + "minimal": "minimal", + "low": "low", + "medium": "medium", + "high": "high", + "max": "high", + "xhigh": "high", + } + google_level = level_map.get(thinking_level) + return ( + { + "thinking_level": google_level, + "include_thoughts": False, + } + if google_level + else {} + ) + + return {} + + @classmethod + def _build_kimi_thinking_kwargs( + cls, model_name: str, thinking_level: str + ) -> dict[str, Any]: + """ + Kimi 当前公开文档仅支持思考开关,不支持显式深度调节。 + """ + if model_name.startswith("kimi-k2-thinking"): + return {} + if thinking_level == "off": + return {"extra_body": {"thinking": {"type": "disabled"}}} + return {} + + @classmethod + def _build_thinking_kwargs( cls, provider: str, model: str | None, + thinking_level: str | None = None, disable_thinking: bool | None = None, + reasoning_effort: str | None = None, ) -> dict[str, Any]: """ - 按 provider/model 生成“禁用思考”相关参数。 + 按 provider/model 生成思考模式相关参数。 优先使用 LangChain/OpenAI SDK 已支持的原生字段;仅在 provider 明确要求自定义请求体时,才回退到 extra_body。 """ - if not cls._should_disable_thinking(disable_thinking): - return {} - provider_name = (provider or "").strip().lower() model_name = cls._normalize_model_name(model) + resolved_thinking_level = cls._resolve_thinking_level( + thinking_level=thinking_level, + disable_thinking=disable_thinking, + reasoning_effort=reasoning_effort, + ) + + if provider_name == "deepseek": + if resolved_thinking_level == "off": + return {"extra_body": {"thinking": {"type": "disabled"}}} + if resolved_thinking_level == "auto": + return {} + + kwargs: dict[str, Any] = {"extra_body": {"thinking": {"type": "enabled"}}} + deepseek_effort = cls._normalize_deepseek_reasoning_effort( + resolved_thinking_level + ) + if deepseek_effort: + kwargs["reasoning_effort"] = deepseek_effort + return kwargs + + if model_name.startswith(("kimi-k2.5", "kimi-k2.6", "kimi-k2-thinking")): + return cls._build_kimi_thinking_kwargs(model_name, resolved_thinking_level) + if not model_name: return {} - # Moonshot Kimi K2.5/K2.6 需要在请求体显式声明 thinking.disabled。 - if model_name.startswith(("kimi-k2.5", "kimi-k2.6")): - return {"extra_body": {"thinking": {"type": "disabled"}}} - # OpenAI 原生推理模型优先走 LangChain 内置 reasoning_effort。 if provider_name == "openai" and model_name.startswith( ("gpt-5", "o1", "o3", "o4") ): - return {"reasoning_effort": "none"} + openai_effort = cls._normalize_openai_reasoning_effort( + resolved_thinking_level + ) + return {"reasoning_effort": openai_effort} if openai_effort else {} # Gemini 使用 google-genai / langchain-google-genai 内置思考控制参数。 if provider_name == "google": - if "gemini-2.5" in model_name: - return { - "thinking_budget": 0, - "include_thoughts": False, - } - if "gemini-3" in model_name: - return { - "thinking_level": "minimal", - "include_thoughts": False, - } + return cls._build_google_thinking_kwargs( + model_name, resolved_thinking_level + ) return {} @@ -151,7 +383,9 @@ class LLMHelper: streaming: bool = False, provider: str | None = None, model: str | None = None, + thinking_level: str | None = None, disable_thinking: bool | None = None, + reasoning_effort: str | None = None, api_key: str | None = None, base_url: str | None = None, ): @@ -166,10 +400,12 @@ class LLMHelper: model_name = model if model is not None else settings.LLM_MODEL api_key_value = api_key if api_key is not None else settings.LLM_API_KEY base_url_value = base_url if base_url is not None else settings.LLM_BASE_URL - thinking_kwargs = LLMHelper._build_disabled_thinking_kwargs( + thinking_kwargs = LLMHelper._build_thinking_kwargs( provider=provider_name, model=model_name, + thinking_level=thinking_level, disable_thinking=disable_thinking, + reasoning_effort=reasoning_effort, ) if not api_key_value: @@ -204,6 +440,7 @@ class LLMHelper: model = ChatDeepSeek( model=model_name, api_key=api_key_value, + api_base=base_url_value, max_retries=3, temperature=settings.LLM_TEMPERATURE, streaming=streaming, @@ -282,7 +519,9 @@ class LLMHelper: timeout: int = 20, provider: str | None = None, model: str | None = None, + thinking_level: str | None = None, disable_thinking: bool | None = None, + reasoning_effort: str | None = None, api_key: str | None = None, base_url: str | None = None, ) -> dict: @@ -298,7 +537,9 @@ class LLMHelper: streaming=False, provider=provider_name, model=model_name, + thinking_level=thinking_level, disable_thinking=disable_thinking, + reasoning_effort=reasoning_effort, api_key=api_key_value, base_url=base_url_value, ) diff --git a/requirements.in b/requirements.in index b8e99d32..1ffd157e 100644 --- a/requirements.in +++ b/requirements.in @@ -76,14 +76,14 @@ pympler~=1.1 smbprotocol~=1.15.0 setproctitle~=1.3.6 httpx[socks]~=0.28.1 -langchain~=1.2.13 -langchain-core~=1.2.20 +langchain~=1.2.15 +langchain-core~=1.3.1 langchain-community~=0.4.1 -langchain-openai~=1.1.11 -langchain-google-genai~=4.2.1 +langchain-openai~=1.2.0 +langchain-google-genai~=4.2.2 langchain-deepseek~=1.0.1 -langgraph~=1.1.3 -openai~=2.29.0 -google-genai~=1.68.0 +langgraph~=1.1.9 +openai~=2.32.0 +google-genai~=1.73.1 ddgs~=9.10.0 websocket-client~=1.8.0 diff --git a/scripts/local_setup.py b/scripts/local_setup.py index 8690e3cb..ddad23a1 100644 --- a/scripts/local_setup.py +++ b/scripts/local_setup.py @@ -1063,6 +1063,40 @@ def _prompt_choice(label: str, choices: dict[str, str], default: str) -> str: print("请输入列表中的可选值。") +def _env_llm_thinking_level_default() -> str: + value = _normalize_choice(_env_default("LLM_THINKING_LEVEL", "")) + alias_map = { + "none": "off", + "disabled": "off", + "disable": "off", + "enabled": "auto", + "enable": "auto", + "default": "auto", + "dynamic": "auto", + } + normalized = alias_map.get(value, value) + if normalized in { + "off", + "auto", + "minimal", + "low", + "medium", + "high", + "max", + "xhigh", + }: + return normalized + + legacy_disable = _env_bool("LLM_DISABLE_THINKING", True) + legacy_effort = _normalize_choice(_env_default("LLM_REASONING_EFFORT", "")) + legacy_effort = alias_map.get(legacy_effort, legacy_effort) + if legacy_disable: + return "off" + if legacy_effort in {"minimal", "low", "medium", "high", "max", "xhigh"}: + return legacy_effort + return "auto" + + def _prompt_path(label: str, *, default: Path, allow_empty: bool = False) -> str: value = _prompt_text(label, default=str(default), allow_empty=allow_empty) if not value: @@ -1476,9 +1510,19 @@ def _collect_agent_config() -> dict[str, Any]: current_value=read_env_value("LLM_API_KEY"), required=True, ), - "LLM_DISABLE_THINKING": _prompt_yes_no( - "是否尽量关闭模型思考/推理", - default=_env_bool("LLM_DISABLE_THINKING", False), + "LLM_THINKING_LEVEL": _prompt_choice( + "LLM 思考模式/深度", + choices={ + "off": "关闭思考", + "auto": "自动", + "minimal": "最小", + "low": "低", + "medium": "中", + "high": "高", + "max": "极高", + "xhigh": "超高", + }, + default=_env_llm_thinking_level_default(), ), "LLM_SUPPORT_IMAGE_INPUT": _prompt_yes_no( "是否启用图片输入支持", diff --git a/tests/test_llm_helper_testcall.py b/tests/test_llm_helper_testcall.py index 8a64f389..0873b0c9 100644 --- a/tests/test_llm_helper_testcall.py +++ b/tests/test_llm_helper_testcall.py @@ -38,7 +38,9 @@ _stub_module( LLM_MODEL="global-model", LLM_API_KEY="global-key", LLM_BASE_URL="https://global.example.com", + LLM_THINKING_LEVEL=None, LLM_DISABLE_THINKING=False, + LLM_REASONING_EFFORT=None, LLM_TEMPERATURE=0.1, LLM_MAX_CONTEXT_TOKENS=64, PROXY_HOST=None, @@ -83,7 +85,9 @@ class LlmHelperTestCallTest(unittest.TestCase): streaming=False, provider="deepseek", model="deepseek-chat", + thinking_level=None, disable_thinking=None, + reasoning_effort=None, api_key="sk-test", base_url="https://api.deepseek.com", ) @@ -138,7 +142,65 @@ class LlmHelperTestCallTest(unittest.TestCase): {"thinking": {"type": "disabled"}}, ) - def test_get_llm_uses_openai_reasoning_effort_none(self): + def test_get_llm_uses_deepseek_thinking_level_controls(self): + calls = [] + + class _FakeChatDeepSeek: + def __init__(self, **kwargs): + calls.append(kwargs) + self.model = kwargs["model"] + self.profile = None + + with patch.dict( + sys.modules, + {"langchain_deepseek": SimpleNamespace(ChatDeepSeek=_FakeChatDeepSeek)}, + ): + llm_module.LLMHelper.get_llm( + provider="deepseek", + model="deepseek-v4-pro", + thinking_level="xhigh", + api_key="sk-test", + base_url="https://api.deepseek.com", + ) + + self.assertEqual(len(calls), 1) + self.assertEqual( + calls[0].get("extra_body"), + {"thinking": {"type": "enabled"}}, + ) + self.assertEqual(calls[0].get("reasoning_effort"), "max") + self.assertEqual(calls[0].get("api_base"), "https://api.deepseek.com") + + def test_get_llm_disables_deepseek_thinking_via_thinking_level(self): + calls = [] + + class _FakeChatDeepSeek: + def __init__(self, **kwargs): + calls.append(kwargs) + self.model = kwargs["model"] + self.profile = None + + with patch.dict( + sys.modules, + {"langchain_deepseek": SimpleNamespace(ChatDeepSeek=_FakeChatDeepSeek)}, + ): + llm_module.LLMHelper.get_llm( + provider="deepseek", + model="deepseek-v4-flash", + thinking_level="off", + api_key="sk-test", + base_url="https://proxy.example.com", + ) + + self.assertEqual(len(calls), 1) + self.assertEqual( + calls[0].get("extra_body"), + {"thinking": {"type": "disabled"}}, + ) + self.assertIsNone(calls[0].get("reasoning_effort")) + self.assertEqual(calls[0].get("api_base"), "https://proxy.example.com") + + def test_get_llm_uses_openai_reasoning_effort_none_for_off(self): calls = [] class _FakeChatOpenAI: @@ -154,7 +216,7 @@ class LlmHelperTestCallTest(unittest.TestCase): llm_module.LLMHelper.get_llm( provider="openai", model="gpt-5-mini", - disable_thinking=True, + thinking_level="off", api_key="sk-test", base_url="https://api.openai.com/v1", ) @@ -162,6 +224,30 @@ class LlmHelperTestCallTest(unittest.TestCase): self.assertEqual(len(calls), 1) self.assertEqual(calls[0].get("reasoning_effort"), "none") + def test_get_llm_maps_unified_max_to_openai_xhigh(self): + calls = [] + + class _FakeChatOpenAI: + def __init__(self, **kwargs): + calls.append(kwargs) + self.model = kwargs["model"] + self.profile = None + + with patch.dict( + sys.modules, + {"langchain_openai": SimpleNamespace(ChatOpenAI=_FakeChatOpenAI)}, + ): + llm_module.LLMHelper.get_llm( + provider="openai", + model="gpt-5.4", + thinking_level="max", + api_key="sk-test", + base_url="https://api.openai.com/v1", + ) + + self.assertEqual(len(calls), 1) + self.assertEqual(calls[0].get("reasoning_effort"), "xhigh") + def test_get_llm_uses_gemini_builtin_thinking_controls(self): calls = [] @@ -182,7 +268,7 @@ class LlmHelperTestCallTest(unittest.TestCase): llm_module.LLMHelper.get_llm( provider="google", model="gemini-2.5-flash", - disable_thinking=True, + thinking_level="off", api_key="sk-test", base_url=None, ) @@ -191,6 +277,35 @@ class LlmHelperTestCallTest(unittest.TestCase): self.assertEqual(calls[0].get("thinking_budget"), 0) self.assertFalse(calls[0].get("include_thoughts")) + def test_get_llm_uses_gemini_3_thinking_level_controls(self): + calls = [] + + class _FakeChatGoogleGenerativeAI: + def __init__(self, **kwargs): + calls.append(kwargs) + self.model = kwargs["model"] + self.profile = None + + with patch.dict( + sys.modules, + { + "langchain_google_genai": SimpleNamespace( + ChatGoogleGenerativeAI=_FakeChatGoogleGenerativeAI + ) + }, + ): + llm_module.LLMHelper.get_llm( + provider="google", + model="gemini-3.1-flash", + thinking_level="xhigh", + api_key="sk-test", + base_url=None, + ) + + self.assertEqual(len(calls), 1) + self.assertEqual(calls[0].get("thinking_level"), "high") + self.assertFalse(calls[0].get("include_thoughts")) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_system_llm_test.py b/tests/test_system_llm_test.py index 66c7a7fe..6714a706 100644 --- a/tests/test_system_llm_test.py +++ b/tests/test_system_llm_test.py @@ -119,6 +119,8 @@ class LlmTestEndpointTest(unittest.TestCase): with patch.object(system_endpoint.settings, "AI_AGENT_ENABLE", True), patch.object( system_endpoint.settings, "LLM_PROVIDER", "deepseek" ), patch.object(system_endpoint.settings, "LLM_MODEL", "deepseek-chat"), patch.object( + system_endpoint.settings, "LLM_THINKING_LEVEL", "max" + ), patch.object( system_endpoint.settings, "LLM_API_KEY", "sk-test" ), patch.object( system_endpoint.settings, "LLM_BASE_URL", "https://api.deepseek.com" @@ -133,7 +135,9 @@ class LlmTestEndpointTest(unittest.TestCase): llm_test_mock.assert_awaited_once_with( provider="deepseek", model="deepseek-chat", - disable_thinking=False, + thinking_level="max", + disable_thinking=None, + reasoning_effort=None, api_key="sk-test", base_url="https://api.deepseek.com", ) @@ -156,7 +160,7 @@ class LlmTestEndpointTest(unittest.TestCase): enabled=True, provider="openai", model="gpt-4.1-mini", - disable_thinking=True, + thinking_level="high", api_key="sk-live", base_url="https://example.com/v1", ) @@ -178,7 +182,9 @@ class LlmTestEndpointTest(unittest.TestCase): llm_test_mock.assert_awaited_once_with( provider="openai", model="gpt-4.1-mini", - disable_thinking=True, + thinking_level="high", + disable_thinking=None, + reasoning_effort=None, api_key="sk-live", base_url="https://example.com/v1", ) @@ -186,6 +192,44 @@ class LlmTestEndpointTest(unittest.TestCase): self.assertEqual(resp.data["provider"], "openai") self.assertEqual(resp.data["model"], "gpt-4.1-mini") + def test_llm_test_supports_legacy_thinking_payload(self): + llm_test_mock = AsyncMock( + return_value={ + "provider": "deepseek", + "model": "deepseek-v4-pro", + "duration_ms": 123, + "reply_preview": "OK", + } + ) + payload = system_endpoint.LlmTestRequest( + enabled=True, + provider="deepseek", + model="deepseek-v4-pro", + disable_thinking=False, + reasoning_effort="xhigh", + api_key="sk-live", + base_url="https://api.deepseek.com", + ) + + with patch.object(system_endpoint.settings, "AI_AGENT_ENABLE", False), patch.object( + system_endpoint.LLMHelper, + "test_current_settings", + llm_test_mock, + create=True, + ): + resp = asyncio.run(system_endpoint.llm_test(payload=payload, _="token")) + + llm_test_mock.assert_awaited_once_with( + provider="deepseek", + model="deepseek-v4-pro", + thinking_level=None, + disable_thinking=False, + reasoning_effort="xhigh", + api_key="sk-live", + base_url="https://api.deepseek.com", + ) + self.assertTrue(resp.success) + def test_llm_test_rejects_empty_reply(self): with patch.object(system_endpoint.settings, "AI_AGENT_ENABLE", True), patch.object( system_endpoint.settings, "LLM_PROVIDER", "deepseek"