功能:交互Agent支持图片驱动的手动工单创建流程
This commit is contained in:
@@ -55,9 +55,12 @@ class AgentDispatcher:
|
||||
return "AI助手未启用,请联系管理员配置。"
|
||||
|
||||
self._pending_images[user_id] = []
|
||||
session = get_session_manager().get(user_id)
|
||||
|
||||
try:
|
||||
reply = await self._langgraph_chat(user_id, content)
|
||||
reply = await self._handle_manual_order_message(user_id, session, content.strip())
|
||||
if reply is None:
|
||||
reply = await self._langgraph_chat(user_id, content)
|
||||
except Exception as e:
|
||||
logger.error(f"Agent对话失败: {e}", exc_info=True)
|
||||
reply = "抱歉,AI助手暂时无法响应,请稍后重试。"
|
||||
@@ -104,17 +107,30 @@ class AgentDispatcher:
|
||||
session.pending_alarm_id = handling_alarm_id
|
||||
return reply
|
||||
|
||||
# 4. VLM 分析
|
||||
from app.services.agent.prompts import IMAGE_ANALYZE_PROMPT
|
||||
analysis = await self._analyze_image(presign_url, IMAGE_ANALYZE_PROMPT)
|
||||
if session.state in {
|
||||
"waiting_manual_order_area",
|
||||
"waiting_manual_order_remark",
|
||||
"waiting_manual_order_confirm",
|
||||
} and session.pending_manual_order_images:
|
||||
session.pending_manual_order_images.append(permanent_url)
|
||||
if session.state == "waiting_manual_order_area":
|
||||
return (
|
||||
f"已追加图片,当前共 {len(session.pending_manual_order_images)} 张。\n"
|
||||
f"请选择区域:\n{self._format_area_options(session.pending_manual_order_area_options)}\n"
|
||||
"请回复区域编号。"
|
||||
)
|
||||
if session.state == "waiting_manual_order_remark":
|
||||
return (
|
||||
f"已追加图片,当前共 {len(session.pending_manual_order_images)} 张。\n"
|
||||
"请继续补充备注信息,可不填,回复“无”即可。"
|
||||
)
|
||||
return (
|
||||
f"已追加图片,当前共 {len(session.pending_manual_order_images)} 张。\n"
|
||||
"请回复“确认”创建工单,或回复“取消”放弃。"
|
||||
)
|
||||
|
||||
if analysis.get("has_anomaly"):
|
||||
desc = analysis.get("description", "异常情况")
|
||||
reply = f"检测到异常:{desc}\n\n如需上报,请描述具体位置和情况。"
|
||||
else:
|
||||
reply = "未检测到明显安全隐患。如有疑问请描述情况。"
|
||||
|
||||
return reply
|
||||
del presign_url
|
||||
return await self._start_manual_order_flow(session, permanent_url)
|
||||
|
||||
# ==================== LangGraph 对话 ====================
|
||||
|
||||
@@ -161,6 +177,194 @@ class AgentDispatcher:
|
||||
|
||||
# ==================== 共用方法 ====================
|
||||
|
||||
async def _start_manual_order_flow(self, session, image_url: str) -> str:
|
||||
"""启动手动工单创建流程。"""
|
||||
session.pending_manual_order_images.append(image_url)
|
||||
session.pending_manual_order_remark = ""
|
||||
session.pending_manual_order_area_id = ""
|
||||
session.pending_manual_order_area_name = ""
|
||||
session.pending_manual_order_area_options = self._list_notify_areas()
|
||||
session.state = "waiting_manual_order_area"
|
||||
|
||||
if not session.pending_manual_order_area_options:
|
||||
session.reset()
|
||||
return "已收到图片,但当前没有可选区域,请联系管理员先配置通知区域。"
|
||||
|
||||
return (
|
||||
"已收到图片,准备创建手动工单。\n"
|
||||
f"请选择区域:\n{self._format_area_options(session.pending_manual_order_area_options)}\n"
|
||||
"请回复区域编号。"
|
||||
)
|
||||
|
||||
async def _handle_manual_order_message(self, user_id: str, session, content: str) -> Optional[str]:
|
||||
"""处理手动工单创建状态机。"""
|
||||
if session.state == "waiting_manual_order_area":
|
||||
area = self._match_area_option(content, session.pending_manual_order_area_options)
|
||||
if not area:
|
||||
return (
|
||||
"未识别到有效区域,请回复区域编号。\n"
|
||||
f"{self._format_area_options(session.pending_manual_order_area_options)}"
|
||||
)
|
||||
|
||||
assignees = self._get_area_assignees(area["area_id"])
|
||||
if not assignees:
|
||||
return f"区域【{area['area_name']}】当前未绑定责任人,请重新选择其他区域。"
|
||||
|
||||
session.pending_manual_order_area_id = area["area_id"]
|
||||
session.pending_manual_order_area_name = area["area_name"]
|
||||
session.state = "waiting_manual_order_remark"
|
||||
return f"已选择区域:【{area['area_name']}】。\n请补充备注信息,可不填,回复“无”即可。"
|
||||
|
||||
if session.state == "waiting_manual_order_remark":
|
||||
session.pending_manual_order_remark = "" if content in {"无", "没有", "none", "None"} else content
|
||||
session.state = "waiting_manual_order_confirm"
|
||||
assignees = self._get_area_assignees(session.pending_manual_order_area_id)
|
||||
assignee_names = "、".join(person["person_name"] for person in assignees)
|
||||
remark = session.pending_manual_order_remark or "无"
|
||||
return (
|
||||
"请确认是否创建工单:\n"
|
||||
f"区域:【{session.pending_manual_order_area_name}】\n"
|
||||
f"备注:{remark}\n"
|
||||
f"图片:{len(session.pending_manual_order_images)} 张\n"
|
||||
f"派发对象:{assignee_names}\n"
|
||||
"回复“确认”创建,回复“取消”放弃。"
|
||||
)
|
||||
|
||||
if session.state == "waiting_manual_order_confirm":
|
||||
if content in {"取消", "算了", "不用了"}:
|
||||
session.reset()
|
||||
return "已取消本次手动工单创建。"
|
||||
|
||||
if content not in {"确认", "是", "创建", "提交"}:
|
||||
return "请回复“确认”创建工单,或回复“取消”放弃。"
|
||||
|
||||
result = await self._create_manual_order(user_id, session)
|
||||
session.reset()
|
||||
return result
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _list_notify_areas() -> List[Dict[str, str]]:
|
||||
from app.models import NotifyArea, get_session
|
||||
|
||||
db = get_session()
|
||||
try:
|
||||
areas = (
|
||||
db.query(NotifyArea)
|
||||
.filter(NotifyArea.enabled == 1)
|
||||
.order_by(NotifyArea.area_name.asc())
|
||||
.all()
|
||||
)
|
||||
return [
|
||||
{"index": str(idx), "area_id": area.area_id, "area_name": area.area_name}
|
||||
for idx, area in enumerate(areas, start=1)
|
||||
]
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@staticmethod
|
||||
def _format_area_options(areas: List[Dict[str, str]]) -> str:
|
||||
return "\n".join(f"{item['index']}. {item['area_name']}" for item in areas)
|
||||
|
||||
@staticmethod
|
||||
def _match_area_option(content: str, areas: List[Dict[str, str]]) -> Optional[Dict[str, str]]:
|
||||
normalized = content.strip()
|
||||
for item in areas:
|
||||
if normalized == item["index"] or normalized == item["area_name"]:
|
||||
return item
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _get_area_assignees(area_id: str) -> List[Dict[str, str]]:
|
||||
from app.models import AreaPersonBinding, get_session
|
||||
|
||||
db = get_session()
|
||||
try:
|
||||
persons = (
|
||||
db.query(AreaPersonBinding)
|
||||
.filter(
|
||||
AreaPersonBinding.area_id == area_id,
|
||||
AreaPersonBinding.enabled == 1,
|
||||
)
|
||||
.order_by(AreaPersonBinding.notify_level.asc(), AreaPersonBinding.id.asc())
|
||||
.all()
|
||||
)
|
||||
return [
|
||||
{
|
||||
"person_name": person.person_name,
|
||||
"wechat_uid": person.wechat_uid,
|
||||
"role": person.role,
|
||||
}
|
||||
for person in persons
|
||||
]
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
async def _create_manual_order(self, user_id: str, session) -> str:
|
||||
"""创建手动工单并通知区域绑定人员。"""
|
||||
assignees = self._get_area_assignees(session.pending_manual_order_area_id)
|
||||
if not assignees:
|
||||
return f"区域【{session.pending_manual_order_area_name}】当前未绑定责任人,工单未创建。"
|
||||
|
||||
from app.services.wechat_service import get_wechat_service
|
||||
from app.services.work_order_service import get_work_order_service
|
||||
|
||||
area_name = session.pending_manual_order_area_name
|
||||
remark = session.pending_manual_order_remark or "无"
|
||||
title = f"【手动上报】{area_name}异常情况"
|
||||
description_lines = [
|
||||
"来源:企微手动上报",
|
||||
f"上报人:{user_id}",
|
||||
f"区域:{area_name}",
|
||||
f"备注:{remark}",
|
||||
f"图片数量:{len(session.pending_manual_order_images)}",
|
||||
]
|
||||
if session.pending_manual_order_images:
|
||||
description_lines.append("图片链接:")
|
||||
description_lines.extend(session.pending_manual_order_images)
|
||||
|
||||
attachments = [
|
||||
{"type": "image", "url": image_url}
|
||||
for image_url in session.pending_manual_order_images
|
||||
]
|
||||
|
||||
primary_assignee = assignees[0]
|
||||
work_order = get_work_order_service().create_work_order(
|
||||
title=title,
|
||||
description="\n".join(description_lines),
|
||||
priority="medium",
|
||||
assignee_uid=primary_assignee["wechat_uid"],
|
||||
assignee_name=primary_assignee["person_name"],
|
||||
attachments=attachments,
|
||||
department=area_name,
|
||||
)
|
||||
if not work_order:
|
||||
return "工单创建失败,请稍后重试。"
|
||||
|
||||
notify_message = (
|
||||
f"收到新的手动工单:\n"
|
||||
f"工单号:{work_order.order_no}\n"
|
||||
f"区域:{area_name}\n"
|
||||
f"备注:{remark}"
|
||||
)
|
||||
wechat = get_wechat_service()
|
||||
for assignee in assignees:
|
||||
try:
|
||||
await wechat.send_text_message(assignee["wechat_uid"], notify_message)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"手动工单通知失败: order={work_order.order_no}, "
|
||||
f"user={assignee['wechat_uid']}, error={e}"
|
||||
)
|
||||
|
||||
return (
|
||||
f"工单已创建。\n"
|
||||
f"工单号:{work_order.order_no}\n"
|
||||
f"区域:【{area_name}】\n"
|
||||
f"已派发给:{'、'.join(person['person_name'] for person in assignees)}"
|
||||
)
|
||||
|
||||
async def _analyze_image(self, image_url: str, prompt: str) -> Dict:
|
||||
"""VLM 分析图片内容"""
|
||||
try:
|
||||
|
||||
@@ -18,13 +18,18 @@ class UserSession:
|
||||
|
||||
def __init__(self, user_id: str):
|
||||
self.user_id = user_id
|
||||
self.state = "idle" # idle / waiting_close_photo
|
||||
self.state = "idle" # idle / waiting_close_photo / waiting_manual_order_area / waiting_manual_order_remark / waiting_manual_order_confirm
|
||||
self.pending_image_url = "" # 暂存的图片 COS key
|
||||
self.pending_analysis = "" # VLM 分析结果描述
|
||||
self.pending_alarm_type = "" # VLM 识别的告警类型
|
||||
self.pending_order_id = "" # 待结单的工单 ID
|
||||
self.pending_alarm_id = "" # 关联的告警 ID
|
||||
self.pending_images: List[str] = [] # 暂存用户上传的图片 URL(用于工单结单)
|
||||
self.pending_manual_order_images: List[str] = []
|
||||
self.pending_manual_order_area_id = ""
|
||||
self.pending_manual_order_area_name = ""
|
||||
self.pending_manual_order_area_options: List[Dict] = []
|
||||
self.pending_manual_order_remark = ""
|
||||
self.history: List[Dict] = [] # 对话历史 [{"role": "user/assistant", "content": ...}]
|
||||
self.updated_at = time.time()
|
||||
|
||||
@@ -43,6 +48,11 @@ class UserSession:
|
||||
self.pending_order_id = ""
|
||||
self.pending_alarm_id = ""
|
||||
self.pending_images = []
|
||||
self.pending_manual_order_images = []
|
||||
self.pending_manual_order_area_id = ""
|
||||
self.pending_manual_order_area_name = ""
|
||||
self.pending_manual_order_area_options = []
|
||||
self.pending_manual_order_remark = ""
|
||||
|
||||
def add_history(self, role: str, content):
|
||||
"""添加对话记录,content 可以是 str 或 list(多模态)"""
|
||||
|
||||
@@ -30,6 +30,8 @@ class WorkOrderService:
|
||||
assignee_uid: str = "",
|
||||
assignee_name: str = "",
|
||||
alarm_id: str = "",
|
||||
attachments: Optional[List[Dict]] = None,
|
||||
department: str = "",
|
||||
) -> Optional[WorkOrder]:
|
||||
"""创建工单"""
|
||||
db = get_session()
|
||||
@@ -42,6 +44,8 @@ class WorkOrderService:
|
||||
priority=WorkOrderPriority(priority) if priority in valid_priorities else WorkOrderPriority.MEDIUM,
|
||||
assignee_id=assignee_uid,
|
||||
assignee_name=assignee_name,
|
||||
department=department,
|
||||
attachments=attachments or [],
|
||||
status=WorkOrderStatus.CREATED,
|
||||
)
|
||||
if alarm_id:
|
||||
|
||||
Reference in New Issue
Block a user