mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-05-30 07:26:48 +00:00
调整 Agent 心跳任务报告方式
This commit is contained in:
@@ -798,6 +798,12 @@ class MoviePilotAgent:
|
||||
always_include_tools = (
|
||||
MoviePilotToolFactory.get_tool_selector_always_include_names(tools)
|
||||
)
|
||||
if self.is_heartbeat_session:
|
||||
available_tool_names = {
|
||||
tool.name for tool in tools if getattr(tool, "name", None)
|
||||
}
|
||||
if "send_message" in available_tool_names:
|
||||
always_include_tools.append("send_message")
|
||||
|
||||
# 中间件
|
||||
middlewares = [
|
||||
@@ -1192,6 +1198,8 @@ class _MessageTask:
|
||||
original_chat_id: Optional[str] = None
|
||||
processing_status: Optional[dict] = None
|
||||
reply_mode: ReplyMode = ReplyMode.DISPATCH
|
||||
persist_output_message: bool = True
|
||||
allow_message_tools: bool = True
|
||||
|
||||
|
||||
class AgentManager:
|
||||
@@ -1335,6 +1343,8 @@ class AgentManager:
|
||||
original_message_id: Optional[str] = None,
|
||||
original_chat_id: Optional[str] = None,
|
||||
reply_mode: ReplyMode = ReplyMode.DISPATCH,
|
||||
persist_output_message: bool = True,
|
||||
allow_message_tools: bool = True,
|
||||
) -> str:
|
||||
"""
|
||||
处理用户消息:将消息放入会话队列,按顺序依次处理。
|
||||
@@ -1352,6 +1362,8 @@ class AgentManager:
|
||||
original_message_id=original_message_id,
|
||||
original_chat_id=original_chat_id,
|
||||
reply_mode=reply_mode,
|
||||
persist_output_message=persist_output_message,
|
||||
allow_message_tools=allow_message_tools,
|
||||
)
|
||||
self._record_session_activity(session_id, user_id)
|
||||
|
||||
@@ -1461,6 +1473,8 @@ class AgentManager:
|
||||
original_message_id=task.original_message_id,
|
||||
original_chat_id=task.original_chat_id,
|
||||
replay_mode=task.reply_mode,
|
||||
persist_output_message=task.persist_output_message,
|
||||
allow_message_tools=task.allow_message_tools,
|
||||
)
|
||||
self.active_agents[session_id] = agent
|
||||
else:
|
||||
@@ -1475,6 +1489,8 @@ class AgentManager:
|
||||
agent.original_message_id = task.original_message_id
|
||||
agent.original_chat_id = task.original_chat_id
|
||||
agent.reply_mode = task.reply_mode
|
||||
agent.persist_output_message = task.persist_output_message
|
||||
agent.allow_message_tools = task.allow_message_tools
|
||||
|
||||
return await agent.process(task.message, images=task.images, files=task.files)
|
||||
|
||||
@@ -1612,7 +1628,9 @@ class AgentManager:
|
||||
channel=None,
|
||||
source=None,
|
||||
username=settings.SUPERUSER,
|
||||
reply_mode=ReplyMode.DISPATCH,
|
||||
reply_mode=ReplyMode.CAPTURE_ONLY,
|
||||
persist_output_message=False,
|
||||
allow_message_tools=True,
|
||||
)
|
||||
|
||||
# 等待消息队列处理完成
|
||||
|
||||
@@ -14,7 +14,11 @@ task_types:
|
||||
- "For 'recurring' jobs, check 'last_run' to determine if it's time to run again."
|
||||
- "For 'once' jobs with status 'pending', execute them now."
|
||||
- "After executing each job, update its status, 'last_run' time, and execution log in the JOB.md file."
|
||||
- "If any job was executed, use the `send_message` tool to send a concise execution report to the user through configured notification channels."
|
||||
empty_result: "If no jobs were executed, output nothing."
|
||||
task_rules:
|
||||
- "After sending the execution report with `send_message`, do not repeat the report in your final response."
|
||||
- "Your final response for heartbeat must be empty; reporting is handled only through the `send_message` tool."
|
||||
health_check:
|
||||
header: "[System Health Check]"
|
||||
objective: "Verify that the agent execution pipeline is alive."
|
||||
|
||||
@@ -252,7 +252,7 @@ class AgentBackgroundOutputTest(unittest.IsolatedAsyncioTestCase):
|
||||
save_messages.assert_called_once()
|
||||
self.assertEqual("后台结果", agent._streamed_output)
|
||||
|
||||
async def test_heartbeat_check_jobs_uses_dispatch_reply_mode(self):
|
||||
async def test_heartbeat_check_jobs_captures_final_reply_and_keeps_message_tools(self):
|
||||
manager = AgentManager()
|
||||
|
||||
with (
|
||||
@@ -271,10 +271,10 @@ class AgentBackgroundOutputTest(unittest.IsolatedAsyncioTestCase):
|
||||
await manager.heartbeat_check_jobs()
|
||||
|
||||
process_message.assert_awaited_once()
|
||||
self.assertEqual(
|
||||
ReplyMode.DISPATCH,
|
||||
process_message.await_args.kwargs["reply_mode"],
|
||||
)
|
||||
kwargs = process_message.await_args.kwargs
|
||||
self.assertEqual(ReplyMode.CAPTURE_ONLY, kwargs["reply_mode"])
|
||||
self.assertFalse(kwargs["persist_output_message"])
|
||||
self.assertTrue(kwargs["allow_message_tools"])
|
||||
|
||||
async def test_heartbeat_check_jobs_skips_when_no_active_jobs(self):
|
||||
manager = AgentManager()
|
||||
@@ -320,6 +320,43 @@ class AgentBackgroundOutputTest(unittest.IsolatedAsyncioTestCase):
|
||||
created["middleware"],
|
||||
)
|
||||
|
||||
async def test_heartbeat_session_always_keeps_send_message_tool(self):
|
||||
agent = MoviePilotAgent(
|
||||
session_id=f"{HEARTBEAT_SESSION_PREFIX}test__",
|
||||
user_id="system",
|
||||
)
|
||||
send_message_tool = SimpleNamespace(name="send_message")
|
||||
agent._initialize_tools = lambda: [send_message_tool]
|
||||
|
||||
captured = {}
|
||||
|
||||
def fake_tool_selector(*args, **kwargs):
|
||||
captured["always_include"] = kwargs["always_include"]
|
||||
return "selector"
|
||||
|
||||
with (
|
||||
patch.object(settings, "LLM_MAX_TOOLS", 1),
|
||||
patch.object(agent, "_initialize_llm", new=AsyncMock(return_value=object())),
|
||||
patch("app.agent.prompt_manager.get_agent_prompt", return_value="PROMPT"),
|
||||
patch(
|
||||
"app.agent.MoviePilotToolFactory.get_tool_selector_always_include_names",
|
||||
return_value=[],
|
||||
),
|
||||
patch("app.agent.SkillsMiddleware", side_effect=lambda *args, **kwargs: "skills"),
|
||||
patch("app.agent.JobsMiddleware", side_effect=lambda *args, **kwargs: "jobs"),
|
||||
patch("app.agent.RuntimeConfigMiddleware", side_effect=lambda *args, **kwargs: "runtime"),
|
||||
patch("app.agent.MemoryMiddleware", side_effect=lambda *args, **kwargs: "memory"),
|
||||
patch("app.agent.SummarizationMiddleware", side_effect=lambda *args, **kwargs: "summary"),
|
||||
patch("app.agent.PatchToolCallsMiddleware", side_effect=lambda *args, **kwargs: "patch"),
|
||||
patch("app.agent.UsageMiddleware", side_effect=lambda *args, **kwargs: "usage"),
|
||||
patch("app.agent.ToolSelectorMiddleware", side_effect=fake_tool_selector),
|
||||
patch("app.agent.InMemorySaver", return_value="checkpointer"),
|
||||
patch("app.agent.create_agent", side_effect=lambda **kwargs: kwargs),
|
||||
):
|
||||
await agent._create_agent(streaming=False)
|
||||
|
||||
self.assertIn("send_message", captured["always_include"])
|
||||
|
||||
async def test_create_agent_keeps_activity_log_for_normal_session(self):
|
||||
agent = MoviePilotAgent(session_id="normal-session", user_id="system")
|
||||
agent._initialize_tools = lambda: []
|
||||
|
||||
@@ -172,6 +172,8 @@ class TestAgentPromptStyle(unittest.TestCase):
|
||||
self.assertIn("[System Heartbeat]", message)
|
||||
self.assertIn("List all jobs with status 'pending' or 'in_progress'.", message)
|
||||
self.assertIn("Do NOT include greetings, explanations, or conversational text.", message)
|
||||
self.assertIn("use the `send_message` tool", message)
|
||||
self.assertIn("Your final response for heartbeat must be empty", message)
|
||||
self.assertIn("If no jobs were executed, output nothing.", message)
|
||||
|
||||
def test_render_system_task_message_renders_template_context(self):
|
||||
|
||||
Reference in New Issue
Block a user