mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-07 00:16:57 +08:00
fix(agent): reply Feishu agent streams with cards
This commit is contained in:
@@ -179,6 +179,8 @@ class MoviePilotAgent:
|
|||||||
channel: str = None,
|
channel: str = None,
|
||||||
source: str = None,
|
source: str = None,
|
||||||
username: str = None,
|
username: str = None,
|
||||||
|
original_message_id: Optional[str] = None,
|
||||||
|
original_chat_id: Optional[str] = None,
|
||||||
replay_mode: ReplyMode = ReplyMode.DISPATCH,
|
replay_mode: ReplyMode = ReplyMode.DISPATCH,
|
||||||
persist_output_message: bool = True,
|
persist_output_message: bool = True,
|
||||||
allow_message_tools: bool = True,
|
allow_message_tools: bool = True,
|
||||||
@@ -189,6 +191,8 @@ class MoviePilotAgent:
|
|||||||
self.channel = channel
|
self.channel = channel
|
||||||
self.source = source
|
self.source = source
|
||||||
self.username = username
|
self.username = username
|
||||||
|
self.original_message_id = original_message_id
|
||||||
|
self.original_chat_id = original_chat_id
|
||||||
self.reply_mode = replay_mode
|
self.reply_mode = replay_mode
|
||||||
self.persist_output_message = persist_output_message
|
self.persist_output_message = persist_output_message
|
||||||
self.allow_message_tools = allow_message_tools
|
self.allow_message_tools = allow_message_tools
|
||||||
@@ -619,6 +623,8 @@ class MoviePilotAgent:
|
|||||||
source=self.source,
|
source=self.source,
|
||||||
user_id=self.user_id,
|
user_id=self.user_id,
|
||||||
username=self.username,
|
username=self.username,
|
||||||
|
original_message_id=self.original_message_id,
|
||||||
|
original_chat_id=self.original_chat_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 流式运行智能体,token 直接推送到 stream_handler
|
# 流式运行智能体,token 直接推送到 stream_handler
|
||||||
@@ -794,6 +800,8 @@ 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
|
||||||
|
original_message_id: Optional[str] = None
|
||||||
|
original_chat_id: Optional[str] = None
|
||||||
reply_mode: ReplyMode = ReplyMode.DISPATCH
|
reply_mode: ReplyMode = ReplyMode.DISPATCH
|
||||||
|
|
||||||
|
|
||||||
@@ -878,6 +886,8 @@ class AgentManager:
|
|||||||
channel: str = None,
|
channel: str = None,
|
||||||
source: str = None,
|
source: str = None,
|
||||||
username: str = None,
|
username: str = None,
|
||||||
|
original_message_id: Optional[str] = None,
|
||||||
|
original_chat_id: Optional[str] = None,
|
||||||
reply_mode: ReplyMode = ReplyMode.DISPATCH,
|
reply_mode: ReplyMode = ReplyMode.DISPATCH,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -893,6 +903,8 @@ class AgentManager:
|
|||||||
channel=channel,
|
channel=channel,
|
||||||
source=source,
|
source=source,
|
||||||
username=username,
|
username=username,
|
||||||
|
original_message_id=original_message_id,
|
||||||
|
original_chat_id=original_chat_id,
|
||||||
reply_mode=reply_mode,
|
reply_mode=reply_mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -980,6 +992,8 @@ class AgentManager:
|
|||||||
channel=task.channel,
|
channel=task.channel,
|
||||||
source=task.source,
|
source=task.source,
|
||||||
username=task.username,
|
username=task.username,
|
||||||
|
original_message_id=task.original_message_id,
|
||||||
|
original_chat_id=task.original_chat_id,
|
||||||
replay_mode=task.reply_mode,
|
replay_mode=task.reply_mode,
|
||||||
)
|
)
|
||||||
self.active_agents[session_id] = agent
|
self.active_agents[session_id] = agent
|
||||||
@@ -992,6 +1006,8 @@ 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.original_message_id = task.original_message_id
|
||||||
|
agent.original_chat_id = task.original_chat_id
|
||||||
agent.reply_mode = task.reply_mode
|
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)
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ class StreamingHandler:
|
|||||||
self._source: Optional[str] = None
|
self._source: Optional[str] = None
|
||||||
self._user_id: Optional[str] = None
|
self._user_id: Optional[str] = None
|
||||||
self._username: Optional[str] = None
|
self._username: Optional[str] = None
|
||||||
|
self._original_message_id: Optional[str] = None
|
||||||
|
self._original_chat_id: Optional[str] = None
|
||||||
self._title: str = ""
|
self._title: str = ""
|
||||||
self._allow_dispatch_without_context = False
|
self._allow_dispatch_without_context = False
|
||||||
# 非啰嗦模式下的待输出工具统计,等下一段文本到来时再统一补一句摘要
|
# 非啰嗦模式下的待输出工具统计,等下一段文本到来时再统一补一句摘要
|
||||||
@@ -147,6 +149,8 @@ class StreamingHandler:
|
|||||||
source: Optional[str] = None,
|
source: Optional[str] = None,
|
||||||
user_id: Optional[str] = None,
|
user_id: Optional[str] = None,
|
||||||
username: Optional[str] = None,
|
username: Optional[str] = None,
|
||||||
|
original_message_id: Optional[str] = None,
|
||||||
|
original_chat_id: Optional[str] = None,
|
||||||
title: str = "",
|
title: str = "",
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -163,6 +167,8 @@ class StreamingHandler:
|
|||||||
self._source = source
|
self._source = source
|
||||||
self._user_id = user_id
|
self._user_id = user_id
|
||||||
self._username = username
|
self._username = username
|
||||||
|
self._original_message_id = original_message_id
|
||||||
|
self._original_chat_id = original_chat_id
|
||||||
self._title = title
|
self._title = title
|
||||||
|
|
||||||
self._streaming_enabled = True
|
self._streaming_enabled = True
|
||||||
@@ -472,6 +478,8 @@ class StreamingHandler:
|
|||||||
mtype=NotificationType.Agent,
|
mtype=NotificationType.Agent,
|
||||||
userid=self._user_id,
|
userid=self._user_id,
|
||||||
username=self._username,
|
username=self._username,
|
||||||
|
original_message_id=self._original_message_id,
|
||||||
|
original_chat_id=self._original_chat_id,
|
||||||
title=self._title,
|
title=self._title,
|
||||||
text=current_text,
|
text=current_text,
|
||||||
),
|
),
|
||||||
@@ -515,6 +523,8 @@ class StreamingHandler:
|
|||||||
mtype=NotificationType.Agent,
|
mtype=NotificationType.Agent,
|
||||||
userid=self._user_id,
|
userid=self._user_id,
|
||||||
username=self._username,
|
username=self._username,
|
||||||
|
original_message_id=self._original_message_id,
|
||||||
|
original_chat_id=self._original_chat_id,
|
||||||
title=self._title,
|
title=self._title,
|
||||||
text=current_text,
|
text=current_text,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -85,12 +85,16 @@ class _OpenAIStreamingHandler(StreamingHandler):
|
|||||||
source: Optional[str] = None,
|
source: Optional[str] = None,
|
||||||
user_id: Optional[str] = None,
|
user_id: Optional[str] = None,
|
||||||
username: Optional[str] = None,
|
username: Optional[str] = None,
|
||||||
|
original_message_id: Optional[str] = None,
|
||||||
|
original_chat_id: Optional[str] = None,
|
||||||
title: str = "",
|
title: str = "",
|
||||||
):
|
):
|
||||||
self._channel = channel
|
self._channel = channel
|
||||||
self._source = source
|
self._source = source
|
||||||
self._user_id = user_id
|
self._user_id = user_id
|
||||||
self._username = username
|
self._username = username
|
||||||
|
self._original_message_id = original_message_id
|
||||||
|
self._original_chat_id = original_chat_id
|
||||||
self._title = title
|
self._title = title
|
||||||
self._streaming_enabled = True
|
self._streaming_enabled = True
|
||||||
self._sent_text = ""
|
self._sent_text = ""
|
||||||
|
|||||||
@@ -269,6 +269,8 @@ class MessageChain(ChainBase):
|
|||||||
source=source,
|
source=source,
|
||||||
userid=userid,
|
userid=userid,
|
||||||
username=username,
|
username=username,
|
||||||
|
original_message_id=original_message_id,
|
||||||
|
original_chat_id=original_chat_id,
|
||||||
images=images,
|
images=images,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
@@ -284,6 +286,8 @@ class MessageChain(ChainBase):
|
|||||||
source=source,
|
source=source,
|
||||||
userid=userid,
|
userid=userid,
|
||||||
username=username,
|
username=username,
|
||||||
|
original_message_id=original_message_id,
|
||||||
|
original_chat_id=original_chat_id,
|
||||||
images=images,
|
images=images,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
@@ -1055,6 +1059,8 @@ class MessageChain(ChainBase):
|
|||||||
source: str,
|
source: str,
|
||||||
userid: Union[str, int],
|
userid: Union[str, int],
|
||||||
username: str,
|
username: str,
|
||||||
|
original_message_id: Optional[Union[str, int]] = None,
|
||||||
|
original_chat_id: Optional[str] = None,
|
||||||
images: Optional[List[CommingMessage.MessageImage]] = None,
|
images: Optional[List[CommingMessage.MessageImage]] = None,
|
||||||
files: Optional[List[CommingMessage.MessageAttachment]] = None,
|
files: Optional[List[CommingMessage.MessageAttachment]] = None,
|
||||||
session_id: Optional[str] = None,
|
session_id: Optional[str] = None,
|
||||||
@@ -1168,6 +1174,8 @@ class MessageChain(ChainBase):
|
|||||||
channel=channel.value if channel else None,
|
channel=channel.value if channel else None,
|
||||||
source=source,
|
source=source,
|
||||||
username=username,
|
username=username,
|
||||||
|
original_message_id=str(original_message_id) if original_message_id else None,
|
||||||
|
original_chat_id=original_chat_id,
|
||||||
),
|
),
|
||||||
global_vars.loop,
|
global_vars.loop,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -652,10 +652,18 @@ class Feishu:
|
|||||||
userid: Optional[str] = None,
|
userid: Optional[str] = None,
|
||||||
chat_id: Optional[str] = None,
|
chat_id: Optional[str] = None,
|
||||||
receive_id_type: Optional[str] = None,
|
receive_id_type: Optional[str] = None,
|
||||||
|
original_message_id: Optional[str] = None,
|
||||||
) -> Optional[dict]:
|
) -> Optional[dict]:
|
||||||
card_id = self._create_streaming_card(title=title, text=text)
|
card_id = self._create_streaming_card(title=title, text=text)
|
||||||
if not card_id:
|
if not card_id:
|
||||||
return None
|
return None
|
||||||
|
if original_message_id:
|
||||||
|
result = self._reply_message(
|
||||||
|
message_id=original_message_id,
|
||||||
|
msg_type="interactive",
|
||||||
|
content={"type": "card", "data": {"card_id": card_id}},
|
||||||
|
)
|
||||||
|
else:
|
||||||
receive_id, resolved_receive_id_type = self._resolve_target(
|
receive_id, resolved_receive_id_type = self._resolve_target(
|
||||||
userid=userid,
|
userid=userid,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
@@ -1111,7 +1119,6 @@ class Feishu:
|
|||||||
message.mtype == NotificationType.Agent
|
message.mtype == NotificationType.Agent
|
||||||
and not message.buttons
|
and not message.buttons
|
||||||
and not message.link
|
and not message.link
|
||||||
and not original_message_id
|
|
||||||
)
|
)
|
||||||
if is_streaming_agent_text:
|
if is_streaming_agent_text:
|
||||||
try:
|
try:
|
||||||
@@ -1121,6 +1128,7 @@ class Feishu:
|
|||||||
userid=userid,
|
userid=userid,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
receive_id_type=receive_id_type,
|
receive_id_type=receive_id_type,
|
||||||
|
original_message_id=original_message_id,
|
||||||
)
|
)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error(f"飞书流式卡片发送失败:{err}")
|
logger.error(f"飞书流式卡片发送失败:{err}")
|
||||||
|
|||||||
@@ -263,6 +263,33 @@ class TestAgentToolStreaming(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
self.assertTrue(handler.has_sent_message)
|
self.assertTrue(handler.has_sent_message)
|
||||||
|
|
||||||
|
def test_flush_passes_original_message_context_to_send_direct_message(self):
|
||||||
|
handler = StreamingHandler()
|
||||||
|
handler._channel = MessageChannel.Feishu.value
|
||||||
|
handler._source = "feishu-main"
|
||||||
|
handler._user_id = "ou_user"
|
||||||
|
handler._username = "tester"
|
||||||
|
handler._original_message_id = "om_origin"
|
||||||
|
handler._original_chat_id = "oc_origin"
|
||||||
|
handler._streaming_enabled = True
|
||||||
|
handler.emit("hello")
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"app.agent.callback.run_in_threadpool", new_callable=AsyncMock
|
||||||
|
) as run_in_threadpool_mock:
|
||||||
|
run_in_threadpool_mock.return_value = MessageResponse(
|
||||||
|
message_id="om_stream",
|
||||||
|
chat_id="oc_origin",
|
||||||
|
source="feishu-main",
|
||||||
|
success=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
asyncio.run(handler._flush())
|
||||||
|
|
||||||
|
notification = run_in_threadpool_mock.await_args.args[1]
|
||||||
|
self.assertEqual(notification.original_message_id, "om_origin")
|
||||||
|
self.assertEqual(notification.original_chat_id, "oc_origin")
|
||||||
|
|
||||||
def test_verbose_background_tool_call_does_not_post_message(self):
|
def test_verbose_background_tool_call_does_not_post_message(self):
|
||||||
async def _run():
|
async def _run():
|
||||||
tool = DummyTool(session_id="session-1", user_id="10001")
|
tool = DummyTool(session_id="session-1", user_id="10001")
|
||||||
|
|||||||
@@ -301,6 +301,30 @@ class TestFeishu(unittest.TestCase):
|
|||||||
self.assertEqual(message_request.request_body.msg_type, "interactive")
|
self.assertEqual(message_request.request_body.msg_type, "interactive")
|
||||||
self.assertEqual(json.loads(message_request.request_body.content)["data"]["card_id"], "card_stream")
|
self.assertEqual(json.loads(message_request.request_body.content)["data"]["card_id"], "card_stream")
|
||||||
|
|
||||||
|
def test_send_notification_replies_with_streaming_card_for_agent_text(self):
|
||||||
|
client = self._build_client()
|
||||||
|
client._api_client, message_api = self._build_message_api(
|
||||||
|
reply_response=self._success_response(message_id="om_reply", chat_id="oc_stream"),
|
||||||
|
card_create_response=self._card_create_success_response("card_stream"),
|
||||||
|
)
|
||||||
|
|
||||||
|
result = client.send_notification(
|
||||||
|
Notification(
|
||||||
|
mtype=NotificationType.Agent,
|
||||||
|
title="MoviePilot助手",
|
||||||
|
text="第一帧内容",
|
||||||
|
),
|
||||||
|
userid="ou_user_stream",
|
||||||
|
original_message_id="om_origin",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(result["success"])
|
||||||
|
message_api.create.assert_not_called()
|
||||||
|
reply_request = message_api.reply.call_args.args[0]
|
||||||
|
self.assertEqual(reply_request.message_id, "om_origin")
|
||||||
|
self.assertEqual(reply_request.request_body.msg_type, "interactive")
|
||||||
|
self.assertEqual(json.loads(reply_request.request_body.content)["data"]["card_id"], "card_stream")
|
||||||
|
|
||||||
def test_edit_message_uses_cardkit_content_for_streaming_card(self):
|
def test_edit_message_uses_cardkit_content_for_streaming_card(self):
|
||||||
client = self._build_client()
|
client = self._build_client()
|
||||||
client._api_client, message_api = self._build_message_api(
|
client._api_client, message_api = self._build_message_api(
|
||||||
|
|||||||
Reference in New Issue
Block a user