fix(agent): prevent chat history persistence for sessions without a channel

This commit is contained in:
jxxghp
2026-06-19 12:10:39 +08:00
parent e02650cce9
commit 570ea60096
3 changed files with 37 additions and 34 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, is_internal_user_id
from app.utils.identity import SYSTEM_INTERNAL_USER_ID
class AgentChain(ChainBase):
@@ -227,7 +227,6 @@ 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 = "智能助手执行失败"
@@ -318,12 +317,7 @@ class MoviePilotAgent:
"""
判断当前 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)
)
return bool(self.channel and self.source)
def _save_display_history_messages(self, messages: List[dict]) -> None:
"""

View File

@@ -81,7 +81,7 @@ class AgentBackgroundOutputTest(unittest.IsolatedAsyncioTestCase):
await agent._execute_agent([])
agent.send_agent_message.assert_not_awaited()
save_messages.assert_called_once()
save_messages.assert_not_called()
self.assertEqual("后台结果", agent._streamed_output)
async def test_non_streaming_image_unsupported_error_sends_friendly_notice(self):
@@ -213,7 +213,7 @@ class AgentBackgroundOutputTest(unittest.IsolatedAsyncioTestCase):
agent.send_agent_message.assert_awaited_once_with(
"后台结果", title="MoviePilot助手"
)
save_messages.assert_called_once()
save_messages.assert_not_called()
self.assertEqual("后台结果", agent._streamed_output)
async def test_background_non_streaming_captures_without_sending_when_capture_only(self):
@@ -236,7 +236,7 @@ class AgentBackgroundOutputTest(unittest.IsolatedAsyncioTestCase):
await agent._execute_agent([])
agent.send_agent_message.assert_not_awaited()
save_messages.assert_called_once()
save_messages.assert_not_called()
self.assertEqual("后台结果", agent._streamed_output)
async def test_heartbeat_check_jobs_captures_final_reply_and_keeps_message_tools(self):

View File

@@ -111,63 +111,72 @@ 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):
"""内部后台任务和心跳会话不应生成标题或创建历史会话。"""
def test_agent_prepare_chat_title_skips_sessions_without_channel(monkeypatch):
"""没有渠道来源的 Agent 会话不应生成标题或创建历史会话。"""
async def fake_initialize_llm(self, streaming=False):
"""后台会话不应初始化标题模型。"""
raise AssertionError("background title generation should be skipped")
"""无渠道会话不应初始化标题模型。"""
raise AssertionError("no-channel 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__",
for session_id, user_id in (
("__agent_background_title__", SYSTEM_INTERNAL_USER_ID),
(f"{HEARTBEAT_SESSION_PREFIX}title__", SYSTEM_INTERNAL_USER_ID),
("mcp-title-session", "mcp"),
("cli-title-session", "cli"),
):
agent = MoviePilotAgent(
session_id=session_id,
user_id=SYSTEM_INTERNAL_USER_ID,
user_id=user_id,
username="admin",
)
asyncio.run(agent.prepare_chat_title("后台任务"))
assert AgentChatOper().get(
session_id=session_id,
user_id=SYSTEM_INTERNAL_USER_ID,
user_id=user_id,
) is None
def test_agent_prepare_chat_title_keeps_user_cli_sessions(monkeypatch):
"""用户显式 CLI 会话即使没有渠道来源也应保留标题生成。"""
def test_agent_prepare_chat_title_keeps_message_channel_sessions(monkeypatch):
"""带渠道来源的消息会话应保留标题生成。"""
class FakeTitleModel:
"""测试用 CLI 标题模型。"""
"""测试用消息渠道标题模型。"""
async def ainvoke(self, messages):
"""返回固定 CLI 标题。"""
return SimpleNamespace(content="CLI 会话排查")
"""返回固定消息渠道标题。"""
return SimpleNamespace(content="Telegram 会话排查")
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",
session_id="telegram-title-session",
user_id="telegram-user",
channel="Telegram",
source="telegram-main",
username="admin",
)
asyncio.run(agent.prepare_chat_title("帮我检查配置"))
chat = AgentChatOper().get(session_id="cli-title-session", user_id="cli")
chat = AgentChatOper().get(
session_id="telegram-title-session",
user_id="telegram-user",
)
assert chat.title == "CLI 会话排查"
assert chat.title == "Telegram 会话排查"
assert chat.channel == "Telegram"
assert chat.source == "telegram-main"
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
def test_agent_execution_without_channel_does_not_persist_chat_history(monkeypatch):
"""没有渠道来源的 Agent 执行完成后不应写入会话历史表。"""
session_id = "mcp-skip-persist"
user_id = "mcp"
memory_manager.clear_memory(session_id, user_id)
class FakeGraphState: