program/ai_analyzer.py

394 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
AI分析模块 - 支持多模型切换
"""
import os
import json
import urllib.request
import urllib.error
import ssl
import logging
from datetime import datetime
from flask import current_app
from tag_names import get_tag_description
# 设置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def call_ai_api(prompt, model_key=None, system_prompt=None):
"""
调用AI API进行分析
:param prompt: 用户提示词
:param model_key: 模型key (kimi/deepseek/qwen/zhipu)
:param system_prompt: 系统提示词
:return: {'status': str, 'content': str, 'model': str}
"""
if model_key is None:
model_key = current_app.config.get('DEFAULT_AI_MODEL', 'kimi')
models = current_app.config.get('AI_MODELS', {})
if model_key not in models:
return {'status': 'error', 'content': f'未找到AI模型: {model_key}', 'model': model_key}
model_config = models[model_key]
api_key = model_config.get('api_key', '')
if not api_key:
return {
'status': 'error',
'content': f'未配置AI模型 {model_config["name"]} 的API Key请在 config.py 中配置',
'model': model_key
}
# 构建消息
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
messages.append({"role": "user", "content": prompt})
# 构建请求
payload = {
"model": model_config['model'],
"messages": messages
}
data = json.dumps(payload).encode('utf-8')
req = urllib.request.Request(
model_config['api_url'],
data=data,
headers={
'Content-Type': 'application/json',
'Authorization': f'Bearer {api_key}'
}
)
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
import time
try:
logger.info(f"AI API请求开始 - 模型: {model_config['model']}")
# 从配置中获取超时时间默认300秒5分钟
api_timeout = current_app.config.get('AI_API_TIMEOUT', 300)
logger.info(f"AI API超时设置: {api_timeout}")
start_time = time.time()
with urllib.request.urlopen(req, context=ctx, timeout=api_timeout) as response:
elapsed = time.time() - start_time
logger.info(f"AI API响应成功耗时: {elapsed:.1f}")
raw_response = response.read().decode('utf-8')
result = json.loads(raw_response)
# 解析响应 - 兼容OpenAI格式
content = ''
if 'choices' in result and len(result['choices']) > 0:
content = result['choices'][0].get('message', {}).get('content', '')
return {
'status': 'success',
'content': content,
'model': model_key,
'model_name': model_config['name'],
'usage': result.get('usage', {})
}
except urllib.error.HTTPError as e:
error_body = e.read().decode('utf-8')
logger.error(f"HTTP错误: {e.code}, 响应: {error_body}")
return {
'status': 'error',
'content': f'API请求失败: HTTP {e.code}\n{error_body}',
'model': model_key
}
except urllib.error.URLError as e:
logger.error(f"URL错误: {str(e.reason)}")
return {
'status': 'error',
'content': f'网络连接失败: {str(e.reason)}',
'model': model_key
}
except Exception as e:
logger.error(f"异常: {type(e).__name__}: {str(e)}")
return {
'status': 'error',
'content': f'AI分析异常: {type(e).__name__}: {str(e)}',
'model': model_key
}
def analyze_fault(data, model_key=None):
"""
故障诊断分析
:param data: 采集数据列表
:param model_key: AI模型
:return: AI分析结果包含data_summary字段方便前端展示
"""
# 构建数据摘要(控制数量防止上下文超限)
data_summary = build_data_summary(data)
system_prompt = """你是一个专业的综采工作面设备故障诊断专家,具有丰富的煤矿机电设备维修经验。
请根据提供的实时监测数据,进行以下分析:
1. 识别存在的故障和异常
2. 分析故障原因
3. 提供维修建议和措施
4. 评估故障严重程度(正常/轻微/中等/严重/紧急)
请保持分析专业、简洁、实用。"""
prompt = f"""请对以下综采工作面实时监测数据进行故障诊断分析:
{data_summary}
请按以下格式输出分析结果:
## 故障诊断报告
### 1. 异常识别
(列出发现的异常项)
### 2. 故障原因分析
(分析可能的原因)
### 3. 维修建议
(提供具体的维修措施)
### 4. 严重程度评估
(整体评估)"""
result = call_ai_api(prompt, model_key, system_prompt)
# 附加数据摘要让前端可以展示传入AI的数据
result['data_summary'] = data_summary
result['data_count'] = len(data) if data else 0
return result
def analyze_trend(data, model_key=None):
"""
趋势分析
:param data: 历史数据列表
:param model_key: AI模型
:return: AI分析结果包含data_summary字段方便前端展示
"""
# 构建趋势摘要(控制数量防止上下文超限)
data_summary = build_trend_summary(data)
system_prompt = """你是一个专业的煤矿生产数据分析专家,擅长分析设备运行趋势,预测潜在问题。
请根据提供的历史数据,进行趋势分析,预测可能的设备状态变化。"""
prompt = f"""请对以下综采工作面历史数据进行趋势分析:
{data_summary}
请按以下格式输出分析结果:
## 趋势分析报告
### 1. 数据趋势概述
(描述整体趋势)
### 2. 关键参数变化
(分析重要参数的变化规律)
### 3. 预测与预警
(基于趋势的预测和预警)
### 4. 优化建议
(生产和设备维护建议)"""
result = call_ai_api(prompt, model_key, system_prompt)
# 附加数据摘要让前端可以展示传入AI的数据
result['data_summary'] = data_summary
result['data_count'] = len(data) if data else 0
return result
def generate_report(data, unit_name, model_key=None):
"""
生成运维报告
:param data: 数据列表
:param unit_name: 单位名称
:param model_key: AI模型
:return: AI生成的报告包含data_summary字段
"""
# 构建数据摘要(控制数量防止上下文超限)
data_summary = build_data_summary(data)
system_prompt = """你是一个专业的煤矿运维报告撰写专家,能够根据设备运行数据生成专业的运维分析报告。
报告应该专业、规范,符合煤矿行业标准。"""
prompt = f"""请为{unit_name}生成一份设备运维分析报告:
采集时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
监测数据:
{data_summary}
请按以下格式生成报告:
# {unit_name}设备运维分析报告
## 一、概述
(简要说明本次分析的范围和时间)
## 二、设备运行状态
(描述各主要设备的运行状态)
## 三、关键参数分析
(分析电流、电压、温度、压力等关键参数)
## 四、存在问题
(列出发现的问题)
## 五、改进建议
(提供针对性的改进措施)
## 六、总结
(整体评价和下一步工作建议)"""
result = call_ai_api(prompt, model_key, system_prompt)
result['data_summary'] = data_summary
result['data_count'] = len(data) if data else 0
return result
def build_data_summary(data, max_total=80, max_per_category=15):
"""
构建数据摘要(使用中文名称)
:param data: 数据列表
:param max_total: 最大总条目数,防止上下文超限
:param max_per_category: 每类最大条目数
:return: 数据摘要字符串
"""
if not data:
return "无数据"
lines = []
total = len(data)
lines.append(f"共采集 {total} 个监测点(以下展示关键数据):")
# 按设备分类
categories = {}
for item in data:
tag = item.get('tag_short_name', item.get('tag_name', ''))
value = item.get('tag_value', item.get('value'))
value_bool = item.get('tag_value_bool', item.get('value_bool'))
unit = item.get('tag_unit', '')
# 优先使用已有的中文名称,否则从映射表查找
chinese_name = item.get('chinese_name')
if not chinese_name:
chinese_name = get_tag_description(tag)
# 分类
if 'Shearer' in tag or 'shearer' in tag:
cat = '采煤机'
elif any(x in tag for x in ['AFC', 'LOA', 'CRU']):
cat = '三机'
elif 'Emulsion' in tag or 'Spray' in tag:
cat = '泵站'
elif 'Loop' in tag:
cat = '开关'
else:
cat = '其他'
if cat not in categories:
categories[cat] = []
display_value = value if value is not None else value_bool
# 中文名称与英文标签都显示方便AI理解
categories[cat].append(f" - {chinese_name}({tag}): {display_value} {unit}")
# 控制总条目数,优先展示关键设备
cat_priority = ['采煤机', '三机', '泵站', '开关', '其他']
remaining = max_total
for cat in cat_priority:
if cat not in categories:
continue
items = categories[cat]
# 每类限制条目数,同时考虑剩余配额
limit = min(max_per_category, remaining)
if limit <= 0:
continue
lines.append(f"\n### {cat}")
lines.extend(items[:limit])
if len(items) > limit:
lines.append(f" ... 还有 {len(items)-limit} 条(省略)")
remaining -= limit
# 处理未在优先级中的类别
for cat, items in categories.items():
if cat not in cat_priority and remaining > 0:
limit = min(max_per_category, remaining)
lines.append(f"\n### {cat}")
lines.extend(items[:limit])
if len(items) > limit:
lines.append(f" ... 还有 {len(items)-limit} 条(省略)")
remaining -= limit
return '\n'.join(lines)
def build_trend_summary(data, max_tags=30):
"""
构建趋势数据摘要(使用中文名称)
:param data: 历史数据列表
:param max_tags: 最多展示的标签数量,防止上下文超限
:return: 趋势摘要字符串
"""
if not data:
return "无数据"
# 按标签分组
tag_data = {}
for item in data:
tag = item.get('tag_short_name', item.get('tag_name', ''))
time = item.get('snapshot_time', item.get('sample_time', ''))
value = item.get('tag_value', item.get('value', 0))
if tag not in tag_data:
tag_data[tag] = []
tag_data[tag].append({'time': str(time), 'value': value})
lines = []
count = 0
for tag, points in tag_data.items():
if count >= max_tags:
break
if len(points) >= 2:
values = [p['value'] for p in points if p['value'] is not None]
if values:
min_val = min(values)
max_val = max(values)
avg_val = sum(values) / len(values)
# 使用中文名称
chinese_name = get_tag_description(tag)
lines.append(f"- {chinese_name}({tag}): 最小={min_val}, 最大={max_val}, 平均={avg_val:.2f}, 采样点数={len(points)}")
count += 1
total_tags = len(tag_data)
result = '\n'.join(lines) if lines else "数据不足以进行趋势分析"
if total_tags > max_tags and count >= max_tags:
result += f"\n... 还有 {total_tags - max_tags} 个标签(省略)"
return result
def get_available_models():
"""获取可用的AI模型列表"""
models = current_app.config.get('AI_MODELS', {})
result = []
for key, config in models.items():
has_key = bool(config.get('api_key', ''))
result.append({
'key': key,
'name': config['name'],
'model': config['model'],
'available': has_key
})
return result