From 89a54abe61400abfd80d8a5f2c414118cc2545af Mon Sep 17 00:00:00 2001 From: JosephTian876 Date: Sun, 2 Aug 2026 22:38:12 +0800 Subject: [PATCH 1/2] fix: initialize providers before plugins A plugin that issues an LLM call from its initialize() runs before any provider has been loaded, because plugin_manager.reload() is awaited before provider_manager.initialize(). Callers that resolve by ID get "Provider X was not found"; callers going through get_using_provider() silently receive provider_insts[0] -- the first entry of the provider array, chosen by config order rather than capability. A vision-only model answering plain text is a realistic outcome, and nothing in the log points at it. The window is not negligible: loading one deepseek provider takes ~1.3s in my setup, and the whole provider phase runs after every plugin has already been constructed and started its background work. Providers have no dependency on plugins, so they can be initialized first. Plugins, on the other hand, legitimately expect providers to exist. The move is a pure reordering of two adjacent blocks; no logic changes. --- astrbot/core/core_lifecycle.py | 12 +++++++----- tests/unit/test_core_lifecycle.py | 16 ++++++++++++++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/astrbot/core/core_lifecycle.py b/astrbot/core/core_lifecycle.py index 0789ba3399..aba49d4286 100644 --- a/astrbot/core/core_lifecycle.py +++ b/astrbot/core/core_lifecycle.py @@ -252,17 +252,19 @@ async def initialize(self) -> None: self.subagent_orchestrator, ) + # 根据配置实例化各个 Provider + # 必须先于插件初始化:插件可能在 initialize() 中发起 LLM 调用, + # 若此时 Provider 尚未装载,会解析到配置数组中的第一个 Provider。 + self._default_chat_provider_warning_emitted = False + await self.provider_manager.initialize() + self._warn_about_unset_default_chat_provider() + # 初始化插件管理器 self.plugin_manager = PluginManager(self.star_context, self.astrbot_config) # 扫描、注册插件、实例化插件类 await self.plugin_manager.reload() - # 根据配置实例化各个 Provider - self._default_chat_provider_warning_emitted = False - await self.provider_manager.initialize() - self._warn_about_unset_default_chat_provider() - await self.kb_manager.initialize() # 初始化消息事件流水线调度器 diff --git a/tests/unit/test_core_lifecycle.py b/tests/unit/test_core_lifecycle.py index 914b737d11..ea923e1e94 100644 --- a/tests/unit/test_core_lifecycle.py +++ b/tests/unit/test_core_lifecycle.py @@ -405,8 +405,13 @@ async def test_initialize_sets_up_all_components( mock_persona_mgr = MagicMock() mock_persona_mgr.initialize = AsyncMock() + # Records the relative order of the two startup steps below. + startup_order: list[str] = [] + mock_provider_manager = MagicMock() - mock_provider_manager.initialize = AsyncMock() + mock_provider_manager.initialize = AsyncMock( + side_effect=lambda: startup_order.append("providers") + ) mock_platform_manager = MagicMock() mock_platform_manager.initialize = AsyncMock() @@ -424,7 +429,9 @@ async def test_initialize_sets_up_all_components( mock_star_context._register_tasks = [] mock_plugin_manager = MagicMock() - mock_plugin_manager.reload = AsyncMock() + mock_plugin_manager.reload = AsyncMock( + side_effect=lambda: startup_order.append("plugins") + ) mock_pipeline_scheduler = MagicMock() mock_pipeline_scheduler.initialize = AsyncMock() @@ -520,6 +527,11 @@ async def test_initialize_sets_up_all_components( # Verify knowledge base manager initialized mock_kb_manager.initialize.assert_awaited_once() + # Providers must be ready before plugins start: a plugin may issue an + # LLM call from initialize(), and an empty instance map would make that + # call resolve to an unrelated provider. + assert startup_order == ["providers", "plugins"] + # Verify pipeline scheduler loaded assert lifecycle.pipeline_scheduler_mapping is not None From 34419e8688bb17b3cc0eebb4881ce80f35c3a55d Mon Sep 17 00:00:00 2001 From: JosephTian876 Date: Sun, 2 Aug 2026 22:52:37 +0800 Subject: [PATCH 2/2] fix: keep the unset-default warning after plugin load Only the provider loading needs to move earlier. Emitting _warn_about_unset_default_chat_provider() before plugins would make it report the base configuration and miss anything a plugin adjusts during initialize(), so it stays where it was. --- astrbot/core/core_lifecycle.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/astrbot/core/core_lifecycle.py b/astrbot/core/core_lifecycle.py index aba49d4286..b9c378b880 100644 --- a/astrbot/core/core_lifecycle.py +++ b/astrbot/core/core_lifecycle.py @@ -257,7 +257,6 @@ async def initialize(self) -> None: # 若此时 Provider 尚未装载,会解析到配置数组中的第一个 Provider。 self._default_chat_provider_warning_emitted = False await self.provider_manager.initialize() - self._warn_about_unset_default_chat_provider() # 初始化插件管理器 self.plugin_manager = PluginManager(self.star_context, self.astrbot_config) @@ -265,6 +264,9 @@ async def initialize(self) -> None: # 扫描、注册插件、实例化插件类 await self.plugin_manager.reload() + # 在插件加载后再检查,这样警告反映的是最终生效的配置。 + self._warn_about_unset_default_chat_provider() + await self.kb_manager.initialize() # 初始化消息事件流水线调度器