mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-15 02:54:20 +08:00
fix(agent): 工具回调消息回填原会话ID,避免群聊@机器人时按钮等交互消息发到私聊 (#6267)
This commit is contained in:
@@ -722,6 +722,9 @@ class MoviePilotAgent:
|
||||
"reply_mode": None,
|
||||
"should_dispatch_reply": should_dispatch_reply,
|
||||
"is_admin": await self._is_system_admin_context(),
|
||||
# 工具回调消息需要发回原会话(群聊@机器人时按钮选择等卡片不能发到私聊),
|
||||
# 后台任务无渠道上下文时置空,交由通知链广播。
|
||||
"original_chat_id": None if self.is_background else self.original_chat_id,
|
||||
}
|
||||
|
||||
def _should_stream(self) -> bool:
|
||||
|
||||
@@ -658,6 +658,16 @@ class MoviePilotTool(BaseTool, metaclass=ABCMeta):
|
||||
"original_chat_id": None,
|
||||
}
|
||||
)
|
||||
elif not notification.original_chat_id:
|
||||
# 工具回调消息默认回填当前会话的原会话 ID,
|
||||
# 保证群聊 @ 机器人时按钮选择、消息发送等交互消息回复到原群,而不是私聊窗口。
|
||||
original_chat_id = str(
|
||||
self._agent_context.get("original_chat_id") or ""
|
||||
).strip() or None
|
||||
if original_chat_id:
|
||||
notification = notification.model_copy(
|
||||
update={"original_chat_id": original_chat_id}
|
||||
)
|
||||
|
||||
await ToolChain().async_post_message(notification)
|
||||
|
||||
|
||||
127
tests/test_agent_tool_message_target.py
Normal file
127
tests/test_agent_tool_message_target.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""Agent 工具回调消息回复目标(original_chat_id 回填)的测试。"""
|
||||
|
||||
import asyncio
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from app.agent.tools.impl.ask_user_choice import (
|
||||
AskUserChoiceTool,
|
||||
UserChoiceOptionInput,
|
||||
)
|
||||
from app.agent.tools.impl.send_message import SendMessageTool
|
||||
from app.schemas import Notification
|
||||
from app.schemas.types import MessageChannel
|
||||
|
||||
|
||||
def _run_choice_tool(agent_context: dict, channel: str, source: str) -> Notification:
|
||||
"""运行按钮选择工具并返回其发送的通知。"""
|
||||
tool = AskUserChoiceTool(session_id="session-1", user_id="ou_xxx")
|
||||
tool.set_message_attr(
|
||||
channel=channel,
|
||||
source=source,
|
||||
username="tester",
|
||||
)
|
||||
tool.set_agent_context(agent_context=agent_context)
|
||||
|
||||
with patch(
|
||||
"app.agent.tools.base.ToolChain.async_post_message",
|
||||
new=AsyncMock(),
|
||||
) as async_post_message:
|
||||
asyncio.run(
|
||||
tool.run(
|
||||
message="请选择",
|
||||
options=[UserChoiceOptionInput(label="继续", value="继续")],
|
||||
)
|
||||
)
|
||||
|
||||
assert async_post_message.await_count == 1
|
||||
return async_post_message.await_args.args[0]
|
||||
|
||||
|
||||
def test_choice_tool_backfills_original_chat_id_from_session_context():
|
||||
"""群聊场景下按钮选择通知应回填会话上下文中的 original_chat_id。"""
|
||||
notification = _run_choice_tool(
|
||||
agent_context={"original_chat_id": "oc_group_123"},
|
||||
channel=MessageChannel.Feishu.value,
|
||||
source="feishu-test",
|
||||
)
|
||||
|
||||
assert notification.original_chat_id == "oc_group_123"
|
||||
assert notification.userid == "ou_xxx"
|
||||
|
||||
|
||||
def test_choice_tool_keeps_explicit_original_chat_id():
|
||||
"""按钮选择通知已显式携带原会话 ID 时不应被上下文覆盖。"""
|
||||
notification = _run_choice_tool(
|
||||
agent_context={"original_chat_id": "oc_group_zzz"},
|
||||
channel=MessageChannel.Telegram.value,
|
||||
source="telegram-test",
|
||||
)
|
||||
|
||||
assert notification.original_chat_id == "oc_group_zzz"
|
||||
|
||||
|
||||
def test_choice_tool_no_context_does_not_backfill():
|
||||
"""会话上下文未携带原会话 ID 时,通知保持原有发送目标。"""
|
||||
notification = _run_choice_tool(
|
||||
agent_context={},
|
||||
channel=MessageChannel.Telegram.value,
|
||||
source="telegram-test",
|
||||
)
|
||||
|
||||
assert notification.original_chat_id is None
|
||||
|
||||
|
||||
def test_background_tool_clears_original_chat_id():
|
||||
"""无渠道上下文的后台任务应清空渠道定位信息交由消息链广播。"""
|
||||
tool = SendMessageTool(session_id="session-1", user_id="ou_xxx")
|
||||
tool.set_agent_context(agent_context={"original_chat_id": "oc_group_123"})
|
||||
|
||||
with patch(
|
||||
"app.agent.tools.base.ToolChain.async_post_message",
|
||||
new=AsyncMock(),
|
||||
) as async_post_message:
|
||||
asyncio.run(tool.send_tool_message("后台任务执行完成"))
|
||||
|
||||
notification = async_post_message.await_args.args[0]
|
||||
assert notification.original_chat_id is None
|
||||
assert notification.channel is None
|
||||
assert notification.userid is None
|
||||
|
||||
|
||||
def test_send_tool_message_backfills_original_chat_id():
|
||||
"""send_tool_message 工具消息同样应回填原会话 ID。"""
|
||||
tool = SendMessageTool(session_id="session-1", user_id="ou_xxx")
|
||||
tool.set_message_attr(
|
||||
channel=MessageChannel.Feishu.value,
|
||||
source="feishu-test",
|
||||
username="tester",
|
||||
)
|
||||
tool.set_agent_context(agent_context={"original_chat_id": "oc_group_123"})
|
||||
|
||||
with patch(
|
||||
"app.agent.tools.base.ToolChain.async_post_message",
|
||||
new=AsyncMock(),
|
||||
) as async_post_message:
|
||||
asyncio.run(tool.send_tool_message("正在执行操作"))
|
||||
|
||||
notification = async_post_message.await_args.args[0]
|
||||
assert notification.original_chat_id == "oc_group_123"
|
||||
|
||||
|
||||
def test_tool_context_includes_original_chat_id():
|
||||
"""工具共享上下文应携带当前会话的原会话 ID。"""
|
||||
from app.agent import MoviePilotAgent
|
||||
|
||||
agent = MoviePilotAgent(
|
||||
session_id="session-1",
|
||||
user_id="ou_xxx",
|
||||
channel=MessageChannel.Feishu.value,
|
||||
source="feishu-test",
|
||||
username="tester",
|
||||
original_chat_id="oc_group_123",
|
||||
)
|
||||
|
||||
with patch.object(MoviePilotAgent, "_is_system_admin_context", return_value=False):
|
||||
context = asyncio.run(agent._build_tool_context(should_dispatch_reply=True))
|
||||
|
||||
assert context["original_chat_id"] == "oc_group_123"
|
||||
Reference in New Issue
Block a user