mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-04 23:17:20 +08:00
refactor(agent): 按需加载 Agent 运行时 (#6336)
This commit is contained in:
@@ -5,6 +5,7 @@ import pytest
|
||||
|
||||
import app.agent.orchestrator as agent_module
|
||||
from app.agent import AgentManager
|
||||
from app.agent.orchestrator import AgentManagerUnavailableError
|
||||
from app.agent.memory import MemoryManager
|
||||
from app.startup import agent_initializer, modules_initializer
|
||||
|
||||
@@ -154,3 +155,179 @@ async def test_disabled_agent_does_not_create_background_tasks(monkeypatch) -> N
|
||||
|
||||
assert await agent_initializer.init_agent() is True
|
||||
manager.initialize.assert_not_awaited()
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_agent_manager_acceptance_gate_rejects_stale_references(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
"""未启动和关闭后的 manager 引用不得创建队列、worker 或 Agent。"""
|
||||
manager = AgentManager()
|
||||
memory_manager = MemoryManager()
|
||||
monkeypatch.setattr(agent_module, "memory_manager", memory_manager)
|
||||
|
||||
with pytest.raises(AgentManagerUnavailableError):
|
||||
await manager.process_message("before-init", "1", "hello")
|
||||
|
||||
await manager.initialize()
|
||||
manager._process_message_internal = AsyncMock(return_value="accepted")
|
||||
assert await manager.process_message(
|
||||
"running",
|
||||
"1",
|
||||
"hello",
|
||||
wait_for_completion=True,
|
||||
) == "accepted"
|
||||
await manager.close()
|
||||
|
||||
with pytest.raises(AgentManagerUnavailableError):
|
||||
await manager.process_message("after-close", "1", "hello")
|
||||
assert manager._session_queues == {}
|
||||
assert manager._session_workers == {}
|
||||
assert manager.active_agents == {}
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_agent_manager_close_serializes_racing_enqueue_and_clear(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
"""关闭、临时会话清理和迟到请求必须串行收口且只清理一次。"""
|
||||
manager = AgentManager()
|
||||
memory_manager = MemoryManager()
|
||||
monkeypatch.setattr(agent_module, "memory_manager", memory_manager)
|
||||
started = asyncio.Event()
|
||||
cleanup_started = asyncio.Event()
|
||||
release_cleanup = asyncio.Event()
|
||||
created = []
|
||||
cleanup_calls = []
|
||||
|
||||
class BlockingAgent:
|
||||
"""用于放大 close 与请求级 clear 竞态窗口。"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__dict__.update(kwargs)
|
||||
created.append(self)
|
||||
|
||||
async def process(self, _message, **_kwargs):
|
||||
started.set()
|
||||
await asyncio.Event().wait()
|
||||
|
||||
async def cleanup(self):
|
||||
cleanup_calls.append(self)
|
||||
cleanup_started.set()
|
||||
await release_cleanup.wait()
|
||||
|
||||
await manager.initialize()
|
||||
waiter = asyncio.create_task(
|
||||
manager.process_message(
|
||||
"closing",
|
||||
"1",
|
||||
"hello",
|
||||
agent_factory=BlockingAgent,
|
||||
wait_for_completion=True,
|
||||
)
|
||||
)
|
||||
await asyncio.wait_for(started.wait(), timeout=1)
|
||||
close_task = asyncio.create_task(manager.close())
|
||||
await asyncio.wait_for(cleanup_started.wait(), timeout=1)
|
||||
late_enqueue = asyncio.create_task(
|
||||
manager.process_message("late", "1", "hello")
|
||||
)
|
||||
request_clear = asyncio.create_task(manager.clear_session("closing", "1"))
|
||||
await asyncio.sleep(0)
|
||||
assert not late_enqueue.done()
|
||||
assert not request_clear.done()
|
||||
|
||||
release_cleanup.set()
|
||||
await asyncio.wait_for(close_task, timeout=1)
|
||||
with pytest.raises(AgentManagerUnavailableError):
|
||||
await late_enqueue
|
||||
await request_clear
|
||||
with pytest.raises(AgentManagerUnavailableError):
|
||||
await waiter
|
||||
|
||||
assert len(created) == 1
|
||||
assert cleanup_calls == created
|
||||
assert manager._session_queues == {}
|
||||
assert manager._session_workers == {}
|
||||
assert manager.active_agents == {}
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_clear_session_settles_current_and_queued_waiters(monkeypatch) -> None:
|
||||
"""清空会话必须同时结束正在执行和尚未执行的等待请求。"""
|
||||
manager = AgentManager()
|
||||
memory_manager = MemoryManager()
|
||||
monkeypatch.setattr(agent_module, "memory_manager", memory_manager)
|
||||
started = asyncio.Event()
|
||||
|
||||
async def block_current(_task):
|
||||
started.set()
|
||||
await asyncio.Event().wait()
|
||||
|
||||
manager._process_message_internal = block_current
|
||||
await manager.initialize()
|
||||
current_waiter = asyncio.create_task(
|
||||
manager.process_message(
|
||||
"session-with-queue",
|
||||
"1",
|
||||
"current",
|
||||
wait_for_completion=True,
|
||||
)
|
||||
)
|
||||
await asyncio.wait_for(started.wait(), timeout=1)
|
||||
queued_waiter = asyncio.create_task(
|
||||
manager.process_message(
|
||||
"session-with-queue",
|
||||
"1",
|
||||
"queued",
|
||||
wait_for_completion=True,
|
||||
)
|
||||
)
|
||||
await asyncio.sleep(0)
|
||||
|
||||
await asyncio.wait_for(
|
||||
manager.clear_session("session-with-queue", "1"),
|
||||
timeout=1,
|
||||
)
|
||||
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await current_waiter
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await asyncio.wait_for(queued_waiter, timeout=1)
|
||||
assert "session-with-queue" not in manager._session_queues
|
||||
assert "session-with-queue" not in manager._session_workers
|
||||
await manager.close()
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_background_prompt_is_owned_and_cancelled_by_manager_close(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
"""后台 prompt 必须进入 manager worker,关闭时同步结束且不残留临时会话。"""
|
||||
manager = AgentManager()
|
||||
memory_manager = MemoryManager()
|
||||
monkeypatch.setattr(agent_module, "memory_manager", memory_manager)
|
||||
started = asyncio.Event()
|
||||
|
||||
async def block_background(task):
|
||||
assert task.session_id.startswith("__managed_background_")
|
||||
started.set()
|
||||
await asyncio.Event().wait()
|
||||
|
||||
manager._process_message_internal = block_background
|
||||
await manager.initialize()
|
||||
execution = asyncio.create_task(
|
||||
manager.run_background_prompt(
|
||||
"background",
|
||||
session_prefix="__managed_background",
|
||||
)
|
||||
)
|
||||
await asyncio.wait_for(started.wait(), timeout=1)
|
||||
assert len(manager._session_workers) == 1
|
||||
|
||||
await manager.close()
|
||||
with pytest.raises(AgentManagerUnavailableError):
|
||||
await execution
|
||||
assert manager._session_queues == {}
|
||||
assert manager._session_workers == {}
|
||||
assert manager.active_agents == {}
|
||||
|
||||
Reference in New Issue
Block a user