mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-07 08:26:53 +08:00
refactor(agent): 按需加载 Agent 运行时 (#6336)
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
"""Agent 重量级 canonical 对象的轻量首用入口。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
from typing import Any
|
||||
|
||||
from app.agent.capabilities import (
|
||||
AGENT_ENTRYPOINT_KIND,
|
||||
AGENT_MANAGER_CAPABILITY_ID,
|
||||
AGENT_SERVICE_CAPABILITY_ID,
|
||||
AGENT_SERVICE_KIND,
|
||||
MOVIEPILOT_AGENT_TYPE_CAPABILITY_ID,
|
||||
TOOL_FACTORY_CAPABILITY_ID,
|
||||
)
|
||||
from app.agent.capabilities.adapter import (
|
||||
AgentEntrypointAdapter,
|
||||
AgentServiceAdapter,
|
||||
build_agent_capability_registry,
|
||||
should_run_agent_service,
|
||||
)
|
||||
from app.runtime.capabilities.model import CapabilityMaterializationState
|
||||
from app.runtime.capabilities.runtime import CapabilityRuntime
|
||||
|
||||
|
||||
_runtime_lock = threading.RLock()
|
||||
_agent_runtime: CapabilityRuntime | None = None
|
||||
|
||||
|
||||
def _build_agent_runtime() -> CapabilityRuntime:
|
||||
"""装配 Agent Runtime;构建阶段只解析 manifests。"""
|
||||
return CapabilityRuntime(
|
||||
build_agent_capability_registry(),
|
||||
adapters={
|
||||
AGENT_ENTRYPOINT_KIND: AgentEntrypointAdapter(),
|
||||
AGENT_SERVICE_KIND: AgentServiceAdapter(),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _ensure_runtime() -> CapabilityRuntime:
|
||||
"""返回进程唯一 Runtime,同进程关闭后不重新创建。"""
|
||||
global _agent_runtime
|
||||
with _runtime_lock:
|
||||
if _agent_runtime is None:
|
||||
_agent_runtime = _build_agent_runtime()
|
||||
return _agent_runtime
|
||||
|
||||
|
||||
def _materialize_entrypoint(capability_id: str) -> Any:
|
||||
"""通过通用 Runtime 完成并发 single-flight 物化,不声明资源运行态。"""
|
||||
return _ensure_runtime().materialize(
|
||||
capability_id,
|
||||
reason="agent_entrypoint_first_use",
|
||||
)
|
||||
|
||||
|
||||
def get_agent_manager() -> Any:
|
||||
"""返回 canonical Agent Manager;关闭门禁生效后稳定拒绝首用。"""
|
||||
return _materialize_entrypoint(AGENT_MANAGER_CAPABILITY_ID)
|
||||
|
||||
|
||||
async def reconcile_agent_service(
|
||||
*,
|
||||
reason: str,
|
||||
changed_keys: set[str] | None = None,
|
||||
retry: bool = False,
|
||||
) -> Any | None:
|
||||
"""按 manifest watch/selector 协调唯一 Agent Service 生命周期。"""
|
||||
runtime = _ensure_runtime()
|
||||
spec = runtime.get_spec(AGENT_SERVICE_CAPABILITY_ID)
|
||||
if spec is None:
|
||||
raise RuntimeError("缺少 agent.service capability")
|
||||
if changed_keys is not None and not changed_keys.intersection(spec.watch):
|
||||
return runtime.get_running(AGENT_SERVICE_CAPABILITY_ID)
|
||||
if not should_run_agent_service(spec):
|
||||
# stop_async 会等待并发首启后再撤销实例;未物化能力则保持零导入。
|
||||
await runtime.stop_async(
|
||||
AGENT_SERVICE_CAPABILITY_ID,
|
||||
reason=reason,
|
||||
)
|
||||
return None
|
||||
return await runtime.activate_async(
|
||||
AGENT_SERVICE_CAPABILITY_ID,
|
||||
reason=reason,
|
||||
retry=retry,
|
||||
)
|
||||
|
||||
|
||||
async def activate_agent_service(*, retry: bool = False) -> Any | None:
|
||||
"""执行启动期协调;selector 未启用时保持 service 未物化。"""
|
||||
return await reconcile_agent_service(
|
||||
reason="agent_service_startup_reconcile",
|
||||
retry=retry,
|
||||
)
|
||||
|
||||
|
||||
def get_running_agent_manager() -> Any | None:
|
||||
"""只读返回 RUNNING Agent Service;未构建 Runtime 时不触发声明发现。"""
|
||||
with _runtime_lock:
|
||||
runtime = _agent_runtime
|
||||
if runtime is None:
|
||||
return None
|
||||
return runtime.get_running(AGENT_SERVICE_CAPABILITY_ID)
|
||||
|
||||
|
||||
def get_moviepilot_agent_type() -> type:
|
||||
"""返回 canonical MoviePilotAgent 类型。"""
|
||||
agent_type = _materialize_entrypoint(MOVIEPILOT_AGENT_TYPE_CAPABILITY_ID)
|
||||
if not isinstance(agent_type, type):
|
||||
raise TypeError("MoviePilot Agent entrypoint 必须是类型")
|
||||
return agent_type
|
||||
|
||||
|
||||
def get_tool_factory() -> type:
|
||||
"""返回 canonical 工具工厂类型。"""
|
||||
factory_type = _materialize_entrypoint(TOOL_FACTORY_CAPABILITY_ID)
|
||||
if not isinstance(factory_type, type):
|
||||
raise TypeError("Agent Tool Factory entrypoint 必须是类型")
|
||||
return factory_type
|
||||
|
||||
|
||||
def is_tool_factory_materialized() -> bool:
|
||||
"""只读判断工具工厂是否已解析;未建 Runtime 时不触发发现或导入。"""
|
||||
with _runtime_lock:
|
||||
runtime = _agent_runtime
|
||||
if runtime is None:
|
||||
return False
|
||||
return (
|
||||
runtime.snapshot(TOOL_FACTORY_CAPABILITY_ID).materialization
|
||||
is CapabilityMaterializationState.RESOLVED
|
||||
)
|
||||
|
||||
|
||||
async def begin_agent_shutdown() -> None:
|
||||
"""不可逆关闭首用闸门,并等待全部同步及异步能力释放。"""
|
||||
await _ensure_runtime().shutdown_async(reason="application_shutdown")
|
||||
Reference in New Issue
Block a user