功能:交互Agent支持图片驱动的手动工单创建流程

This commit is contained in:
2026-03-31 18:07:41 +08:00
parent 95c58e8b3f
commit 4e03938334
3 changed files with 230 additions and 12 deletions

View File

@@ -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:

View File

@@ -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多模态"""

View File

@@ -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: