fix(agent): keep chat reads on native async path

This commit is contained in:
InfinityPacer
2026-08-23 11:43:07 +08:00
parent 21ce70fbfc
commit f560c19d65
9 changed files with 106 additions and 66 deletions
+3
View File
@@ -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,
+46
View File
@@ -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,
)
+16 -19
View File
@@ -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()
+1
View File
@@ -29,6 +29,7 @@ def _chat() -> SimpleNamespace:
created_at=None,
updated_at=None,
display_messages=[],
agent_messages=[],
)
+8 -7
View File
@@ -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():