mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 23:47:41 +08:00
Refine agent background reply handling
This commit is contained in:
+42
-9
@@ -5,6 +5,7 @@ import traceback
|
|||||||
import uuid
|
import uuid
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from enum import Enum
|
||||||
from typing import Any, Callable, Dict, List, Optional
|
from typing import Any, Callable, Dict, List, Optional
|
||||||
|
|
||||||
from langchain.agents import create_agent
|
from langchain.agents import create_agent
|
||||||
@@ -150,6 +151,15 @@ class _ThinkTagStripper:
|
|||||||
self.buffer = ""
|
self.buffer = ""
|
||||||
|
|
||||||
|
|
||||||
|
class ReplyMode(str, Enum):
|
||||||
|
"""
|
||||||
|
Agent 最终回复处理模式。
|
||||||
|
"""
|
||||||
|
|
||||||
|
DISPATCH = "dispatch"
|
||||||
|
CAPTURE_ONLY = "capture_only"
|
||||||
|
|
||||||
|
|
||||||
class MoviePilotAgent:
|
class MoviePilotAgent:
|
||||||
"""
|
"""
|
||||||
MoviePilot AI智能体(基于 LangChain v1 + LangGraph)
|
MoviePilot AI智能体(基于 LangChain v1 + LangGraph)
|
||||||
@@ -171,7 +181,7 @@ class MoviePilotAgent:
|
|||||||
self._tool_context: Dict[str, object] = {}
|
self._tool_context: Dict[str, object] = {}
|
||||||
self.output_callback: Optional[Callable[[str], None]] = None
|
self.output_callback: Optional[Callable[[str], None]] = None
|
||||||
self.force_streaming = False
|
self.force_streaming = False
|
||||||
self.suppress_user_reply = False
|
self.reply_mode = ReplyMode.DISPATCH
|
||||||
self.persist_output_message = True
|
self.persist_output_message = True
|
||||||
self.allow_message_tools = True
|
self.allow_message_tools = True
|
||||||
self._streamed_output = ""
|
self._streamed_output = ""
|
||||||
@@ -268,6 +278,13 @@ class MoviePilotAgent:
|
|||||||
"""
|
"""
|
||||||
return not self.channel or not self.source
|
return not self.channel or not self.source
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_dispatch_reply(self) -> bool:
|
||||||
|
"""
|
||||||
|
是否应将最终回复真正发送到消息渠道。
|
||||||
|
"""
|
||||||
|
return self.reply_mode == ReplyMode.DISPATCH
|
||||||
|
|
||||||
def _should_stream(self) -> bool:
|
def _should_stream(self) -> bool:
|
||||||
"""
|
"""
|
||||||
判断是否应启用流式输出:
|
判断是否应启用流式输出:
|
||||||
@@ -490,7 +507,7 @@ class MoviePilotAgent:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_message = f"处理消息时发生错误: {str(e)}"
|
error_message = f"处理消息时发生错误: {str(e)}"
|
||||||
logger.error(error_message)
|
logger.error(error_message)
|
||||||
if self.suppress_user_reply:
|
if not self.should_dispatch_reply:
|
||||||
raise
|
raise
|
||||||
await self.send_agent_message(error_message)
|
await self.send_agent_message(error_message)
|
||||||
return error_message
|
return error_message
|
||||||
@@ -543,7 +560,7 @@ class MoviePilotAgent:
|
|||||||
"""
|
"""
|
||||||
调用 LangGraph Agent 执行推理。
|
调用 LangGraph Agent 执行推理。
|
||||||
根据运行环境选择不同的执行模式:
|
根据运行环境选择不同的执行模式:
|
||||||
- 后台任务模式(无渠道信息):非流式 LLM + ainvoke,仅广播最终结果
|
- 后台任务模式(无渠道信息):非流式 LLM + ainvoke,由 reply_mode 决定是发送还是仅捕获
|
||||||
- 渠道不支持消息编辑:非流式 LLM + ainvoke,完成后发送最终回复
|
- 渠道不支持消息编辑:非流式 LLM + ainvoke,完成后发送最终回复
|
||||||
- 渠道支持消息编辑:流式 LLM + astream,实时推送 token
|
- 渠道支持消息编辑:流式 LLM + astream,实时推送 token
|
||||||
"""
|
"""
|
||||||
@@ -602,10 +619,20 @@ class MoviePilotAgent:
|
|||||||
self._emit_output(unsent_text)
|
self._emit_output(unsent_text)
|
||||||
if (
|
if (
|
||||||
remaining_text
|
remaining_text
|
||||||
and not self.suppress_user_reply
|
and self.should_dispatch_reply
|
||||||
and not self._tool_context.get("user_reply_sent")
|
and not self._tool_context.get("user_reply_sent")
|
||||||
):
|
):
|
||||||
await self.send_agent_message(remaining_text)
|
await self.send_agent_message(remaining_text)
|
||||||
|
elif (
|
||||||
|
remaining_text
|
||||||
|
and self.persist_output_message
|
||||||
|
and not self._tool_context.get("user_reply_sent")
|
||||||
|
):
|
||||||
|
title = "MoviePilot助手" if self.is_background else ""
|
||||||
|
await self._save_agent_message_to_db(
|
||||||
|
remaining_text,
|
||||||
|
title=title,
|
||||||
|
)
|
||||||
elif streamed_text and self.persist_output_message:
|
elif streamed_text and self.persist_output_message:
|
||||||
# 流式输出已发送全部内容,但未记录到数据库,补充保存消息记录
|
# 流式输出已发送全部内容,但未记录到数据库,补充保存消息记录
|
||||||
await self._save_agent_message_to_db(streamed_text)
|
await self._save_agent_message_to_db(streamed_text)
|
||||||
@@ -639,11 +666,11 @@ class MoviePilotAgent:
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
final_text
|
final_text
|
||||||
and not self.suppress_user_reply
|
and self.should_dispatch_reply
|
||||||
and not self._tool_context.get("user_reply_sent")
|
and not self._tool_context.get("user_reply_sent")
|
||||||
):
|
):
|
||||||
if self.is_background:
|
if self.is_background:
|
||||||
# 后台任务仅广播最终回复,带标题
|
# 后台任务发送最终回复时统一带标题
|
||||||
await self.send_agent_message(
|
await self.send_agent_message(
|
||||||
final_text, title="MoviePilot助手"
|
final_text, title="MoviePilot助手"
|
||||||
)
|
)
|
||||||
@@ -732,6 +759,7 @@ class _MessageTask:
|
|||||||
channel: Optional[str] = None
|
channel: Optional[str] = None
|
||||||
source: Optional[str] = None
|
source: Optional[str] = None
|
||||||
username: Optional[str] = None
|
username: Optional[str] = None
|
||||||
|
reply_mode: ReplyMode = ReplyMode.DISPATCH
|
||||||
|
|
||||||
|
|
||||||
class AgentManager:
|
class AgentManager:
|
||||||
@@ -815,6 +843,7 @@ class AgentManager:
|
|||||||
channel: str = None,
|
channel: str = None,
|
||||||
source: str = None,
|
source: str = None,
|
||||||
username: str = None,
|
username: str = None,
|
||||||
|
reply_mode: ReplyMode = ReplyMode.DISPATCH,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
处理用户消息:将消息放入会话队列,按顺序依次处理。
|
处理用户消息:将消息放入会话队列,按顺序依次处理。
|
||||||
@@ -829,6 +858,7 @@ class AgentManager:
|
|||||||
channel=channel,
|
channel=channel,
|
||||||
source=source,
|
source=source,
|
||||||
username=username,
|
username=username,
|
||||||
|
reply_mode=reply_mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 获取或创建会话队列
|
# 获取或创建会话队列
|
||||||
@@ -916,6 +946,7 @@ class AgentManager:
|
|||||||
source=task.source,
|
source=task.source,
|
||||||
username=task.username,
|
username=task.username,
|
||||||
)
|
)
|
||||||
|
agent.reply_mode = task.reply_mode
|
||||||
self.active_agents[session_id] = agent
|
self.active_agents[session_id] = agent
|
||||||
else:
|
else:
|
||||||
agent = self.active_agents[session_id]
|
agent = self.active_agents[session_id]
|
||||||
@@ -926,6 +957,7 @@ class AgentManager:
|
|||||||
agent.source = task.source
|
agent.source = task.source
|
||||||
if task.username:
|
if task.username:
|
||||||
agent.username = task.username
|
agent.username = task.username
|
||||||
|
agent.reply_mode = task.reply_mode
|
||||||
|
|
||||||
return await agent.process(task.message, images=task.images, files=task.files)
|
return await agent.process(task.message, images=task.images, files=task.files)
|
||||||
|
|
||||||
@@ -992,10 +1024,10 @@ class AgentManager:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def run_background_prompt(
|
async def run_background_prompt(
|
||||||
message: str,
|
message: str,
|
||||||
session_prefix: str = "__agent_background",
|
session_prefix: str = "__agent_background",
|
||||||
output_callback: Optional[Callable[[str], None]] = None,
|
output_callback: Optional[Callable[[str], None]] = None,
|
||||||
suppress_user_reply: bool = False,
|
reply_mode: ReplyMode = ReplyMode.CAPTURE_ONLY,
|
||||||
persist_output_message: bool = True,
|
persist_output_message: bool = True,
|
||||||
allow_message_tools: bool = True,
|
allow_message_tools: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -1013,7 +1045,7 @@ class AgentManager:
|
|||||||
)
|
)
|
||||||
agent.output_callback = output_callback
|
agent.output_callback = output_callback
|
||||||
agent.force_streaming = bool(output_callback)
|
agent.force_streaming = bool(output_callback)
|
||||||
agent.suppress_user_reply = suppress_user_reply
|
agent.reply_mode = reply_mode
|
||||||
agent.persist_output_message = persist_output_message
|
agent.persist_output_message = persist_output_message
|
||||||
agent.allow_message_tools = allow_message_tools
|
agent.allow_message_tools = allow_message_tools
|
||||||
|
|
||||||
@@ -1048,6 +1080,7 @@ class AgentManager:
|
|||||||
channel=None,
|
channel=None,
|
||||||
source=None,
|
source=None,
|
||||||
username=settings.SUPERUSER,
|
username=settings.SUPERUSER,
|
||||||
|
reply_mode=ReplyMode.DISPATCH,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 等待消息队列处理完成
|
# 等待消息队列处理完成
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
version: 2
|
version: 2
|
||||||
shared_rules:
|
shared_rules:
|
||||||
- This is a background system task, NOT a user conversation.
|
- This is a background system task, NOT a user conversation.
|
||||||
- Your final response will be broadcast as a notification.
|
- Your final response will be consumed by the system. Keep it concise and task-focused.
|
||||||
- Do NOT include greetings, explanations, or conversational text.
|
- Do NOT include greetings, explanations, or conversational text.
|
||||||
- Respond in Chinese (中文).
|
- Respond in Chinese (中文).
|
||||||
task_types:
|
task_types:
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app import schemas
|
from app import schemas
|
||||||
from app.agent import prompt_manager, agent_manager
|
from app.agent import ReplyMode, prompt_manager, agent_manager
|
||||||
from app.chain.storage import StorageChain
|
from app.chain.storage import StorageChain
|
||||||
from app.core.config import settings, global_vars
|
from app.core.config import settings, global_vars
|
||||||
from app.core.event import eventmanager
|
from app.core.event import eventmanager
|
||||||
@@ -130,7 +130,7 @@ def _start_ai_redo_task(history_id: int, prompt: str, progress_key: str):
|
|||||||
message=prompt,
|
message=prompt,
|
||||||
session_prefix=f"__agent_manual_redo_{history_id}",
|
session_prefix=f"__agent_manual_redo_{history_id}",
|
||||||
output_callback=update_output,
|
output_callback=update_output,
|
||||||
suppress_user_reply=True,
|
reply_mode=ReplyMode.CAPTURE_ONLY,
|
||||||
persist_output_message=False,
|
persist_output_message=False,
|
||||||
allow_message_tools=False,
|
allow_message_tools=False,
|
||||||
)
|
)
|
||||||
@@ -176,7 +176,7 @@ def _start_batch_ai_redo_task(
|
|||||||
message=prompt,
|
message=prompt,
|
||||||
session_prefix="__agent_manual_redo_batch",
|
session_prefix="__agent_manual_redo_batch",
|
||||||
output_callback=update_output,
|
output_callback=update_output,
|
||||||
suppress_user_reply=True,
|
reply_mode=ReplyMode.CAPTURE_ONLY,
|
||||||
persist_output_message=False,
|
persist_output_message=False,
|
||||||
allow_message_tools=False,
|
allow_message_tools=False,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from pathlib import Path
|
|||||||
from typing import Any, Optional, Dict, Union, List
|
from typing import Any, Optional, Dict, Union, List
|
||||||
from urllib.parse import unquote, urlparse
|
from urllib.parse import unquote, urlparse
|
||||||
|
|
||||||
from app.agent import agent_manager, prompt_manager
|
from app.agent import ReplyMode, agent_manager, prompt_manager
|
||||||
from app.chain import ChainBase
|
from app.chain import ChainBase
|
||||||
from app.chain.interaction import (
|
from app.chain.interaction import (
|
||||||
MediaInteractionChain,
|
MediaInteractionChain,
|
||||||
@@ -635,7 +635,7 @@ class MessageChain(ChainBase):
|
|||||||
message=redo_prompt,
|
message=redo_prompt,
|
||||||
session_prefix=f"__agent_manual_redo_{history_id}",
|
session_prefix=f"__agent_manual_redo_{history_id}",
|
||||||
output_callback=_capture_output,
|
output_callback=_capture_output,
|
||||||
suppress_user_reply=True,
|
reply_mode=ReplyMode.CAPTURE_ONLY,
|
||||||
)
|
)
|
||||||
await self.async_post_message(
|
await self.async_post_message(
|
||||||
Notification(
|
Notification(
|
||||||
|
|||||||
+2
-2
@@ -210,7 +210,7 @@ class SearchChain(ChainBase):
|
|||||||
"""
|
"""
|
||||||
通过统一后台提示词机制执行资源推荐。
|
通过统一后台提示词机制执行资源推荐。
|
||||||
"""
|
"""
|
||||||
from app.agent import agent_manager
|
from app.agent import ReplyMode, agent_manager
|
||||||
from app.agent.prompt import prompt_manager
|
from app.agent.prompt import prompt_manager
|
||||||
|
|
||||||
prompt = prompt_manager.render_system_task_message(
|
prompt = prompt_manager.render_system_task_message(
|
||||||
@@ -226,7 +226,7 @@ class SearchChain(ChainBase):
|
|||||||
message=prompt,
|
message=prompt,
|
||||||
session_prefix="__agent_search_recommend",
|
session_prefix="__agent_search_recommend",
|
||||||
output_callback=on_output,
|
output_callback=on_output,
|
||||||
suppress_user_reply=True,
|
reply_mode=ReplyMode.CAPTURE_ONLY,
|
||||||
persist_output_message=False,
|
persist_output_message=False,
|
||||||
allow_message_tools=False,
|
allow_message_tools=False,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from pathlib import Path
|
|||||||
from typing import List, Optional, Tuple, Union, Dict, Callable
|
from typing import List, Optional, Tuple, Union, Dict, Callable
|
||||||
|
|
||||||
from app import schemas
|
from app import schemas
|
||||||
from app.agent import prompt_manager, agent_manager
|
from app.agent import ReplyMode, prompt_manager, agent_manager
|
||||||
from app.chain import ChainBase
|
from app.chain import ChainBase
|
||||||
from app.chain.media import MediaChain
|
from app.chain.media import MediaChain
|
||||||
from app.chain.storage import StorageChain
|
from app.chain.storage import StorageChain
|
||||||
@@ -688,6 +688,7 @@ class FailedRetryScheduler:
|
|||||||
await agent_manager.run_background_prompt(
|
await agent_manager.run_background_prompt(
|
||||||
message=self._build_retry_transfer_prompt(history_ids),
|
message=self._build_retry_transfer_prompt(history_ids),
|
||||||
session_prefix="__agent_retry_transfer_batch",
|
session_prefix="__agent_retry_transfer_batch",
|
||||||
|
reply_mode=ReplyMode.DISPATCH,
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"智能体重试整理:批量处理完成 IDs=[{ids_str}] (group={group_key})"
|
f"智能体重试整理:批量处理完成 IDs=[{ids_str}] (group={group_key})"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from unittest.mock import AsyncMock, patch
|
|||||||
|
|
||||||
from langchain_core.messages import AIMessage
|
from langchain_core.messages import AIMessage
|
||||||
|
|
||||||
from app.agent import MoviePilotAgent
|
from app.agent import MoviePilotAgent, AgentManager, ReplyMode
|
||||||
from app.agent.memory import memory_manager
|
from app.agent.memory import memory_manager
|
||||||
|
|
||||||
|
|
||||||
@@ -25,39 +25,11 @@ class _FakeAgent:
|
|||||||
|
|
||||||
|
|
||||||
class AgentBackgroundOutputTest(unittest.IsolatedAsyncioTestCase):
|
class AgentBackgroundOutputTest(unittest.IsolatedAsyncioTestCase):
|
||||||
async def test_background_non_streaming_still_sends_when_reply_not_suppressed(self):
|
async def test_background_non_streaming_does_not_send_by_default(self):
|
||||||
agent = MoviePilotAgent(session_id="bg-test", user_id="system")
|
agent = MoviePilotAgent(session_id="bg-test", user_id="system")
|
||||||
agent.channel = None
|
agent.channel = None
|
||||||
agent.source = None
|
agent.source = None
|
||||||
agent.suppress_user_reply = False
|
agent.reply_mode = ReplyMode.CAPTURE_ONLY
|
||||||
agent.persist_output_message = False
|
|
||||||
agent._tool_context = {"user_reply_sent": False}
|
|
||||||
agent._streamed_output = ""
|
|
||||||
agent.stream_handler = SimpleNamespace(
|
|
||||||
stop_streaming=AsyncMock(return_value=(False, ""))
|
|
||||||
)
|
|
||||||
agent._should_stream = lambda: False
|
|
||||||
agent._create_agent = lambda streaming=False: _FakeAgent(
|
|
||||||
[AIMessage(content="后台结果")]
|
|
||||||
)
|
|
||||||
agent.send_agent_message = AsyncMock()
|
|
||||||
agent._save_agent_message_to_db = AsyncMock()
|
|
||||||
|
|
||||||
with patch.object(memory_manager, "save_agent_messages") as save_messages:
|
|
||||||
await agent._execute_agent([])
|
|
||||||
|
|
||||||
agent.send_agent_message.assert_awaited_once_with(
|
|
||||||
"后台结果", title="MoviePilot助手"
|
|
||||||
)
|
|
||||||
agent._save_agent_message_to_db.assert_not_awaited()
|
|
||||||
save_messages.assert_called_once()
|
|
||||||
self.assertEqual("后台结果", agent._streamed_output)
|
|
||||||
|
|
||||||
async def test_background_non_streaming_persists_without_sending_when_reply_suppressed(self):
|
|
||||||
agent = MoviePilotAgent(session_id="bg-test", user_id="system")
|
|
||||||
agent.channel = None
|
|
||||||
agent.source = None
|
|
||||||
agent.suppress_user_reply = True
|
|
||||||
agent.persist_output_message = True
|
agent.persist_output_message = True
|
||||||
agent._tool_context = {"user_reply_sent": False}
|
agent._tool_context = {"user_reply_sent": False}
|
||||||
agent._streamed_output = ""
|
agent._streamed_output = ""
|
||||||
@@ -81,6 +53,77 @@ class AgentBackgroundOutputTest(unittest.IsolatedAsyncioTestCase):
|
|||||||
save_messages.assert_called_once()
|
save_messages.assert_called_once()
|
||||||
self.assertEqual("后台结果", agent._streamed_output)
|
self.assertEqual("后台结果", agent._streamed_output)
|
||||||
|
|
||||||
|
async def test_background_non_streaming_sends_when_reply_mode_dispatch(self):
|
||||||
|
agent = MoviePilotAgent(session_id="bg-test", user_id="system")
|
||||||
|
agent.channel = None
|
||||||
|
agent.source = None
|
||||||
|
agent.reply_mode = ReplyMode.DISPATCH
|
||||||
|
agent.persist_output_message = False
|
||||||
|
agent._tool_context = {"user_reply_sent": False}
|
||||||
|
agent._streamed_output = ""
|
||||||
|
agent.stream_handler = SimpleNamespace(
|
||||||
|
stop_streaming=AsyncMock(return_value=(False, ""))
|
||||||
|
)
|
||||||
|
agent._should_stream = lambda: False
|
||||||
|
agent._create_agent = lambda streaming=False: _FakeAgent(
|
||||||
|
[AIMessage(content="后台结果")]
|
||||||
|
)
|
||||||
|
agent.send_agent_message = AsyncMock()
|
||||||
|
agent._save_agent_message_to_db = AsyncMock()
|
||||||
|
|
||||||
|
with patch.object(memory_manager, "save_agent_messages") as save_messages:
|
||||||
|
await agent._execute_agent([])
|
||||||
|
|
||||||
|
agent.send_agent_message.assert_awaited_once_with(
|
||||||
|
"后台结果", title="MoviePilot助手"
|
||||||
|
)
|
||||||
|
agent._save_agent_message_to_db.assert_not_awaited()
|
||||||
|
save_messages.assert_called_once()
|
||||||
|
self.assertEqual("后台结果", agent._streamed_output)
|
||||||
|
|
||||||
|
async def test_background_non_streaming_persists_without_sending_when_capture_only(self):
|
||||||
|
agent = MoviePilotAgent(session_id="bg-test", user_id="system")
|
||||||
|
agent.channel = None
|
||||||
|
agent.source = None
|
||||||
|
agent.reply_mode = ReplyMode.CAPTURE_ONLY
|
||||||
|
agent.persist_output_message = True
|
||||||
|
agent._tool_context = {"user_reply_sent": False}
|
||||||
|
agent._streamed_output = ""
|
||||||
|
agent.stream_handler = SimpleNamespace(
|
||||||
|
stop_streaming=AsyncMock(return_value=(False, ""))
|
||||||
|
)
|
||||||
|
agent._should_stream = lambda: False
|
||||||
|
agent._create_agent = lambda streaming=False: _FakeAgent(
|
||||||
|
[AIMessage(content="后台结果")]
|
||||||
|
)
|
||||||
|
agent.send_agent_message = AsyncMock()
|
||||||
|
agent._save_agent_message_to_db = AsyncMock()
|
||||||
|
|
||||||
|
with patch.object(memory_manager, "save_agent_messages") as save_messages:
|
||||||
|
await agent._execute_agent([])
|
||||||
|
|
||||||
|
agent.send_agent_message.assert_not_awaited()
|
||||||
|
agent._save_agent_message_to_db.assert_awaited_once_with(
|
||||||
|
"后台结果", title="MoviePilot助手"
|
||||||
|
)
|
||||||
|
save_messages.assert_called_once()
|
||||||
|
self.assertEqual("后台结果", agent._streamed_output)
|
||||||
|
|
||||||
|
async def test_heartbeat_check_jobs_uses_dispatch_reply_mode(self):
|
||||||
|
manager = AgentManager()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch.object(manager, "_build_heartbeat_prompt", return_value="HEARTBEAT"),
|
||||||
|
patch.object(manager, "process_message", new=AsyncMock()) as process_message,
|
||||||
|
):
|
||||||
|
await manager.heartbeat_check_jobs()
|
||||||
|
|
||||||
|
process_message.assert_awaited_once()
|
||||||
|
self.assertEqual(
|
||||||
|
ReplyMode.DISPATCH,
|
||||||
|
process_message.await_args.kwargs["reply_mode"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ _stub_module("qbittorrentapi", TorrentFilesList=list)
|
|||||||
_stub_module("transmission_rpc", File=object)
|
_stub_module("transmission_rpc", File=object)
|
||||||
|
|
||||||
from app.agent.tools.factory import MoviePilotToolFactory
|
from app.agent.tools.factory import MoviePilotToolFactory
|
||||||
|
from app.agent import ReplyMode
|
||||||
from app.chain.search import SearchChain
|
from app.chain.search import SearchChain
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
|
|
||||||
@@ -125,7 +126,7 @@ class SearchChainAIRecommendTest(unittest.IsolatedAsyncioTestCase):
|
|||||||
result = await chain._invoke_recommend_llm("Candidates")
|
result = await chain._invoke_recommend_llm("Candidates")
|
||||||
|
|
||||||
self.assertEqual("[0, 2]", result)
|
self.assertEqual("[0, 2]", result)
|
||||||
self.assertTrue(captured["suppress_user_reply"])
|
self.assertEqual(ReplyMode.CAPTURE_ONLY, captured["reply_mode"])
|
||||||
self.assertFalse(captured["persist_output_message"])
|
self.assertFalse(captured["persist_output_message"])
|
||||||
self.assertFalse(captured["allow_message_tools"])
|
self.assertFalse(captured["allow_message_tools"])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user