From fbdd340a8ec5e66a20d20edb4417f5c626c5b1f6 Mon Sep 17 00:00:00 2001 From: 16337 <1633794139@qq.com> Date: Tue, 24 Mar 2026 11:50:54 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9AAgent=E7=A6=81?= =?UTF-8?q?=E7=94=A8markdown=E8=AF=AD=E6=B3=95=20+=20=E5=91=8A=E8=AD=A6?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E8=87=AA=E5=8A=A8=E5=8F=91=E9=80=81=E6=88=AA?= =?UTF-8?q?=E5=9B=BE=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 系统提示词禁止markdown图片语法,企微不支持渲染 - 告警详情查询时截图自动通过企微图片消息发送 - 工具返回中用has_snapshot替代snapshot_url,避免模型输出链接 --- app/services/agent_dispatcher.py | 53 +++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/app/services/agent_dispatcher.py b/app/services/agent_dispatcher.py index c4ee788..f819e65 100644 --- a/app/services/agent_dispatcher.py +++ b/app/services/agent_dispatcher.py @@ -36,7 +36,9 @@ SYSTEM_PROMPT = """你是VSP安防AI助手,通过企业微信协助安保人 - 用户发图片时,如果有待处理工单,询问是否作为处理结果上传 - 用户说"处理完了"并附带描述,自动提交结单 - 回复简洁,适合手机阅读 -- 重要信息用【】标注""" +- 重要信息用【】标注 +- 禁止使用markdown语法(如![](url)、**加粗**、# 标题),企微聊天不支持 +- 告警截图会自动发送图片消息,文字回复中不要包含图片链接""" IMAGE_ANALYZE_PROMPT = """你是物业安防图片分析员。分析这张图片,判断是否存在安全隐患或需要上报的情况。 @@ -231,6 +233,8 @@ class AgentDispatcher: self._client: Optional[AsyncOpenAI] = None self._vlm_client: Optional[AsyncOpenAI] = None self._enabled = False + # 临时存储:工具执行期间需要发送的图片 URL + self._pending_images: Dict[str, List[str]] = {} # user_id -> [image_urls] def init(self, config): """初始化Agent""" @@ -261,12 +265,20 @@ class AgentDispatcher: session = get_session_manager().get(user_id) session.add_history("user", content) + # 清空待发图片队列 + self._pending_images[user_id] = [] + try: reply = await self._chat_with_tools(session, user_id) except Exception as e: logger.error(f"Agent FC 对话失败: {e}", exc_info=True) reply = "抱歉,AI助手暂时无法响应,请稍后重试。" + # 发送工具执行期间收集的图片(如告警截图) + pending = self._pending_images.pop(user_id, []) + if pending: + await self._send_images_to_user(user_id, pending) + session.add_history("assistant", reply) return reply @@ -371,7 +383,7 @@ class AgentDispatcher: elif name == "list_alarms": return await self._tool_list_alarms(args) elif name == "get_alarm_detail": - return await self._tool_get_alarm_detail(args) + return await self._tool_get_alarm_detail(args, user_id) elif name == "update_alarm_status": return await self._tool_update_alarm_status(args, user_id) elif name == "list_my_orders": @@ -475,7 +487,7 @@ class AgentDispatcher: return {"range": range_label, "total": total, "items": items} - async def _tool_get_alarm_detail(self, args: dict) -> dict: + async def _tool_get_alarm_detail(self, args: dict, user_id: str) -> dict: """告警详情""" from app.services.alarm_event_service import get_alarm_event_service svc = get_alarm_event_service() @@ -485,6 +497,13 @@ class AgentDispatcher: if not detail: return {"error": f"未找到告警: {alarm_id}"} + # 截图:加入待发图片队列,由 handle_message 统一发送 + snapshot_url = detail.get("snapshot_url", "") + if snapshot_url: + if user_id not in self._pending_images: + self._pending_images[user_id] = [] + self._pending_images[user_id].append(snapshot_url) + result = { "alarm_id": detail.get("alarm_id"), "alarm_type": ALARM_TYPE_NAMES.get(detail.get("alarm_type", ""), detail.get("alarm_type", "")), @@ -494,7 +513,7 @@ class AgentDispatcher: "event_time": str(detail.get("event_time", ""))[:19], "handle_status": detail.get("handle_status"), "handler": detail.get("handler"), - "snapshot_url": detail.get("snapshot_url", ""), + "has_snapshot": bool(snapshot_url), } # 摄像头名称 @@ -746,6 +765,32 @@ class AgentDispatcher: # ==================== 辅助方法 ==================== + async def _send_images_to_user(self, user_id: str, image_urls: List[str]): + """通过企微发送图片消息给用户""" + from app.services.wechat_service import get_wechat_service + wechat = get_wechat_service() + if not wechat.enabled: + return + + for url in image_urls: + try: + media_id = await wechat.upload_media_from_url(url) + if media_id: + # 发送图片消息(复用企微发送图片的能力) + access_token = await wechat._get_access_token() + import httpx + msg = { + "touser": user_id, + "msgtype": "image", + "agentid": wechat.agent_id_int, + "image": {"media_id": media_id}, + } + api_url = f"https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={access_token}" + async with httpx.AsyncClient(timeout=10) as client: + await client.post(api_url, json=msg) + except Exception as e: + logger.error(f"发送告警截图失败: user={user_id}, error={e}") + @staticmethod def _parse_time_range(time_range: str): """解析时间范围,返回 (start_time, label)"""