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

View File

@@ -57,7 +57,7 @@ from app.log import logger
from app.schemas import AgentLLMProviderEventData, AgentTokensUsageEventData, Notification, NotificationType
from app.schemas.message import ChannelCapabilityManager, ChannelCapability
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):
@@ -227,6 +227,7 @@ class ReplyMode(str, Enum):
CAPTURE_ONLY = "capture_only"
INTERNAL_AGENT_SESSION_PREFIX = "__agent_"
HEARTBEAT_SESSION_PREFIX = "__agent_heartbeat_"
UNSUPPORTED_IMAGE_INPUT_MESSAGE = "当前模型不支持图片输入,请更换支持图片输入的模型,或在系统设置中关闭图片输入支持后重试。"
AGENT_EXECUTION_ERROR_PREFIX = "智能助手执行失败"
@@ -313,6 +314,17 @@ class MoviePilotAgent:
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:
"""
将一组可见消息追加到 Agent 会话历史表。
@@ -372,6 +384,8 @@ class MoviePilotAgent:
"""
首次对话时生成并保存会话标题。
"""
if not self._should_persist_agent_chat():
return
if self._tool_context.get("chat_title_prepared"):
return
self._tool_context["chat_title_prepared"] = True
@@ -1373,12 +1387,12 @@ class MoviePilotAgent:
break
self._save_assistant_display_message_once(display_text)
# 保存消息
memory_manager.save_agent_messages(
session_id=self.session_id,
user_id=self.user_id,
messages=agent.get_state(agent_config).values.get("messages", []),
)
if self._should_persist_agent_chat():
memory_manager.save_agent_messages(
session_id=self.session_id,
user_id=self.user_id,
messages=agent.get_state(agent_config).values.get("messages", []),
)
execution_success = True
except asyncio.CancelledError:

View File

@@ -1,11 +1,12 @@
import asyncio
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.db.agentchat_oper import AgentChatOper
from app.utils.identity import SYSTEM_INTERNAL_USER_ID
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"
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():
"""内存缓存缺失时应从 Agent 会话历史表恢复原始 messages。"""
session_id = "session-memory"