Compare commits
2 Commits
12860a0c83
...
7c7b7455f6
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c7b7455f6 | |||
| 731a6d631c |
@@ -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(
|
||||
|
||||
@@ -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
@@ -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():
|
||||
|
||||
@@ -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(多模态)"""
|
||||
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user