mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-06 07:56:52 +08:00
Merge remote-tracking branch 'origin/v3' into v3
# Conflicts: # app/api/endpoints/agent.py # app/api/endpoints/anthropic.py # app/api/endpoints/openai.py # app/chain/__init__.py # app/chain/message.py # app/chain/site.py # app/chain/subscribe.py # app/chain/transfer.py # app/modules/discord/__init__.py # app/modules/qqbot/__init__.py # app/modules/slack/__init__.py # app/modules/telegram/__init__.py # app/modules/wechat/__init__.py # app/runtime/extensions/module_manager.py # app/runtime/extensions/service_registry.py # tests/test_agent_interaction.py # tests/test_slash_command_interactions.py # tests/test_web_agent_stream.py
This commit is contained in:
@@ -1,6 +1,81 @@
|
||||
from app.agent.orchestrator import agent_manager
|
||||
from typing import Any
|
||||
|
||||
from app.agent.runtime_loader import (
|
||||
activate_agent_service,
|
||||
begin_agent_shutdown,
|
||||
get_agent_manager as get_runtime_agent_manager,
|
||||
get_running_agent_manager as get_runtime_running_agent_manager,
|
||||
is_tool_factory_materialized,
|
||||
reconcile_agent_service,
|
||||
)
|
||||
from app.application.agent import register_agent_service_providers
|
||||
from app.runtime.config import settings
|
||||
from app.runtime.events import Event, eventmanager
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.types import EventType
|
||||
|
||||
|
||||
# 嵌入式启动器可显式注入 manager;常规进程使用 Capability Runtime。
|
||||
agent_manager: Any = None
|
||||
|
||||
|
||||
def _event_changed_keys(event: Event | None) -> set[str]:
|
||||
"""兼容对象和 dict 两种配置事件载荷。"""
|
||||
if event is None:
|
||||
return set()
|
||||
event_data = event.event_data
|
||||
if isinstance(event_data, dict):
|
||||
keys = event_data.get("key", set())
|
||||
else:
|
||||
keys = getattr(event_data, "key", set())
|
||||
if isinstance(keys, str):
|
||||
return {keys}
|
||||
return {str(key) for key in (keys or set())}
|
||||
|
||||
|
||||
def _get_agent_manager() -> Any:
|
||||
"""兼容显式注入对象,否则按需解析 canonical manager。"""
|
||||
return agent_manager if agent_manager is not None else get_runtime_agent_manager()
|
||||
|
||||
|
||||
def _get_running_agent_manager() -> Any | None:
|
||||
"""只返回已运行实例,状态探测不得触发 Agent 物化。"""
|
||||
if agent_initializer._compat_injected:
|
||||
return agent_initializer._manager
|
||||
return get_runtime_running_agent_manager()
|
||||
|
||||
|
||||
def _get_prompt_manager() -> Any:
|
||||
"""首个提示词调用才导入模板管理器。"""
|
||||
from app.agent.prompt import prompt_manager
|
||||
|
||||
return prompt_manager
|
||||
|
||||
|
||||
def _get_capability_manager() -> Any:
|
||||
"""首个多模态调用才导入 Agent 能力管理器。"""
|
||||
from app.agent.llm import AgentCapabilityManager
|
||||
|
||||
return AgentCapabilityManager
|
||||
|
||||
|
||||
def _get_llm_helper() -> Any:
|
||||
"""首个模型能力查询才导入 LLM helper。"""
|
||||
from app.agent.llm import LLMHelper
|
||||
|
||||
return LLMHelper
|
||||
|
||||
|
||||
def _get_manual_redo_prompt_builder() -> Any:
|
||||
"""首个整理接管请求才导入对应提示词构建器。"""
|
||||
from app.agent.prompt.transfer_redo import build_manual_redo_prompt
|
||||
|
||||
return build_manual_redo_prompt
|
||||
|
||||
|
||||
async def _handle_agent_config_changed(event: Event) -> None:
|
||||
"""把配置事件交给当前全局 initializer,避免监听器持有过期实例。"""
|
||||
await agent_initializer.handle_config_changed(event)
|
||||
|
||||
|
||||
class AgentInitializer:
|
||||
@@ -10,17 +85,33 @@ class AgentInitializer:
|
||||
|
||||
def __init__(self):
|
||||
self._initialized = False
|
||||
self._manager: Any = None
|
||||
self._compat_injected = False
|
||||
self._shutdown_complete = False
|
||||
eventmanager.add_event_listener(
|
||||
EventType.ConfigChanged,
|
||||
_handle_agent_config_changed,
|
||||
)
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""
|
||||
初始化AI智能体管理器
|
||||
"""
|
||||
try:
|
||||
if not settings.AI_AGENT_ENABLE:
|
||||
logger.info("AI智能体功能未启用")
|
||||
return True
|
||||
|
||||
await agent_manager.initialize()
|
||||
self._shutdown_complete = False
|
||||
if agent_manager is not None:
|
||||
if not settings.AI_AGENT_ENABLE:
|
||||
logger.info("AI智能体功能未启用")
|
||||
return True
|
||||
self._manager = agent_manager
|
||||
self._compat_injected = True
|
||||
await agent_manager.initialize()
|
||||
else:
|
||||
self._manager = await activate_agent_service()
|
||||
self._compat_injected = False
|
||||
if self._manager is None:
|
||||
logger.info("AI智能体功能未启用")
|
||||
return True
|
||||
self._initialized = True
|
||||
logger.info("AI智能体管理器初始化成功")
|
||||
return True
|
||||
@@ -29,16 +120,38 @@ class AgentInitializer:
|
||||
logger.error(f"AI智能体管理器初始化失败: {e}")
|
||||
return False
|
||||
|
||||
async def cleanup(self) -> None:
|
||||
"""
|
||||
清理AI智能体管理器
|
||||
"""
|
||||
async def handle_config_changed(self, event: Event) -> None:
|
||||
"""仅在 manifest watch 命中时协调 service,关闭态保持 fail closed。"""
|
||||
changed_keys = _event_changed_keys(event)
|
||||
if not changed_keys or self._compat_injected or self._shutdown_complete:
|
||||
return
|
||||
try:
|
||||
if not self._initialized:
|
||||
return
|
||||
await agent_manager.close()
|
||||
self._manager = await reconcile_agent_service(
|
||||
reason="agent_service_config_changed",
|
||||
changed_keys=changed_keys,
|
||||
retry=True,
|
||||
)
|
||||
self._initialized = self._manager is not None
|
||||
except Exception as error:
|
||||
self._manager = None
|
||||
self._initialized = False
|
||||
logger.info("AI智能体管理器已关闭")
|
||||
logger.debug(f"配置变更协调AI智能体失败: {error}")
|
||||
|
||||
async def cleanup(self) -> None:
|
||||
"""清理 initializer 引用;显式注入对象同时在此关闭。"""
|
||||
try:
|
||||
manager = self._manager
|
||||
compat_injected = self._compat_injected
|
||||
if manager is None:
|
||||
return
|
||||
try:
|
||||
if compat_injected:
|
||||
await manager.close()
|
||||
logger.info("AI智能体管理器已关闭")
|
||||
finally:
|
||||
self._initialized = False
|
||||
self._manager = None
|
||||
self._compat_injected = False
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"关闭AI智能体管理器时发生错误: {e}")
|
||||
@@ -47,16 +160,22 @@ class AgentInitializer:
|
||||
# 全局AI智能体初始化器实例
|
||||
agent_initializer = AgentInitializer()
|
||||
|
||||
# application 门面仅保存 provider;下列注册不会导入 Agent 实现。
|
||||
register_agent_service_providers(
|
||||
agent_manager_provider=_get_agent_manager,
|
||||
running_agent_manager_provider=_get_running_agent_manager,
|
||||
prompt_manager_provider=_get_prompt_manager,
|
||||
capability_manager_provider=_get_capability_manager,
|
||||
llm_helper_provider=_get_llm_helper,
|
||||
manual_redo_prompt_builder_provider=_get_manual_redo_prompt_builder,
|
||||
)
|
||||
|
||||
|
||||
async def init_agent() -> bool:
|
||||
"""
|
||||
在应用事件循环中初始化AI智能体。
|
||||
"""
|
||||
try:
|
||||
if not settings.AI_AGENT_ENABLE:
|
||||
logger.info("AI智能体功能未启用")
|
||||
return True
|
||||
|
||||
return await agent_initializer.initialize()
|
||||
|
||||
except Exception as e:
|
||||
@@ -69,6 +188,16 @@ async def stop_agent():
|
||||
停止AI智能体(异步版本,用于在应用关闭时调用)
|
||||
"""
|
||||
try:
|
||||
await agent_initializer.cleanup()
|
||||
if not agent_initializer._shutdown_complete:
|
||||
if agent_initializer._compat_injected:
|
||||
await agent_initializer.cleanup()
|
||||
else:
|
||||
await begin_agent_shutdown()
|
||||
await agent_initializer.cleanup()
|
||||
agent_initializer._shutdown_complete = True
|
||||
if is_tool_factory_materialized():
|
||||
from app.agent.tools.base import shutdown_blocking_executors
|
||||
|
||||
shutdown_blocking_executors(cancel_futures=True)
|
||||
except Exception as e:
|
||||
logger.error(f"停止AI智能体时发生错误: {e}")
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
from app.application.commands import register_command_class
|
||||
from app.command import Command
|
||||
|
||||
# 导入期即向 application 门面注册命令类,保证工具调用时不依赖静态边。
|
||||
register_command_class(Command)
|
||||
|
||||
|
||||
def init_command():
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
"""Managed Resource 的启动组合与进程关闭入口。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
from typing import Optional
|
||||
|
||||
from app.runtime.capabilities.runtime import CapabilityRuntime
|
||||
from app.runtime.extensions.managed_resource_adapter import (
|
||||
AsyncManagedResourceAdapter,
|
||||
SyncManagedResourceAdapter,
|
||||
build_managed_resource_registry,
|
||||
)
|
||||
from app.runtime.managed_resources import (
|
||||
MANAGED_RESOURCE_ASYNC_KIND,
|
||||
MANAGED_RESOURCE_SYNC_KIND,
|
||||
configure_managed_resource_runtime,
|
||||
)
|
||||
|
||||
|
||||
_runtime_lock = threading.RLock()
|
||||
_managed_resource_runtime: Optional[CapabilityRuntime] = None
|
||||
|
||||
|
||||
def init_managed_resources() -> CapabilityRuntime:
|
||||
"""构建并注入资源 Runtime;只发现声明,不物化或启动任何资源。"""
|
||||
global _managed_resource_runtime
|
||||
with _runtime_lock:
|
||||
if _managed_resource_runtime is None:
|
||||
_managed_resource_runtime = CapabilityRuntime(
|
||||
build_managed_resource_registry(),
|
||||
adapters={
|
||||
MANAGED_RESOURCE_SYNC_KIND: SyncManagedResourceAdapter(),
|
||||
MANAGED_RESOURCE_ASYNC_KIND: AsyncManagedResourceAdapter(),
|
||||
},
|
||||
)
|
||||
configure_managed_resource_runtime(_managed_resource_runtime)
|
||||
return _managed_resource_runtime
|
||||
|
||||
|
||||
async def stop_managed_resources() -> None:
|
||||
"""关闭已经初始化的资源 Runtime;未初始化时不执行发现或激活。"""
|
||||
with _runtime_lock:
|
||||
runtime = _managed_resource_runtime
|
||||
if runtime is None:
|
||||
return
|
||||
await runtime.shutdown_async(reason="application_shutdown")
|
||||
@@ -22,7 +22,6 @@ from app.runtime.extensions.module_manager import ModuleManager
|
||||
from app.runtime.events import EventManager
|
||||
from app.runtime.state import SystemHelper
|
||||
from app.runtime.thread import ThreadHelper
|
||||
from app.adapters.system.display import DisplayHelper
|
||||
from app.adapters.network.doh import DohHelper
|
||||
from app.adapters.system.resource import (
|
||||
ResourceHelper,
|
||||
@@ -36,6 +35,10 @@ from app.command import CommandChain
|
||||
from app.schemas import Message, MessageType
|
||||
from app.schemas.types import SystemConfigKey
|
||||
from app.startup.agent_initializer import init_agent, stop_agent
|
||||
from app.startup.managed_resources_initializer import (
|
||||
init_managed_resources,
|
||||
stop_managed_resources,
|
||||
)
|
||||
from app.application.security.access import set_superuser_token_payload_provider
|
||||
from app.application.security.auth import build_superuser_token_payload
|
||||
from app.application.image import configure_wallpaper_providers
|
||||
@@ -170,6 +173,13 @@ def update_resources() -> None:
|
||||
logger.error(f"资源更新完成但自动重启失败:{message}")
|
||||
|
||||
|
||||
def close_browser_sessions() -> None:
|
||||
"""在托管资源关闭前释放所有浏览器上下文及其工作线程。"""
|
||||
from app.adapters.network.browser import BrowserSessionHelper
|
||||
|
||||
BrowserSessionHelper.close_all_sessions()
|
||||
|
||||
|
||||
async def stop_modules():
|
||||
"""
|
||||
服务关闭
|
||||
@@ -184,9 +194,10 @@ async def stop_modules():
|
||||
logger.error(f"关闭{name}失败:{err}")
|
||||
|
||||
await run_step("AI智能体", stop_agent)
|
||||
await run_step("模块", lambda: ModuleManager().stop())
|
||||
await run_step("模块", lambda: ModuleManager().shutdown())
|
||||
await run_step("事件消费", lambda: EventManager().stop())
|
||||
await run_step("虚拟显示", lambda: DisplayHelper().stop())
|
||||
await run_step("浏览器会话", close_browser_sessions)
|
||||
await run_step("托管资源", stop_managed_resources)
|
||||
await run_step("DoH服务", lambda: DohHelper().shutdown())
|
||||
await run_step("线程池", lambda: ThreadHelper().shutdown())
|
||||
await run_step("消息服务", stop_message)
|
||||
@@ -201,12 +212,12 @@ async def init_modules():
|
||||
"""
|
||||
启动模块
|
||||
"""
|
||||
# 托管资源只在这里装配声明与 adapter,具体资源仍由首个消费者显式激活。
|
||||
init_managed_resources()
|
||||
# 应用服务不反向依赖 Chain,由启动组合层注入壁纸来源。
|
||||
configure_wallpaper_services()
|
||||
# 认证访问层不反向依赖数据库实现,由启动组合层注入载荷提供器。
|
||||
set_superuser_token_payload_provider(build_superuser_token_payload)
|
||||
# 虚拟显示
|
||||
DisplayHelper()
|
||||
# DoH
|
||||
DohHelper()
|
||||
# 站点管理
|
||||
|
||||
@@ -1,25 +1,40 @@
|
||||
from pathlib import Path
|
||||
|
||||
from app.runtime.compat.diagnostics import (
|
||||
configure_legacy_import_diagnostics,
|
||||
scan_plugin_legacy_imports,
|
||||
)
|
||||
from app.runtime.compat.resource_imports import scan_plugin_resource_imports
|
||||
from app.runtime.config import global_vars
|
||||
from app.runtime.extensions.plugin_manager import (
|
||||
PluginManager,
|
||||
configure_plugin_install_reporter,
|
||||
configure_plugin_legacy_import_services,
|
||||
configure_plugin_resource_import_preparer,
|
||||
configure_site_auth_level_provider,
|
||||
)
|
||||
from app.runtime.managed_resources import acquire_managed_resource
|
||||
from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module
|
||||
from app.adapters.external.server import MoviePilotServerHelper
|
||||
from app.runtime.log import logger
|
||||
|
||||
|
||||
def _prepare_legacy_plugin_import(*, plugin_id: str, plugin_dir: Path) -> None:
|
||||
"""在执行旧插件顶层代码前准备其静态导入所需的宿主资源。"""
|
||||
for capability_id in scan_plugin_resource_imports(plugin_id, plugin_dir):
|
||||
acquire_managed_resource(
|
||||
capability_id,
|
||||
reason="legacy_plugin_import",
|
||||
)
|
||||
|
||||
|
||||
def _configure_plugin_services() -> None:
|
||||
"""把兼容诊断、远程上报和站点认证等级装配到插件管理器。"""
|
||||
configure_plugin_legacy_import_services(
|
||||
diagnostics_configurator=configure_legacy_import_diagnostics,
|
||||
import_scanner=scan_plugin_legacy_imports,
|
||||
)
|
||||
configure_plugin_resource_import_preparer(_prepare_legacy_plugin_import)
|
||||
configure_plugin_install_reporter(MoviePilotServerHelper.install_plugin_reg)
|
||||
configure_site_auth_level_provider(lambda: SitesHelper().auth_level)
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
from app.application.scheduling import register_scheduler_class
|
||||
from app.scheduler import Scheduler
|
||||
|
||||
# 导入期即向 application 门面注册调度器类,保证工具调用时不依赖静态边。
|
||||
register_scheduler_class(Scheduler)
|
||||
|
||||
|
||||
def init_scheduler():
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user