mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-07 08:26:53 +08:00
fix(agent): keep chat reads on native async path
This commit is contained in:
@@ -119,7 +119,9 @@ def configure_plugin_system_services():
|
||||
)
|
||||
from app.application.messaging.message import MessageHelper, MessageQueueManager
|
||||
from app.application.messaging.chat import (
|
||||
AgentChatService,
|
||||
AgentChatPersistenceService,
|
||||
configure_agent_chat_service,
|
||||
configure_agent_chat_persistence,
|
||||
)
|
||||
from app.runtime.cache import AsyncFileCache, FileCache
|
||||
@@ -265,6 +267,7 @@ def configure_plugin_system_services():
|
||||
async_executor=database_executor,
|
||||
)
|
||||
)
|
||||
configure_agent_chat_service(AgentChatService(repository=AgentChatOper()))
|
||||
from app.adapters.external.market import (
|
||||
PluginHelper,
|
||||
VERSION_BACKWARD_COMPATIBLE_FLAGS,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import asyncio
|
||||
import json
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from langchain_core.messages import AIMessage, HumanMessage
|
||||
|
||||
@@ -278,3 +279,48 @@ def test_memory_manager_restores_agent_messages_from_database():
|
||||
assert len(messages) == 1
|
||||
assert isinstance(messages[0], HumanMessage)
|
||||
assert messages[0].content == "继续之前的话题"
|
||||
|
||||
|
||||
def test_async_memory_manager_restores_through_native_async_service(monkeypatch):
|
||||
"""异步记忆恢复只能通过会话应用服务的异步查询端口。"""
|
||||
session_id = "session-memory-async"
|
||||
user_id = "3"
|
||||
memory_manager.clear_memory(session_id, user_id)
|
||||
service = SimpleNamespace(
|
||||
get=AsyncMock(
|
||||
return_value=SimpleNamespace(
|
||||
agent_messages=[
|
||||
{
|
||||
"type": "human",
|
||||
"data": {
|
||||
"content": "异步恢复",
|
||||
"additional_kwargs": {},
|
||||
"response_metadata": {},
|
||||
"type": "human",
|
||||
"name": None,
|
||||
"id": None,
|
||||
"example": False,
|
||||
},
|
||||
}
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"app.agent.memory.get_configured_agent_chat_service",
|
||||
lambda: service,
|
||||
)
|
||||
|
||||
messages = asyncio.run(
|
||||
memory_manager.async_get_agent_messages(
|
||||
session_id=session_id,
|
||||
user_id=user_id,
|
||||
)
|
||||
)
|
||||
|
||||
assert len(messages) == 1
|
||||
assert messages[0].content == "异步恢复"
|
||||
service.get.assert_awaited_once_with(
|
||||
session_id=session_id,
|
||||
user_id=user_id,
|
||||
)
|
||||
|
||||
@@ -5,13 +5,11 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import threading
|
||||
from uuid import uuid4
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from app.application.messaging.chat import AgentChatPersistenceService
|
||||
from app.application.messaging.chat import AgentChatPersistenceService, AgentChatService
|
||||
from app.db.oper.agentchat import AgentChatOper
|
||||
from app.db.models.agentchat import AgentChat
|
||||
from app.db.worker import DatabaseWorker
|
||||
|
||||
|
||||
@@ -39,10 +37,6 @@ class _Repository:
|
||||
def __init__(self) -> None:
|
||||
self.calls: list[tuple[str, dict]] = []
|
||||
|
||||
def get(self, **kwargs):
|
||||
self.calls.append(("get", kwargs))
|
||||
return SimpleNamespace(agent_messages=[])
|
||||
|
||||
def append_display_messages(self, **kwargs):
|
||||
self.calls.append(("append_display_messages", kwargs))
|
||||
return None
|
||||
@@ -60,7 +54,7 @@ class _Repository:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_chat_persistence_runs_sync_repository_inside_worker() -> None:
|
||||
"""同步查询和写入都必须经过一次 worker admission。"""
|
||||
"""同步 AgentChat 写入必须经过一次 worker admission。"""
|
||||
executor = _Executor()
|
||||
repository = _Repository()
|
||||
service = AgentChatPersistenceService(
|
||||
@@ -69,7 +63,6 @@ async def test_agent_chat_persistence_runs_sync_repository_inside_worker() -> No
|
||||
)
|
||||
caller_thread_id = threading.get_ident()
|
||||
|
||||
await service.async_get("session-1", user_id="1")
|
||||
await service.async_append_display_messages(
|
||||
session_id="session-1",
|
||||
user_id="1",
|
||||
@@ -91,10 +84,9 @@ async def test_agent_chat_persistence_runs_sync_repository_inside_worker() -> No
|
||||
title="标题",
|
||||
)
|
||||
|
||||
assert executor.calls == 5
|
||||
assert executor.calls == 4
|
||||
assert executor.worker_thread_id != caller_thread_id
|
||||
assert [name for name, _kwargs in repository.calls] == [
|
||||
"get",
|
||||
"append_display_messages",
|
||||
"save_display_messages",
|
||||
"save_agent_messages",
|
||||
@@ -125,17 +117,18 @@ async def test_agent_chat_persistence_propagates_worker_failure() -> None:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_chat_persistence_uses_real_worker_and_sqlite_transaction() -> None:
|
||||
"""真实 AgentChat Oper 经 worker 写入后可被后续 worker 查询恢复。"""
|
||||
"""真实 AgentChat Oper 经 worker 写入后可被 native async 查询恢复。"""
|
||||
worker = DatabaseWorker(max_workers=1, capacity=4)
|
||||
await worker.start()
|
||||
session_id = f"worker-{uuid4().hex}"
|
||||
service = AgentChatPersistenceService(
|
||||
persistence = AgentChatPersistenceService(
|
||||
repository=AgentChatOper,
|
||||
async_executor=worker,
|
||||
)
|
||||
query = AgentChatService(repository=AgentChatOper())
|
||||
|
||||
try:
|
||||
await service.async_save_display_messages(
|
||||
await persistence.async_save_display_messages(
|
||||
session_id=session_id,
|
||||
user_id="worker-user",
|
||||
username="worker-user",
|
||||
@@ -143,12 +136,16 @@ async def test_agent_chat_persistence_uses_real_worker_and_sqlite_transaction()
|
||||
source="worker-test",
|
||||
messages=[{"role": "user", "content": "worker"}],
|
||||
)
|
||||
chat = await service.async_get(session_id, user_id="worker-user")
|
||||
chat = await query.get(
|
||||
session_id,
|
||||
user_id="worker-user",
|
||||
)
|
||||
assert chat is not None
|
||||
assert chat.message_count == 1
|
||||
assert chat.display_messages[0]["content"] == "worker"
|
||||
assert chat.messages[0]["content"] == "worker"
|
||||
finally:
|
||||
chat = AgentChatOper().get(session_id=session_id, user_id="worker-user")
|
||||
if chat is not None:
|
||||
AgentChat.delete(rid=chat.id)
|
||||
await AgentChatOper().async_delete(
|
||||
session_id=session_id,
|
||||
user_id="worker-user",
|
||||
)
|
||||
await worker.shutdown()
|
||||
|
||||
@@ -29,6 +29,7 @@ def _chat() -> SimpleNamespace:
|
||||
created_at=None,
|
||||
updated_at=None,
|
||||
display_messages=[],
|
||||
agent_messages=[],
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -175,28 +175,29 @@ def test_build_web_agent_session_id_reuses_accessible_history():
|
||||
assert _build_web_agent_session_id(user, "telegram-session") == "telegram-session"
|
||||
|
||||
|
||||
def test_build_web_agent_session_id_async_uses_worker_persistence():
|
||||
"""异步 Web 会话解析应通过 AgentChat worker 端口读取历史。"""
|
||||
def test_build_web_agent_session_id_async_uses_native_async_persistence():
|
||||
"""异步 Web 会话解析应通过 native async 会话服务读取历史。"""
|
||||
user = SimpleNamespace(id=1, name="admin", is_superuser=True)
|
||||
persistence = SimpleNamespace(
|
||||
async_get=AsyncMock(
|
||||
service = SimpleNamespace(
|
||||
get=AsyncMock(
|
||||
return_value=SimpleNamespace(
|
||||
user_id="telegram-user",
|
||||
username="tester",
|
||||
agent_messages=[],
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
with patch(
|
||||
"app.api.endpoints.agent.get_configured_agent_chat_persistence",
|
||||
return_value=persistence,
|
||||
"app.api.endpoints.agent.get_configured_agent_chat_service",
|
||||
return_value=service,
|
||||
):
|
||||
session_id = asyncio.run(
|
||||
_build_web_agent_session_id_async(user, "telegram-session")
|
||||
)
|
||||
|
||||
assert session_id == "telegram-session"
|
||||
persistence.async_get.assert_awaited_once_with("telegram-session")
|
||||
service.get.assert_awaited_once_with("telegram-session")
|
||||
|
||||
|
||||
def test_apply_web_agent_display_event_updates_snapshot():
|
||||
|
||||
Reference in New Issue
Block a user