戒酒的李白

Integrating the OpenAI API for in-depth comment analysis, with usability to be debugged.

  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 + }
  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 %}