mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-28 19:47:41 +08:00
fix(agent): refresh LLM runtime config on each call
Read the latest LLM connection settings when building runtime clients so Web updates take effect immediately instead of reusing module-import defaults. Closes #5757
This commit is contained in:
+16
-10
@@ -512,9 +512,9 @@ class LLMHelper:
|
|||||||
provider: str | None = None,
|
provider: str | None = None,
|
||||||
model: str | None = None,
|
model: str | None = None,
|
||||||
thinking_level: str | None = None,
|
thinking_level: str | None = None,
|
||||||
api_key: str | None = settings.LLM_API_KEY,
|
api_key: str | None = None,
|
||||||
base_url: str | None = settings.LLM_BASE_URL,
|
base_url: str | None = None,
|
||||||
base_url_preset: str | None = settings.LLM_BASE_URL_PRESET,
|
base_url_preset: str | None = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
获取LLM实例
|
获取LLM实例
|
||||||
@@ -525,12 +525,18 @@ class LLMHelper:
|
|||||||
是否启用思考模式)。支持的级别包括 "off"(关闭)、"auto"(自动)、"minimal"、"low"、"medium"、"high"、"max"/"xhigh"(最大)。
|
是否启用思考模式)。支持的级别包括 "off"(关闭)、"auto"(自动)、"minimal"、"low"、"medium"、"high"、"max"/"xhigh"(最大)。
|
||||||
不同模型对思考模式的支持和表现不同,具体映射关系请
|
不同模型对思考模式的支持和表现不同,具体映射关系请
|
||||||
参考代码实现。对于不支持思考模式的模型,该参数将被忽略。
|
参考代码实现。对于不支持思考模式的模型,该参数将被忽略。
|
||||||
:param api_key: API Key,默认为配置项LLM_API_KEY。对于某些提供商(如 DeepSeek),可能需要同时提供 base_url。
|
:param api_key: API Key。未显式传入时使用当前配置项 LLM_API_KEY。对于某些提供商(如 DeepSeek),可能需要同时提供 base_url。
|
||||||
:param base_url: API Base URL,默认为配置项LLM_BASE_URL。
|
:param base_url: API Base URL。未显式传入时使用当前配置项 LLM_BASE_URL。
|
||||||
|
:param base_url_preset: Base URL 预设。未显式传入时使用当前配置项 LLM_BASE_URL_PRESET。
|
||||||
:return: LLM实例
|
:return: LLM实例
|
||||||
"""
|
"""
|
||||||
provider_name = str(provider if provider is not None else settings.LLM_PROVIDER).lower()
|
provider_name = str(provider if provider is not None else settings.LLM_PROVIDER).lower()
|
||||||
model_name = model if model is not None else settings.LLM_MODEL
|
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
|
||||||
|
base_url_preset_value = (
|
||||||
|
base_url_preset if base_url_preset is not None else settings.LLM_BASE_URL_PRESET
|
||||||
|
)
|
||||||
normalized_thinking_level = cls._resolve_thinking_level(
|
normalized_thinking_level = cls._resolve_thinking_level(
|
||||||
thinking_level=thinking_level,
|
thinking_level=thinking_level,
|
||||||
)
|
)
|
||||||
@@ -542,17 +548,17 @@ class LLMHelper:
|
|||||||
runtime = await LLMProviderManager().resolve_runtime(
|
runtime = await LLMProviderManager().resolve_runtime(
|
||||||
provider_id=provider_name,
|
provider_id=provider_name,
|
||||||
model=model_name,
|
model=model_name,
|
||||||
api_key=api_key,
|
api_key=api_key_value,
|
||||||
base_url=base_url,
|
base_url=base_url_value,
|
||||||
base_url_preset_id=base_url_preset,
|
base_url_preset_id=base_url_preset_value,
|
||||||
)
|
)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.debug(f"LLM provider 目录不可用,回退到旧运行时逻辑: {err}")
|
logger.debug(f"LLM provider 目录不可用,回退到旧运行时逻辑: {err}")
|
||||||
runtime = cls._build_legacy_runtime(
|
runtime = cls._build_legacy_runtime(
|
||||||
provider_name=provider_name,
|
provider_name=provider_name,
|
||||||
model_name=model_name,
|
model_name=model_name,
|
||||||
api_key=api_key,
|
api_key=api_key_value,
|
||||||
base_url=base_url,
|
base_url=base_url_value,
|
||||||
)
|
)
|
||||||
model_name = runtime.get("model_id") or model_name
|
model_name = runtime.get("model_id") or model_name
|
||||||
thinking_kwargs = cls._build_thinking_kwargs(
|
thinking_kwargs = cls._build_thinking_kwargs(
|
||||||
|
|||||||
@@ -244,6 +244,70 @@ class LlmHelperTestCallTest(unittest.TestCase):
|
|||||||
self.assertEqual(len(calls), 1)
|
self.assertEqual(len(calls), 1)
|
||||||
self.assertEqual(calls[0].get("reasoning_effort"), "none")
|
self.assertEqual(calls[0].get("reasoning_effort"), "none")
|
||||||
|
|
||||||
|
def test_get_llm_reads_latest_settings_when_runtime_args_omitted(self):
|
||||||
|
resolve_calls = []
|
||||||
|
llm_calls = []
|
||||||
|
|
||||||
|
class _FakeProviderManager:
|
||||||
|
async def resolve_runtime(self, **kwargs):
|
||||||
|
resolve_calls.append(kwargs)
|
||||||
|
return {
|
||||||
|
"provider_id": kwargs["provider_id"],
|
||||||
|
"runtime": "openai_compatible",
|
||||||
|
"model_id": kwargs["model"],
|
||||||
|
"api_key": kwargs["api_key"],
|
||||||
|
"base_url": kwargs["base_url"],
|
||||||
|
"default_headers": None,
|
||||||
|
"use_responses_api": None,
|
||||||
|
"model_record": None,
|
||||||
|
"model_metadata": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FakeChatOpenAI:
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
llm_calls.append(kwargs)
|
||||||
|
self.model = kwargs["model"]
|
||||||
|
self.profile = None
|
||||||
|
|
||||||
|
provider_module = ModuleType("app.agent.llm.provider")
|
||||||
|
provider_module.LLMProviderManager = _FakeProviderManager
|
||||||
|
openai_module = ModuleType("langchain_openai")
|
||||||
|
openai_module.ChatOpenAI = _FakeChatOpenAI
|
||||||
|
|
||||||
|
with patch.object(llm_module.settings, "LLM_PROVIDER", "deepseek"), patch.object(
|
||||||
|
llm_module.settings, "LLM_MODEL", "deepseek-chat"
|
||||||
|
), patch.object(llm_module.settings, "LLM_API_KEY", "updated-key"), patch.object(
|
||||||
|
llm_module.settings, "LLM_BASE_URL", "https://updated.example.com/v1"
|
||||||
|
), patch.object(
|
||||||
|
llm_module.settings, "LLM_BASE_URL_PRESET", "updated-preset"
|
||||||
|
), patch.dict(
|
||||||
|
sys.modules,
|
||||||
|
{
|
||||||
|
"app.agent.llm.provider": provider_module,
|
||||||
|
"langchain_openai": openai_module,
|
||||||
|
},
|
||||||
|
):
|
||||||
|
asyncio.run(llm_module.LLMHelper.get_llm())
|
||||||
|
|
||||||
|
self.assertEqual(len(resolve_calls), 1)
|
||||||
|
self.assertEqual(
|
||||||
|
resolve_calls[0],
|
||||||
|
{
|
||||||
|
"provider_id": "deepseek",
|
||||||
|
"model": "deepseek-chat",
|
||||||
|
"api_key": "updated-key",
|
||||||
|
"base_url": "https://updated.example.com/v1",
|
||||||
|
"base_url_preset_id": "updated-preset",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(len(llm_calls), 1)
|
||||||
|
self.assertEqual(llm_calls[0].get("model"), "deepseek-chat")
|
||||||
|
self.assertEqual(llm_calls[0].get("api_key"), "updated-key")
|
||||||
|
self.assertEqual(
|
||||||
|
llm_calls[0].get("base_url"),
|
||||||
|
"https://updated.example.com/v1",
|
||||||
|
)
|
||||||
|
|
||||||
def test_get_llm_maps_unified_max_to_openai_xhigh(self):
|
def test_get_llm_maps_unified_max_to_openai_xhigh(self):
|
||||||
calls = []
|
calls = []
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user