fix: skip background agent chat persistence

This commit is contained in:
jxxghp
2026-06-19 12:05:47 +08:00
parent 60c7268301
commit e02650cce9
2 changed files with 117 additions and 9 deletions
+21 -7
View File
@@ -57,7 +57,7 @@ from app.log import logger
from app.schemas import AgentLLMProviderEventData, AgentTokensUsageEventData, Notification, NotificationType from app.schemas import AgentLLMProviderEventData, AgentTokensUsageEventData, Notification, NotificationType
from app.schemas.message import ChannelCapabilityManager, ChannelCapability from app.schemas.message import ChannelCapabilityManager, ChannelCapability
from app.schemas.types import ChainEventType, EventType, MessageChannel from app.schemas.types import ChainEventType, EventType, MessageChannel
from app.utils.identity import SYSTEM_INTERNAL_USER_ID from app.utils.identity import SYSTEM_INTERNAL_USER_ID, is_internal_user_id
class AgentChain(ChainBase): class AgentChain(ChainBase):
@@ -227,6 +227,7 @@ class ReplyMode(str, Enum):
CAPTURE_ONLY = "capture_only" CAPTURE_ONLY = "capture_only"
INTERNAL_AGENT_SESSION_PREFIX = "__agent_"
HEARTBEAT_SESSION_PREFIX = "__agent_heartbeat_" HEARTBEAT_SESSION_PREFIX = "__agent_heartbeat_"
UNSUPPORTED_IMAGE_INPUT_MESSAGE = "当前模型不支持图片输入,请更换支持图片输入的模型,或在系统设置中关闭图片输入支持后重试。" UNSUPPORTED_IMAGE_INPUT_MESSAGE = "当前模型不支持图片输入,请更换支持图片输入的模型,或在系统设置中关闭图片输入支持后重试。"
AGENT_EXECUTION_ERROR_PREFIX = "智能助手执行失败" AGENT_EXECUTION_ERROR_PREFIX = "智能助手执行失败"
@@ -313,6 +314,17 @@ class MoviePilotAgent:
and self.channel not in AGENT_DISPLAY_HISTORY_SKIP_CHANNELS and self.channel not in AGENT_DISPLAY_HISTORY_SKIP_CHANNELS
) )
def _should_persist_agent_chat(self) -> bool:
"""
判断当前 Agent 是否需要写入会话历史表。
"""
if self.is_heartbeat_session:
return False
return not (
is_internal_user_id(self.user_id)
and self.session_id.startswith(INTERNAL_AGENT_SESSION_PREFIX)
)
def _save_display_history_messages(self, messages: List[dict]) -> None: def _save_display_history_messages(self, messages: List[dict]) -> None:
""" """
将一组可见消息追加到 Agent 会话历史表。 将一组可见消息追加到 Agent 会话历史表。
@@ -372,6 +384,8 @@ class MoviePilotAgent:
""" """
首次对话时生成并保存会话标题。 首次对话时生成并保存会话标题。
""" """
if not self._should_persist_agent_chat():
return
if self._tool_context.get("chat_title_prepared"): if self._tool_context.get("chat_title_prepared"):
return return
self._tool_context["chat_title_prepared"] = True self._tool_context["chat_title_prepared"] = True
@@ -1373,12 +1387,12 @@ class MoviePilotAgent:
break break
self._save_assistant_display_message_once(display_text) self._save_assistant_display_message_once(display_text)
# 保存消息 if self._should_persist_agent_chat():
memory_manager.save_agent_messages( memory_manager.save_agent_messages(
session_id=self.session_id, session_id=self.session_id,
user_id=self.user_id, user_id=self.user_id,
messages=agent.get_state(agent_config).values.get("messages", []), messages=agent.get_state(agent_config).values.get("messages", []),
) )
execution_success = True execution_success = True
except asyncio.CancelledError: except asyncio.CancelledError:
+96 -2
View File
@@ -1,11 +1,12 @@
import asyncio import asyncio
from types import SimpleNamespace from types import SimpleNamespace
from langchain_core.messages import HumanMessage from langchain_core.messages import AIMessage, HumanMessage
from app.agent import MoviePilotAgent from app.agent import HEARTBEAT_SESSION_PREFIX, MoviePilotAgent
from app.agent.memory import memory_manager from app.agent.memory import memory_manager
from app.db.agentchat_oper import AgentChatOper from app.db.agentchat_oper import AgentChatOper
from app.utils.identity import SYSTEM_INTERNAL_USER_ID
def test_agent_chat_oper_saves_display_messages_with_channel(): def test_agent_chat_oper_saves_display_messages_with_channel():
@@ -110,6 +111,99 @@ def test_agent_prepare_chat_title_generates_title(monkeypatch):
assert chat.source == "web-agent" assert chat.source == "web-agent"
def test_agent_prepare_chat_title_skips_internal_background_sessions(monkeypatch):
"""内部后台任务和心跳会话不应生成标题或创建历史会话。"""
async def fake_initialize_llm(self, streaming=False):
"""后台会话不应初始化标题模型。"""
raise AssertionError("background title generation should be skipped")
monkeypatch.setattr(MoviePilotAgent, "_initialize_llm", fake_initialize_llm)
for session_id in (
"__agent_background_title__",
f"{HEARTBEAT_SESSION_PREFIX}title__",
):
agent = MoviePilotAgent(
session_id=session_id,
user_id=SYSTEM_INTERNAL_USER_ID,
username="admin",
)
asyncio.run(agent.prepare_chat_title("后台任务"))
assert AgentChatOper().get(
session_id=session_id,
user_id=SYSTEM_INTERNAL_USER_ID,
) is None
def test_agent_prepare_chat_title_keeps_user_cli_sessions(monkeypatch):
"""用户显式 CLI 会话即使没有渠道来源也应保留标题生成。"""
class FakeTitleModel:
"""测试用 CLI 标题模型。"""
async def ainvoke(self, messages):
"""返回固定 CLI 标题。"""
return SimpleNamespace(content="CLI 会话排查")
async def fake_initialize_llm(self, streaming=False):
"""返回测试 CLI 标题模型。"""
return FakeTitleModel()
monkeypatch.setattr(MoviePilotAgent, "_initialize_llm", fake_initialize_llm)
agent = MoviePilotAgent(
session_id="cli-title-session",
user_id="cli",
username="admin",
)
asyncio.run(agent.prepare_chat_title("帮我检查配置"))
chat = AgentChatOper().get(session_id="cli-title-session", user_id="cli")
assert chat.title == "CLI 会话排查"
def test_internal_background_agent_execution_does_not_persist_chat_history(monkeypatch):
"""内部后台任务执行完成后不应写入 Agent 会话历史表。"""
session_id = "__agent_background_skip_persist__"
user_id = SYSTEM_INTERNAL_USER_ID
memory_manager.clear_memory(session_id, user_id)
class FakeGraphState:
"""测试用 LangGraph 状态。"""
def __init__(self, messages):
self.values = {"messages": messages}
class FakeAgent:
"""测试用 LangGraph Agent。"""
async def ainvoke(self, _payload, config=None):
"""模拟非流式 Agent 执行。"""
return None
def get_state(self, _config):
"""返回包含最终回复的状态。"""
return FakeGraphState([AIMessage(content="后台结果")])
async def fake_create_agent(self, streaming=False):
"""返回测试 Agent,避免真实初始化模型。"""
return FakeAgent()
monkeypatch.setattr(MoviePilotAgent, "_create_agent", fake_create_agent)
agent = MoviePilotAgent(
session_id=session_id,
user_id=user_id,
username="admin",
)
asyncio.run(agent._execute_agent([]))
assert AgentChatOper().get(session_id=session_id, user_id=user_id) is None
assert memory_manager.get_memory(session_id, user_id) is None
def test_memory_manager_restores_agent_messages_from_database(): def test_memory_manager_restores_agent_messages_from_database():
"""内存缓存缺失时应从 Agent 会话历史表恢复原始 messages。""" """内存缓存缺失时应从 Agent 会话历史表恢复原始 messages。"""
session_id = "session-memory" session_id = "session-memory"