diff --git a/app/services/agent/tools/alarm_action.py b/app/services/agent/tools/alarm_action.py deleted file mode 100644 index 6e2148d..0000000 --- a/app/services/agent/tools/alarm_action.py +++ /dev/null @@ -1,125 +0,0 @@ -""" -告警操作工具:确认接单、忽略、完成、误报 -""" - -import json -from langchain_core.tools import tool -from langchain_core.runnables import RunnableConfig - -from app.utils.logger import logger - - -def _get_order_id_for_alarm(alarm_id: str) -> str: - """从 alarm_event_ext 中获取关联的工单ID""" - from app.models import get_session, AlarmEventExt - db = get_session() - try: - ext = db.query(AlarmEventExt).filter( - AlarmEventExt.alarm_id == alarm_id, - AlarmEventExt.ext_type == "WORK_ORDER", - ).first() - if ext and ext.ext_data: - return ext.ext_data.get("order_id", "") - return "" - except Exception as e: - logger.error(f"查询工单ID失败: alarm={alarm_id}, error={e}") - return "" - finally: - db.close() - - -async def _update_wechat_card(alarm_id: str, user_id: str, action: str): - """更新企微卡片状态""" - try: - from app.services.wechat_service import get_wechat_service - wechat = get_wechat_service() - response_code = wechat.get_response_code(alarm_id) - if not response_code: - return - - if action == "confirm": - await wechat.update_alarm_card_step2( - response_code=response_code, user_ids=[user_id], - alarm_id=alarm_id, operator_name=user_id, - ) - else: - await wechat.update_alarm_card_terminal( - response_code=response_code, user_ids=[user_id], - alarm_id=alarm_id, action=action, operator_name=user_id, - ) - except Exception as e: - logger.error(f"更新企微卡片失败: alarm={alarm_id}, error={e}") - - -@tool -async def update_alarm_status(alarm_id: str, action: str, config: RunnableConfig) -> str: - """更新告警状态:确认接单(confirm)、忽略(ignore)、处理完成(complete)、标记误报(false) - - Args: - alarm_id: 告警ID - action: 操作类型 confirm=确认接单 ignore=忽略 complete=处理完成 false=标记误报 - """ - from app.services.alarm_event_service import get_alarm_event_service - - user_id = config.get("configurable", {}).get("user_id", "") - svc = get_alarm_event_service() - - # 查告警是否存在 - detail = svc.get_alarm(alarm_id) - if not detail: - return json.dumps({"error": f"未找到告警: {alarm_id}"}, ensure_ascii=False) - - # 获取关联工单ID - order_id = _get_order_id_for_alarm(alarm_id) - - from app.services.work_order_client import get_work_order_client - wo_client = get_work_order_client() - - # 操作映射 - action_map = { - "confirm": ("CONFIRMED", "HANDLING", "企微Agent确认接单"), - "ignore": ("FALSE", "IGNORED", "企微Agent忽略"), - "complete": ("CLOSED", "DONE", "企微Agent已处理"), - "false": ("FALSE", "IGNORED", "企微Agent标记误报"), - } - - if action not in action_map: - return json.dumps({"error": f"未知操作: {action}"}, ensure_ascii=False) - - alarm_status, handle_status, remark = action_map[action] - - # IoT 工单操作 - if order_id and wo_client.enabled: - iot_success = False - if action == "confirm": - iot_success = await wo_client.confirm_order(order_id) - elif action in ("ignore", "false"): - iot_success = await wo_client.false_alarm(order_id) - elif action == "complete": - iot_success = await wo_client.submit_order(order_id, result=f"已处理 by {user_id}") - if not iot_success: - remark += "(IoT降级)" - - # 本地状态更新(不管 IoT 成功与否) - svc.handle_alarm( - alarm_id=alarm_id, - alarm_status=alarm_status, - handle_status=handle_status, - handler=user_id, - remark=remark, - ) - - # 更新企微卡片 - card_action = action if action != "confirm" else "confirm" - await _update_wechat_card(alarm_id, user_id, card_action) - - action_labels = { - "confirm": "已确认接单", - "ignore": "已忽略", - "complete": "已处理完成", - "false": "已标记误报", - } - return json.dumps( - {"success": True, "message": f"{action_labels[action]}: {alarm_id}"}, - ensure_ascii=False, - ) diff --git a/app/services/agent/tools/alarm_query.py b/app/services/agent/tools/alarm_query.py deleted file mode 100644 index 7ec7330..0000000 --- a/app/services/agent/tools/alarm_query.py +++ /dev/null @@ -1,196 +0,0 @@ -""" -告警查询工具:统计、列表、详情 -""" - -import json -from datetime import timedelta -from typing import Optional -from langchain_core.tools import tool -from langchain_core.runnables import RunnableConfig - -from app.utils.logger import logger -from app.utils.timezone import beijing_now - - -# 告警类型/级别/状态 中文映射 -ALARM_TYPE_NAMES = { - "leave_post": "人员离岗", "intrusion": "周界入侵", - "illegal_parking": "车辆违停", "vehicle_congestion": "车辆拥堵", -} -ALARM_LEVEL_NAMES = {0: "紧急", 1: "重要", 2: "普通", 3: "轻微"} -ALARM_STATUS_NAMES = { - "NEW": "待处理", "CONFIRMED": "处理中", - "FALSE": "误报", "CLOSED": "已关闭", -} - - -def _parse_time_range(time_range: str): - """解析时间范围,返回 (start_time, label)""" - now = beijing_now() - if time_range == "week": - start = now - timedelta(days=now.weekday()) - start = start.replace(hour=0, minute=0, second=0, microsecond=0) - return start, "本周" - elif time_range == "month": - start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) - return start, "本月" - else: - start = now.replace(hour=0, minute=0, second=0, microsecond=0) - return start, "今日" - - -def _get_camera_display_name(device_id: str) -> str: - """同步获取摄像头显示名称""" - try: - import asyncio - from app.services.camera_name_service import get_camera_name_service - camera_service = get_camera_name_service() - loop = asyncio.get_event_loop() - if loop.is_running(): - import concurrent.futures - with concurrent.futures.ThreadPoolExecutor() as pool: - cam_info = pool.submit( - asyncio.run, camera_service.get_camera_info(device_id) - ).result(timeout=5) - else: - cam_info = asyncio.run(camera_service.get_camera_info(device_id)) - return camera_service.format_display_name(device_id, cam_info) - except Exception: - return device_id - - -@tool -def query_alarm_stats(time_range: str = "today", alarm_type: str = "all") -> str: - """查询告警统计数据(总数、按类型分布、按状态分布) - - Args: - time_range: 时间范围 today=今日 week=本周 month=本月 - alarm_type: 告警类型筛选 leave_post/intrusion/illegal_parking/vehicle_congestion/all - """ - from app.services.alarm_event_service import get_alarm_event_service - svc = get_alarm_event_service() - - start, range_label = _parse_time_range(time_range) - now = beijing_now() - - alarm_type_filter = None if alarm_type == "all" else alarm_type - - alarms, total = svc.get_alarms( - alarm_type=alarm_type_filter, - start_time=start, - end_time=now, - page=1, - page_size=10000, - ) - - type_count = {} - status_count = {"NEW": 0, "CONFIRMED": 0, "FALSE": 0, "CLOSED": 0} - for a in alarms: - type_count[a.alarm_type] = type_count.get(a.alarm_type, 0) + 1 - if a.alarm_status in status_count: - status_count[a.alarm_status] += 1 - - result = { - "range": range_label, - "total": total, - "by_type": {ALARM_TYPE_NAMES.get(t, t): c for t, c in type_count.items()}, - "by_status": {ALARM_STATUS_NAMES.get(s, s): c for s, c in status_count.items()}, - } - return json.dumps(result, ensure_ascii=False) - - -@tool -def list_alarms( - time_range: str = "today", - alarm_type: str = "all", - alarm_status: str = "", - limit: int = 10, -) -> str: - """查询告警列表,返回最近的告警记录(含ID、类型、摄像头、状态、时间) - - Args: - time_range: 时间范围 today/week/month - alarm_type: 告警类型筛选 leave_post/intrusion/illegal_parking/vehicle_congestion/all - alarm_status: 告警状态筛选 NEW=待处理 CONFIRMED=处理中 FALSE=误报 CLOSED=已关闭 - limit: 返回条数,默认10,最多20 - """ - from app.services.alarm_event_service import get_alarm_event_service - svc = get_alarm_event_service() - - start, range_label = _parse_time_range(time_range) - now = beijing_now() - - alarm_type_filter = None if alarm_type == "all" else alarm_type - status_filter = alarm_status if alarm_status else None - limit = min(limit, 20) - - alarms, total = svc.get_alarms( - alarm_type=alarm_type_filter, - alarm_status=status_filter, - start_time=start, - end_time=now, - page=1, - page_size=limit, - ) - - items = [] - for a in alarms: - cam_name = _get_camera_display_name(a.device_id) - event_time = "" - if a.event_time: - try: - event_time = a.event_time.strftime("%m-%d %H:%M") - except Exception: - event_time = str(a.event_time)[:16] - items.append({ - "alarm_id": a.alarm_id, - "type": ALARM_TYPE_NAMES.get(a.alarm_type, a.alarm_type), - "camera": cam_name, - "status": ALARM_STATUS_NAMES.get(a.alarm_status, a.alarm_status), - "level": ALARM_LEVEL_NAMES.get(a.alarm_level, "普通"), - "time": event_time, - }) - - result = {"range": range_label, "total": total, "items": items} - return json.dumps(result, ensure_ascii=False) - - -@tool -def get_alarm_detail(alarm_id: str, config: RunnableConfig) -> str: - """查询单条告警的详细信息(含扩展信息和AI分析结果) - - Args: - alarm_id: 告警ID(如 edge_xxx 或 ALM_xxx) - """ - from app.services.alarm_event_service import get_alarm_event_service - svc = get_alarm_event_service() - - detail = svc.get_alarm(alarm_id) - if not detail: - return json.dumps({"error": f"未找到告警: {alarm_id}"}, ensure_ascii=False) - - snapshot_url = detail.get("snapshot_url", "") - - result = { - "alarm_id": detail.get("alarm_id"), - "alarm_type": ALARM_TYPE_NAMES.get(detail.get("alarm_type", ""), detail.get("alarm_type", "")), - "device_id": detail.get("device_id"), - "alarm_status": ALARM_STATUS_NAMES.get(detail.get("alarm_status", ""), detail.get("alarm_status", "")), - "alarm_level": ALARM_LEVEL_NAMES.get(detail.get("alarm_level"), "普通"), - "event_time": str(detail.get("event_time", ""))[:19], - "handle_status": detail.get("handle_status"), - "handler": detail.get("handler"), - "has_snapshot": bool(snapshot_url), - "snapshot_url": snapshot_url, - } - - # 摄像头名称 - result["camera_name"] = _get_camera_display_name(detail.get("device_id", "")) - - # LLM 分析 - analyses = detail.get("llm_analyses", []) - if analyses: - latest = analyses[-1] - result["ai_analysis"] = latest.get("summary", "") - - return json.dumps(result, ensure_ascii=False) diff --git a/app/services/agent/tools/order_tools.py b/app/services/agent/tools/order_tools.py deleted file mode 100644 index efd551e..0000000 --- a/app/services/agent/tools/order_tools.py +++ /dev/null @@ -1,149 +0,0 @@ -""" -工单工具:查询我的工单、提交处理结果 -""" - -import json -from typing import List, Optional -from langchain_core.tools import tool -from langchain_core.runnables import RunnableConfig - -from app.utils.logger import logger -from .alarm_query import ALARM_TYPE_NAMES, _get_camera_display_name -from .alarm_action import _get_order_id_for_alarm - - -@tool -def list_my_orders(config: RunnableConfig, status: str = "HANDLING") -> str: - """查询我的待处理工单列表 - - Args: - status: 工单状态筛选 HANDLING=处理中 ALL=全部 - """ - from app.services.alarm_event_service import get_alarm_event_service - svc = get_alarm_event_service() - - user_id = config.get("configurable", {}).get("user_id", "") - - alarms, total = svc.get_alarms( - alarm_status="CONFIRMED", - page=1, - page_size=20, - ) - - my_alarms = [a for a in alarms if a.handler == user_id] - - if not my_alarms: - return json.dumps({"total": 0, "items": [], "message": "当前没有待处理的工单"}, ensure_ascii=False) - - items = [] - for a in my_alarms: - cam_name = _get_camera_display_name(a.device_id) - event_time = "" - if a.event_time: - try: - event_time = a.event_time.strftime("%m-%d %H:%M") - except Exception: - event_time = str(a.event_time)[:16] - items.append({ - "alarm_id": a.alarm_id, - "type": ALARM_TYPE_NAMES.get(a.alarm_type, a.alarm_type), - "camera": cam_name, - "time": event_time, - }) - - return json.dumps({"total": len(my_alarms), "items": items}, ensure_ascii=False) - - -@tool -async def submit_order_result( - alarm_id: str, - result_text: str, - config: RunnableConfig, - image_urls: Optional[List[str]] = None, -) -> str: - """提交工单处理结果(文字描述+处理后照片URL) - - Args: - alarm_id: 关联的告警ID - result_text: 处理结果描述 - image_urls: 处理后照片URL列表(COS永久URL) - """ - from app.services.alarm_event_service import get_alarm_event_service - from app.services.wechat_service import get_wechat_service - - user_id = config.get("configurable", {}).get("user_id", "") - svc = get_alarm_event_service() - wechat = get_wechat_service() - - if image_urls is None: - image_urls = [] - - # 检查告警是否存在 - detail = svc.get_alarm(alarm_id) - if not detail: - return json.dumps({"error": f"未找到告警: {alarm_id}"}, ensure_ascii=False) - - # 合并 session 中暂存的图片 - from app.services.session_manager import get_session_manager - session = get_session_manager().get(user_id) - if session.pending_images: - image_urls = session.pending_images + image_urls - session.pending_images = [] - - # 获取关联工单ID - order_id = _get_order_id_for_alarm(alarm_id) - - from app.services.work_order_client import get_work_order_client - wo_client = get_work_order_client() - - remark = f"企微Agent结单: {result_text}" - if image_urls: - remark += f" (附{len(image_urls)}张图片)" - - if order_id and wo_client.enabled: - if not await wo_client.submit_order( - order_id, - result=f"{result_text} by {user_id}", - result_img_urls=image_urls or None, - ): - remark += "(IoT降级)" - - svc.handle_alarm(alarm_id=alarm_id, alarm_status="CLOSED", - handle_status="DONE", handler=user_id, remark=remark) - - # 持久化处理结果图片到 alarm_event_ext - if image_urls: - try: - from app.models import get_session as get_db_session, AlarmEventExt - db = get_db_session() - try: - ext = AlarmEventExt( - alarm_id=alarm_id, - ext_type="HANDLER_RESULT", - ext_data={"result_text": result_text, "image_urls": image_urls, "handler": user_id}, - ) - db.add(ext) - db.commit() - except Exception as e: - db.rollback() - logger.error(f"持久化处理结果图片失败: {e}") - finally: - db.close() - except Exception as e: - logger.error(f"保存处理结果失败: {e}") - - # 更新卡片到终态 - response_code = wechat.get_response_code(alarm_id) - if response_code: - await wechat.update_alarm_card_terminal( - response_code=response_code, user_ids=[user_id], - alarm_id=alarm_id, action="complete", operator_name=user_id, - ) - - result = { - "success": True, - "message": f"工单已提交: {alarm_id}", - "result": result_text, - "images_count": len(image_urls), - } - return json.dumps(result, ensure_ascii=False)