Integrating the OpenAI API for in-depth comment analysis, with usability to be debugged.
Showing
4 changed files
with
375 additions
and
0 deletions
models/ai_analysis.py
0 → 100644
| 1 | +from sqlalchemy import Column, Integer, String, Float, DateTime, Text, JSON | ||
| 2 | +from sqlalchemy.ext.declarative import declarative_base | ||
| 3 | +from datetime import datetime | ||
| 4 | + | ||
| 5 | +Base = declarative_base() | ||
| 6 | + | ||
| 7 | +class AIAnalysis(Base): | ||
| 8 | + __tablename__ = 'ai_analysis' | ||
| 9 | + | ||
| 10 | + id = Column(Integer, primary_key=True) | ||
| 11 | + message_id = Column(Integer, nullable=False) | ||
| 12 | + sentiment = Column(String(10), nullable=False) | ||
| 13 | + sentiment_score = Column(Float, nullable=False) | ||
| 14 | + keywords = Column(JSON, nullable=False) | ||
| 15 | + key_points = Column(Text, nullable=False) | ||
| 16 | + influence_analysis = Column(Text, nullable=False) | ||
| 17 | + risk_level = Column(String(10), nullable=False) | ||
| 18 | + created_at = Column(DateTime, default=datetime.now) | ||
| 19 | + | ||
| 20 | + def to_dict(self): | ||
| 21 | + return { | ||
| 22 | + 'id': self.id, | ||
| 23 | + 'message_id': self.message_id, | ||
| 24 | + 'sentiment': self.sentiment, | ||
| 25 | + 'sentiment_score': f"{self.sentiment_score:.2%}", | ||
| 26 | + 'keywords': self.keywords, | ||
| 27 | + 'key_points': self.key_points, | ||
| 28 | + 'influence': self.influence_analysis, | ||
| 29 | + 'risk_level': self.risk_level, | ||
| 30 | + 'analysis_time': self.created_at.strftime('%Y-%m-%d %H:%M:%S') | ||
| 31 | + } |
utils/ai_analyzer.py
0 → 100644
| 1 | +import openai | ||
| 2 | +import json | ||
| 3 | +from typing import List, Dict | ||
| 4 | +import os | ||
| 5 | +from datetime import datetime | ||
| 6 | +from utils.logger import app_logger as logging | ||
| 7 | + | ||
| 8 | +class AIAnalyzer: | ||
| 9 | + def __init__(self): | ||
| 10 | + # 从环境变量获取API密钥 | ||
| 11 | + self.api_key = os.getenv('OPENAI_API_KEY') | ||
| 12 | + if not self.api_key: | ||
| 13 | + raise ValueError("请设置OPENAI_API_KEY环境变量") | ||
| 14 | + | ||
| 15 | + openai.api_key = self.api_key | ||
| 16 | + | ||
| 17 | + # 系统提示词,限制AI的输出格式 | ||
| 18 | + self.system_prompt = """你是一个专业的舆情分析助手。你的任务是分析每条消息的情感倾向、关键词和潜在影响。 | ||
| 19 | +请严格按照以下JSON格式返回分析结果: | ||
| 20 | +{ | ||
| 21 | + "analysis_results": [ | ||
| 22 | + { | ||
| 23 | + "message_id": "消息ID", | ||
| 24 | + "sentiment": "情感倾向 (积极/消极/中性)", | ||
| 25 | + "sentiment_score": "情感分数 (0-1)", | ||
| 26 | + "keywords": ["关键词1", "关键词2", "关键词3"], | ||
| 27 | + "key_points": "核心观点概述", | ||
| 28 | + "influence_analysis": "潜在影响分析", | ||
| 29 | + "risk_level": "风险等级 (低/中/高)", | ||
| 30 | + "timestamp": "分析时间戳" | ||
| 31 | + } | ||
| 32 | + ] | ||
| 33 | +} | ||
| 34 | +请确保每个字段都有值,并保持JSON格式的一致性。""" | ||
| 35 | + | ||
| 36 | + async def analyze_messages(self, messages: List[Dict]) -> List[Dict]: | ||
| 37 | + """分析一批消息并返回分析结果""" | ||
| 38 | + try: | ||
| 39 | + # 构建输入消息 | ||
| 40 | + formatted_messages = [] | ||
| 41 | + for msg in messages: | ||
| 42 | + formatted_messages.append(f"消息ID: {msg['id']}\n内容: {msg['content']}") | ||
| 43 | + | ||
| 44 | + messages_text = "\n---\n".join(formatted_messages) | ||
| 45 | + | ||
| 46 | + # 调用OpenAI API | ||
| 47 | + response = await openai.ChatCompletion.acreate( | ||
| 48 | + model="gpt-3.5-turbo", | ||
| 49 | + messages=[ | ||
| 50 | + {"role": "system", "content": self.system_prompt}, | ||
| 51 | + {"role": "user", "content": f"请分析以下消息:\n{messages_text}"} | ||
| 52 | + ], | ||
| 53 | + temperature=0.3, # 降低随机性 | ||
| 54 | + max_tokens=2000, | ||
| 55 | + n=1 | ||
| 56 | + ) | ||
| 57 | + | ||
| 58 | + # 解析返回结果 | ||
| 59 | + try: | ||
| 60 | + result = json.loads(response.choices[0].message.content) | ||
| 61 | + # 验证结果格式 | ||
| 62 | + if not isinstance(result, dict) or 'analysis_results' not in result: | ||
| 63 | + raise ValueError("AI返回格式不正确") | ||
| 64 | + return result['analysis_results'] | ||
| 65 | + except json.JSONDecodeError: | ||
| 66 | + logging.error("AI返回结果解析失败") | ||
| 67 | + return [] | ||
| 68 | + | ||
| 69 | + except Exception as e: | ||
| 70 | + logging.error(f"AI分析过程出错: {e}") | ||
| 71 | + return [] | ||
| 72 | + | ||
| 73 | + def format_analysis_for_display(self, analysis: Dict) -> Dict: | ||
| 74 | + """将分析结果格式化为前端显示格式""" | ||
| 75 | + return { | ||
| 76 | + 'id': analysis['message_id'], | ||
| 77 | + 'sentiment': analysis['sentiment'], | ||
| 78 | + 'sentiment_score': f"{float(analysis['sentiment_score']):.2%}", | ||
| 79 | + 'keywords': ', '.join(analysis['keywords']), | ||
| 80 | + 'key_points': analysis['key_points'], | ||
| 81 | + 'influence': analysis['influence_analysis'], | ||
| 82 | + 'risk_level': analysis['risk_level'], | ||
| 83 | + 'analysis_time': datetime.fromtimestamp( | ||
| 84 | + float(analysis['timestamp']) | ||
| 85 | + ).strftime('%Y-%m-%d %H:%M:%S') | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | +# 创建全局AI分析器实例 | ||
| 89 | +ai_analyzer = AIAnalyzer() |
| @@ -9,6 +9,11 @@ from utils.getTopicPageData import * | @@ -9,6 +9,11 @@ from utils.getTopicPageData import * | ||
| 9 | from utils.yuqingpredict import * | 9 | from utils.yuqingpredict import * |
| 10 | from utils.logger import app_logger as logging | 10 | from utils.logger import app_logger as logging |
| 11 | from utils.cache_manager import prediction_cache | 11 | from utils.cache_manager import prediction_cache |
| 12 | +from utils.ai_analyzer import ai_analyzer | ||
| 13 | +from models.ai_analysis import AIAnalysis | ||
| 14 | +from sqlalchemy.orm import Session | ||
| 15 | +from sqlalchemy import create_engine | ||
| 16 | +import asyncio | ||
| 12 | import torch | 17 | import torch |
| 13 | from BCAT_front.predict import model_manager | 18 | from BCAT_front.predict import model_manager |
| 14 | 19 | ||
| @@ -31,6 +36,11 @@ try: | @@ -31,6 +36,11 @@ try: | ||
| 31 | except Exception as e: | 36 | except Exception as e: |
| 32 | logging.error(f"模型加载失败: {e}") | 37 | logging.error(f"模型加载失败: {e}") |
| 33 | 38 | ||
| 39 | +# 数据库配置 | ||
| 40 | +DATABASE_URL = "sqlite:///ai_analysis.db" | ||
| 41 | +engine = create_engine(DATABASE_URL) | ||
| 42 | +AIAnalysis.metadata.create_all(engine) | ||
| 43 | + | ||
| 34 | def predict_sentiment(text): | 44 | def predict_sentiment(text): |
| 35 | """使用改进版模型预测单个文本的情感""" | 45 | """使用改进版模型预测单个文本的情感""" |
| 36 | try: | 46 | try: |
| @@ -294,3 +304,99 @@ def articleChar(id): | @@ -294,3 +304,99 @@ def articleChar(id): | ||
| 294 | except Exception as e: | 304 | except Exception as e: |
| 295 | logging.error(f"获取文章详情时发生错误: {e}") | 305 | logging.error(f"获取文章详情时发生错误: {e}") |
| 296 | return render_template('error.html', error_message="加载文章详情失败") | 306 | return render_template('error.html', error_message="加载文章详情失败") |
| 307 | + | ||
| 308 | +@pb.route('/api/analyze_messages', methods=['POST']) | ||
| 309 | +async def analyze_messages(): | ||
| 310 | + try: | ||
| 311 | + # 获取最近50条消息 | ||
| 312 | + messages = getRecentMessages(50) # 需要实现这个函数 | ||
| 313 | + | ||
| 314 | + # 调用AI进行分析 | ||
| 315 | + analysis_results = await ai_analyzer.analyze_messages(messages) | ||
| 316 | + | ||
| 317 | + # 保存到数据库 | ||
| 318 | + with Session(engine) as session: | ||
| 319 | + for result in analysis_results: | ||
| 320 | + analysis = AIAnalysis( | ||
| 321 | + message_id=result['message_id'], | ||
| 322 | + sentiment=result['sentiment'], | ||
| 323 | + sentiment_score=float(result['sentiment_score']), | ||
| 324 | + keywords=result['keywords'], | ||
| 325 | + key_points=result['key_points'], | ||
| 326 | + influence_analysis=result['influence_analysis'], | ||
| 327 | + risk_level=result['risk_level'] | ||
| 328 | + ) | ||
| 329 | + session.add(analysis) | ||
| 330 | + session.commit() | ||
| 331 | + | ||
| 332 | + # 格式化结果用于显示 | ||
| 333 | + display_results = [ | ||
| 334 | + ai_analyzer.format_analysis_for_display(result) | ||
| 335 | + for result in analysis_results | ||
| 336 | + ] | ||
| 337 | + | ||
| 338 | + return jsonify({ | ||
| 339 | + 'success': True, | ||
| 340 | + 'data': display_results | ||
| 341 | + }) | ||
| 342 | + | ||
| 343 | + except Exception as e: | ||
| 344 | + logging.error(f"AI分析过程出错: {e}") | ||
| 345 | + return jsonify({ | ||
| 346 | + 'success': False, | ||
| 347 | + 'error': str(e) | ||
| 348 | + }), 500 | ||
| 349 | + | ||
| 350 | +@pb.route('/api/get_analysis/<int:message_id>') | ||
| 351 | +def get_message_analysis(message_id): | ||
| 352 | + """获取特定消息的分析结果""" | ||
| 353 | + try: | ||
| 354 | + with Session(engine) as session: | ||
| 355 | + analysis = session.query(AIAnalysis)\ | ||
| 356 | + .filter(AIAnalysis.message_id == message_id)\ | ||
| 357 | + .order_by(AIAnalysis.created_at.desc())\ | ||
| 358 | + .first() | ||
| 359 | + | ||
| 360 | + if analysis: | ||
| 361 | + return jsonify({ | ||
| 362 | + 'success': True, | ||
| 363 | + 'data': analysis.to_dict() | ||
| 364 | + }) | ||
| 365 | + else: | ||
| 366 | + return jsonify({ | ||
| 367 | + 'success': False, | ||
| 368 | + 'error': '未找到分析结果' | ||
| 369 | + }), 404 | ||
| 370 | + | ||
| 371 | + except Exception as e: | ||
| 372 | + logging.error(f"获取分析结果时出错: {e}") | ||
| 373 | + return jsonify({ | ||
| 374 | + 'success': False, | ||
| 375 | + 'error': str(e) | ||
| 376 | + }), 500 | ||
| 377 | + | ||
| 378 | +def getRecentMessages(limit=50): | ||
| 379 | + """获取最近的消息""" | ||
| 380 | + # 这里需要根据你的数据库结构实现具体的查询逻辑 | ||
| 381 | + messages = [] | ||
| 382 | + try: | ||
| 383 | + # 示例查询逻辑 | ||
| 384 | + with Session(engine) as session: | ||
| 385 | + results = session.execute( | ||
| 386 | + """ | ||
| 387 | + SELECT id, content | ||
| 388 | + FROM comments | ||
| 389 | + ORDER BY created_at DESC | ||
| 390 | + LIMIT :limit | ||
| 391 | + """, | ||
| 392 | + {'limit': limit} | ||
| 393 | + ).fetchall() | ||
| 394 | + | ||
| 395 | + messages = [ | ||
| 396 | + {'id': row[0], 'content': row[1]} | ||
| 397 | + for row in results | ||
| 398 | + ] | ||
| 399 | + except Exception as e: | ||
| 400 | + logging.error(f"获取最近消息时出错: {e}") | ||
| 401 | + | ||
| 402 | + return messages |
| @@ -445,8 +445,157 @@ | @@ -445,8 +445,157 @@ | ||
| 445 | </div> | 445 | </div> |
| 446 | </div> | 446 | </div> |
| 447 | </div> | 447 | </div> |
| 448 | + | ||
| 449 | +<!-- AI分析结果展示区域 --> | ||
| 450 | +<div class="row"> | ||
| 451 | + <div class="col-lg-12"> | ||
| 452 | + <div class="card"> | ||
| 453 | + <div class="card-header d-flex justify-content-between"> | ||
| 454 | + <div class="header-title"> | ||
| 455 | + <h4 class="card-title">AI深度分析</h4> | ||
| 456 | + </div> | ||
| 457 | + <button class="btn btn-primary" onclick="requestAIAnalysis()"> | ||
| 458 | + 开始AI分析 | ||
| 459 | + </button> | ||
| 460 | + </div> | ||
| 461 | + <div class="card-body"> | ||
| 462 | + <div id="ai-analysis-results" class="analysis-container"> | ||
| 463 | + <!-- 分析结果将在这里动态显示 --> | ||
| 464 | + </div> | ||
| 465 | + </div> | ||
| 466 | + </div> | ||
| 467 | + </div> | ||
| 448 | </div> | 468 | </div> |
| 449 | 469 | ||
| 470 | +<!-- 添加必要的CSS样式 --> | ||
| 471 | +<style> | ||
| 472 | +.analysis-container { | ||
| 473 | + max-height: 600px; | ||
| 474 | + overflow-y: auto; | ||
| 475 | +} | ||
| 476 | + | ||
| 477 | +.analysis-card { | ||
| 478 | + border: 1px solid #eee; | ||
| 479 | + border-radius: 8px; | ||
| 480 | + padding: 15px; | ||
| 481 | + margin-bottom: 15px; | ||
| 482 | + background-color: #fff; | ||
| 483 | + box-shadow: 0 2px 4px rgba(0,0,0,0.1); | ||
| 484 | +} | ||
| 485 | + | ||
| 486 | +.analysis-card:hover { | ||
| 487 | + box-shadow: 0 4px 8px rgba(0,0,0,0.15); | ||
| 488 | +} | ||
| 489 | + | ||
| 490 | +.risk-level { | ||
| 491 | + padding: 4px 8px; | ||
| 492 | + border-radius: 4px; | ||
| 493 | + font-weight: bold; | ||
| 494 | +} | ||
| 495 | + | ||
| 496 | +.risk-low { | ||
| 497 | + background-color: #e8f5e9; | ||
| 498 | + color: #2e7d32; | ||
| 499 | +} | ||
| 500 | + | ||
| 501 | +.risk-medium { | ||
| 502 | + background-color: #fff3e0; | ||
| 503 | + color: #f57c00; | ||
| 504 | +} | ||
| 505 | + | ||
| 506 | +.risk-high { | ||
| 507 | + background-color: #ffebee; | ||
| 508 | + color: #c62828; | ||
| 509 | +} | ||
| 510 | + | ||
| 511 | +.keywords-container { | ||
| 512 | + display: flex; | ||
| 513 | + flex-wrap: wrap; | ||
| 514 | + gap: 8px; | ||
| 515 | + margin: 10px 0; | ||
| 516 | +} | ||
| 517 | + | ||
| 518 | +.keyword-tag { | ||
| 519 | + background-color: #e3f2fd; | ||
| 520 | + color: #1976d2; | ||
| 521 | + padding: 4px 8px; | ||
| 522 | + border-radius: 16px; | ||
| 523 | + font-size: 0.9em; | ||
| 524 | +} | ||
| 525 | +</style> | ||
| 526 | + | ||
| 527 | +<!-- 添加必要的JavaScript代码 --> | ||
| 528 | +<script> | ||
| 529 | +async function requestAIAnalysis() { | ||
| 530 | + try { | ||
| 531 | + const response = await fetch('/page/api/analyze_messages', { | ||
| 532 | + method: 'POST', | ||
| 533 | + headers: { | ||
| 534 | + 'Content-Type': 'application/json' | ||
| 535 | + } | ||
| 536 | + }); | ||
| 537 | + | ||
| 538 | + const result = await response.json(); | ||
| 539 | + if (result.success) { | ||
| 540 | + displayAnalysisResults(result.data); | ||
| 541 | + } else { | ||
| 542 | + alert('分析失败: ' + result.error); | ||
| 543 | + } | ||
| 544 | + } catch (error) { | ||
| 545 | + console.error('AI分析请求失败:', error); | ||
| 546 | + alert('请求失败,请稍后重试'); | ||
| 547 | + } | ||
| 548 | +} | ||
| 549 | + | ||
| 550 | +function displayAnalysisResults(results) { | ||
| 551 | + const container = document.getElementById('ai-analysis-results'); | ||
| 552 | + container.innerHTML = ''; // 清空现有结果 | ||
| 553 | + | ||
| 554 | + results.forEach(analysis => { | ||
| 555 | + const card = document.createElement('div'); | ||
| 556 | + card.className = 'analysis-card'; | ||
| 557 | + | ||
| 558 | + const riskLevelClass = | ||
| 559 | + analysis.risk_level === '高' ? 'risk-high' : | ||
| 560 | + analysis.risk_level === '中' ? 'risk-medium' : 'risk-low'; | ||
| 561 | + | ||
| 562 | + card.innerHTML = ` | ||
| 563 | + <div class="d-flex justify-content-between align-items-center"> | ||
| 564 | + <h5 class="mb-2">消息ID: ${analysis.id}</h5> | ||
| 565 | + <span class="risk-level ${riskLevelClass}"> | ||
| 566 | + 风险等级: ${analysis.risk_level} | ||
| 567 | + </span> | ||
| 568 | + </div> | ||
| 569 | + <div class="mb-2"> | ||
| 570 | + <strong>情感倾向:</strong> ${analysis.sentiment} | ||
| 571 | + <span class="ml-2">(${analysis.sentiment_score})</span> | ||
| 572 | + </div> | ||
| 573 | + <div class="keywords-container"> | ||
| 574 | + ${analysis.keywords.split(',').map(keyword => | ||
| 575 | + `<span class="keyword-tag">${keyword.trim()}</span>` | ||
| 576 | + ).join('')} | ||
| 577 | + </div> | ||
| 578 | + <div class="mb-2"> | ||
| 579 | + <strong>核心观点:</strong> | ||
| 580 | + <p class="mb-1">${analysis.key_points}</p> | ||
| 581 | + </div> | ||
| 582 | + <div class="mb-2"> | ||
| 583 | + <strong>影响分析:</strong> | ||
| 584 | + <p class="mb-1">${analysis.influence}</p> | ||
| 585 | + </div> | ||
| 586 | + <div class="text-muted"> | ||
| 587 | + 分析时间: ${analysis.analysis_time} | ||
| 588 | + </div> | ||
| 589 | + `; | ||
| 590 | + | ||
| 591 | + container.appendChild(card); | ||
| 592 | + }); | ||
| 593 | +} | ||
| 594 | + | ||
| 595 | +// 页面加载完成后自动请求一次AI分析 | ||
| 596 | +document.addEventListener('DOMContentLoaded', requestAIAnalysis); | ||
| 597 | +</script> | ||
| 598 | + | ||
| 450 | {% endblock %} | 599 | {% endblock %} |
| 451 | 600 | ||
| 452 | {% block echarts %} | 601 | {% block echarts %} |
-
Please register or login to post a comment