Compare commits

...

2 Commits

Author SHA1 Message Date
7c7b7455f6 优化:平均响应时间只统计近7天且排除>6h异常值
避免历史旧数据和长时间未处理告警拉高平均响应时间
2026-03-24 11:17:23 +08:00
731a6d631c 重构:Agent升级为Qwen3.5-Plus Function Calling架构
- config: AgentConfig新增model(qwen3.5-plus)和timeout(30s)字段
- agent_dispatcher: 完全重写,替换意图标记为原生FC,实现7个工具
  (query_alarm_stats/list_alarms/get_alarm_detail/update_alarm_status/
   list_my_orders/submit_order_result/query_camera)
- session_manager: 移除waiting_location/waiting_confirm状态,新增pending_images
- wechat_service: 禁用H5跳转,card_action移除URL,step2提示改为对话框操作
- wechat_callback: 图片消息智能路由(有待处理工单→暂存,无→VLM分析)
2026-03-24 11:17:12 +08:00
6 changed files with 670 additions and 454 deletions

View File

@@ -67,11 +67,13 @@ class WeChatConfig:
@dataclass
class AgentConfig:
"""交互Agent配置(统一使用 VLM 多模态模型)"""
"""交互Agent配置"""
vlm_api_key: str = ""
vlm_base_url: str = "https://dashscope.aliyuncs.com/compatible-mode/v1"
vlm_model: str = "qwen3-vl-flash-2026-01-22"
vlm_model: str = "qwen3-vl-flash-2026-01-22" # 视觉模型(图片分析保留)
model: str = "qwen3.5-plus" # 文本模型Function Calling
vlm_timeout: int = 15
timeout: int = 30 # FC 多轮超时
enabled: bool = False
@@ -197,7 +199,9 @@ def load_settings() -> Settings:
vlm_api_key=os.getenv("DASHSCOPE_API_KEY", ""),
vlm_base_url=os.getenv("AGENT_VLM_BASE_URL", "https://dashscope.aliyuncs.com/compatible-mode/v1"),
vlm_model=os.getenv("AGENT_VLM_MODEL", os.getenv("VLM_MODEL", "qwen3-vl-flash-2026-01-22")),
model=os.getenv("AGENT_MODEL", "qwen3.5-plus"),
vlm_timeout=int(os.getenv("AGENT_VLM_TIMEOUT", "15")),
timeout=int(os.getenv("AGENT_TIMEOUT", "30")),
enabled=os.getenv("AGENT_ENABLED", "false").lower() == "true",
),
work_order=WorkOrderConfig(

View File

@@ -97,14 +97,13 @@ async def _process_agent_message(user_id: str, content: str):
async def _process_agent_image(user_id: str, media_id: str):
"""异步处理图片消息:先回复"正在分析",再调 VLM 分析"""
"""异步处理图片消息:下载+持久化+智能路由(待处理工单关联 or VLM分析"""
try:
from app.services.agent_dispatcher import get_agent_dispatcher
from app.services.wechat_service import get_wechat_service
wechat = get_wechat_service()
# 先回复提示,避免用户等太久
await wechat.send_text_message(user_id, "正在分析图片,请稍候...")
await wechat.send_text_message(user_id, "收到图片,正在处理...")
dispatcher = get_agent_dispatcher()
reply = await dispatcher.handle_image(user_id, media_id)

File diff suppressed because it is too large Load Diff

View File

@@ -522,8 +522,9 @@ class AlarmEventService:
AlarmEvent.handle_status.in_(["DONE", "IGNORED"])
).count()
# 平均响应时间(从 event_time 到 handled_at只算已处理且时间合理的
# 平均响应时间(只算近7天已处理的排除>6h的异常值
from sqlalchemy.sql.expression import literal_column
stats_since = today_start - timedelta(days=7)
avg_response = db.query(
func.avg(
func.timestampdiff(
@@ -535,6 +536,8 @@ class AlarmEventService:
).filter(
AlarmEvent.handled_at.isnot(None),
AlarmEvent.handled_at > AlarmEvent.event_time,
AlarmEvent.event_time >= stats_since,
func.timestampdiff(literal_column("MINUTE"), AlarmEvent.event_time, AlarmEvent.handled_at) <= 360,
).scalar()
# 按 alarm_status 计数
@@ -676,9 +679,15 @@ class AlarmEventService:
handled_count = db.query(AlarmEvent).filter(AlarmEvent.handle_status.in_(["DONE", "IGNORED"])).count()
from sqlalchemy.sql.expression import literal_column
stats_since = today_start - timedelta(days=7)
avg_response = db.query(
func.avg(func.timestampdiff(literal_column("MINUTE"), AlarmEvent.event_time, AlarmEvent.handled_at))
).filter(AlarmEvent.handled_at.isnot(None), AlarmEvent.handled_at > AlarmEvent.event_time).scalar()
).filter(
AlarmEvent.handled_at.isnot(None),
AlarmEvent.handled_at > AlarmEvent.event_time,
AlarmEvent.event_time >= stats_since,
func.timestampdiff(literal_column("MINUTE"), AlarmEvent.event_time, AlarmEvent.handled_at) <= 360,
).scalar()
by_type = {}
for r in db.query(AlarmEvent.alarm_type, func.count(AlarmEvent.alarm_id)).group_by(AlarmEvent.alarm_type).all():

View File

@@ -18,12 +18,13 @@ class UserSession:
def __init__(self, user_id: str):
self.user_id = user_id
self.state = "idle" # idle / waiting_location / waiting_confirm / waiting_close_photo
self.state = "idle" # idle / waiting_close_photo
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.history: List[Dict] = [] # 对话历史 [{"role": "user/assistant", "content": ...}]
self.updated_at = time.time()
@@ -41,6 +42,7 @@ class UserSession:
self.pending_alarm_type = ""
self.pending_order_id = ""
self.pending_alarm_id = ""
self.pending_images = []
def add_history(self, role: str, content):
"""添加对话记录content 可以是 str 或 list多模态"""

View File

@@ -69,9 +69,7 @@ class WeChatService:
return int(self._agent_id) if self._agent_id else 0
def _alarm_detail_url(self, alarm_id: str) -> str:
"""构造 H5 工单详情页 URL"""
if self._service_base_url:
return f"{self._service_base_url}/work-order?alarmId={alarm_id}"
"""H5 工单详情页已禁用,所有交互在企微对话框完成"""
return ""
async def _get_access_token(self) -> str:
@@ -276,7 +274,6 @@ class WeChatService:
],
"card_action": {
"type": 1,
"url": self._alarm_detail_url(alarm_id) or "https://work.weixin.qq.com",
},
"button_list": [
{
@@ -346,10 +343,9 @@ class WeChatService:
"main_title": {
"title": f"已接单 - {operator_name}" if operator_name else "已接单",
},
"sub_title_text": "点击卡片进入详情页提交处理结果",
"sub_title_text": "在对话框中回复处理结果,或点击按钮快速操作",
"card_action": {
"type": 1,
"url": self._alarm_detail_url(alarm_id) or "https://work.weixin.qq.com",
},
"button_list": [
{