feat: 交互Agent + VLM优化 + 企微演示模式
- 新增交互Agent调度器(意图识别 + 工单/查询/报表/闲聊4个Handler) - 新增工单服务、Excel报表生成器、企微消息加解密模块 - VLM提示词优化(角色设定、≤25字描述、布尔值优先输出) - VLM降级策略(入侵默认放行、离岗默认拦截) - 企微演示模式(WECHAT_TEST_UIDS兜底 + SERVICE_BASE_URL修复) - 新增Agent回调路由和测试接口 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,32 +16,22 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# 算法类型 → VLM Prompt 模板
|
||||
VLM_PROMPTS = {
|
||||
"leave_post": """分析这张岗位监控截图。
|
||||
摄像头位置:{camera_name},监控区域:{roi_name}。
|
||||
边缘AI检测到该区域无人在岗,请你复核:该区域内是否确实没有工作人员在岗?
|
||||
"leave_post": """你是安防监控AI复核员。判断{roi_name}岗位区域内是否有人在岗。
|
||||
confirmed=true表示确实无人在岗(告警成立),false表示有人(误报)。
|
||||
description用≤25字描述画面。
|
||||
仅输出JSON:{{"confirmed":true,"description":"..."}}""",
|
||||
|
||||
输出严格的JSON格式(不要输出其他内容):
|
||||
{{"confirmed": true, "description": "一句话描述当前画面"}}
|
||||
|
||||
说明:confirmed=true 表示确实无人在岗(告警成立),confirmed=false 表示有人在岗(误报)。""",
|
||||
|
||||
"intrusion": """分析这张周界监控截图。
|
||||
摄像头位置:{camera_name},监控区域:{roi_name}。
|
||||
边缘AI检测到该区域有人员入侵,请你复核:该区域内是否确实有人员出现?
|
||||
|
||||
输出严格的JSON格式(不要输出其他内容):
|
||||
{{"confirmed": true, "description": "一句话描述当前画面"}}
|
||||
|
||||
说明:confirmed=true 表示确实有人入侵(告警成立),confirmed=false 表示无人(误报)。""",
|
||||
"intrusion": """你是安防监控AI复核员。判断{roi_name}周界区域内是否有人员入侵。
|
||||
confirmed=true表示确实有人入侵(告警成立),false表示无人(误报)。
|
||||
description用≤25字描述画面。
|
||||
仅输出JSON:{{"confirmed":true,"description":"..."}}""",
|
||||
}
|
||||
|
||||
# 通用降级 prompt(未知算法类型时使用)
|
||||
DEFAULT_PROMPT = """分析这张监控截图。
|
||||
摄像头位置:{camera_name},监控区域:{roi_name}。
|
||||
边缘AI触发了 {alarm_type} 告警,请判断告警是否属实。
|
||||
|
||||
输出严格的JSON格式(不要输出其他内容):
|
||||
{{"confirmed": true, "description": "一句话描述当前画面"}}"""
|
||||
DEFAULT_PROMPT = """你是安防监控AI复核员。边缘AI触发了{alarm_type}告警,判断告警是否属实。
|
||||
confirmed=true表示告警成立,false表示误报。
|
||||
description用≤25字描述画面。
|
||||
仅输出JSON:{{"confirmed":true,"description":"..."}}"""
|
||||
|
||||
|
||||
class VLMService:
|
||||
@@ -74,6 +64,16 @@ class VLMService:
|
||||
def enabled(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
@staticmethod
|
||||
def _fallback_result(alarm_type: str, camera_name: str, reason: str) -> Dict:
|
||||
"""降级结果:入侵默认放行(宁可多报),离岗默认拦截(避免VLM不可用时误推)"""
|
||||
confirmed = alarm_type != "leave_post"
|
||||
return {
|
||||
"confirmed": confirmed,
|
||||
"description": f"{camera_name or '未知位置'} 触发 {alarm_type} 告警({reason})",
|
||||
"skipped": True,
|
||||
}
|
||||
|
||||
async def verify_alarm(
|
||||
self,
|
||||
snapshot_url: str,
|
||||
@@ -95,19 +95,11 @@ class VLMService:
|
||||
- skipped=True 表示 VLM 未调用(降级处理)
|
||||
"""
|
||||
if not self._enabled or not self._client:
|
||||
return {
|
||||
"confirmed": True,
|
||||
"description": f"{camera_name or '未知位置'} 触发 {alarm_type} 告警",
|
||||
"skipped": True,
|
||||
}
|
||||
return self._fallback_result(alarm_type, camera_name, "VLM未启用")
|
||||
|
||||
if not snapshot_url:
|
||||
logger.warning("告警无截图URL,跳过 VLM 复核")
|
||||
return {
|
||||
"confirmed": True,
|
||||
"description": f"{camera_name or '未知位置'} 触发 {alarm_type} 告警(无截图)",
|
||||
"skipped": True,
|
||||
}
|
||||
return self._fallback_result(alarm_type, camera_name, "无截图")
|
||||
|
||||
# 选择 prompt 模板
|
||||
template = VLM_PROMPTS.get(alarm_type, DEFAULT_PROMPT)
|
||||
@@ -144,7 +136,7 @@ class VLMService:
|
||||
result = json.loads(content)
|
||||
logger.info(
|
||||
f"VLM 复核完成: confirmed={result.get('confirmed')}, "
|
||||
f"desc={result.get('description', '')[:50]}"
|
||||
f"desc={result.get('description', '')[:30]}"
|
||||
)
|
||||
return {
|
||||
"confirmed": result.get("confirmed", True),
|
||||
@@ -154,25 +146,13 @@ class VLMService:
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
logger.warning(f"VLM 复核超时 ({self._timeout}s),降级处理")
|
||||
return {
|
||||
"confirmed": True,
|
||||
"description": f"{camera_name or '未知位置'} 触发 {alarm_type} 告警(VLM超时)",
|
||||
"skipped": True,
|
||||
}
|
||||
return self._fallback_result(alarm_type, camera_name, "VLM超时")
|
||||
except json.JSONDecodeError as e:
|
||||
logger.warning(f"VLM 返回内容解析失败: {e}, 原始内容: {content[:200]}")
|
||||
return {
|
||||
"confirmed": True,
|
||||
"description": content[:100] if content else "VLM返回异常",
|
||||
"skipped": True,
|
||||
}
|
||||
return self._fallback_result(alarm_type, camera_name, "解析失败")
|
||||
except Exception as e:
|
||||
logger.error(f"VLM 调用异常: {e}")
|
||||
return {
|
||||
"confirmed": True,
|
||||
"description": f"{camera_name or '未知位置'} 触发 {alarm_type} 告警(VLM异常)",
|
||||
"skipped": True,
|
||||
}
|
||||
return self._fallback_result(alarm_type, camera_name, "VLM异常")
|
||||
|
||||
|
||||
# 全局单例
|
||||
|
||||
Reference in New Issue
Block a user