Completely refactor the LLM integration method to easily replace the LLM used by each module and optimize the retransmission mechanism.

This commit is contained in:
666ghj
2025-10-09 13:45:39 +08:00
parent ce74f00137
commit 154b29c0d7
73 changed files with 942 additions and 51758 deletions
+6 -7
View File
@@ -12,7 +12,7 @@ import re
# 添加项目根目录到Python路径以导入config
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from config import GUIJI_QWEN3_API_KEY, GUIJI_QWEN3_BASE_URL
from config import FORUM_HOST_API_KEY, FORUM_HOST_BASE_URL, FORUM_HOST_MODEL_NAME
# 添加utils目录到Python路径
current_dir = os.path.dirname(os.path.abspath(__file__))
@@ -30,7 +30,7 @@ class ForumHost:
使用Qwen3-235B模型作为智能主持人
"""
def __init__(self, api_key: str = None, base_url: Optional[str] = None):
def __init__(self, api_key: str = None, base_url: Optional[str] = None, model_name: Optional[str] = None):
"""
初始化论坛主持人
@@ -38,18 +38,18 @@ class ForumHost:
api_key: 硅基流动API密钥,如果不提供则从配置文件读取
base_url: 接口基础地址,默认使用配置文件提供的SiliconFlow地址
"""
self.api_key = api_key or GUIJI_QWEN3_API_KEY
self.api_key = api_key or FORUM_HOST_API_KEY
if not self.api_key:
raise ValueError("未找到硅基流动API密钥,请在config.py中设置GUIJI_QWEN3_API_KEY")
raise ValueError("未找到硅基流动API密钥,请在config.py中设置FORUM_HOST_API_KEY")
self.base_url = base_url or GUIJI_QWEN3_BASE_URL
self.base_url = base_url or FORUM_HOST_BASE_URL
self.client = OpenAI(
api_key=self.api_key,
base_url=self.base_url
)
self.model = "Qwen/Qwen3-235B-A22B-Instruct-2507" # Use larger model variant
self.model = model_name or FORUM_HOST_MODEL_NAME # Use configured model
# Track previous summaries to avoid duplicates
self.previous_summaries = []
@@ -217,7 +217,6 @@ class ForumHost:
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
],
max_tokens=14639,
temperature=0.6,
top_p=0.9,
)
+7 -21
View File
@@ -9,7 +9,7 @@ import re
from datetime import datetime
from typing import Optional, Dict, Any, List, Union
from .llms import DeepSeekLLM, OpenAILLM, KimiLLM, BaseLLM
from .llms import LLMClient
from .nodes import (
ReportStructureNode,
FirstSearchNode,
@@ -67,27 +67,13 @@ class DeepSearchAgent:
print(f"搜索工具集: MediaCrawlerDB (支持5种本地数据库查询工具)")
print(f"情感分析: WeiboMultilingualSentiment (支持22种语言的情感分析)")
def _initialize_llm(self) -> BaseLLM:
def _initialize_llm(self) -> LLMClient:
"""初始化LLM客户端"""
if self.config.default_llm_provider == "deepseek":
return DeepSeekLLM(
api_key=self.config.deepseek_api_key,
model_name=self.config.deepseek_model,
base_url=self.config.deepseek_base_url
)
elif self.config.default_llm_provider == "openai":
return OpenAILLM(
api_key=self.config.openai_api_key,
model_name=self.config.openai_model
)
elif self.config.default_llm_provider == "kimi":
return KimiLLM(
api_key=self.config.kimi_api_key,
model_name=self.config.kimi_model,
base_url=self.config.kimi_base_url
)
else:
raise ValueError(f"不支持的LLM提供商: {self.config.default_llm_provider}")
return LLMClient(
api_key=self.config.llm_api_key,
model_name=self.config.llm_model_name,
base_url=self.config.llm_base_url,
)
def _initialize_nodes(self):
"""初始化处理节点"""
+4 -7
View File
@@ -1,11 +1,8 @@
"""
LLM调用模块
支持多种大语言模型的统一接口
LLM module
Provides a unified OpenAI-compatible client for the Insight Engine.
"""
from .base import BaseLLM
from .deepseek import DeepSeekLLM
from .openai_llm import OpenAILLM
from .kimi import KimiLLM
from .base import LLMClient
__all__ = ["BaseLLM", "DeepSeekLLM", "OpenAILLM", "KimiLLM"]
__all__ = ["LLMClient"]
+78 -50
View File
@@ -1,61 +1,89 @@
"""
LLM基础抽象类
定义所有LLM实现需要遵循的接口标准
Unified OpenAI-compatible LLM client for the Insight Engine, with retry support.
"""
from abc import ABC, abstractmethod
from typing import Optional, Dict, Any
import os
import sys
from typing import Any, Dict, Optional
from openai import OpenAI
current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(os.path.dirname(current_dir))
utils_dir = os.path.join(project_root, "utils")
if utils_dir not in sys.path:
sys.path.append(utils_dir)
try:
from retry_helper import with_retry, LLM_RETRY_CONFIG
except ImportError:
def with_retry(config=None):
def decorator(func):
return func
return decorator
LLM_RETRY_CONFIG = None
class BaseLLM(ABC):
"""LLM基础抽象类"""
def __init__(self, api_key: str, model_name: Optional[str] = None):
"""
初始化LLM客户端
Args:
api_key: API密钥
model_name: 模型名称,如果不指定则使用默认模型
"""
class LLMClient:
"""Minimal wrapper around the OpenAI-compatible chat completion API."""
def __init__(self, api_key: str, model_name: str, base_url: Optional[str] = None):
if not api_key:
raise ValueError("Insight Engine LLM API key is required.")
if not model_name:
raise ValueError("Insight Engine model name is required.")
self.api_key = api_key
self.base_url = base_url
self.model_name = model_name
@abstractmethod
self.provider = model_name
timeout_fallback = os.getenv("LLM_REQUEST_TIMEOUT") or os.getenv("INSIGHT_ENGINE_REQUEST_TIMEOUT") or "180"
try:
self.timeout = float(timeout_fallback)
except ValueError:
self.timeout = 300.0
client_kwargs: Dict[str, Any] = {
"api_key": api_key,
"max_retries": 0,
}
if base_url:
client_kwargs["base_url"] = base_url
self.client = OpenAI(**client_kwargs)
@with_retry(LLM_RETRY_CONFIG)
def invoke(self, system_prompt: str, user_prompt: str, **kwargs) -> str:
"""
调用LLM生成回复
Args:
system_prompt: 系统提示词
user_prompt: 用户输入
**kwargs: 其他参数,如temperature、max_tokens等
Returns:
LLM生成的回复文本
"""
pass
@abstractmethod
def get_default_model(self) -> str:
"""
获取默认模型名称
Returns:
默认模型名称
"""
pass
def validate_response(self, response: str) -> str:
"""
验证和清理响应内容
Args:
response: LLM原始响应
Returns:
清理后的响应内容
"""
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
]
allowed_keys = {"temperature", "top_p", "presence_penalty", "frequency_penalty", "stream"}
extra_params = {key: value for key, value in kwargs.items() if key in allowed_keys and value is not None}
timeout = kwargs.pop("timeout", self.timeout)
response = self.client.chat.completions.create(
model=self.model_name,
messages=messages,
timeout=timeout,
**extra_params,
)
if response.choices and response.choices[0].message:
return self.validate_response(response.choices[0].message.content)
return ""
@staticmethod
def validate_response(response: Optional[str]) -> str:
if response is None:
return ""
return response.strip()
def get_model_info(self) -> Dict[str, Any]:
return {
"provider": self.provider,
"model": self.model_name,
"api_base": self.base_url or "default",
}
-119
View File
@@ -1,119 +0,0 @@
"""
DeepSeek LLM实现
使用DeepSeek API进行文本生成
"""
import os
from typing import Optional, Dict, Any
from openai import OpenAI
from .base import BaseLLM
import sys
DEFAULT_DEEPSEEK_BASE_URL = "https://api.deepseek.com"
# 添加utils目录到Python路径
current_dir = os.path.dirname(os.path.abspath(__file__))
root_dir = os.path.dirname(os.path.dirname(current_dir))
utils_dir = os.path.join(root_dir, 'utils')
if utils_dir not in sys.path:
sys.path.append(utils_dir)
try:
from retry_helper import with_retry, with_graceful_retry, LLM_RETRY_CONFIG
except ImportError:
# 如果无法导入重试模块,使用空装饰器
def with_retry(config):
def decorator(func):
return func
return decorator
LLM_RETRY_CONFIG = None
class DeepSeekLLM(BaseLLM):
"""DeepSeek LLM实现类"""
def __init__(self, api_key: Optional[str] = None, model_name: Optional[str] = None, base_url: Optional[str] = None):
"""
初始化DeepSeek客户端
Args:
api_key: DeepSeek API密钥,如果不提供则从环境变量读取
model_name: 模型名称,默认使用deepseek-chat
base_url: DeepSeek API基础地址
"""
if api_key is None:
api_key = os.getenv("DEEPSEEK_API_KEY")
if not api_key:
raise ValueError("DeepSeek API Key未找到!请设置DEEPSEEK_API_KEY环境变量或在初始化时提供")
super().__init__(api_key, model_name)
self.base_url = base_url or os.getenv("DEEPSEEK_BASE_URL") or DEFAULT_DEEPSEEK_BASE_URL
# 初始化OpenAI客户端,使用DeepSeek的endpoint
self.client = OpenAI(
api_key=self.api_key,
base_url=self.base_url
)
self.default_model = model_name or self.get_default_model()
def get_default_model(self) -> str:
"""获取默认模型名称"""
return "deepseek-chat"
@with_retry(LLM_RETRY_CONFIG)
def invoke(self, system_prompt: str, user_prompt: str, **kwargs) -> str:
"""
调用DeepSeek API生成回复
Args:
system_prompt: 系统提示词
user_prompt: 用户输入
**kwargs: 其他参数,如temperature、max_tokens等
Returns:
DeepSeek生成的回复文本
"""
try:
# 构建消息
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
# 设置默认参数
params = {
"model": self.default_model,
"messages": messages,
"temperature": kwargs.get("temperature", 0.7),
"max_tokens": kwargs.get("max_tokens", 4000),
"stream": False
}
# 调用API
response = self.client.chat.completions.create(**params)
# 提取回复内容
if response.choices and response.choices[0].message:
content = response.choices[0].message.content
return self.validate_response(content)
else:
return ""
except Exception as e:
print(f"DeepSeek API调用错误: {str(e)}")
raise e
def get_model_info(self) -> Dict[str, Any]:
"""
获取当前模型信息
Returns:
模型信息字典
"""
return {
"provider": "DeepSeek",
"model": self.default_model,
"api_base": self.base_url
}
-167
View File
@@ -1,167 +0,0 @@
"""
Kimi LLM实现
使用Moonshot AI的Kimi API进行文本生成
"""
import os
import sys
from typing import Optional, Dict, Any
from openai import OpenAI
# 假设 .base 模块和 BaseLLM 类已存在
from .base import BaseLLM
DEFAULT_KIMI_BASE_URL = "https://api.moonshot.cn/v1"
# 添加utils目录到Python路径并导入重试模块
try:
current_dir = os.path.dirname(os.path.abspath(__file__))
root_dir = os.path.dirname(os.path.dirname(current_dir))
utils_dir = os.path.join(root_dir, 'utils')
if utils_dir not in sys.path:
sys.path.append(utils_dir)
from retry_helper import with_retry, with_graceful_retry, LLM_RETRY_CONFIG
except ImportError:
# 如果无法导入重试模块,使用空装饰器避免报错
def with_retry(config):
def decorator(func):
return func
return decorator
LLM_RETRY_CONFIG = None
class KimiLLM(BaseLLM):
"""Kimi LLM实现类"""
def __init__(self, api_key: Optional[str] = None, model_name: Optional[str] = None, base_url: Optional[str] = None):
"""
初始化Kimi客户端
Args:
api_key: Kimi API密钥,如果不提供则从环境变量读取
model_name: 模型名称,默认使用kimi-k2-0711-preview
base_url: Kimi API基础地址
"""
if api_key is None:
api_key = os.getenv("KIMI_API_KEY")
if not api_key:
raise ValueError("Kimi API Key未找到!请设置KIMI_API_KEY环境变量或在初始化时提供")
super().__init__(api_key, model_name)
self.base_url = base_url or os.getenv("KIMI_BASE_URL") or DEFAULT_KIMI_BASE_URL
# 初始化OpenAI客户端,使用Kimi的endpoint
self.client = OpenAI(
api_key=self.api_key,
base_url=self.base_url
)
self.default_model = model_name or self.get_default_model()
def get_default_model(self) -> str:
"""获取默认模型名称"""
return "kimi-k2-0711-preview"
@with_retry(LLM_RETRY_CONFIG)
def invoke(self, system_prompt: str, user_prompt: str, **kwargs) -> str:
"""
调用Kimi API生成回复
Args:
system_prompt: 系统提示词
user_prompt: 用户输入
**kwargs: 其他参数,如temperature、max_tokens等
Returns:
Kimi生成的回复文本
"""
try:
# 构建消息
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
# 智能计算max_tokens - 根据输入长度自动调整输出长度
input_length = len(system_prompt) + len(user_prompt)
if input_length > 100000: # 超长文本
default_max_tokens = 81920
elif input_length > 50000: # 超长文本
default_max_tokens = 40960
elif input_length > 20000: # 长文本
default_max_tokens = 16384
elif input_length > 5000: # 中等文本
default_max_tokens = 8192
else: # 短文本
default_max_tokens = 4096
# 设置默认参数,针对长文本处理优化
params = {
"model": self.default_model,
"messages": messages,
"temperature": kwargs.get("temperature", 0.6), # Kimi建议使用0.6
"max_tokens": kwargs.get("max_tokens", default_max_tokens), # 智能调整token限制
"stream": False
}
# 添加其他可选参数
if "top_p" in kwargs:
params["top_p"] = kwargs["top_p"]
if "presence_penalty" in kwargs:
params["presence_penalty"] = kwargs["presence_penalty"]
if "frequency_penalty" in kwargs:
params["frequency_penalty"] = kwargs["frequency_penalty"]
if "stop" in kwargs:
params["stop"] = kwargs["stop"]
# 输出调试信息(仅在使用Kimi时)
print(f"[Kimi] 输入长度: {input_length}, 使用max_tokens: {params['max_tokens']}")
# 调用API
response = self.client.chat.completions.create(**params)
# 提取回复内容
if response.choices and response.choices[0].message:
content = response.choices[0].message.content
return self.validate_response(content)
else:
return ""
except Exception as e:
print(f"Kimi API调用错误: {str(e)}")
raise e
def get_model_info(self) -> Dict[str, Any]:
"""
获取当前模型信息
Returns:
模型信息字典
"""
return {
"provider": "Kimi",
"model": self.default_model,
"api_base": self.base_url,
"max_context_length": "长文本支持(200K+ tokens"
}
# ==================== 代码修改部分 ====================
def invoke_long_context(self, system_prompt: str, user_prompt: str, **kwargs) -> str:
"""
专门用于长文本处理的调用方法 (作为invoke的兼容接口)。
此方法通过设置推荐的默认参数,然后调用通用的invoke方法来处理请求。
Args:
system_prompt: 系统提示词
user_prompt: 用户输入
**kwargs: 其他参数
Returns:
Kimi生成的回复文本
"""
# 为长文本场景,设置一个慷慨的默认 max_tokens,仅当用户未指定时生效。
# 您原有的16384是一个非常合理的值。
kwargs.setdefault("max_tokens", 16384)
# 直接调用核心的invoke方法,将所有参数(包括预设的默认值)传递给它。
return self.invoke(system_prompt, user_prompt, **kwargs)
-108
View File
@@ -1,108 +0,0 @@
"""
OpenAI LLM实现
使用OpenAI API进行文本生成
"""
import os
import sys
from typing import Optional, Dict, Any
from openai import OpenAI
from .base import BaseLLM
# 添加utils目录到Python路径并导入重试模块
try:
current_dir = os.path.dirname(os.path.abspath(__file__))
root_dir = os.path.dirname(os.path.dirname(current_dir))
utils_dir = os.path.join(root_dir, 'utils')
if utils_dir not in sys.path:
sys.path.append(utils_dir)
from retry_helper import with_retry, with_graceful_retry, LLM_RETRY_CONFIG
except ImportError:
# 如果无法导入重试模块,使用空装饰器避免报错
def with_retry(config):
def decorator(func):
return func
return decorator
LLM_RETRY_CONFIG = None
class OpenAILLM(BaseLLM):
"""OpenAI LLM实现类"""
def __init__(self, api_key: Optional[str] = None, model_name: Optional[str] = None):
"""
初始化OpenAI客户端
Args:
api_key: OpenAI API密钥,如果不提供则从环境变量读取
model_name: 模型名称,默认使用gpt-4o-mini
"""
if api_key is None:
api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
raise ValueError("OpenAI API Key未找到!请设置OPENAI_API_KEY环境变量或在初始化时提供")
super().__init__(api_key, model_name)
# 初始化OpenAI客户端
self.client = OpenAI(api_key=self.api_key)
self.default_model = model_name or self.get_default_model()
def get_default_model(self) -> str:
"""获取默认模型名称"""
return "gpt-4o-mini"
@with_retry(LLM_RETRY_CONFIG)
def invoke(self, system_prompt: str, user_prompt: str, **kwargs) -> str:
"""
调用OpenAI API生成回复
Args:
system_prompt: 系统提示词
user_prompt: 用户输入
**kwargs: 其他参数,如temperature、max_tokens等
Returns:
OpenAI生成的回复文本
"""
try:
# 构建消息
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
# 设置默认参数
params = {
"model": self.default_model,
"messages": messages,
"temperature": kwargs.get("temperature", 0.7),
"max_tokens": kwargs.get("max_tokens", 4000)
}
# 调用API
response = self.client.chat.completions.create(**params)
# 提取回复内容
if response.choices and response.choices[0].message:
content = response.choices[0].message.content
return self.validate_response(content)
else:
return ""
except Exception as e:
print(f"OpenAI API调用错误: {str(e)}")
raise e
def get_model_info(self) -> Dict[str, Any]:
"""
获取当前模型信息
Returns:
模型信息字典
"""
return {
"provider": "OpenAI",
"model": self.default_model,
"api_base": "https://api.openai.com"
}
+2 -2
View File
@@ -5,14 +5,14 @@
from abc import ABC, abstractmethod
from typing import Any, Dict, Optional
from ..llms.base import BaseLLM
from ..llms.base import LLMClient
from ..state.state import State
class BaseNode(ABC):
"""节点基类"""
def __init__(self, llm_client: BaseLLM, node_name: str = ""):
def __init__(self, llm_client: LLMClient, node_name: str = ""):
"""
初始化节点
+6 -7
View File
@@ -12,7 +12,7 @@ from dataclasses import dataclass
# 添加项目根目录到Python路径以导入config
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
from config import GUIJI_QWEN3_API_KEY, GUIJI_QWEN3_BASE_URL
from config import KEYWORD_OPTIMIZER_API_KEY, KEYWORD_OPTIMIZER_BASE_URL, KEYWORD_OPTIMIZER_MODEL_NAME
# 添加utils目录到Python路径
current_dir = os.path.dirname(os.path.abspath(__file__))
@@ -38,7 +38,7 @@ class KeywordOptimizer:
使用硅基流动的Qwen3模型将Agent生成的搜索词优化为更贴近真实舆情的关键词
"""
def __init__(self, api_key: str = None, base_url: str = None):
def __init__(self, api_key: str = None, base_url: str = None, model_name: str = None):
"""
初始化关键词优化器
@@ -46,18 +46,18 @@ class KeywordOptimizer:
api_key: 硅基流动API密钥,如果不提供则从配置文件读取
base_url: 接口基础地址,默认使用配置文件提供的SiliconFlow地址
"""
self.api_key = api_key or GUIJI_QWEN3_API_KEY
self.api_key = api_key or KEYWORD_OPTIMIZER_API_KEY
if not self.api_key:
raise ValueError("未找到硅基流动API密钥,请在config.py中设置GUIJI_QWEN3_API_KEY")
raise ValueError("未找到硅基流动API密钥,请在config.py中设置KEYWORD_OPTIMIZER_API_KEY")
self.base_url = base_url or GUIJI_QWEN3_BASE_URL
self.base_url = base_url or KEYWORD_OPTIMIZER_BASE_URL
self.client = OpenAI(
api_key=self.api_key,
base_url=self.base_url
)
self.model = "Qwen/Qwen3-30B-A3B-Instruct-2507"
self.model = model_name or KEYWORD_OPTIMIZER_MODEL_NAME
def optimize_keywords(self, original_query: str, context: str = "") -> KeywordOptimizationResponse:
"""
@@ -192,7 +192,6 @@ class KeywordOptimizer:
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
],
max_tokens=10000,
temperature=0.7,
)
+161 -171
View File
@@ -1,6 +1,6 @@
"""
配置管理模块
处理环境变量和配置参数
Configuration management module for the Insight Engine.
Handles environment variables and config file parameters.
"""
import os
@@ -8,226 +8,216 @@ from dataclasses import dataclass
from typing import Optional
def _get_value(source, key: str, default=None):
"""
Helper to fetch a configuration value with environment fallback.
"""
value = None
if isinstance(source, dict):
value = source.get(key)
else:
value = getattr(source, key, None)
if value is None:
value = os.getenv(key, default)
return value if value not in ("", None) else default
@dataclass
class Config:
"""配置类"""
# API密钥
deepseek_api_key: Optional[str] = None
openai_api_key: Optional[str] = None
kimi_api_key: Optional[str] = None
deepseek_base_url: str = "https://api.deepseek.com"
openai_base_url: Optional[str] = None
kimi_base_url: str = "https://api.moonshot.cn/v1"
# 数据库配置
"""Insight Engine configuration."""
# LLM configuration
llm_api_key: Optional[str] = None
llm_base_url: Optional[str] = None
llm_model_name: Optional[str] = None
llm_provider: Optional[str] = None # kept for backward compatibility
# Database configuration
db_host: Optional[str] = None
db_user: Optional[str] = None
db_password: Optional[str] = None
db_name: Optional[str] = None
db_port: int = 3306
db_charset: str = "utf8mb4"
# 模型配置
default_llm_provider: str = "deepseek" # deepseek、openai 或 kimi
deepseek_model: str = "deepseek-chat"
openai_model: str = "gpt-4o-mini"
kimi_model: str = "kimi-k2-0711-preview"
# 搜索配置
# Model behaviour configuration
max_reflections: int = 3
max_paragraphs: int = 6
search_timeout: int = 240
max_content_length: int = 500000 # 提高5倍以充分利用Kimi的长文本能力
# 数据库查询限制
max_content_length: int = 500000
# Search result limits
default_search_hot_content_limit: int = 100
default_search_topic_globally_limit_per_table: int = 50
default_search_topic_by_date_limit_per_table: int = 100
default_get_comments_for_topic_limit: int = 500
default_search_topic_on_platform_limit: int = 200
# Agent配置
max_reflections: int = 3
max_paragraphs: int = 6
# 结果处理限制
max_search_results_for_llm: int = 0 # 0表示不限制,传递所有搜索结果给LLM
max_high_confidence_sentiment_results: int = 0 # 0表示不限制,返回所有高置信度情感分析结果
# 输出配置
max_search_results_for_llm: int = 0
max_high_confidence_sentiment_results: int = 0
# Output configuration
output_dir: str = "reports"
save_intermediate_states: bool = True
def __post_init__(self):
if not self.llm_provider and self.llm_model_name:
# Provider is no longer used, but keep the attribute for compatibility.
self.llm_provider = self.llm_model_name
def validate(self) -> bool:
"""验证配置"""
# 检查必需的API密钥
if self.default_llm_provider == "deepseek" and not self.deepseek_api_key:
print("错误: DeepSeek API Key未设置")
"""Validate configuration."""
if not self.llm_api_key:
print("错误: Insight Engine LLM API Key 未设置 (INSIGHT_ENGINE_API_KEY)。")
return False
if self.default_llm_provider == "openai" and not self.openai_api_key:
print("错误: OpenAI API Key未设置")
if not self.llm_model_name:
print("错误: Insight Engine 模型名称未设置 (INSIGHT_ENGINE_MODEL_NAME)。")
return False
if not all([self.db_host, self.db_user, self.db_password, self.db_name]):
print("错误: 数据库连接信息不完整")
print("错误: 数据库连接信息不完整,请检查 config.py 中的 DB_* 配置。")
return False
return True
@classmethod
def from_file(cls, config_file: str) -> "Config":
"""从配置文件创建配置"""
if config_file.endswith('.py'):
# Python配置文件
"""Create configuration from file."""
if config_file.endswith(".py"):
import importlib.util
# 动态导入配置文件
spec = importlib.util.spec_from_file_location("config", config_file)
config_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(config_module)
return cls(
deepseek_api_key=getattr(config_module, "DEEPSEEK_API_KEY", None),
openai_api_key=getattr(config_module, "OPENAI_API_KEY", None),
kimi_api_key=getattr(config_module, "KIMI_API_KEY", None),
deepseek_base_url=getattr(config_module, "DEEPSEEK_BASE_URL", "https://api.deepseek.com"),
openai_base_url=getattr(config_module, "OPENAI_BASE_URL", None),
kimi_base_url=getattr(config_module, "KIMI_BASE_URL", "https://api.moonshot.cn/v1"),
db_host=getattr(config_module, "DB_HOST", None),
db_user=getattr(config_module, "DB_USER", None),
db_password=getattr(config_module, "DB_PASSWORD", None),
db_name=getattr(config_module, "DB_NAME", None),
db_port=getattr(config_module, "DB_PORT", 3306),
db_charset=getattr(config_module, "DB_CHARSET", "utf8mb4"),
default_llm_provider=getattr(config_module, "DEFAULT_LLM_PROVIDER", "deepseek"),
deepseek_model=getattr(config_module, "DEEPSEEK_MODEL", "deepseek-chat"),
openai_model=getattr(config_module, "OPENAI_MODEL", "gpt-4o-mini"),
search_timeout=getattr(config_module, "SEARCH_TIMEOUT", 240),
max_content_length=getattr(config_module, "SEARCH_CONTENT_MAX_LENGTH", 200000),
default_search_hot_content_limit=getattr(config_module, "DEFAULT_SEARCH_HOT_CONTENT_LIMIT", 100),
default_search_topic_globally_limit_per_table=getattr(config_module, "DEFAULT_SEARCH_TOPIC_GLOBALLY_LIMIT_PER_TABLE", 50),
default_search_topic_by_date_limit_per_table=getattr(config_module, "DEFAULT_SEARCH_TOPIC_BY_DATE_LIMIT_PER_TABLE", 100),
default_get_comments_for_topic_limit=getattr(config_module, "DEFAULT_GET_COMMENTS_FOR_TOPIC_LIMIT", 500),
default_search_topic_on_platform_limit=getattr(config_module, "DEFAULT_SEARCH_TOPIC_ON_PLATFORM_LIMIT", 200),
max_reflections=getattr(config_module, "MAX_REFLECTIONS", 2),
max_paragraphs=getattr(config_module, "MAX_PARAGRAPHS", 5),
max_search_results_for_llm=getattr(config_module, "MAX_SEARCH_RESULTS_FOR_LLM", 0),
max_high_confidence_sentiment_results=getattr(config_module, "MAX_HIGH_CONFIDENCE_SENTIMENT_RESULTS", 0),
output_dir=getattr(config_module, "OUTPUT_DIR", "reports"),
save_intermediate_states=getattr(config_module, "SAVE_INTERMEDIATE_STATES", True)
)
else:
# .env格式配置文件
config_dict = {}
if os.path.exists(config_file):
with open(config_file, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
config_dict[key.strip()] = value.strip()
return cls(
deepseek_api_key=config_dict.get("DEEPSEEK_API_KEY"),
openai_api_key=config_dict.get("OPENAI_API_KEY"),
kimi_api_key=config_dict.get("KIMI_API_KEY"),
deepseek_base_url=config_dict.get("DEEPSEEK_BASE_URL", "https://api.deepseek.com"),
openai_base_url=config_dict.get("OPENAI_BASE_URL"),
kimi_base_url=config_dict.get("KIMI_BASE_URL", "https://api.moonshot.cn/v1"),
db_host=config_dict.get("DB_HOST"),
db_user=config_dict.get("DB_USER"),
db_password=config_dict.get("DB_PASSWORD"),
db_name=config_dict.get("DB_NAME"),
db_port=int(config_dict.get("DB_PORT", "3306")),
db_charset=config_dict.get("DB_CHARSET", "utf8mb4"),
default_llm_provider=config_dict.get("DEFAULT_LLM_PROVIDER", "deepseek"),
deepseek_model=config_dict.get("DEEPSEEK_MODEL", "deepseek-chat"),
openai_model=config_dict.get("OPENAI_MODEL", "gpt-4o-mini"),
kimi_model=config_dict.get("KIMI_MODEL", "kimi-k2-0711-preview"),
search_timeout=int(config_dict.get("SEARCH_TIMEOUT", "240")),
max_content_length=int(config_dict.get("SEARCH_CONTENT_MAX_LENGTH", "500000")),
default_search_hot_content_limit=int(config_dict.get("DEFAULT_SEARCH_HOT_CONTENT_LIMIT", "100")),
default_search_topic_globally_limit_per_table=int(config_dict.get("DEFAULT_SEARCH_TOPIC_GLOBALLY_LIMIT_PER_TABLE", "50")),
default_search_topic_by_date_limit_per_table=int(config_dict.get("DEFAULT_SEARCH_TOPIC_BY_DATE_LIMIT_PER_TABLE", "100")),
default_get_comments_for_topic_limit=int(config_dict.get("DEFAULT_GET_COMMENTS_FOR_TOPIC_LIMIT", "500")),
default_search_topic_on_platform_limit=int(config_dict.get("DEFAULT_SEARCH_TOPIC_ON_PLATFORM_LIMIT", "200")),
max_reflections=int(config_dict.get("MAX_REFLECTIONS", "2")),
max_paragraphs=int(config_dict.get("MAX_PARAGRAPHS", "5")),
max_search_results_for_llm=int(config_dict.get("MAX_SEARCH_RESULTS_FOR_LLM", "0")),
max_high_confidence_sentiment_results=int(config_dict.get("MAX_HIGH_CONFIDENCE_SENTIMENT_RESULTS", "0")),
output_dir=config_dict.get("OUTPUT_DIR", "reports"),
save_intermediate_states=config_dict.get("SAVE_INTERMEDIATE_STATES", "true").lower() == "true"
llm_api_key=_get_value(config_module, "INSIGHT_ENGINE_API_KEY"),
llm_base_url=_get_value(config_module, "INSIGHT_ENGINE_BASE_URL"),
llm_model_name=_get_value(config_module, "INSIGHT_ENGINE_MODEL_NAME"),
db_host=_get_value(config_module, "DB_HOST"),
db_user=_get_value(config_module, "DB_USER"),
db_password=_get_value(config_module, "DB_PASSWORD"),
db_name=_get_value(config_module, "DB_NAME"),
db_port=int(_get_value(config_module, "DB_PORT", 3306)),
db_charset=_get_value(config_module, "DB_CHARSET", "utf8mb4"),
max_reflections=int(_get_value(config_module, "MAX_REFLECTIONS", 3)),
max_paragraphs=int(_get_value(config_module, "MAX_PARAGRAPHS", 6)),
search_timeout=int(_get_value(config_module, "SEARCH_TIMEOUT", 240)),
max_content_length=int(_get_value(config_module, "SEARCH_CONTENT_MAX_LENGTH", 500000)),
default_search_hot_content_limit=int(
_get_value(config_module, "DEFAULT_SEARCH_HOT_CONTENT_LIMIT", 100)
),
default_search_topic_globally_limit_per_table=int(
_get_value(config_module, "DEFAULT_SEARCH_TOPIC_GLOBALLY_LIMIT_PER_TABLE", 50)
),
default_search_topic_by_date_limit_per_table=int(
_get_value(config_module, "DEFAULT_SEARCH_TOPIC_BY_DATE_LIMIT_PER_TABLE", 100)
),
default_get_comments_for_topic_limit=int(
_get_value(config_module, "DEFAULT_GET_COMMENTS_FOR_TOPIC_LIMIT", 500)
),
default_search_topic_on_platform_limit=int(
_get_value(config_module, "DEFAULT_SEARCH_TOPIC_ON_PLATFORM_LIMIT", 200)
),
max_search_results_for_llm=int(_get_value(config_module, "MAX_SEARCH_RESULTS_FOR_LLM", 0)),
max_high_confidence_sentiment_results=int(
_get_value(config_module, "MAX_HIGH_CONFIDENCE_SENTIMENT_RESULTS", 0)
),
output_dir=_get_value(config_module, "OUTPUT_DIR", "reports"),
save_intermediate_states=str(
_get_value(config_module, "SAVE_INTERMEDIATE_STATES", "true")
).lower()
in ("true", "1", "yes"),
)
# .env style configuration
config_dict = {}
if os.path.exists(config_file):
with open(config_file, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line and not line.startswith("#") and "=" in line:
key, value = line.split("=", 1)
config_dict[key.strip()] = value.strip()
return cls(
llm_api_key=_get_value(config_dict, "INSIGHT_ENGINE_API_KEY"),
llm_base_url=_get_value(config_dict, "INSIGHT_ENGINE_BASE_URL"),
llm_model_name=_get_value(config_dict, "INSIGHT_ENGINE_MODEL_NAME"),
db_host=_get_value(config_dict, "DB_HOST"),
db_user=_get_value(config_dict, "DB_USER"),
db_password=_get_value(config_dict, "DB_PASSWORD"),
db_name=_get_value(config_dict, "DB_NAME"),
db_port=int(_get_value(config_dict, "DB_PORT", 3306)),
db_charset=_get_value(config_dict, "DB_CHARSET", "utf8mb4"),
max_reflections=int(_get_value(config_dict, "MAX_REFLECTIONS", 3)),
max_paragraphs=int(_get_value(config_dict, "MAX_PARAGRAPHS", 6)),
search_timeout=int(_get_value(config_dict, "SEARCH_TIMEOUT", 240)),
max_content_length=int(_get_value(config_dict, "SEARCH_CONTENT_MAX_LENGTH", 500000)),
default_search_hot_content_limit=int(
_get_value(config_dict, "DEFAULT_SEARCH_HOT_CONTENT_LIMIT", 100)
),
default_search_topic_globally_limit_per_table=int(
_get_value(config_dict, "DEFAULT_SEARCH_TOPIC_GLOBALLY_LIMIT_PER_TABLE", 50)
),
default_search_topic_by_date_limit_per_table=int(
_get_value(config_dict, "DEFAULT_SEARCH_TOPIC_BY_DATE_LIMIT_PER_TABLE", 100)
),
default_get_comments_for_topic_limit=int(
_get_value(config_dict, "DEFAULT_GET_COMMENTS_FOR_TOPIC_LIMIT", 500)
),
default_search_topic_on_platform_limit=int(
_get_value(config_dict, "DEFAULT_SEARCH_TOPIC_ON_PLATFORM_LIMIT", 200)
),
max_search_results_for_llm=int(_get_value(config_dict, "MAX_SEARCH_RESULTS_FOR_LLM", 0)),
max_high_confidence_sentiment_results=int(
_get_value(config_dict, "MAX_HIGH_CONFIDENCE_SENTIMENT_RESULTS", 0)
),
output_dir=_get_value(config_dict, "OUTPUT_DIR", "reports"),
save_intermediate_states=str(
_get_value(config_dict, "SAVE_INTERMEDIATE_STATES", "true")
).lower()
in ("true", "1", "yes"),
)
def load_config(config_file: Optional[str] = None) -> Config:
"""
加载配置
Args:
config_file: 配置文件路径,如果不指定则使用默认路径
Returns:
配置对象
Load configuration.
"""
# 确定配置文件路径
if config_file:
if not os.path.exists(config_file):
raise FileNotFoundError(f"配置文件不存在: {config_file}")
file_to_load = config_file
else:
# 尝试加载常见的配置文件
for config_path in ["config.py", "config.env", ".env"]:
if os.path.exists(config_path):
file_to_load = config_path
print(f"已找到配置文件: {config_path}")
for candidate in ("config.py", "config.env", ".env"):
if os.path.exists(candidate):
file_to_load = candidate
print(f"已找到配置文件: {candidate}")
break
else:
raise FileNotFoundError("未找到配置文件,请创建 config.py 文件")
# 创建配置对象
raise FileNotFoundError("未找到配置文件,请创建 config.py")
config = Config.from_file(file_to_load)
# 验证配置
if not config.validate():
raise ValueError("配置验失败,请检查配置文件中的API密钥")
raise ValueError("配置验失败,请检查 config.py 中的相关配置。")
return config
def print_config(config: Config):
"""打印配置信息(隐藏敏感信息)"""
print("\n=== 当前配置 ===")
print(f"LLM提供商: {config.default_llm_provider}")
print(f"DeepSeek模型: {config.deepseek_model}")
print(f"OpenAI模型: {config.openai_model}")
print(f"搜索超时: {config.search_timeout}")
print(f"最大内容长度: {config.max_content_length}")
"""Print configuration (sensitive values masked)."""
print("\n=== Insight Engine 配置 ===")
print(f"LLM 模型: {config.llm_model_name}")
print(f"LLM Base URL: {config.llm_base_url or '(默认)'}")
print(f"搜索超时: {config.search_timeout}")
print(f"最长内容长度: {config.max_content_length}")
print(f"最大反思次数: {config.max_reflections}")
print(f"最大段落数: {config.max_paragraphs}")
print(f"输出目录: {config.output_dir}")
print(f"保存中间状态: {config.save_intermediate_states}")
# 显示API密钥和数据库状态(不显示实际密钥)
print(f"DeepSeek API Key: {'已设置' if config.deepseek_api_key else '未设置'}")
print(f"OpenAI API Key: {'已设置' if config.openai_api_key else '未设置'}")
print(f"LLM API Key: {'已配置' if config.llm_api_key else '未配置'}")
print(f"数据库连接: {'已配置' if all([config.db_host, config.db_user, config.db_password, config.db_name]) else '未配置'}")
print(f"数据库主机: {config.db_host}")
print(f"数据库端口: {config.db_port}")
print(f"数据库名称: {config.db_name}")
print("==================\n")
print("========================\n")
+9 -21
View File
@@ -9,7 +9,7 @@ import re
from datetime import datetime
from typing import Optional, Dict, Any, List
from .llms import DeepSeekLLM, OpenAILLM, GeminiLLM, BaseLLM
from .llms import LLMClient
from .nodes import (
ReportStructureNode,
FirstSearchNode,
@@ -35,6 +35,8 @@ class DeepSearchAgent:
"""
# 加载配置
self.config = config or load_config()
os.environ["BOCHA_API_KEY"] = self.config.bocha_api_key or ""
os.environ["BOCHA_WEB_SEARCH_API_KEY"] = self.config.bocha_api_key or ""
# 初始化LLM客户端
self.llm_client = self._initialize_llm()
@@ -55,27 +57,13 @@ class DeepSearchAgent:
print(f"使用LLM: {self.llm_client.get_model_info()}")
print(f"搜索工具集: BochaMultimodalSearch (支持5种多模态搜索工具)")
def _initialize_llm(self) -> BaseLLM:
def _initialize_llm(self) -> LLMClient:
"""初始化LLM客户端"""
if self.config.default_llm_provider == "deepseek":
return DeepSeekLLM(
api_key=self.config.deepseek_api_key,
model_name=self.config.deepseek_model,
base_url=self.config.deepseek_base_url
)
elif self.config.default_llm_provider == "openai":
return OpenAILLM(
api_key=self.config.openai_api_key,
model_name=self.config.openai_model
)
elif self.config.default_llm_provider == "gemini":
return GeminiLLM(
api_key=self.config.gemini_api_key,
model_name=self.config.gemini_model,
base_url=self.config.gemini_base_url
)
else:
raise ValueError(f"不支持的LLM提供商: {self.config.default_llm_provider}")
return LLMClient(
api_key=self.config.llm_api_key,
model_name=self.config.llm_model_name,
base_url=self.config.llm_base_url,
)
def _initialize_nodes(self):
"""初始化处理节点"""
+3 -7
View File
@@ -1,11 +1,7 @@
"""
LLM调用模块
支持多种大语言模型的统一接口
LLM module for the Media Engine.
"""
from .base import BaseLLM
from .deepseek import DeepSeekLLM
from .openai_llm import OpenAILLM
from .gemini_llm import GeminiLLM
from .base import LLMClient
__all__ = ["BaseLLM", "DeepSeekLLM", "OpenAILLM", "GeminiLLM"]
__all__ = ["LLMClient"]
+81 -50
View File
@@ -1,61 +1,92 @@
"""
LLM基础抽象类
定义所有LLM实现需要遵循的接口标准
Unified OpenAI-compatible LLM client for the Media Engine, with retry support.
"""
from abc import ABC, abstractmethod
from typing import Optional, Dict, Any
import os
import sys
from typing import Any, Dict, Optional
from openai import OpenAI
# Ensure project-level retry helper is importable
current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(os.path.dirname(current_dir))
utils_dir = os.path.join(project_root, "utils")
if utils_dir not in sys.path:
sys.path.append(utils_dir)
try:
from retry_helper import with_retry, LLM_RETRY_CONFIG
except ImportError:
def with_retry(config=None):
def decorator(func):
return func
return decorator
LLM_RETRY_CONFIG = None
class BaseLLM(ABC):
"""LLM基础抽象类"""
def __init__(self, api_key: str, model_name: Optional[str] = None):
"""
初始化LLM客户端
Args:
api_key: API密钥
model_name: 模型名称,如果不指定则使用默认模型
"""
class LLMClient:
"""
Minimal wrapper around the OpenAI-compatible chat completion API.
"""
def __init__(self, api_key: str, model_name: str, base_url: Optional[str] = None):
if not api_key:
raise ValueError("Media Engine LLM API key is required.")
if not model_name:
raise ValueError("Media Engine model name is required.")
self.api_key = api_key
self.base_url = base_url
self.model_name = model_name
@abstractmethod
self.provider = model_name
timeout_fallback = os.getenv("LLM_REQUEST_TIMEOUT") or os.getenv("MEDIA_ENGINE_REQUEST_TIMEOUT") or "180"
try:
self.timeout = float(timeout_fallback)
except ValueError:
self.timeout = 300.0
client_kwargs: Dict[str, Any] = {
"api_key": api_key,
"max_retries": 0,
}
if base_url:
client_kwargs["base_url"] = base_url
self.client = OpenAI(**client_kwargs)
@with_retry(LLM_RETRY_CONFIG)
def invoke(self, system_prompt: str, user_prompt: str, **kwargs) -> str:
"""
调用LLM生成回复
Args:
system_prompt: 系统提示词
user_prompt: 用户输入
**kwargs: 其他参数,如temperature、max_tokens等
Returns:
LLM生成的回复文本
"""
pass
@abstractmethod
def get_default_model(self) -> str:
"""
获取默认模型名称
Returns:
默认模型名称
"""
pass
def validate_response(self, response: str) -> str:
"""
验证和清理响应内容
Args:
response: LLM原始响应
Returns:
清理后的响应内容
"""
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
]
allowed_keys = {"temperature", "top_p", "presence_penalty", "frequency_penalty", "stream"}
extra_params = {key: value for key, value in kwargs.items() if key in allowed_keys and value is not None}
timeout = kwargs.pop("timeout", self.timeout)
response = self.client.chat.completions.create(
model=self.model_name,
messages=messages,
timeout=timeout,
**extra_params,
)
if response.choices and response.choices[0].message:
return self.validate_response(response.choices[0].message.content)
return ""
@staticmethod
def validate_response(response: Optional[str]) -> str:
if response is None:
return ""
return response.strip()
def get_model_info(self) -> Dict[str, Any]:
return {
"provider": self.provider,
"model": self.model_name,
"api_base": self.base_url or "default",
}
-118
View File
@@ -1,118 +0,0 @@
"""
DeepSeek LLM实现
使用DeepSeek API进行文本生成
"""
import os
import sys
from typing import Optional, Dict, Any
from openai import OpenAI
from .base import BaseLLM
DEFAULT_DEEPSEEK_BASE_URL = "https://api.deepseek.com"
# 添加utils目录到Python路径并导入重试模块
try:
current_dir = os.path.dirname(os.path.abspath(__file__))
root_dir = os.path.dirname(os.path.dirname(current_dir))
utils_dir = os.path.join(root_dir, 'utils')
if utils_dir not in sys.path:
sys.path.append(utils_dir)
from retry_helper import with_retry, with_graceful_retry, LLM_RETRY_CONFIG
except ImportError:
# 如果无法导入重试模块,使用空装饰器避免报错
def with_retry(config):
def decorator(func):
return func
return decorator
LLM_RETRY_CONFIG = None
class DeepSeekLLM(BaseLLM):
"""DeepSeek LLM实现类"""
def __init__(self, api_key: Optional[str] = None, model_name: Optional[str] = None, base_url: Optional[str] = None):
"""
初始化DeepSeek客户端
Args:
api_key: DeepSeek API密钥,如果不提供则从环境变量读取
model_name: 模型名称,默认使用deepseek-chat
base_url: DeepSeek API基础地址
"""
if api_key is None:
api_key = os.getenv("DEEPSEEK_API_KEY")
if not api_key:
raise ValueError("DeepSeek API Key未找到!请设置DEEPSEEK_API_KEY环境变量或在初始化时提供")
super().__init__(api_key, model_name)
self.base_url = base_url or os.getenv("DEEPSEEK_BASE_URL") or DEFAULT_DEEPSEEK_BASE_URL
# 初始化OpenAI客户端,使用DeepSeek的endpoint
self.client = OpenAI(
api_key=self.api_key,
base_url=self.base_url
)
self.default_model = model_name or self.get_default_model()
def get_default_model(self) -> str:
"""获取默认模型名称"""
return "deepseek-chat"
@with_retry(LLM_RETRY_CONFIG)
def invoke(self, system_prompt: str, user_prompt: str, **kwargs) -> str:
"""
调用DeepSeek API生成回复
Args:
system_prompt: 系统提示词
user_prompt: 用户输入
**kwargs: 其他参数,如temperature、max_tokens等
Returns:
DeepSeek生成的回复文本
"""
try:
# 构建消息
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
# 设置默认参数
params = {
"model": self.default_model,
"messages": messages,
"temperature": kwargs.get("temperature", 0.7),
"max_tokens": kwargs.get("max_tokens", 30000), # 提高到30000以支持一万字报告
"stream": False
}
# 调用API
response = self.client.chat.completions.create(**params)
# 提取回复内容
if response.choices and response.choices[0].message:
content = response.choices[0].message.content
return self.validate_response(content)
else:
return ""
except Exception as e:
print(f"DeepSeek API调用错误: {str(e)}")
raise e
def get_model_info(self) -> Dict[str, Any]:
"""
获取当前模型信息
Returns:
模型信息字典
"""
return {
"provider": "DeepSeek",
"model": self.default_model,
"api_base": self.base_url
}
-118
View File
@@ -1,118 +0,0 @@
"""
Gemini LLM实现
使用Gemini 2.5-pro中转API进行文本生成
"""
import os
import sys
from typing import Optional, Dict, Any
from openai import OpenAI
from .base import BaseLLM
DEFAULT_GEMINI_BASE_URL = "https://www.chataiapi.com/v1"
# 添加utils目录到Python路径并导入重试模块
try:
current_dir = os.path.dirname(os.path.abspath(__file__))
root_dir = os.path.dirname(os.path.dirname(current_dir))
utils_dir = os.path.join(root_dir, 'utils')
if utils_dir not in sys.path:
sys.path.append(utils_dir)
from retry_helper import with_retry, with_graceful_retry, LLM_RETRY_CONFIG
except ImportError:
# 如果无法导入重试模块,使用空装饰器避免报错
def with_retry(config):
def decorator(func):
return func
return decorator
LLM_RETRY_CONFIG = None
class GeminiLLM(BaseLLM):
"""Gemini LLM实现类"""
def __init__(self, api_key: Optional[str] = None, model_name: Optional[str] = None, base_url: Optional[str] = None):
"""
初始化Gemini客户端
Args:
api_key: Gemini API密钥,如果不提供则从环境变量读取
model_name: 模型名称,默认使用gemini-2.5-pro
base_url: Gemini API基础地址
"""
if api_key is None:
api_key = os.getenv("GEMINI_API_KEY")
if not api_key:
raise ValueError("Gemini API Key未找到!请设置GEMINI_API_KEY环境变量或在初始化时提供")
super().__init__(api_key, model_name)
self.base_url = base_url or os.getenv("GEMINI_BASE_URL") or DEFAULT_GEMINI_BASE_URL
# 初始化OpenAI客户端,使用Gemini的中转endpoint
self.client = OpenAI(
api_key=self.api_key,
base_url=self.base_url
)
self.default_model = model_name or self.get_default_model()
def get_default_model(self) -> str:
"""获取默认模型名称"""
return "gemini-2.5-pro"
@with_retry(LLM_RETRY_CONFIG)
def invoke(self, system_prompt: str, user_prompt: str, **kwargs) -> str:
"""
调用Gemini API生成回复
Args:
system_prompt: 系统提示词
user_prompt: 用户输入
**kwargs: 其他参数,如temperature、max_tokens等
Returns:
Gemini生成的回复文本
"""
try:
# 构建消息
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
# 设置默认参数
params = {
"model": self.default_model,
"messages": messages,
"temperature": kwargs.get("temperature", 0.7),
"max_tokens": kwargs.get("max_tokens", 30000), # 提高到30000以支持一万字报告
"stream": False
}
# 调用API
response = self.client.chat.completions.create(**params)
# 提取回复内容
if response.choices and response.choices[0].message:
content = response.choices[0].message.content
return self.validate_response(content)
else:
return ""
except Exception as e:
print(f"Gemini API调用错误: {str(e)}")
raise e
def get_model_info(self) -> Dict[str, Any]:
"""
获取当前模型信息
Returns:
模型信息字典
"""
return {
"provider": "Gemini",
"model": self.default_model,
"api_base": self.base_url
}
-108
View File
@@ -1,108 +0,0 @@
"""
OpenAI LLM实现
使用OpenAI API进行文本生成
"""
import os
import sys
from typing import Optional, Dict, Any
from openai import OpenAI
from .base import BaseLLM
# 添加utils目录到Python路径并导入重试模块
try:
current_dir = os.path.dirname(os.path.abspath(__file__))
root_dir = os.path.dirname(os.path.dirname(current_dir))
utils_dir = os.path.join(root_dir, 'utils')
if utils_dir not in sys.path:
sys.path.append(utils_dir)
from retry_helper import with_retry, with_graceful_retry, LLM_RETRY_CONFIG
except ImportError:
# 如果无法导入重试模块,使用空装饰器避免报错
def with_retry(config):
def decorator(func):
return func
return decorator
LLM_RETRY_CONFIG = None
class OpenAILLM(BaseLLM):
"""OpenAI LLM实现类"""
def __init__(self, api_key: Optional[str] = None, model_name: Optional[str] = None):
"""
初始化OpenAI客户端
Args:
api_key: OpenAI API密钥,如果不提供则从环境变量读取
model_name: 模型名称,默认使用gpt-4o-mini
"""
if api_key is None:
api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
raise ValueError("OpenAI API Key未找到!请设置OPENAI_API_KEY环境变量或在初始化时提供")
super().__init__(api_key, model_name)
# 初始化OpenAI客户端
self.client = OpenAI(api_key=self.api_key)
self.default_model = model_name or self.get_default_model()
def get_default_model(self) -> str:
"""获取默认模型名称"""
return "gpt-4o-mini"
@with_retry(LLM_RETRY_CONFIG)
def invoke(self, system_prompt: str, user_prompt: str, **kwargs) -> str:
"""
调用OpenAI API生成回复
Args:
system_prompt: 系统提示词
user_prompt: 用户输入
**kwargs: 其他参数,如temperature、max_tokens等
Returns:
OpenAI生成的回复文本
"""
try:
# 构建消息
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
# 设置默认参数
params = {
"model": self.default_model,
"messages": messages,
"temperature": kwargs.get("temperature", 0.7),
"max_tokens": kwargs.get("max_tokens", 30000) # 提高到30000以支持一万字报告
}
# 调用API
response = self.client.chat.completions.create(**params)
# 提取回复内容
if response.choices and response.choices[0].message:
content = response.choices[0].message.content
return self.validate_response(content)
else:
return ""
except Exception as e:
print(f"OpenAI API调用错误: {str(e)}")
raise e
def get_model_info(self) -> Dict[str, Any]:
"""
获取当前模型信息
Returns:
模型信息字典
"""
return {
"provider": "OpenAI",
"model": self.default_model,
"api_base": "https://api.openai.com"
}
+2 -2
View File
@@ -5,14 +5,14 @@
from abc import ABC, abstractmethod
from typing import Any, Dict, Optional
from ..llms.base import BaseLLM
from ..llms.base import LLMClient
from ..state.state import State
class BaseNode(ABC):
"""节点基类"""
def __init__(self, llm_client: BaseLLM, node_name: str = ""):
def __init__(self, llm_client: LLMClient, node_name: str = ""):
"""
初始化节点
+3 -4
View File
@@ -67,11 +67,10 @@ class ReportFormattingNode(BaseNode):
self.log_info("正在格式化最终报告")
# 调用LLM,传递更大的max_tokens以支持长文本报告
# 调用LLM生成Markdown格式
response = self.llm_client.invoke(
SYSTEM_PROMPT_REPORT_FORMATTING,
message,
max_tokens=30000 # 支持一万字的报告输出
SYSTEM_PROMPT_REPORT_FORMATTING,
message,
)
# 处理响应
+4 -6
View File
@@ -98,11 +98,10 @@ class FirstSummaryNode(StateMutationNode):
self.log_info("正在生成首次段落总结")
# 调用LLM,增加max_tokens以支持更长的总结
# 调用LLM生成总结
response = self.llm_client.invoke(
SYSTEM_PROMPT_FIRST_SUMMARY,
SYSTEM_PROMPT_FIRST_SUMMARY,
message,
max_tokens=15000 # 支持更长的总结内容
)
# 处理响应
@@ -267,11 +266,10 @@ class ReflectionSummaryNode(StateMutationNode):
self.log_info("正在生成反思总结")
# 调用LLM,增加max_tokens以支持更长的总结
# 调用LLM生成总结
response = self.llm_client.invoke(
SYSTEM_PROMPT_REFLECTION_SUMMARY,
SYSTEM_PROMPT_REFLECTION_SUMMARY,
message,
max_tokens=15000 # 支持更长的总结内容
)
# 处理响应
+106 -128
View File
@@ -1,6 +1,5 @@
"""
配置管理模块
处理环境变量和配置参数
Configuration management module for the Media Engine.
"""
import os
@@ -8,172 +7,151 @@ from dataclasses import dataclass
from typing import Optional
def _get_value(source, key: str, default=None, *fallback_keys: str):
candidates = (key,) + fallback_keys
value = None
for candidate in candidates:
if isinstance(source, dict):
value = source.get(candidate)
else:
value = getattr(source, candidate, None)
if value not in (None, ""):
break
if value in (None, ""):
for candidate in candidates:
env_val = os.getenv(candidate)
if env_val not in (None, ""):
value = env_val
break
return value if value not in (None, "") else default
@dataclass
class Config:
"""配置类"""
# API密钥
deepseek_api_key: Optional[str] = None
openai_api_key: Optional[str] = None
gemini_api_key: Optional[str] = None
"""Media Engine configuration."""
llm_api_key: Optional[str] = None
llm_base_url: Optional[str] = None
llm_model_name: Optional[str] = None
llm_provider: Optional[str] = None # compatibility
bocha_api_key: Optional[str] = None
deepseek_base_url: str = "https://api.deepseek.com"
openai_base_url: Optional[str] = None
gemini_base_url: str = "https://www.chataiapi.com/v1"
# 模型配置
default_llm_provider: str = "deepseek" # deepseek、openai 或 gemini
deepseek_model: str = "deepseek-chat"
openai_model: str = "gpt-4o-mini"
gemini_model: str = "gemini-2.5-pro"
# 搜索配置
search_timeout: int = 240
max_content_length: int = 20000
# Agent配置
max_reflections: int = 2
max_paragraphs: int = 5
# 输出配置
output_dir: str = "reports"
save_intermediate_states: bool = True
def __post_init__(self):
if not self.llm_provider and self.llm_model_name:
self.llm_provider = self.llm_model_name
def validate(self) -> bool:
"""验证配置"""
# 检查必需的API密钥
if self.default_llm_provider == "deepseek" and not self.deepseek_api_key:
print("错误: DeepSeek API Key未设置")
if not self.llm_api_key:
print("错误: Media Engine LLM API Key 未设置 (MEDIA_ENGINE_API_KEY)。")
return False
if self.default_llm_provider == "openai" and not self.openai_api_key:
print("错误: OpenAI API Key未设置")
if not self.llm_model_name:
print("错误: Media Engine 模型名称未设置 (MEDIA_ENGINE_MODEL_NAME)。")
return False
if self.default_llm_provider == "gemini" and not self.gemini_api_key:
print("错误: Gemini API Key未设置")
return False
if not self.bocha_api_key:
print("错误: Bocha API Key未设置")
print("错误: Bocha API Key 未设置 (BOCHA_WEB_SEARCH_API_KEY)。")
return False
return True
@classmethod
def from_file(cls, config_file: str) -> "Config":
"""从配置文件创建配置"""
if config_file.endswith('.py'):
# Python配置文件
if config_file.endswith(".py"):
import importlib.util
# 动态导入配置文件
spec = importlib.util.spec_from_file_location("config", config_file)
config_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(config_module)
return cls(
deepseek_api_key=getattr(config_module, "DEEPSEEK_API_KEY", None),
openai_api_key=getattr(config_module, "OPENAI_API_KEY", None),
gemini_api_key=getattr(config_module, "GEMINI_API_KEY", None),
deepseek_base_url=getattr(config_module, "DEEPSEEK_BASE_URL", "https://api.deepseek.com"),
openai_base_url=getattr(config_module, "OPENAI_BASE_URL", None),
gemini_base_url=getattr(config_module, "GEMINI_BASE_URL", "https://www.chataiapi.com/v1"),
bocha_api_key=getattr(config_module, "BOCHA_API_KEY", None),
default_llm_provider=getattr(config_module, "DEFAULT_LLM_PROVIDER", "deepseek"),
deepseek_model=getattr(config_module, "DEEPSEEK_MODEL", "deepseek-chat"),
openai_model=getattr(config_module, "OPENAI_MODEL", "gpt-4o-mini"),
gemini_model=getattr(config_module, "GEMINI_MODEL", "gemini-2.5-pro"),
search_timeout=getattr(config_module, "SEARCH_TIMEOUT", 240),
max_content_length=getattr(config_module, "SEARCH_CONTENT_MAX_LENGTH", 20000),
max_reflections=getattr(config_module, "MAX_REFLECTIONS", 2),
max_paragraphs=getattr(config_module, "MAX_PARAGRAPHS", 5),
output_dir=getattr(config_module, "OUTPUT_DIR", "reports"),
save_intermediate_states=getattr(config_module, "SAVE_INTERMEDIATE_STATES", True)
)
else:
# .env格式配置文件
config_dict = {}
if os.path.exists(config_file):
with open(config_file, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
config_dict[key.strip()] = value.strip()
return cls(
deepseek_api_key=config_dict.get("DEEPSEEK_API_KEY"),
openai_api_key=config_dict.get("OPENAI_API_KEY"),
gemini_api_key=config_dict.get("GEMINI_API_KEY"),
deepseek_base_url=config_dict.get("DEEPSEEK_BASE_URL", "https://api.deepseek.com"),
openai_base_url=config_dict.get("OPENAI_BASE_URL"),
gemini_base_url=config_dict.get("GEMINI_BASE_URL", "https://www.chataiapi.com/v1"),
bocha_api_key=config_dict.get("BOCHA_API_KEY"),
default_llm_provider=config_dict.get("DEFAULT_LLM_PROVIDER", "deepseek"),
deepseek_model=config_dict.get("DEEPSEEK_MODEL", "deepseek-chat"),
openai_model=config_dict.get("OPENAI_MODEL", "gpt-4o-mini"),
gemini_model=config_dict.get("GEMINI_MODEL", "gemini-2.5-pro"),
search_timeout=int(config_dict.get("SEARCH_TIMEOUT", "240")),
max_content_length=int(config_dict.get("SEARCH_CONTENT_MAX_LENGTH", "20000")),
max_reflections=int(config_dict.get("MAX_REFLECTIONS", "2")),
max_paragraphs=int(config_dict.get("MAX_PARAGRAPHS", "5")),
output_dir=config_dict.get("OUTPUT_DIR", "reports"),
save_intermediate_states=config_dict.get("SAVE_INTERMEDIATE_STATES", "true").lower() == "true"
llm_api_key=_get_value(config_module, "MEDIA_ENGINE_API_KEY"),
llm_base_url=_get_value(config_module, "MEDIA_ENGINE_BASE_URL"),
llm_model_name=_get_value(config_module, "MEDIA_ENGINE_MODEL_NAME"),
bocha_api_key=_get_value(
config_module,
"BOCHA_WEB_SEARCH_API_KEY",
None,
"BOCHA_API_KEY",
),
search_timeout=int(_get_value(config_module, "SEARCH_TIMEOUT", 240)),
max_content_length=int(_get_value(config_module, "SEARCH_CONTENT_MAX_LENGTH", 20000)),
max_reflections=int(_get_value(config_module, "MAX_REFLECTIONS", 2)),
max_paragraphs=int(_get_value(config_module, "MAX_PARAGRAPHS", 5)),
output_dir=_get_value(config_module, "OUTPUT_DIR", "reports"),
save_intermediate_states=str(
_get_value(config_module, "SAVE_INTERMEDIATE_STATES", "true")
).lower()
in ("true", "1", "yes"),
)
config_dict = {}
if os.path.exists(config_file):
with open(config_file, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line and not line.startswith("#") and "=" in line:
key, value = line.split("=", 1)
config_dict[key.strip()] = value.strip()
return cls(
llm_api_key=_get_value(config_dict, "MEDIA_ENGINE_API_KEY"),
llm_base_url=_get_value(config_dict, "MEDIA_ENGINE_BASE_URL"),
llm_model_name=_get_value(config_dict, "MEDIA_ENGINE_MODEL_NAME"),
bocha_api_key=_get_value(
config_dict,
"BOCHA_WEB_SEARCH_API_KEY",
None,
"BOCHA_API_KEY",
),
search_timeout=int(_get_value(config_dict, "SEARCH_TIMEOUT", 240)),
max_content_length=int(_get_value(config_dict, "SEARCH_CONTENT_MAX_LENGTH", 20000)),
max_reflections=int(_get_value(config_dict, "MAX_REFLECTIONS", 2)),
max_paragraphs=int(_get_value(config_dict, "MAX_PARAGRAPHS", 5)),
output_dir=_get_value(config_dict, "OUTPUT_DIR", "reports"),
save_intermediate_states=str(
_get_value(config_dict, "SAVE_INTERMEDIATE_STATES", "true")
).lower()
in ("true", "1", "yes"),
)
def load_config(config_file: Optional[str] = None) -> Config:
"""
加载配置
Args:
config_file: 配置文件路径,如果不指定则使用默认路径
Returns:
配置对象
"""
# 确定配置文件路径
if config_file:
if not os.path.exists(config_file):
raise FileNotFoundError(f"配置文件不存在: {config_file}")
file_to_load = config_file
else:
# 尝试加载常见的配置文件
for config_path in ["config.py", "config.env", ".env"]:
if os.path.exists(config_path):
file_to_load = config_path
print(f"已找到配置文件: {config_path}")
for candidate in ("config.py", "config.env", ".env"):
if os.path.exists(candidate):
file_to_load = candidate
print(f"已找到配置文件: {candidate}")
break
else:
raise FileNotFoundError("未找到配置文件,请创建 config.py 文件")
# 创建配置对象
raise FileNotFoundError("未找到配置文件,请创建 config.py")
config = Config.from_file(file_to_load)
# 验证配置
if not config.validate():
raise ValueError("配置验失败,请检查配置文件中的API密钥")
raise ValueError("配置验失败,请检查 config.py 中的相关配置。")
return config
def print_config(config: Config):
"""打印配置信息(隐藏敏感信息)"""
print("\n=== 当前配置 ===")
print(f"LLM提供商: {config.default_llm_provider}")
print(f"DeepSeek模型: {config.deepseek_model}")
print(f"OpenAI模型: {config.openai_model}")
print(f"搜索超时: {config.search_timeout}")
print(f"最大内容长度: {config.max_content_length}")
print("\n=== Media Engine 配置 ===")
print(f"LLM 模型: {config.llm_model_name}")
print(f"LLM Base URL: {config.llm_base_url or '(默认)'}")
print(f"Bocha API Key: {'已配置' if config.bocha_api_key else '未配置'}")
print(f"搜索超时: {config.search_timeout}")
print(f"最长内容长度: {config.max_content_length}")
print(f"最大反思次数: {config.max_reflections}")
print(f"最大段落数: {config.max_paragraphs}")
print(f"输出目录: {config.output_dir}")
print(f"保存中间状态: {config.save_intermediate_states}")
# 显示API密钥状态(不显示实际密钥)
print(f"DeepSeek API Key: {'已设置' if config.deepseek_api_key else '未设置'}")
print(f"OpenAI API Key: {'已设置' if config.openai_api_key else '未设置'}")
print(f"Bocha API Key: {'已设置' if config.bocha_api_key else '未设置'}")
print("==================\n")
print(f"LLM API Key: {'已配置' if config.llm_api_key else '未配置'}")
print("========================\n")
+8 -15
View File
@@ -9,7 +9,7 @@ import re
from datetime import datetime
from typing import Optional, Dict, Any, List
from .llms import DeepSeekLLM, OpenAILLM, BaseLLM
from .llms import LLMClient
from .nodes import (
ReportStructureNode,
FirstSearchNode,
@@ -35,6 +35,7 @@ class DeepSearchAgent:
"""
# 加载配置
self.config = config or load_config()
os.environ["TAVILY_API_KEY"] = self.config.tavily_api_key or ""
# 初始化LLM客户端
self.llm_client = self._initialize_llm()
@@ -55,21 +56,13 @@ class DeepSearchAgent:
print(f"使用LLM: {self.llm_client.get_model_info()}")
print(f"搜索工具集: TavilyNewsAgency (支持6种搜索工具)")
def _initialize_llm(self) -> BaseLLM:
def _initialize_llm(self) -> LLMClient:
"""初始化LLM客户端"""
if self.config.default_llm_provider == "deepseek":
return DeepSeekLLM(
api_key=self.config.deepseek_api_key,
model_name=self.config.deepseek_model,
base_url=self.config.deepseek_base_url
)
elif self.config.default_llm_provider == "openai":
return OpenAILLM(
api_key=self.config.openai_api_key,
model_name=self.config.openai_model
)
else:
raise ValueError(f"不支持的LLM提供商: {self.config.default_llm_provider}")
return LLMClient(
api_key=self.config.llm_api_key,
model_name=self.config.llm_model_name,
base_url=self.config.llm_base_url,
)
def _initialize_nodes(self):
"""初始化处理节点"""
+3 -6
View File
@@ -1,10 +1,7 @@
"""
LLM调用模块
支持多种大语言模型的统一接口
LLM module for the Query Engine.
"""
from .base import BaseLLM
from .deepseek import DeepSeekLLM
from .openai_llm import OpenAILLM
from .base import LLMClient
__all__ = ["BaseLLM", "DeepSeekLLM", "OpenAILLM"]
__all__ = ["LLMClient"]
+78 -50
View File
@@ -1,61 +1,89 @@
"""
LLM基础抽象类
定义所有LLM实现需要遵循的接口标准
Unified OpenAI-compatible LLM client for the Query Engine, with retry support.
"""
from abc import ABC, abstractmethod
from typing import Optional, Dict, Any
import os
import sys
from typing import Any, Dict, Optional
from openai import OpenAI
current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(os.path.dirname(current_dir))
utils_dir = os.path.join(project_root, "utils")
if utils_dir not in sys.path:
sys.path.append(utils_dir)
try:
from retry_helper import with_retry, LLM_RETRY_CONFIG
except ImportError:
def with_retry(config=None):
def decorator(func):
return func
return decorator
LLM_RETRY_CONFIG = None
class BaseLLM(ABC):
"""LLM基础抽象类"""
def __init__(self, api_key: str, model_name: Optional[str] = None):
"""
初始化LLM客户端
Args:
api_key: API密钥
model_name: 模型名称,如果不指定则使用默认模型
"""
class LLMClient:
"""Minimal wrapper around the OpenAI-compatible chat completion API."""
def __init__(self, api_key: str, model_name: str, base_url: Optional[str] = None):
if not api_key:
raise ValueError("Query Engine LLM API key is required.")
if not model_name:
raise ValueError("Query Engine model name is required.")
self.api_key = api_key
self.base_url = base_url
self.model_name = model_name
@abstractmethod
self.provider = model_name
timeout_fallback = os.getenv("LLM_REQUEST_TIMEOUT") or os.getenv("QUERY_ENGINE_REQUEST_TIMEOUT") or "180"
try:
self.timeout = float(timeout_fallback)
except ValueError:
self.timeout = 180.0
client_kwargs: Dict[str, Any] = {
"api_key": api_key,
"max_retries": 0,
}
if base_url:
client_kwargs["base_url"] = base_url
self.client = OpenAI(**client_kwargs)
@with_retry(LLM_RETRY_CONFIG)
def invoke(self, system_prompt: str, user_prompt: str, **kwargs) -> str:
"""
调用LLM生成回复
Args:
system_prompt: 系统提示词
user_prompt: 用户输入
**kwargs: 其他参数,如temperature、max_tokens等
Returns:
LLM生成的回复文本
"""
pass
@abstractmethod
def get_default_model(self) -> str:
"""
获取默认模型名称
Returns:
默认模型名称
"""
pass
def validate_response(self, response: str) -> str:
"""
验证和清理响应内容
Args:
response: LLM原始响应
Returns:
清理后的响应内容
"""
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
]
allowed_keys = {"temperature", "top_p", "presence_penalty", "frequency_penalty", "stream"}
extra_params = {key: value for key, value in kwargs.items() if key in allowed_keys and value is not None}
timeout = kwargs.pop("timeout", self.timeout)
response = self.client.chat.completions.create(
model=self.model_name,
messages=messages,
timeout=timeout,
**extra_params,
)
if response.choices and response.choices[0].message:
return self.validate_response(response.choices[0].message.content)
return ""
@staticmethod
def validate_response(response: Optional[str]) -> str:
if response is None:
return ""
return response.strip()
def get_model_info(self) -> Dict[str, Any]:
return {
"provider": self.provider,
"model": self.model_name,
"api_base": self.base_url or "default",
}
-118
View File
@@ -1,118 +0,0 @@
"""
DeepSeek LLM实现
使用DeepSeek API进行文本生成
"""
import os
import sys
from typing import Optional, Dict, Any
from openai import OpenAI
from .base import BaseLLM
DEFAULT_DEEPSEEK_BASE_URL = "https://api.deepseek.com"
# 添加utils目录到Python路径并导入重试模块
try:
current_dir = os.path.dirname(os.path.abspath(__file__))
root_dir = os.path.dirname(os.path.dirname(current_dir))
utils_dir = os.path.join(root_dir, 'utils')
if utils_dir not in sys.path:
sys.path.append(utils_dir)
from retry_helper import with_retry, with_graceful_retry, LLM_RETRY_CONFIG
except ImportError:
# 如果无法导入重试模块,使用空装饰器避免报错
def with_retry(config):
def decorator(func):
return func
return decorator
LLM_RETRY_CONFIG = None
class DeepSeekLLM(BaseLLM):
"""DeepSeek LLM实现类"""
def __init__(self, api_key: Optional[str] = None, model_name: Optional[str] = None, base_url: Optional[str] = None):
"""
初始化DeepSeek客户端
Args:
api_key: DeepSeek API密钥,如果不提供则从环境变量读取
model_name: 模型名称,默认使用deepseek-chat
base_url: DeepSeek API基础地址
"""
if api_key is None:
api_key = os.getenv("DEEPSEEK_API_KEY")
if not api_key:
raise ValueError("DeepSeek API Key未找到!请设置DEEPSEEK_API_KEY环境变量或在初始化时提供")
super().__init__(api_key, model_name)
self.base_url = base_url or os.getenv("DEEPSEEK_BASE_URL") or DEFAULT_DEEPSEEK_BASE_URL
# 初始化OpenAI客户端,使用DeepSeek的endpoint
self.client = OpenAI(
api_key=self.api_key,
base_url=self.base_url
)
self.default_model = model_name or self.get_default_model()
def get_default_model(self) -> str:
"""获取默认模型名称"""
return "deepseek-chat"
@with_retry(LLM_RETRY_CONFIG)
def invoke(self, system_prompt: str, user_prompt: str, **kwargs) -> str:
"""
调用DeepSeek API生成回复
Args:
system_prompt: 系统提示词
user_prompt: 用户输入
**kwargs: 其他参数,如temperature、max_tokens等
Returns:
DeepSeek生成的回复文本
"""
try:
# 构建消息
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
# 设置默认参数
params = {
"model": self.default_model,
"messages": messages,
"temperature": kwargs.get("temperature", 0.7),
"max_tokens": kwargs.get("max_tokens", 8192), # 提高到30000以支持一万字报告
"stream": False
}
# 调用API
response = self.client.chat.completions.create(**params)
# 提取回复内容
if response.choices and response.choices[0].message:
content = response.choices[0].message.content
return self.validate_response(content)
else:
return ""
except Exception as e:
print(f"DeepSeek API调用错误: {str(e)}")
raise e
def get_model_info(self) -> Dict[str, Any]:
"""
获取当前模型信息
Returns:
模型信息字典
"""
return {
"provider": "DeepSeek",
"model": self.default_model,
"api_base": self.base_url
}
-108
View File
@@ -1,108 +0,0 @@
"""
OpenAI LLM实现
使用OpenAI API进行文本生成
"""
import os
import sys
from typing import Optional, Dict, Any
from openai import OpenAI
from .base import BaseLLM
# 添加utils目录到Python路径并导入重试模块
try:
current_dir = os.path.dirname(os.path.abspath(__file__))
root_dir = os.path.dirname(os.path.dirname(current_dir))
utils_dir = os.path.join(root_dir, 'utils')
if utils_dir not in sys.path:
sys.path.append(utils_dir)
from retry_helper import with_retry, with_graceful_retry, LLM_RETRY_CONFIG
except ImportError:
# 如果无法导入重试模块,使用空装饰器避免报错
def with_retry(config):
def decorator(func):
return func
return decorator
LLM_RETRY_CONFIG = None
class OpenAILLM(BaseLLM):
"""OpenAI LLM实现类"""
def __init__(self, api_key: Optional[str] = None, model_name: Optional[str] = None):
"""
初始化OpenAI客户端
Args:
api_key: OpenAI API密钥,如果不提供则从环境变量读取
model_name: 模型名称,默认使用gpt-4o-mini
"""
if api_key is None:
api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
raise ValueError("OpenAI API Key未找到!请设置OPENAI_API_KEY环境变量或在初始化时提供")
super().__init__(api_key, model_name)
# 初始化OpenAI客户端
self.client = OpenAI(api_key=self.api_key)
self.default_model = model_name or self.get_default_model()
def get_default_model(self) -> str:
"""获取默认模型名称"""
return "gpt-4o-mini"
@with_retry(LLM_RETRY_CONFIG)
def invoke(self, system_prompt: str, user_prompt: str, **kwargs) -> str:
"""
调用OpenAI API生成回复
Args:
system_prompt: 系统提示词
user_prompt: 用户输入
**kwargs: 其他参数,如temperature、max_tokens等
Returns:
OpenAI生成的回复文本
"""
try:
# 构建消息
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
# 设置默认参数
params = {
"model": self.default_model,
"messages": messages,
"temperature": kwargs.get("temperature", 0.7),
"max_tokens": kwargs.get("max_tokens", 8192) # 提高到30000以支持一万字报告
}
# 调用API
response = self.client.chat.completions.create(**params)
# 提取回复内容
if response.choices and response.choices[0].message:
content = response.choices[0].message.content
return self.validate_response(content)
else:
return ""
except Exception as e:
print(f"OpenAI API调用错误: {str(e)}")
raise e
def get_model_info(self) -> Dict[str, Any]:
"""
获取当前模型信息
Returns:
模型信息字典
"""
return {
"provider": "OpenAI",
"model": self.default_model,
"api_base": "https://api.openai.com"
}
+2 -2
View File
@@ -5,14 +5,14 @@
from abc import ABC, abstractmethod
from typing import Any, Dict, Optional
from ..llms.base import BaseLLM
from ..llms.base import LLMClient
from ..state.state import State
class BaseNode(ABC):
"""节点基类"""
def __init__(self, llm_client: BaseLLM, node_name: str = ""):
def __init__(self, llm_client: LLMClient, node_name: str = ""):
"""
初始化节点
+3 -4
View File
@@ -67,11 +67,10 @@ class ReportFormattingNode(BaseNode):
self.log_info("正在格式化最终报告")
# 调用LLM,传递更大的max_tokens以支持长文本报告
# 调用LLM生成Markdown格式
response = self.llm_client.invoke(
SYSTEM_PROMPT_REPORT_FORMATTING,
message,
max_tokens=8192 # 支持一万字的报告输出
SYSTEM_PROMPT_REPORT_FORMATTING,
message,
)
# 处理响应
+4 -6
View File
@@ -98,11 +98,10 @@ class FirstSummaryNode(StateMutationNode):
self.log_info("正在生成首次段落总结")
# 调用LLM,增加max_tokens以支持更长的总结
# 调用LLM生成总结
response = self.llm_client.invoke(
SYSTEM_PROMPT_FIRST_SUMMARY,
SYSTEM_PROMPT_FIRST_SUMMARY,
message,
max_tokens=8192 # 支持更长的总结内容
)
# 处理响应
@@ -267,11 +266,10 @@ class ReflectionSummaryNode(StateMutationNode):
self.log_info("正在生成反思总结")
# 调用LLM,增加max_tokens以支持更长的总结
# 调用LLM生成总结
response = self.llm_client.invoke(
SYSTEM_PROMPT_REFLECTION_SUMMARY,
SYSTEM_PROMPT_REFLECTION_SUMMARY,
message,
max_tokens=8192 # 支持更长的总结内容
)
# 处理响应
+99 -116
View File
@@ -1,6 +1,5 @@
"""
配置管理模块
处理环境变量和配置参数
Configuration management module for the Query Engine.
"""
import os
@@ -8,161 +7,145 @@ from dataclasses import dataclass
from typing import Optional
def _get_value(source, key: str, default=None, *fallback_keys: str):
candidates = (key,) + fallback_keys
value = None
for candidate in candidates:
if isinstance(source, dict):
value = source.get(candidate)
else:
value = getattr(source, candidate, None)
if value not in (None, ""):
break
if value in (None, ""):
for candidate in candidates:
env_val = os.getenv(candidate)
if env_val not in (None, ""):
value = env_val
break
return value if value not in (None, "") else default
@dataclass
class Config:
"""配置类"""
# API密钥
deepseek_api_key: Optional[str] = None
openai_api_key: Optional[str] = None
"""Query Engine configuration."""
llm_api_key: Optional[str] = None
llm_base_url: Optional[str] = None
llm_model_name: Optional[str] = None
llm_provider: Optional[str] = None # compatibility
tavily_api_key: Optional[str] = None
deepseek_base_url: str = "https://api.deepseek.com"
openai_base_url: Optional[str] = None
# 模型配置
default_llm_provider: str = "deepseek" # deepseek 或 openai
deepseek_model: str = "deepseek-chat"
openai_model: str = "gpt-4o-mini"
# 搜索配置
search_timeout: int = 240
max_content_length: int = 20000
# Agent配置
max_reflections: int = 2
max_paragraphs: int = 5
# 输出配置
max_search_results: int = 20
output_dir: str = "reports"
save_intermediate_states: bool = True
def __post_init__(self):
if not self.llm_provider and self.llm_model_name:
self.llm_provider = self.llm_model_name
def validate(self) -> bool:
"""验证配置"""
# 检查必需的API密钥
if self.default_llm_provider == "deepseek" and not self.deepseek_api_key:
print("错误: DeepSeek API Key未设置")
if not self.llm_api_key:
print("错误: Query Engine LLM API Key 未设置 (QUERY_ENGINE_API_KEY)。")
return False
if self.default_llm_provider == "openai" and not self.openai_api_key:
print("错误: OpenAI API Key未设置")
if not self.llm_model_name:
print("错误: Query Engine 模型名称未设置 (QUERY_ENGINE_MODEL_NAME)。")
return False
if not self.tavily_api_key:
print("错误: Tavily API Key未设置")
print("错误: Tavily API Key 未设置 (TAVILY_API_KEY)。")
return False
return True
@classmethod
def from_file(cls, config_file: str) -> "Config":
"""从配置文件创建配置"""
if config_file.endswith('.py'):
# Python配置文件
if config_file.endswith(".py"):
import importlib.util
# 动态导入配置文件
spec = importlib.util.spec_from_file_location("config", config_file)
config_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(config_module)
return cls(
deepseek_api_key=getattr(config_module, "DEEPSEEK_API_KEY", None),
openai_api_key=getattr(config_module, "OPENAI_API_KEY", None),
tavily_api_key=getattr(config_module, "TAVILY_API_KEY", None),
deepseek_base_url=getattr(config_module, "DEEPSEEK_BASE_URL", "https://api.deepseek.com"),
openai_base_url=getattr(config_module, "OPENAI_BASE_URL", None),
default_llm_provider=getattr(config_module, "DEFAULT_LLM_PROVIDER", "deepseek"),
deepseek_model=getattr(config_module, "DEEPSEEK_MODEL", "deepseek-chat"),
openai_model=getattr(config_module, "OPENAI_MODEL", "gpt-4o-mini"),
search_timeout=getattr(config_module, "SEARCH_TIMEOUT", 240),
max_content_length=getattr(config_module, "SEARCH_CONTENT_MAX_LENGTH", 20000),
max_reflections=getattr(config_module, "MAX_REFLECTIONS", 2),
max_paragraphs=getattr(config_module, "MAX_PARAGRAPHS", 5),
output_dir=getattr(config_module, "OUTPUT_DIR", "reports"),
save_intermediate_states=getattr(config_module, "SAVE_INTERMEDIATE_STATES", True)
)
else:
# .env格式配置文件
config_dict = {}
if os.path.exists(config_file):
with open(config_file, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
config_dict[key.strip()] = value.strip()
return cls(
deepseek_api_key=config_dict.get("DEEPSEEK_API_KEY"),
openai_api_key=config_dict.get("OPENAI_API_KEY"),
tavily_api_key=config_dict.get("TAVILY_API_KEY"),
deepseek_base_url=config_dict.get("DEEPSEEK_BASE_URL", "https://api.deepseek.com"),
openai_base_url=config_dict.get("OPENAI_BASE_URL"),
default_llm_provider=config_dict.get("DEFAULT_LLM_PROVIDER", "deepseek"),
deepseek_model=config_dict.get("DEEPSEEK_MODEL", "deepseek-chat"),
openai_model=config_dict.get("OPENAI_MODEL", "gpt-4o-mini"),
search_timeout=int(config_dict.get("SEARCH_TIMEOUT", "240")),
max_content_length=int(config_dict.get("SEARCH_CONTENT_MAX_LENGTH", "20000")),
max_reflections=int(config_dict.get("MAX_REFLECTIONS", "2")),
max_paragraphs=int(config_dict.get("MAX_PARAGRAPHS", "5")),
output_dir=config_dict.get("OUTPUT_DIR", "reports"),
save_intermediate_states=config_dict.get("SAVE_INTERMEDIATE_STATES", "true").lower() == "true"
llm_api_key=_get_value(config_module, "QUERY_ENGINE_API_KEY"),
llm_base_url=_get_value(config_module, "QUERY_ENGINE_BASE_URL"),
llm_model_name=_get_value(config_module, "QUERY_ENGINE_MODEL_NAME"),
tavily_api_key=_get_value(config_module, "TAVILY_API_KEY"),
search_timeout=int(_get_value(config_module, "SEARCH_TIMEOUT", 240)),
max_content_length=int(_get_value(config_module, "SEARCH_CONTENT_MAX_LENGTH", 20000)),
max_reflections=int(_get_value(config_module, "MAX_REFLECTIONS", 2)),
max_paragraphs=int(_get_value(config_module, "MAX_PARAGRAPHS", 5)),
max_search_results=int(_get_value(config_module, "MAX_SEARCH_RESULTS", 20)),
output_dir=_get_value(config_module, "OUTPUT_DIR", "reports"),
save_intermediate_states=str(
_get_value(config_module, "SAVE_INTERMEDIATE_STATES", "true")
).lower()
in ("true", "1", "yes"),
)
config_dict = {}
if os.path.exists(config_file):
with open(config_file, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line and not line.startswith("#") and "=" in line:
key, value = line.split("=", 1)
config_dict[key.strip()] = value.strip()
return cls(
llm_api_key=_get_value(config_dict, "QUERY_ENGINE_API_KEY"),
llm_base_url=_get_value(config_dict, "QUERY_ENGINE_BASE_URL"),
llm_model_name=_get_value(config_dict, "QUERY_ENGINE_MODEL_NAME"),
tavily_api_key=_get_value(config_dict, "TAVILY_API_KEY"),
search_timeout=int(_get_value(config_dict, "SEARCH_TIMEOUT", 240)),
max_content_length=int(_get_value(config_dict, "SEARCH_CONTENT_MAX_LENGTH", 20000)),
max_reflections=int(_get_value(config_dict, "MAX_REFLECTIONS", 2)),
max_paragraphs=int(_get_value(config_dict, "MAX_PARAGRAPHS", 5)),
max_search_results=int(_get_value(config_dict, "MAX_SEARCH_RESULTS", 20)),
output_dir=_get_value(config_dict, "OUTPUT_DIR", "reports"),
save_intermediate_states=str(
_get_value(config_dict, "SAVE_INTERMEDIATE_STATES", "true")
).lower()
in ("true", "1", "yes"),
)
def load_config(config_file: Optional[str] = None) -> Config:
"""
加载配置
Args:
config_file: 配置文件路径,如果不指定则使用默认路径
Returns:
配置对象
"""
# 确定配置文件路径
if config_file:
if not os.path.exists(config_file):
raise FileNotFoundError(f"配置文件不存在: {config_file}")
file_to_load = config_file
else:
# 尝试加载常见的配置文件
for config_path in ["config.py", "config.env", ".env"]:
if os.path.exists(config_path):
file_to_load = config_path
print(f"已找到配置文件: {config_path}")
for candidate in ("config.py", "config.env", ".env"):
if os.path.exists(candidate):
file_to_load = candidate
print(f"已找到配置文件: {candidate}")
break
else:
raise FileNotFoundError("未找到配置文件,请创建 config.py 文件")
# 创建配置对象
raise FileNotFoundError("未找到配置文件,请创建 config.py")
config = Config.from_file(file_to_load)
# 验证配置
if not config.validate():
raise ValueError("配置验失败,请检查配置文件中的API密钥")
raise ValueError("配置验失败,请检查 config.py 中的相关配置。")
return config
def print_config(config: Config):
"""打印配置信息(隐藏敏感信息)"""
print("\n=== 当前配置 ===")
print(f"LLM提供商: {config.default_llm_provider}")
print(f"DeepSeek模型: {config.deepseek_model}")
print(f"OpenAI模型: {config.openai_model}")
print(f"大搜索结果数: {config.max_search_results}")
print(f"搜索超时: {config.search_timeout}")
print(f"最大内容长度: {config.max_content_length}")
print("\n=== Query Engine 配置 ===")
print(f"LLM 模型: {config.llm_model_name}")
print(f"LLM Base URL: {config.llm_base_url or '(默认)'}")
print(f"Tavily API Key: {'已配置' if config.tavily_api_key else '未配置'}")
print(f"搜索超时: {config.search_timeout}")
print(f"长内容长度: {config.max_content_length}")
print(f"最大反思次数: {config.max_reflections}")
print(f"最大段落数: {config.max_paragraphs}")
print(f"最大搜索结果数: {config.max_search_results}")
print(f"输出目录: {config.output_dir}")
print(f"保存中间状态: {config.save_intermediate_states}")
# 显示API密钥状态(不显示实际密钥)
print(f"DeepSeek API Key: {'已设置' if config.deepseek_api_key else '未设置'}")
print(f"OpenAI API Key: {'已设置' if config.openai_api_key else '未设置'}")
print(f"Tavily API Key: {'已设置' if config.tavily_api_key else '未设置'}")
print("==================\n")
print(f"LLM API Key: {'已配置' if config.llm_api_key else '未配置'}")
print("========================\n")
+7 -11
View File
@@ -9,7 +9,7 @@ import logging
from datetime import datetime
from typing import Optional, Dict, Any, List
from .llms import GeminiLLM, BaseLLM
from .llms import LLMClient
from .nodes import (
TemplateSelectionNode,
HTMLGenerationNode
@@ -186,17 +186,13 @@ class ReportAgent:
}
self.file_baseline.initialize_baseline(directories)
def _initialize_llm(self) -> BaseLLM:
def _initialize_llm(self) -> LLMClient:
"""初始化LLM客户端"""
if self.config.default_llm_provider == "gemini":
return GeminiLLM(
api_key=self.config.gemini_api_key,
model_name=self.config.gemini_model,
base_url=self.config.gemini_base_url,
config=self.config # 传入配置对象以支持动态超时设置
)
else:
raise ValueError(f"不支持的LLM提供商: {self.config.default_llm_provider}")
return LLMClient(
api_key=self.config.llm_api_key,
model_name=self.config.llm_model_name,
base_url=self.config.llm_base_url,
)
def _initialize_nodes(self):
"""初始化处理节点"""
+3 -5
View File
@@ -1,9 +1,7 @@
"""
Report Engine LLM模块
包含各种大语言模型的接口实现
LLM module for the Report Engine.
"""
from .base import BaseLLM
from .gemini_llm import GeminiLLM
from .base import LLMClient
__all__ = ["BaseLLM", "GeminiLLM"]
__all__ = ["LLMClient"]
+80 -86
View File
@@ -1,95 +1,89 @@
"""
Report Engine LLM基类
定义所有LLM实现的基础接口
Unified OpenAI-compatible LLM client for the Report Engine, with retry support.
"""
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional
import os
import sys
from typing import Any, Dict, Optional
from openai import OpenAI
current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(os.path.dirname(current_dir))
utils_dir = os.path.join(project_root, "utils")
if utils_dir not in sys.path:
sys.path.append(utils_dir)
try:
from retry_helper import with_retry, LLM_RETRY_CONFIG
except ImportError:
def with_retry(config=None):
def decorator(func):
return func
return decorator
LLM_RETRY_CONFIG = None
class BaseLLM(ABC):
"""LLM基类"""
def __init__(self, api_key: str, model_name: Optional[str] = None):
"""
初始化LLM客户端
Args:
api_key: API密钥
model_name: 模型名称
"""
class LLMClient:
"""Minimal wrapper around the OpenAI-compatible chat completion API."""
def __init__(self, api_key: str, model_name: str, base_url: Optional[str] = None):
if not api_key:
raise ValueError("Report Engine LLM API key is required.")
if not model_name:
raise ValueError("Report Engine model name is required.")
self.api_key = api_key
self.base_url = base_url
self.model_name = model_name
@abstractmethod
self.provider = model_name
timeout_fallback = os.getenv("LLM_REQUEST_TIMEOUT") or os.getenv("REPORT_ENGINE_REQUEST_TIMEOUT") or "180"
try:
self.timeout = float(timeout_fallback)
except ValueError:
self.timeout = 300.0
client_kwargs: Dict[str, Any] = {
"api_key": api_key,
"max_retries": 0,
}
if base_url:
client_kwargs["base_url"] = base_url
self.client = OpenAI(**client_kwargs)
@with_retry(LLM_RETRY_CONFIG)
def invoke(self, system_prompt: str, user_prompt: str, **kwargs) -> str:
"""
调用LLM生成回复
Args:
system_prompt: 系统提示词
user_prompt: 用户输入
**kwargs: 其他参数
Returns:
生成的回复文本
"""
pass
@abstractmethod
def get_model_info(self) -> Dict[str, Any]:
"""
获取当前模型信息
Returns:
模型信息字典
"""
pass
@abstractmethod
def get_default_model(self) -> str:
"""
获取默认模型名称
Returns:
默认模型名称
"""
pass
def validate_response(self, response: str) -> str:
"""
验证和清理响应内容
Args:
response: 原始响应
Returns:
清理后的响应
"""
if not response:
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
]
allowed_keys = {"temperature", "top_p", "presence_penalty", "frequency_penalty", "stream"}
extra_params = {key: value for key, value in kwargs.items() if key in allowed_keys and value is not None}
timeout = kwargs.pop("timeout", self.timeout)
response = self.client.chat.completions.create(
model=self.model_name,
messages=messages,
timeout=timeout,
**extra_params,
)
if response.choices and response.choices[0].message:
return self.validate_response(response.choices[0].message.content)
return ""
@staticmethod
def validate_response(response: Optional[str]) -> str:
if response is None:
return ""
# 移除多余的空白字符
response = response.strip()
# 确保响应不为空
if not response:
return "抱歉,生成的内容为空。"
return response
def estimate_tokens(self, text: str) -> int:
"""
估算文本的token数量简单实现
Args:
text: 输入文本
Returns:
估算的token数量
"""
# 简单估算:中文字符按1.5个token计算,英文单词按1个token计算
chinese_chars = len([c for c in text if '\u4e00' <= c <= '\u9fff'])
english_words = len(text.split()) - chinese_chars
return int(chinese_chars * 1.5 + english_words)
return response.strip()
def get_model_info(self) -> Dict[str, Any]:
return {
"provider": self.provider,
"model": self.model_name,
"api_base": self.base_url or "default",
}
-203
View File
@@ -1,203 +0,0 @@
"""
Report Engine Gemini LLM实现
使用Gemini 2.5-pro中转API进行文本生成
"""
import os
import sys
from typing import Optional, Dict, Any
from openai import OpenAI
from .base import BaseLLM
DEFAULT_GEMINI_BASE_URL = "https://www.chataiapi.com/v1"
# 导入根目录的config
try:
current_dir = os.path.dirname(os.path.abspath(__file__))
root_dir = os.path.dirname(os.path.dirname(current_dir))
if root_dir not in sys.path:
sys.path.append(root_dir)
import config
except ImportError:
config = None
# 添加utils目录到Python路径并导入重试模块
try:
if root_dir:
utils_dir = os.path.join(root_dir, 'utils')
if utils_dir not in sys.path:
sys.path.append(utils_dir)
from retry_helper import with_retry, with_graceful_retry, LLM_RETRY_CONFIG, RetryConfig
# 创建动态重试配置生成函数
def create_report_retry_config(config=None):
"""创建ReportEngine专用的重试配置,适应7分钟平均生成时间"""
return RetryConfig(
max_retries=config.max_retries if config and hasattr(config, 'max_retries') else 8,
initial_delay=8.0, # 初始延迟增加到8秒,适应长时间生成
backoff_factor=2.0, # 保持2倍退避
max_delay=config.max_retry_delay if config and hasattr(config, 'max_retry_delay') else 180.0
)
# 创建默认配置用于模块导入时的向后兼容
REPORT_LLM_RETRY_CONFIG = create_report_retry_config()
except ImportError:
# 如果无法导入重试模块,使用空装饰器避免报错
def with_retry(config):
def decorator(func):
return func
return decorator
LLM_RETRY_CONFIG = None
REPORT_LLM_RETRY_CONFIG = None
class GeminiLLM(BaseLLM):
"""Report Engine Gemini LLM实现类"""
def __init__(self, api_key: Optional[str] = None, model_name: Optional[str] = None, base_url: Optional[str] = None, config=None):
"""
初始化Gemini客户端
Args:
api_key: Gemini API密钥如果不提供则从config或环境变量读取
model_name: 模型名称默认使用gemini-2.5-pro
base_url: Gemini API基础地址
config: 配置对象用于获取超时设置
"""
if api_key is None:
# 优先从根目录config读取
if config and hasattr(config, 'GEMINI_API_KEY'):
api_key = config.GEMINI_API_KEY
else:
# 备选方案:从环境变量读取
api_key = os.getenv("GEMINI_API_KEY")
if not api_key:
raise ValueError("Gemini API Key未找到!请在config.py中设置GEMINI_API_KEY或设置环境变量")
super().__init__(api_key, model_name)
# 存储配置对象
self.config = config
# 从配置获取超时时间,默认15分钟(适应7分钟平均生成时间)
timeout = config.api_timeout if config and hasattr(config, 'api_timeout') else 900.0
self.base_url = (
base_url
or (getattr(self.config, 'gemini_base_url', None) if self.config else None)
or os.getenv('GEMINI_BASE_URL')
or DEFAULT_GEMINI_BASE_URL
)
# 创建针对此实例的重试配置
self.retry_config = create_report_retry_config(config)
# 初始化OpenAI客户端,使用Gemini的中转endpoint
# 专门为报告生成设置长超时(15分钟),适应7分钟平均生成时间
self.client = OpenAI(
api_key=self.api_key,
base_url=self.base_url,
timeout=timeout
)
self.default_model = model_name or self.get_default_model()
def get_default_model(self) -> str:
"""获取默认模型名称"""
return "gemini-2.5-pro"
def _make_api_call(self, system_prompt: str, user_prompt: str, **kwargs) -> str:
"""
内部API调用方法
Args:
system_prompt: 系统提示词
user_prompt: 用户输入
**kwargs: 其他参数
Returns:
API响应内容
"""
# 构建消息
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
# 设置默认参数
params = {
"model": self.default_model,
"messages": messages,
"temperature": kwargs.get("temperature", 0.7),
"max_tokens": kwargs.get("max_tokens", 50000),
"stream": False
}
# 调用API
response = self.client.chat.completions.create(**params)
# 提取回复内容
if response.choices and response.choices[0].message:
content = response.choices[0].message.content
return self.validate_response(content)
else:
return ""
def invoke(self, system_prompt: str, user_prompt: str, **kwargs) -> str:
"""
调用Gemini API生成回复带动态重试配置
Args:
system_prompt: 系统提示词
user_prompt: 用户输入
**kwargs: 其他参数如temperaturemax_tokens等
Returns:
Gemini生成的回复文本
"""
import time
last_exception = None
for attempt in range(self.retry_config.max_retries + 1):
try:
result = self._make_api_call(system_prompt, user_prompt, **kwargs)
if attempt > 0:
print(f"Report Engine Gemini API在第 {attempt + 1} 次尝试后成功")
return result
except Exception as e:
last_exception = e
if attempt == self.retry_config.max_retries:
print(f"Report Engine Gemini API在 {self.retry_config.max_retries + 1} 次尝试后仍然失败")
print(f"最终错误: {str(e)}")
raise e
# 计算延迟时间
delay = min(
self.retry_config.initial_delay * (self.retry_config.backoff_factor ** attempt),
self.retry_config.max_delay
)
print(f"Report Engine Gemini API第 {attempt + 1} 次尝试失败: {str(e)}")
print(f"将在 {delay:.1f} 秒后进行第 {attempt + 2} 次尝试...")
time.sleep(delay)
# 这里不应该到达,但作为安全网
if last_exception:
raise last_exception
def get_model_info(self) -> Dict[str, Any]:
"""
获取当前模型信息
Returns:
模型信息字典
"""
return {
"provider": "Gemini",
"model": self.default_model,
"api_base": self.base_url,
"purpose": "Report Generation"
}
+2 -2
View File
@@ -6,14 +6,14 @@ Report Engine节点基类
import logging
from abc import ABC, abstractmethod
from typing import Any, Dict, Optional
from ..llms.base import BaseLLM
from ..llms.base import LLMClient
from ..state.state import ReportState
class BaseNode(ABC):
"""节点基类"""
def __init__(self, llm_client: BaseLLM, node_name: str = ""):
def __init__(self, llm_client: LLMClient, node_name: str = ""):
"""
初始化节点
+2 -2
View File
@@ -8,7 +8,7 @@ from datetime import datetime
from typing import Dict, Any
from .base_node import StateMutationNode
from ..llms.base import BaseLLM
from ..llms.base import LLMClient
from ..state.state import ReportState
from ..prompts import SYSTEM_PROMPT_HTML_GENERATION
# 不再需要text_processing依赖
@@ -17,7 +17,7 @@ from ..prompts import SYSTEM_PROMPT_HTML_GENERATION
class HTMLGenerationNode(StateMutationNode):
"""HTML生成处理节点"""
def __init__(self, llm_client: BaseLLM):
def __init__(self, llm_client: LLMClient):
"""
初始化HTML生成节点
+104 -103
View File
@@ -1,6 +1,5 @@
"""
Report Engine配置管理模块
处理环境变量和配置参数
Configuration management module for the Report Engine.
"""
import os
@@ -8,144 +7,146 @@ from dataclasses import dataclass
from typing import Optional
def _get_value(source, key: str, default=None, *fallback_keys: str):
candidates = (key,) + fallback_keys
value = None
for candidate in candidates:
if isinstance(source, dict):
value = source.get(candidate)
else:
value = getattr(source, candidate, None)
if value not in (None, ""):
break
if value in (None, ""):
for candidate in candidates:
env_val = os.getenv(candidate)
if env_val not in (None, ""):
value = env_val
break
return value if value not in (None, "") else default
@dataclass
class Config:
"""Report Engine配置类"""
# API密钥
gemini_api_key: Optional[str] = None
gemini_base_url: str = "https://www.chataiapi.com/v1"
# 模型配置
default_llm_provider: str = "gemini"
gemini_model: str = "gemini-2.5-pro"
# 报告配置
"""Report Engine configuration."""
llm_api_key: Optional[str] = None
llm_base_url: Optional[str] = None
llm_model_name: Optional[str] = None
llm_provider: Optional[str] = None # compatibility
max_content_length: int = 200000
output_dir: str = "final_reports"
template_dir: str = "ReportEngine/report_template"
# 超时配置 - 专门为长报告生成优化(平均生成时间7分钟)
api_timeout: float = 900.0 # API调用超时时间(秒),设置为15分钟,适应7分钟平均生成时间
max_retry_delay: float = 180.0 # 最大重试延迟(秒),设置为3分钟
max_retries: int = 8 # 最大重试次数,增加到8次
# 日志配置
api_timeout: float = 900.0
max_retry_delay: float = 180.0
max_retries: int = 8
log_file: str = "logs/report.log"
# HTML导出配置
enable_pdf_export: bool = True
chart_style: str = "modern" # modern, classic, minimal
chart_style: str = "modern"
def __post_init__(self):
if not self.llm_provider and self.llm_model_name:
self.llm_provider = self.llm_model_name
def validate(self) -> bool:
"""验证配置"""
if not self.gemini_api_key:
print("错误: Gemini API Key未设置")
if not self.llm_api_key:
print("错误: Report Engine LLM API Key 未设置 (REPORT_ENGINE_API_KEY)。")
return False
if not self.llm_model_name:
print("错误: Report Engine 模型名称未设置 (REPORT_ENGINE_MODEL_NAME)。")
return False
return True
@classmethod
def from_file(cls, config_file: str) -> "Config":
"""从配置文件创建配置"""
if config_file.endswith('.py'):
# Python配置文件
if config_file.endswith(".py"):
import importlib.util
# 动态导入配置文件
spec = importlib.util.spec_from_file_location("config", config_file)
config_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(config_module)
return cls(
gemini_api_key=getattr(config_module, "GEMINI_API_KEY", None),
gemini_base_url=getattr(config_module, "GEMINI_BASE_URL", "https://www.chataiapi.com/v1"),
default_llm_provider=getattr(config_module, "DEFAULT_LLM_PROVIDER", "gemini"),
gemini_model=getattr(config_module, "GEMINI_MODEL", "gemini-2.5-pro"),
max_content_length=getattr(config_module, "MAX_CONTENT_LENGTH", 200000),
output_dir=getattr(config_module, "REPORT_OUTPUT_DIR", "final_reports"),
template_dir=getattr(config_module, "TEMPLATE_DIR", "ReportEngine/report_template"),
api_timeout=getattr(config_module, "REPORT_API_TIMEOUT", 900.0),
max_retry_delay=getattr(config_module, "REPORT_MAX_RETRY_DELAY", 180.0),
max_retries=getattr(config_module, "REPORT_MAX_RETRIES", 8),
log_file=getattr(config_module, "REPORT_LOG_FILE", "logs/report.log"),
enable_pdf_export=getattr(config_module, "ENABLE_PDF_EXPORT", True),
chart_style=getattr(config_module, "CHART_STYLE", "modern")
)
else:
# .env格式配置文件
config_dict = {}
if os.path.exists(config_file):
with open(config_file, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
config_dict[key.strip()] = value.strip()
return cls(
gemini_api_key=config_dict.get("GEMINI_API_KEY"),
gemini_base_url=config_dict.get("GEMINI_BASE_URL", "https://www.chataiapi.com/v1"),
default_llm_provider=config_dict.get("DEFAULT_LLM_PROVIDER", "gemini"),
gemini_model=config_dict.get("GEMINI_MODEL", "gemini-2.5-pro"),
max_content_length=int(config_dict.get("MAX_CONTENT_LENGTH", "200000")),
output_dir=config_dict.get("REPORT_OUTPUT_DIR", "final_reports"),
template_dir=config_dict.get("TEMPLATE_DIR", "ReportEngine/report_template"),
api_timeout=float(config_dict.get("REPORT_API_TIMEOUT", "900.0")),
max_retry_delay=float(config_dict.get("REPORT_MAX_RETRY_DELAY", "180.0")),
max_retries=int(config_dict.get("REPORT_MAX_RETRIES", "8")),
log_file=config_dict.get("REPORT_LOG_FILE", "logs/report.log"),
enable_pdf_export=config_dict.get("ENABLE_PDF_EXPORT", "true").lower() == "true",
chart_style=config_dict.get("CHART_STYLE", "modern")
llm_api_key=_get_value(config_module, "REPORT_ENGINE_API_KEY"),
llm_base_url=_get_value(config_module, "REPORT_ENGINE_BASE_URL"),
llm_model_name=_get_value(config_module, "REPORT_ENGINE_MODEL_NAME"),
max_content_length=int(_get_value(config_module, "MAX_CONTENT_LENGTH", 200000)),
output_dir=_get_value(config_module, "REPORT_OUTPUT_DIR", "final_reports"),
template_dir=_get_value(config_module, "TEMPLATE_DIR", "ReportEngine/report_template"),
api_timeout=float(_get_value(config_module, "REPORT_API_TIMEOUT", 900.0)),
max_retry_delay=float(_get_value(config_module, "REPORT_MAX_RETRY_DELAY", 180.0)),
max_retries=int(_get_value(config_module, "REPORT_MAX_RETRIES", 8)),
log_file=_get_value(config_module, "REPORT_LOG_FILE", "logs/report.log"),
enable_pdf_export=str(
_get_value(config_module, "ENABLE_PDF_EXPORT", "true")
).lower()
in ("true", "1", "yes"),
chart_style=_get_value(config_module, "CHART_STYLE", "modern"),
)
config_dict = {}
if os.path.exists(config_file):
with open(config_file, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line and not line.startswith("#") and "=" in line:
key, value = line.split("=", 1)
config_dict[key.strip()] = value.strip()
return cls(
llm_api_key=_get_value(config_dict, "REPORT_ENGINE_API_KEY"),
llm_base_url=_get_value(config_dict, "REPORT_ENGINE_BASE_URL"),
llm_model_name=_get_value(config_dict, "REPORT_ENGINE_MODEL_NAME"),
max_content_length=int(_get_value(config_dict, "MAX_CONTENT_LENGTH", 200000)),
output_dir=_get_value(config_dict, "REPORT_OUTPUT_DIR", "final_reports"),
template_dir=_get_value(config_dict, "TEMPLATE_DIR", "ReportEngine/report_template"),
api_timeout=float(_get_value(config_dict, "REPORT_API_TIMEOUT", 900.0)),
max_retry_delay=float(_get_value(config_dict, "REPORT_MAX_RETRY_DELAY", 180.0)),
max_retries=int(_get_value(config_dict, "REPORT_MAX_RETRIES", 8)),
log_file=_get_value(config_dict, "REPORT_LOG_FILE", "logs/report.log"),
enable_pdf_export=str(
_get_value(config_dict, "ENABLE_PDF_EXPORT", "true")
).lower()
in ("true", "1", "yes"),
chart_style=_get_value(config_dict, "CHART_STYLE", "modern"),
)
def load_config(config_file: Optional[str] = None) -> Config:
"""
加载配置
Args:
config_file: 配置文件路径如果不指定则使用默认路径
Returns:
配置对象
"""
# 确定配置文件路径
if config_file:
if not os.path.exists(config_file):
raise FileNotFoundError(f"配置文件不存在: {config_file}")
file_to_load = config_file
else:
# 尝试加载常见的配置文件
for config_path in ["config.py", "config.env", ".env"]:
if os.path.exists(config_path):
file_to_load = config_path
for candidate in ("config.py", "config.env", ".env"):
if os.path.exists(candidate):
file_to_load = candidate
print(f"已找到配置文件: {candidate}")
break
else:
raise FileNotFoundError("未找到配置文件,请创建 config.py 文件")
# 创建配置对象
raise FileNotFoundError("未找到配置文件,请创建 config.py")
config = Config.from_file(file_to_load)
# 验证配置
if not config.validate():
raise ValueError("Report Engine配置验失败,请检查配置文件中的API密钥")
raise ValueError("Report Engine 配置验失败,请检查 config.py 中的相关配置。")
return config
def print_config(config: Config):
"""打印配置信息(隐藏敏感信息)"""
print("\n=== Report Engine配置 ===")
print(f"LLM提供商: {config.default_llm_provider}")
print(f"Gemini模型: {config.gemini_model}")
print("\n=== Report Engine 配置 ===")
print(f"LLM 模型: {config.llm_model_name}")
print(f"LLM Base URL: {config.llm_base_url or '(默认)'}")
print(f"最大内容长度: {config.max_content_length}")
print(f"输出目录: {config.output_dir}")
print(f"模板目录: {config.template_dir}")
print(f"API超时时间: {config.api_timeout}{config.api_timeout/60:.1f}分钟)")
print(f"最大重试延迟: {config.max_retry_delay}{config.max_retry_delay/60:.1f}分钟)")
print(f"最大重试次数: {config.max_retries}")
print(f"API 超时时间: {config.api_timeout} ")
print(f"最大重试间隔: {config.max_retry_delay} ")
print(f"最大重试次数: {config.max_retries}")
print(f"日志文件: {config.log_file}")
print(f"PDF导出: {config.enable_pdf_export}")
print(f"PDF 导出: {config.enable_pdf_export}")
print(f"图表样式: {config.chart_style}")
print(f"Gemini API Key: {'' if config.gemini_api_key else ''}")
print(f"LLM API Key: {'' if config.llm_api_key else ''}")
print("========================\n")
+10 -18
View File
@@ -28,10 +28,9 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from InsightEngine import DeepSearchAgent, Config
from config import (
DEEPSEEK_API_KEY,
DEEPSEEK_BASE_URL,
KIMI_API_KEY,
KIMI_BASE_URL,
INSIGHT_ENGINE_API_KEY,
INSIGHT_ENGINE_BASE_URL,
INSIGHT_ENGINE_MODEL_NAME,
DB_HOST,
DB_USER,
DB_PASSWORD,
@@ -67,8 +66,7 @@ def main():
# ----- 配置被硬编码 -----
# 强制使用 Kimi
llm_provider = "kimi"
model_name = "kimi-k2-0711-preview"
model_name = INSIGHT_ENGINE_MODEL_NAME or "kimi-k2-0711-preview"
# 默认高级配置
max_reflections = 2
max_content_length = 500000 # Kimi支持长文本
@@ -104,9 +102,9 @@ def main():
st.error("请输入研究查询")
return
# 由于强制使用Kimi,只检查KIMI_API_KEY
if not KIMI_API_KEY:
st.error("请在您的配置文件(config.py)中设置KIMI_API_KEY")
# 检查配置中的LLM密钥
if not INSIGHT_ENGINE_API_KEY:
st.error("请在您的配置文件(config.py)中设置INSIGHT_ENGINE_API_KEY")
return
# 自动使用配置文件中的API密钥和数据库配置
@@ -119,21 +117,15 @@ def main():
# 创建配置
config = Config(
deepseek_api_key=None,
openai_api_key=None,
kimi_api_key=KIMI_API_KEY, # 强制使用配置文件中的Kimi Key
deepseek_base_url=DEEPSEEK_BASE_URL,
kimi_base_url=KIMI_BASE_URL,
llm_api_key=INSIGHT_ENGINE_API_KEY,
llm_base_url=INSIGHT_ENGINE_BASE_URL,
llm_model_name=model_name,
db_host=db_host,
db_user=db_user,
db_password=db_password,
db_name=db_name,
db_port=db_port,
db_charset=db_charset,
default_llm_provider=llm_provider,
deepseek_model="deepseek-chat", # 保留默认值以兼容
openai_model="gpt-4o-mini", # 保留默认值以兼容
kimi_model=model_name,
max_reflections=max_reflections,
max_content_length=max_content_length,
output_dir="insight_engine_streamlit_reports"
+14 -22
View File
@@ -28,11 +28,10 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from MediaEngine import DeepSearchAgent, Config
from config import (
DEEPSEEK_API_KEY,
DEEPSEEK_BASE_URL,
BOCHA_Web_Search_API_KEY,
GEMINI_API_KEY,
GEMINI_BASE_URL,
MEDIA_ENGINE_API_KEY,
MEDIA_ENGINE_BASE_URL,
MEDIA_ENGINE_MODEL_NAME,
BOCHA_WEB_SEARCH_API_KEY,
)
@@ -63,8 +62,7 @@ def main():
# ----- 配置被硬编码 -----
# 强制使用 Gemini
llm_provider = "gemini"
model_name = "gemini-2.5-pro"
model_name = MEDIA_ENGINE_MODEL_NAME or "gemini-2.5-pro"
# 默认高级配置
max_reflections = 2
max_content_length = 20000
@@ -101,29 +99,23 @@ def main():
return
# 由于强制使用Gemini,检查相关的API密钥
if not GEMINI_API_KEY:
st.error("请在您的配置文件(config.py)中设置GEMINI_API_KEY")
if not MEDIA_ENGINE_API_KEY:
st.error("请在您的配置文件(config.py)中设置MEDIA_ENGINE_API_KEY")
return
if not BOCHA_Web_Search_API_KEY:
st.error("请在您的配置文件(config.py)中设置BOCHA_Web_Search_API_KEY")
if not BOCHA_WEB_SEARCH_API_KEY:
st.error("请在您的配置文件(config.py)中设置BOCHA_WEB_SEARCH_API_KEY")
return
# 自动使用配置文件中的API密钥
gemini_key = GEMINI_API_KEY
bocha_key = BOCHA_Web_Search_API_KEY
engine_key = MEDIA_ENGINE_API_KEY
bocha_key = BOCHA_WEB_SEARCH_API_KEY
# 创建配置
config = Config(
deepseek_api_key=None,
openai_api_key=None,
gemini_api_key=gemini_key,
llm_api_key=engine_key,
llm_base_url=MEDIA_ENGINE_BASE_URL,
llm_model_name=model_name,
bocha_api_key=bocha_key,
deepseek_base_url=DEEPSEEK_BASE_URL,
gemini_base_url=GEMINI_BASE_URL,
default_llm_provider=llm_provider,
deepseek_model="deepseek-chat", # 保留默认值以兼容
openai_model="gpt-4o-mini", # 保留默认值以兼容
gemini_model=model_name,
max_reflections=max_reflections,
max_content_length=max_content_length,
output_dir="media_engine_streamlit_reports"
+8 -12
View File
@@ -27,7 +27,7 @@ except locale.Error:
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from QueryEngine import DeepSearchAgent, Config
from config import DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL, TAVILY_API_KEY
from config import QUERY_ENGINE_API_KEY, QUERY_ENGINE_BASE_URL, QUERY_ENGINE_MODEL_NAME, TAVILY_API_KEY
def main():
@@ -56,8 +56,7 @@ def main():
# ----- 配置被硬编码 -----
# 强制使用 DeepSeek
llm_provider = "deepseek"
model_name = "deepseek-chat"
model_name = QUERY_ENGINE_MODEL_NAME or "deepseek-chat"
# 默认高级配置
max_reflections = 2
max_content_length = 20000
@@ -94,26 +93,23 @@ def main():
return
# 由于强制使用DeepSeek,检查相关的API密钥
if not DEEPSEEK_API_KEY:
st.error("请在您的配置文件(config.py)中设置DEEPSEEK_API_KEY")
if not QUERY_ENGINE_API_KEY:
st.error("请在您的配置文件(config.py)中设置QUERY_ENGINE_API_KEY")
return
if not TAVILY_API_KEY:
st.error("请在您的配置文件(config.py)中设置TAVILY_API_KEY")
return
# 自动使用配置文件中的API密钥
deepseek_key = DEEPSEEK_API_KEY
engine_key = QUERY_ENGINE_API_KEY
tavily_key = TAVILY_API_KEY
# 创建配置
config = Config(
deepseek_api_key=deepseek_key,
openai_api_key=None,
llm_api_key=engine_key,
llm_base_url=QUERY_ENGINE_BASE_URL,
llm_model_name=model_name,
tavily_api_key=tavily_key,
deepseek_base_url=DEEPSEEK_BASE_URL,
default_llm_provider=llm_provider,
deepseek_model=model_name,
openai_model="gpt-4o-mini", # 保留默认值以兼容
max_reflections=max_reflections,
max_content_length=max_content_length,
output_dir="query_engine_streamlit_reports"
+39 -37
View File
@@ -1,55 +1,57 @@
# -*- coding: utf-8 -*-
"""
Intelligence Public Opinion Analysis Platform Configuration File
Stores database connection information and API keys
微舆配置文件
"""
# ============================== 数据库配置 ==============================
# MySQL Database Configuration
DB_HOST = "your_database_host" # e.g., "localhost" or "127.0.0.1"
DB_PORT = 3306 # e.g., 3306
DB_USER = "your_database_user"
DB_PASSWORD = "your_database_password"
DB_NAME = "your_database_name"
# 配置这些值以连接到您的MySQL实例。
DB_HOST = "your_db_host" # 例如:"localhost" "127.0.0.1"
DB_PORT = 3306
DB_USER = "your_db_user"
DB_PASSWORD = "your_db_password"
DB_NAME = "your_db_name"
DB_CHARSET = "utf8mb4"
# 我们也提供云数据库资源便捷配置,日均10w+数据,学术研究可免费申请,联系我们:670939375@qq.com
# ============================== LLM配置 ==============================
# 重要提醒:推荐第一次先按照默认模型安排配置,成功跑通后再更改自己的模型!
# 您可以更改每个部分LLM使用的API,🚩只要兼容OpenAI请求格式都可以,定义好KEY、BASE_URL与MODEL_NAME即可正常使用。
# 重要提醒:我们强烈推荐您先使用推荐的配置申请API,先跑通再进行您的更改!
# DeepSeek API Key (openai调用格式)
# 用于Query Agent
# 申请地址https://www.deepseek.com/
DEEPSEEK_API_KEY = "sk-xxxxxxxxxxxxxxxxx"
DEEPSEEK_BASE_URL = "https://api.deepseek.com"
# Insight Agent(推荐Kimi,申请地址:https://platform.moonshot.cn/
INSIGHT_ENGINE_API_KEY = "your_api_key"
INSIGHT_ENGINE_BASE_URL = "https://api.moonshot.cn/v1"
INSIGHT_ENGINE_MODEL_NAME = "kimi-k2-0711-preview"
# Kimi API Key (openai调用格式)
# 用于Insight Agent
# 申请地址https://platform.moonshot.cn/
KIMI_API_KEY = "sk-xxxxxxxxxxxxxxxxx"
KIMI_BASE_URL = "https://api.moonshot.cn/v1"
# Media Agent(推荐Gemini,这里我用了一个中转厂商,你也可以换成你自己的,申请地址:https://www.chataiapi.com/
MEDIA_ENGINE_API_KEY = "your_api_key"
MEDIA_ENGINE_BASE_URL = "https://www.chataiapi.com/v1"
MEDIA_ENGINE_MODEL_NAME = "gemini-2.5-pro"
# Gemini API Key (openai调用格式)
# 用于Media Agent与Report Agent
# 这里我用了一个中转api来接入Gemini,申请地址https://api.chataiapi.com/,你也可以使用其他
GEMINI_API_KEY = "sk-xxxxxxxxxxxxxxxxx"
GEMINI_BASE_URL = "https://www.chataiapi.com/v1"
# Query Agent(推荐DeepSeek,申请地址:https://www.deepseek.com/
QUERY_ENGINE_API_KEY = "your_api_key"
QUERY_ENGINE_BASE_URL = "https://api.deepseek.com"
QUERY_ENGINE_MODEL_NAME = "deepseek-reasoner"
# Siliconflow API Key (openai调用格式)
# 用于Forum Host与keyword Optimizer
# 申请地址https://siliconflow.cn/
GUIJI_QWEN3_API_KEY = "sk-xxxxxxxxxxxxxxxxx"
GUIJI_QWEN3_BASE_URL = "https://api.siliconflow.cn/v1"
# Report Agent(推荐Gemini,这里我用了一个中转厂商,你也可以换成你自己的,申请地址:https://www.chataiapi.com/
REPORT_ENGINE_API_KEY = "your_api_key"
REPORT_ENGINE_BASE_URL = "https://www.chataiapi.com/v1"
REPORT_ENGINE_MODEL_NAME = "gemini-2.5-pro"
# 调试阶段出于成本考虑,没有使用ChatGPT与Claude,您也可以接入自己的模型,只要符合openai调用格式即可
# Forum Host(Qwen3最新模型,这里我使用了硅基流动这个平台,申请地址:https://cloud.siliconflow.cn/
FORUM_HOST_API_KEY = "your_api_key"
FORUM_HOST_BASE_URL = "https://api.siliconflow.cn/v1"
FORUM_HOST_MODEL_NAME = "Qwen/Qwen3-235B-A22B-Instruct-2507"
# SQL keyword Optimizer(小参数Qwen3模型,这里我使用了硅基流动这个平台,申请地址:https://cloud.siliconflow.cn/
KEYWORD_OPTIMIZER_API_KEY = "your_api_key"
KEYWORD_OPTIMIZER_BASE_URL = "https://api.siliconflow.cn/v1"
KEYWORD_OPTIMIZER_MODEL_NAME = "Qwen/Qwen3-30B-A3B-Instruct-2507"
# ============================== Web工具配置 ==============================
# Tavily Search API Key
# 申请地址https://www.tavily.com/
TAVILY_API_KEY = "tvly-xxxxxxxxxxxxxxxxx"
# ============================== 网络工具配置 ==============================
# Tavily API(申请地址:https://www.tavily.com/
TAVILY_API_KEY = "your_api_key"
# Bocha Search API Key
# 申请地址https://open.bochaai.com/
BOCHA_Web_Search_API_KEY = "sk-xxxxxxxxxxxxxxxxx"
# Bocha API(申请地址:https://open.bochaai.com/
BOCHA_WEB_SEARCH_API_KEY = "your_api_key"
@@ -1,98 +0,0 @@
# 深度研究报告
- [深度研究报告](#深度研究报告)
- [1. 事件全景:从5秒抓痒到18亿阅读](#1-事件全景从5秒抓痒到18亿阅读)
- [2. 当事双方:谁在风暴中心](#2-当事双方谁在风暴中心)
- [3. 舆论温度表:数据·声浪·情绪](#3-舆论温度表数据声浪情绪)
- [3.1 平台热度排行榜](#31-平台热度排行榜)
- [3.2 学生集体表情](#32-学生集体表情)
- [4. 现行防治机制:漏洞与鸿沟](#4-现行防治机制漏洞与鸿沟)
- [5. 改革呼声:制度补洞,人心如何补](#5-改革呼声制度补洞人心如何补)
- [5.1 针锋相对的两种方案](#51-针锋相对的两种方案)
- [5.2 学生真实焦虑](#52-学生真实焦虑)
- [6. 结论:当樱花再次飘落](#6-结论当樱花再次飘落)
---
## 1. 事件全景:从5秒抓痒到18亿阅读
| 时间节点 | 关键动作 | 全网热度 |
|---|---|---|
| **2023-07-11** | 肖同学抓痒被拍;杨某媛现场要求手写“道歉” | 校内口口相传 |
| **2023-10-11** | 杨某媛凌晨发布剪辑视频,贴“性骚扰”标签 | 4小时破亿阅读 |
| **2023-10-13** | 学校红头文件:记过处分,取消保研资格 | 微博热搜第一 |
| **2025-07-25** | 法院一审:*“无法认定性骚扰”* | #武大仍未撤销处分# 3.7亿阅读 |
| **2025-07-27** | 杨某媛晒香港浸会大学博士录取 | 小红书3.2万赞“姐姐好飒” |
| **2025-07-31** | 校长回应“等上级安排” | B站弹幕刷屏“青春谁来赔” |
---
## 2. 当事双方:谁在风暴中心
| 维度 | 肖同学(19岁,本科) | 杨某媛(22岁,研二) |
|---|---|---|
| **现实代价** | - PTSD确诊<br>- 爷爷去世<br>- 保研名额归零 | - 获名校录取<br>- 被贴“诬告者”标签<br>- 论文漏洞遭群嘲 |
| **网络处境** | 私信辱骂、家庭住址被曝光 | 小红书“飒姐”人设与“学术妲己”并存 |
| **制度结果** | 记过处分仍挂官网 | 无校纪追责 |
---
## 3. 舆论温度表:数据·声浪·情绪
### 3.1 平台热度排行榜
| 平台 | 主话题阅读量/播放量 | 最高同时在线 |
|---|---|---|
| 微博 | 18.4亿 | 62%情绪为“愤怒” |
| 抖音 | 12.7亿 | “气死了”弹幕 3.7条/10秒 |
| 知乎 | 4.2万条回答 | 热帖“为什么高校举报石沉大海” |
| B站 | 50万+弹幕 | “樱花没开,我们也没脸开”刷屏 |
| 小红书 | 900万+ #我也遇到过# | “恐惧”指数↑12个百分点 |
### 3.2 学生集体表情
- **珞珈山水BBS**:深夜在线4100+,热帖《旧图书馆的猫》2.3万点亮
- **匿名树洞**:“我们不是沉默,是怕成为下一个杨某” 1.1万赞
- **微信群/QQ群**统一刷屏:
> “如果樱花会说话,它会哭吗?”
---
## 4. 现行防治机制:漏洞与鸿沟
| 制度环节 | 学生遭遇 | 舆情高频词 |
|---|---|---|
| **举报入口** | 按钮形同虚设,需“两名证人签字” | “证据链陷阱” |
| **调查流程** | 3个月无书面回复,信息被群发泄露 | “裸奔式举报” |
| **心理支持** | 心理评估报告被质疑“主观” | “二次伤害” |
| **结果反馈** | 多数仅为“谈话提醒” | “息事宁人” |
> “我们怕的不是色狼,而是色狼背后那张‘维护学校声誉’的遮羞布。”
> ——微博高赞留言,转发5.2万次
---
## 5. 改革呼声:制度补洞,人心如何补
### 5.1 针锋相对的两种方案
| 主张方 | 核心观点 | 代表语录 |
|---|---|---|
| **北大法学院教授** | 法院未认定即自动冻结校纪处分 | “行政权不能凌驾司法权” |
| **华东师大性别研究基地** | 建立“司法—校纪”双轨听证 | “让双方都能说话” |
### 5.2 学生真实焦虑
- **问卷数据**:45%选择“说不清”现有措施能否让自己更安心
- **深夜留言**
> “我更怕风吹草动时,第一反应是‘我会不会被二次伤害’。”
---
## 6. 结论:当樱花再次飘落
1. **真相跑不赢情绪**:从5秒抓痒到18亿阅读,网络审判只用了4小时。
2. **制度性缺位**:封闭调查、信息泄露、权力不对等,让学生不敢按下“发送”。
3. **双输结局**:肖同学失去前途与健康,杨某媛背负标签与质疑,学校公信力折损。
4. **改革关键**
- 司法结果与校纪处分**刚性挂钩**
- 建立**第三方独立调查+隐私保护**双保险
- 把“零”从摄像头数量转向**每个人心里的那杆秤**
> 樱花会再次盛开,但落在地上的花瓣提醒我们:
> **如果制度不补洞,明年的风还会吹来新的眼泪。**
@@ -1,98 +0,0 @@
# 深度研究报告
- [深度研究报告](#深度研究报告)
- [1. 事件全景:从5秒抓痒到18亿阅读](#1-事件全景从5秒抓痒到18亿阅读)
- [2. 当事双方:谁在风暴中心](#2-当事双方谁在风暴中心)
- [3. 舆论温度表:数据·声浪·情绪](#3-舆论温度表数据声浪情绪)
- [3.1 平台热度排行榜](#31-平台热度排行榜)
- [3.2 学生集体表情](#32-学生集体表情)
- [4. 现行防治机制:漏洞与鸿沟](#4-现行防治机制漏洞与鸿沟)
- [5. 改革呼声:制度补洞,人心如何补](#5-改革呼声制度补洞人心如何补)
- [5.1 针锋相对的两种方案](#51-针锋相对的两种方案)
- [5.2 学生真实焦虑](#52-学生真实焦虑)
- [6. 结论:当樱花再次飘落](#6-结论当樱花再次飘落)
---
## 1. 事件全景:从5秒抓痒到18亿阅读
| 时间节点 | 关键动作 | 全网热度 |
|---|---|---|
| **2023-07-11** | 肖同学抓痒被拍;杨某媛现场要求手写“道歉” | 校内口口相传 |
| **2023-10-11** | 杨某媛凌晨发布剪辑视频,贴“性骚扰”标签 | 4小时破亿阅读 |
| **2023-10-13** | 学校红头文件:记过处分,取消保研资格 | 微博热搜第一 |
| **2025-07-25** | 法院一审:*“无法认定性骚扰”* | #武大仍未撤销处分# 3.7亿阅读 |
| **2025-07-27** | 杨某媛晒香港浸会大学博士录取 | 小红书3.2万赞“姐姐好飒” |
| **2025-07-31** | 校长回应“等上级安排” | B站弹幕刷屏“青春谁来赔” |
---
## 2. 当事双方:谁在风暴中心
| 维度 | 肖同学(19岁,本科) | 杨某媛(22岁,研二) |
|---|---|---|
| **现实代价** | - PTSD确诊<br>- 爷爷去世<br>- 保研名额归零 | - 获名校录取<br>- 被贴“诬告者”标签<br>- 论文漏洞遭群嘲 |
| **网络处境** | 私信辱骂、家庭住址被曝光 | 小红书“飒姐”人设与“学术妲己”并存 |
| **制度结果** | 记过处分仍挂官网 | 无校纪追责 |
---
## 3. 舆论温度表:数据·声浪·情绪
### 3.1 平台热度排行榜
| 平台 | 主话题阅读量/播放量 | 最高同时在线 |
|---|---|---|
| 微博 | 18.4亿 | 62%情绪为“愤怒” |
| 抖音 | 12.7亿 | “气死了”弹幕 3.7条/10秒 |
| 知乎 | 4.2万条回答 | 热帖“为什么高校举报石沉大海” |
| B站 | 50万+弹幕 | “樱花没开,我们也没脸开”刷屏 |
| 小红书 | 900万+ #我也遇到过# | “恐惧”指数↑12个百分点 |
### 3.2 学生集体表情
- **珞珈山水BBS**:深夜在线4100+,热帖《旧图书馆的猫》2.3万点亮
- **匿名树洞**:“我们不是沉默,是怕成为下一个杨某” 1.1万赞
- **微信群/QQ群**统一刷屏:
> “如果樱花会说话,它会哭吗?”
---
## 4. 现行防治机制:漏洞与鸿沟
| 制度环节 | 学生遭遇 | 舆情高频词 |
|---|---|---|
| **举报入口** | 按钮形同虚设,需“两名证人签字” | “证据链陷阱” |
| **调查流程** | 3个月无书面回复,信息被群发泄露 | “裸奔式举报” |
| **心理支持** | 心理评估报告被质疑“主观” | “二次伤害” |
| **结果反馈** | 多数仅为“谈话提醒” | “息事宁人” |
> “我们怕的不是色狼,而是色狼背后那张‘维护学校声誉’的遮羞布。”
> ——微博高赞留言,转发5.2万次
---
## 5. 改革呼声:制度补洞,人心如何补
### 5.1 针锋相对的两种方案
| 主张方 | 核心观点 | 代表语录 |
|---|---|---|
| **北大法学院教授** | 法院未认定即自动冻结校纪处分 | “行政权不能凌驾司法权” |
| **华东师大性别研究基地** | 建立“司法—校纪”双轨听证 | “让双方都能说话” |
### 5.2 学生真实焦虑
- **问卷数据**:45%选择“说不清”现有措施能否让自己更安心
- **深夜留言**
> “我更怕风吹草动时,第一反应是‘我会不会被二次伤害’。”
---
## 6. 结论:当樱花再次飘落
1. **真相跑不赢情绪**:从5秒抓痒到18亿阅读,网络审判只用了4小时。
2. **制度性缺位**:封闭调查、信息泄露、权力不对等,让学生不敢按下“发送”。
3. **双输结局**:肖同学失去前途与健康,杨某媛背负标签与质疑,学校公信力折损。
4. **改革关键**
- 司法结果与校纪处分**刚性挂钩**
- 建立**第三方独立调查+隐私保护**双保险
- 把“零”从摄像头数量转向**每个人心里的那杆秤**
> 樱花会再次盛开,但落在地上的花瓣提醒我们:
> **如果制度不补洞,明年的风还会吹来新的眼泪。**
@@ -1,82 +0,0 @@
# 珞珈山舆情全景报告
**“百年名校的骄傲、焦虑与烟火气”**
> 数据周期:2024-03-01 至 2024-04-30
> 监测平台:微博、抖音、B 站、小红书、知乎、贴吧、树洞等 12 个信源
> 情感样本:≈ 210 万条有效文本、3.8 亿次阅读、420 万条互动
---
## 一、百年校史:从“薛定谔的起点”到“共享的青春现场”
| 维度 | 高光片段 | 争议点 | 情绪占比 |
|---|---|---|---|
| 1893 VS 1913 | 抖音#国立武汉大学门楼# 1.2 万条模仿“学大汉武立国”倒读梗 | 贴吧“80 周年还是 100 周年”贴 15 页楼,**“荒诞”21%** | 自豪 38% / 吐槽 34% |
| 樱花季 | 微博#武大樱花预约# 3.8 亿阅读 | “抢票像春运”暴躁 34%,安慰陌生人 27% | 浪漫 42% / 焦虑 28% |
| 校友记忆 | 知乎匿名答 2.7 万赞:*“百年光阴也就一坡之隔”* | 00 后主播带货雪糕:“母校变景点?” | 怀旧 45% / 商业化 21% |
> **结论**:历史是武大人共同的“开放剧本”,人人可改台词,却从未离场。
---
## 二、学科与科研:从“世界第一”到“工资条沉默”
| 学科 | 高光叙事 | 焦虑痛点 | 情感走势 |
|---|---|---|---|
| **测绘遥感** | 知乎“世界第一”4.5 万赞 | 硕士春招中位数 7.2k,“买不起武汉一平米” | 自豪↓55%→51% / 焦虑↑17%→24% |
| **法学** | 省考“双第一”报喜九宫格 | 红圈所实习 5k 不包宿,“法学生的温柔铠甲”裂缝 | 温情↓18%→14% / 考公↑6%→11% |
| **口腔医学** | B 站“阿牙”拔牙 300 万播 | 规培时薪 < 奶茶店,“连筷子都拿不稳” | 信赖 12%→10% / 规培累 5%→9% |
| **病毒学** | 抖音“00 后通宵 WB”120 万赞 | 后台 3.7% 私信含“猝死”关键词 | 敬意 8%→6% / 科研高压 5% |
> **结论**:学科光芒没有熄灭,只是被年轻人的房租、夜班、脱发折射得更立体。
---
## 三、人才与师资:院士隔壁的“二等公民”
| 指标 | 官方数字 | 学生体感 | 舆情热词 |
|---|---|---|---|
| 全职院士 | 11 人 | “院士把卫星数据当糖果发” vs “PCR 仪排三周” | **“资源落差”** |
| 博雅计划 | 淘汰率 20% | “神仙打架”课表刷屏 | **“卷到凌晨三点”** |
| 诺奖光环 | 谢克曼返校演讲 | “失败是科研入场券”弹幕 80 万播 | **“含金量飙升”** |
> **结论**:顶尖师资是珞珈山最强磁场,也是“内部不平等”最显眼坐标。
---
## 四、校园文化与国际化:樱花、讲坛与学分坑
| 场景 | 浪漫叙事 | 现实裂缝 | 情感两极 |
|---|---|---|---|
| 樱花节 | 小红书 10 万赞“童话滤镜” | #武汉人挤不进武大# 3.8 万条愤怒 | 游客 47% / 学生 36% |
| 珞珈讲坛 | 京都大学学生朗诵《将进酒》 80 万播 | 哈佛交换“300 美元房租劝退普通家庭” | 文化共振 58% / 性价比 31% |
| 海外校区 | “高级游学”抖音 460 万播 | “学分坑比房租更可怕” | 理想 42% / 质疑 37% |
> **结论**:樱花年年开,**浪漫与焦虑的拉锯**也年年更新。
---
## 五、社会服务:从“家门口的国家队”到“光谷房租推手”
| 议题 | 正向案例 | 负面/焦虑 | 情感分布 |
|---|---|---|---|
| **长江大保护** | 渔民放流中华鲟 32.7 万赞 | —— | 非常正面 64% |
| **抗疫记忆** | “救命盒”试剂、“ECMO 拉回父亲” | 科研经费、医生待遇 28% | 心疼+致敬 60% |
| **乡村振兴** | “珞珈红”茶叶、菌菇大王爷爷 | 销路隐忧 10% | 期待 55% |
| **大科学装置** | “给原子拍 CT” 4.3 万赞 | 光谷房租一年涨 12% | 感谢 38% / 生活不易 29% |
> **结论**:武大让湖北人既谈亲人又谈底气,**“感谢”与“房租焦虑”并存**是最市井的温度。
---
## 六、结论:珞珈山的三重面孔
| 面孔 | 关键词 | 数据注脚 |
|---|---|---|
| **骄傲** | 世界第一学科、11 位院士、抗疫硬核 | 自豪声量 51% |
| **焦虑** | 房租、规培、绩点、抢票 | 焦虑声量 24% |
| **烟火气** | 樱花滤镜、校史段子、菌菇大王 | 温情/怀旧 45% |
> **一句话总结**
> 在珞珈山,**百年名校不是博物馆,而是一场永不停更的青春现场**——
> 你可以用任何姿势与它发生关系,它也用所有情绪回应你:
> *骄傲给你翅膀,焦虑给你重量,烟火气给你继续生活的理由。*
@@ -1,31 +0,0 @@
# 武汉大学舆情分析报告
## 武汉大学舆情概述与定义
武汉大学舆情是指在高等教育和社会背景下,围绕武汉大学及其相关事件产生的公众意见、情绪和态度的总和。舆情监测范围涵盖学术活动、校园管理、师生行为、社会服务等多个方面,常见类型包括正面热点(如学术成就、学生善行)、负面事件(如管理争议、安全事件)以及周期性事件(如招生、毕业季)。在高等教育领域,舆情管理至关重要,因为积极的舆情能提升学校声誉和社会影响力,而消极舆情可能冲击学生价值观和学校形象,甚至引发公关危机。有效的舆情监测需借助专业系统(如乐思、蚁坊软件),这些系统为政府和教育部门提供专业的互联网舆情监测服务,包括舆情分析、预警和疏导,实现全网络舆论实时采集和快速发现。例如,蚁坊软件舆情监测系统平台通过大数据技术为舆情监测提供先机,支持舆情监测、全网络舆论分析和预警工作;乐思舆情监测则强调信息全面性和定向搜索能力,共同支持高校舆情管理中的预警机制和应对策略,以维护学校稳定和发展。此外,舆情监测技术还可辅助教学建模分析,提升品牌营销能力,体现了其在教育领域的多维应用价值。技术应用案例包括AI驱动的智能体,通过调用API和数据训练,更好地发挥数据价值,以及自适应噪声抵消等关键技术研究,这些创新进一步增强了舆情监测的精度和实用性。
## 近期武汉大学舆情事件分析
武汉大学近期舆情事件主要集中在杨景媛学术不端事件和图书馆诬告案两大核心问题上。2025年7月,武汉大学硕士毕业生杨景媛因长期诬告肖姓学弟性骚扰败诉后,其硕士学位论文《中印生育行为影响家庭暴力的经济学分析》被曝光存在严重学术造假问题,包括虚构不存在的《离婚法》、数据来源伪造(将世卫组织公布的36.1%数据篡改为28.3%)、历史常识错误(如将1949年误写为1049年)以及逻辑错误、预设结论、大量抄袭、变量操纵等系统性学术不端行为。这一事件不仅暴露了杨景媛个人学术诚信的缺失,更揭示了武汉大学在研究生培养、论文审核机制以及学术伦理建设方面的系统性漏洞。公众反应强烈,质疑导师指导责任和答辩委员会审查失效,同时批评校方在事件曝光后的迟缓应对态度——直到8月1日央媒关注后才宣布组建工作专班进行全面调查复核。此外,该事件与图书馆诬告案交织:肖同学因被诬告遭受记过处分,丧失保研与法考资格,其家庭更因网暴陷入长期创伤(爷爷受刺激去世、外公成植物人);而杨景媛却获得保研资格并被香港浸会大学录取(后证实为研究助理而非博士录取),甚至在败诉后公开炫耀成就,引发对高校程序正义和道德审查机制的广泛质疑。香港浸会大学虽于7月31日发出道德核查函并启动独立审查程序,但8月6日流传的"撤销录取资格"消息被证实为谣言,校方仅表示按纪律程序处理而未公布具体决定,这种处理方式与公众对学术不端"零容忍"的期待形成鲜明落差。事件已对武汉大学校誉和公信力造成重创,成为反思中国高等教育学术诚信与制度监管的典型案例。值得注意的是,杨景媛在调查期间曾试图通过百度网盘上传论文修正文件为自己辩解,但根据中国学术管理规定,已归档学位论文原则上不允许修改,这一行为进一步引发公众对学术规范执行力的质疑。目前校方对论文修改争议仍保持沉默,武汉大学和香港浸会大学的最终处理结果仍悬而未决,公众持续关注事件进展。
## 舆情应对策略与措施
武汉大学在舆情管理方面展现出多层次的应对策略,但近年来的危机事件暴露了其机制中的挑战。以2023年图书馆诬告案为例,学校初期基于单方指控快速处分学生,试图通过‘先处理为敬’的方式平息舆情,却在法院判决反转后引发更严重的舆论反噬,凸显了危机处理中调查不足、反应滞后和急于问责的问题。官方回应方面,校长张平文的‘等上级安排’言论反映了内部决策迟缓,导致‘高度重视’仅停留在内部层面,形成悬殊的公众感知温差,损害了信任;校方在事件中未提供具体调查依据,信息空窗期过长,加剧了隐瞒印象。学校通过保卫部发布通报澄清谣言(如2025年机动车逼停事件),并采取报案等法律行动,体现了沟通和行动结合的策略,但学用脱节问题(如依赖‘落地劝删’等落后手段和缺乏动态舆情监测机制)表明需加强实战能力,避免理论知识与实际处置脱节。舆情研判不足导致未能预判风向反转,应对话术机械被动,错失修复信任窗口。此外,事件还揭示了学校对‘极端女权’等社会舆论现象的应对不足,需更深入理解民意背景。总体而言,武大需优化响应速度、确保调查公正性、建立透明沟通机制(如及时发布‘一对一’式核查回应和补救措施),并通过动态跟踪和预警体系缓解负面舆情,以修复声誉和提升舆情管理效能。
## 舆情对武汉大学声誉的影响
武汉大学近年来面临多起舆情事件,对学校声誉、招生、学术合作和社会形象产生了显著影响。根据知微数据分析,2021年武汉大学'和服赏樱'冲突事件影响力指数达65.4,高于同类事件均值10.8%,引发广泛舆论关注。事件初期,负面观点占比较高,质疑学校狭隘和保安暴力行为。但通过及时公关回应,武汉大学发布情况说明,强调游客未预约和言语挑衅,舆论风向逆转,支持学校决定的比例从26%升至48%。这体现了正面回应在舆情管理中的有效性,有助于维护社会形象。
然而,其他事件如2025年的校园交通冲突,部分自媒体传播'教职工子女蛮横别停学生'等不实信息,导致负面讨论滋生,网民质疑校园管理特权问题,例如取消车辆通行授权三个月的惩罚措施细节和校外人员校园行驶权限。尽管校方迅速澄清涉事驾驶员为校外退休职工子女、无特权行为,并报案处理谣言,事件仍暴露了谣言对声誉的潜在危害。此外,学术相关争议如肖某某纪律处分和杨某某论文调查,通过媒体和社交平台扩散,影响学术合作信任度。
招生方面,2024年武汉大学招生总人数有所增加,面向全国招生7215人,强基计划招生专业从8个增加到9个,显示学校在扩大招生规模上的努力。然而,校长张平文在宣传片中不当言论曾引发网络质疑,需警惕对招生的潜在负面影响。值得注意的是,全国高等教育性别格局变化显著,2023年本科在校生女性占比达52.22%,招生中女生占比高达63%,但顶尖高校如C9联盟女性占比仅37.7%,武汉大学作为综合性大学,需关注专业性别分化(如计算机学院男女比4.88:1,新闻传播学院女生超80%)对招生多样性和社会形象的影响。
总体而言,舆情事件对招生和合作可能带来短期波动,但武汉大学的应对策略——如快速响应和透明沟通——在一定程度上 mitigates 负面影响,凸显高校需加强舆情监测和公关智慧以保护声誉。新华社等媒体评论指出舆情应对应避免'唯上不唯实',强调高校需提升行政敏感度,防止事件处理失能进一步损害形象。
## 未来舆情趋势与建议
武汉大学未来可能面临的舆情挑战主要集中在透明度不足、沟通效率低下和预防机制缺失等方面。基于搜索结果,武汉大学在图书馆事件中暴露出反应滞后、信息空窗期过长的问题,导致公众信任流失。未来需加强舆情监测和预警体系,采用人工智能技术(如BERT模型、情感分析)实时跟踪网络舆论动态,提升研判能力。建议建立快速响应机制,确保调查流程公开透明,避免模糊策略;优化内部沟通流程,减少层层汇报导致的延误;同时引入区块链技术或深度学习模型(如CNN)加强校园舆情分析,实现多维度事件处理。此外,应定期复盘舆情案例,完善危机公关预案,通过专业、善意的沟通回应公众关切,修复声誉损害。结合前沿技术趋势,武汉大学可借鉴大数据、云计算和人工智能在智慧城市管理中的创新应用,推动舆情管理手段和模式升级,如利用人工智能和区块链提升供应链韧性和协同效益,加强2024年后的业务效率改善。通过整合5G、半导体等新一代信息技术,加快智能化的舆情预警和响应系统建设,提升透明度和沟通效率,预防潜在危机。值得注意的是,在2024年金融科技创新大赛中,武汉大学团队展示了区块链和人工智能的应用潜力,如“区块链大战供应链融碳生金”项目,这为舆情管理提供了技术参考,可探索区块链用于数据透明存证和AI驱动的情感分析,以增强舆情应对的实时性和可信度。同时,关注AI伦理和治理框架,如避免算法偏见和隐私风险,确保技术应用符合国际标准,提升整体舆情管理的可持续性和普惠性。
## 结论
综合以上分析,武汉大学舆情管理面临显著挑战,尤其在学术诚信、透明度和响应机制方面。近期事件如杨景媛学术不端和图书馆诬告案暴露了系统性漏洞,对学校声誉造成冲击。未来,武汉大学需加强技术应用(如AI和区块链)、优化沟通策略,并建立预防性机制,以提升舆情应对能力,维护长期声誉和发展。
@@ -1,31 +0,0 @@
# 武汉大学舆情分析报告
## 武汉大学舆情概述与定义
武汉大学舆情是指在高等教育和社会背景下,围绕武汉大学及其相关事件产生的公众意见、情绪和态度的总和。舆情监测范围涵盖学术活动、校园管理、师生行为、社会服务等多个方面,常见类型包括正面热点(如学术成就、学生善行)、负面事件(如管理争议、安全事件)以及周期性事件(如招生、毕业季)。在高等教育领域,舆情管理至关重要,因为积极的舆情能提升学校声誉和社会影响力,而消极舆情可能冲击学生价值观和学校形象,甚至引发公关危机。有效的舆情监测需借助专业系统(如乐思、蚁坊软件),这些系统为政府和教育部门提供专业的互联网舆情监测服务,包括舆情分析、预警和疏导,实现全网络舆论实时采集和快速发现。例如,蚁坊软件舆情监测系统平台通过大数据技术为舆情监测提供先机,支持舆情监测、全网络舆论分析和预警工作;乐思舆情监测则强调信息全面性和定向搜索能力,共同支持高校舆情管理中的预警机制和应对策略,以维护学校稳定和发展。此外,舆情监测技术还可辅助教学建模分析,提升品牌营销能力,体现了其在教育领域的多维应用价值。技术应用案例包括AI驱动的智能体,通过调用API和数据训练,更好地发挥数据价值,以及自适应噪声抵消等关键技术研究,这些创新进一步增强了舆情监测的精度和实用性。
## 近期武汉大学舆情事件分析
武汉大学近期舆情事件主要集中在杨景媛学术不端事件和图书馆诬告案两大核心问题上。2025年7月,武汉大学硕士毕业生杨景媛因长期诬告肖姓学弟性骚扰败诉后,其硕士学位论文《中印生育行为影响家庭暴力的经济学分析》被曝光存在严重学术造假问题,包括虚构不存在的《离婚法》、数据来源伪造(将世卫组织公布的36.1%数据篡改为28.3%)、历史常识错误(如将1949年误写为1049年)以及逻辑错误、预设结论、大量抄袭、变量操纵等系统性学术不端行为。这一事件不仅暴露了杨景媛个人学术诚信的缺失,更揭示了武汉大学在研究生培养、论文审核机制以及学术伦理建设方面的系统性漏洞。公众反应强烈,质疑导师指导责任和答辩委员会审查失效,同时批评校方在事件曝光后的迟缓应对态度——直到8月1日央媒关注后才宣布组建工作专班进行全面调查复核。此外,该事件与图书馆诬告案交织:肖同学因被诬告遭受记过处分,丧失保研与法考资格,其家庭更因网暴陷入长期创伤(爷爷受刺激去世、外公成植物人);而杨景媛却获得保研资格并被香港浸会大学录取(后证实为研究助理而非博士录取),甚至在败诉后公开炫耀成就,引发对高校程序正义和道德审查机制的广泛质疑。香港浸会大学虽于7月31日发出道德核查函并启动独立审查程序,但8月6日流传的"撤销录取资格"消息被证实为谣言,校方仅表示按纪律程序处理而未公布具体决定,这种处理方式与公众对学术不端"零容忍"的期待形成鲜明落差。事件已对武汉大学校誉和公信力造成重创,成为反思中国高等教育学术诚信与制度监管的典型案例。值得注意的是,杨景媛在调查期间曾试图通过百度网盘上传论文修正文件为自己辩解,但根据中国学术管理规定,已归档学位论文原则上不允许修改,这一行为进一步引发公众对学术规范执行力的质疑。目前校方对论文修改争议仍保持沉默,武汉大学和香港浸会大学的最终处理结果仍悬而未决,公众持续关注事件进展。
## 舆情应对策略与措施
武汉大学在舆情管理方面展现出多层次的应对策略,但近年来的危机事件暴露了其机制中的挑战。以2023年图书馆诬告案为例,学校初期基于单方指控快速处分学生,试图通过‘先处理为敬’的方式平息舆情,却在法院判决反转后引发更严重的舆论反噬,凸显了危机处理中调查不足、反应滞后和急于问责的问题。官方回应方面,校长张平文的‘等上级安排’言论反映了内部决策迟缓,导致‘高度重视’仅停留在内部层面,形成悬殊的公众感知温差,损害了信任;校方在事件中未提供具体调查依据,信息空窗期过长,加剧了隐瞒印象。学校通过保卫部发布通报澄清谣言(如2025年机动车逼停事件),并采取报案等法律行动,体现了沟通和行动结合的策略,但学用脱节问题(如依赖‘落地劝删’等落后手段和缺乏动态舆情监测机制)表明需加强实战能力,避免理论知识与实际处置脱节。舆情研判不足导致未能预判风向反转,应对话术机械被动,错失修复信任窗口。此外,事件还揭示了学校对‘极端女权’等社会舆论现象的应对不足,需更深入理解民意背景。总体而言,武大需优化响应速度、确保调查公正性、建立透明沟通机制(如及时发布‘一对一’式核查回应和补救措施),并通过动态跟踪和预警体系缓解负面舆情,以修复声誉和提升舆情管理效能。
## 舆情对武汉大学声誉的影响
武汉大学近年来面临多起舆情事件,对学校声誉、招生、学术合作和社会形象产生了显著影响。根据知微数据分析,2021年武汉大学'和服赏樱'冲突事件影响力指数达65.4,高于同类事件均值10.8%,引发广泛舆论关注。事件初期,负面观点占比较高,质疑学校狭隘和保安暴力行为。但通过及时公关回应,武汉大学发布情况说明,强调游客未预约和言语挑衅,舆论风向逆转,支持学校决定的比例从26%升至48%。这体现了正面回应在舆情管理中的有效性,有助于维护社会形象。
然而,其他事件如2025年的校园交通冲突,部分自媒体传播'教职工子女蛮横别停学生'等不实信息,导致负面讨论滋生,网民质疑校园管理特权问题,例如取消车辆通行授权三个月的惩罚措施细节和校外人员校园行驶权限。尽管校方迅速澄清涉事驾驶员为校外退休职工子女、无特权行为,并报案处理谣言,事件仍暴露了谣言对声誉的潜在危害。此外,学术相关争议如肖某某纪律处分和杨某某论文调查,通过媒体和社交平台扩散,影响学术合作信任度。
招生方面,2024年武汉大学招生总人数有所增加,面向全国招生7215人,强基计划招生专业从8个增加到9个,显示学校在扩大招生规模上的努力。然而,校长张平文在宣传片中不当言论曾引发网络质疑,需警惕对招生的潜在负面影响。值得注意的是,全国高等教育性别格局变化显著,2023年本科在校生女性占比达52.22%,招生中女生占比高达63%,但顶尖高校如C9联盟女性占比仅37.7%,武汉大学作为综合性大学,需关注专业性别分化(如计算机学院男女比4.88:1,新闻传播学院女生超80%)对招生多样性和社会形象的影响。
总体而言,舆情事件对招生和合作可能带来短期波动,但武汉大学的应对策略——如快速响应和透明沟通——在一定程度上 mitigates 负面影响,凸显高校需加强舆情监测和公关智慧以保护声誉。新华社等媒体评论指出舆情应对应避免'唯上不唯实',强调高校需提升行政敏感度,防止事件处理失能进一步损害形象。
## 未来舆情趋势与建议
武汉大学未来可能面临的舆情挑战主要集中在透明度不足、沟通效率低下和预防机制缺失等方面。基于搜索结果,武汉大学在图书馆事件中暴露出反应滞后、信息空窗期过长的问题,导致公众信任流失。未来需加强舆情监测和预警体系,采用人工智能技术(如BERT模型、情感分析)实时跟踪网络舆论动态,提升研判能力。建议建立快速响应机制,确保调查流程公开透明,避免模糊策略;优化内部沟通流程,减少层层汇报导致的延误;同时引入区块链技术或深度学习模型(如CNN)加强校园舆情分析,实现多维度事件处理。此外,应定期复盘舆情案例,完善危机公关预案,通过专业、善意的沟通回应公众关切,修复声誉损害。结合前沿技术趋势,武汉大学可借鉴大数据、云计算和人工智能在智慧城市管理中的创新应用,推动舆情管理手段和模式升级,如利用人工智能和区块链提升供应链韧性和协同效益,加强2024年后的业务效率改善。通过整合5G、半导体等新一代信息技术,加快智能化的舆情预警和响应系统建设,提升透明度和沟通效率,预防潜在危机。值得注意的是,在2024年金融科技创新大赛中,武汉大学团队展示了区块链和人工智能的应用潜力,如“区块链大战供应链融碳生金”项目,这为舆情管理提供了技术参考,可探索区块链用于数据透明存证和AI驱动的情感分析,以增强舆情应对的实时性和可信度。同时,关注AI伦理和治理框架,如避免算法偏见和隐私风险,确保技术应用符合国际标准,提升整体舆情管理的可持续性和普惠性。
## 结论
综合以上分析,武汉大学舆情管理面临显著挑战,尤其在学术诚信、透明度和响应机制方面。近期事件如杨景媛学术不端和图书馆诬告案暴露了系统性漏洞,对学校声誉造成冲击。未来,武汉大学需加强技术应用(如AI和区块链)、优化沟通策略,并建立预防性机制,以提升舆情应对能力,维护长期声誉和发展。
@@ -1,31 +0,0 @@
# 武汉大学舆情分析报告
## 武汉大学舆情概述与定义
武汉大学舆情是指在高等教育和社会背景下,围绕武汉大学及其相关事件产生的公众意见、情绪和态度的总和。舆情监测范围涵盖学术活动、校园管理、师生行为、社会服务等多个方面,常见类型包括正面热点(如学术成就、学生善行)、负面事件(如管理争议、安全事件)以及周期性事件(如招生、毕业季)。在高等教育领域,舆情管理至关重要,因为积极的舆情能提升学校声誉和社会影响力,而消极舆情可能冲击学生价值观和学校形象,甚至引发公关危机。有效的舆情监测需借助专业系统(如乐思、蚁坊软件),这些系统为政府和教育部门提供专业的互联网舆情监测服务,包括舆情分析、预警和疏导,实现全网络舆论实时采集和快速发现。例如,蚁坊软件舆情监测系统平台通过大数据技术为舆情监测提供先机,支持舆情监测、全网络舆论分析和预警工作;乐思舆情监测则强调信息全面性和定向搜索能力,共同支持高校舆情管理中的预警机制和应对策略,以维护学校稳定和发展。此外,舆情监测技术还可辅助教学建模分析,提升品牌营销能力,体现了其在教育领域的多维应用价值。技术应用案例包括AI驱动的智能体,通过调用API和数据训练,更好地发挥数据价值,以及自适应噪声抵消等关键技术研究,这些创新进一步增强了舆情监测的精度和实用性。
## 近期武汉大学舆情事件分析
武汉大学近期舆情事件主要集中在杨景媛学术不端事件和图书馆诬告案两大核心问题上。2025年7月,武汉大学硕士毕业生杨景媛因长期诬告肖姓学弟性骚扰败诉后,其硕士学位论文《中印生育行为影响家庭暴力的经济学分析》被曝光存在严重学术造假问题,包括虚构不存在的《离婚法》、数据来源伪造(将世卫组织公布的36.1%数据篡改为28.3%)、历史常识错误(如将1949年误写为1049年)以及逻辑错误、预设结论、大量抄袭、变量操纵等系统性学术不端行为。这一事件不仅暴露了杨景媛个人学术诚信的缺失,更揭示了武汉大学在研究生培养、论文审核机制以及学术伦理建设方面的系统性漏洞。公众反应强烈,质疑导师指导责任和答辩委员会审查失效,同时批评校方在事件曝光后的迟缓应对态度——直到8月1日央媒关注后才宣布组建工作专班进行全面调查复核。此外,该事件与图书馆诬告案交织:肖同学因被诬告遭受记过处分,丧失保研与法考资格,其家庭更因网暴陷入长期创伤(爷爷受刺激去世、外公成植物人);而杨景媛却获得保研资格并被香港浸会大学录取(后证实为研究助理而非博士录取),甚至在败诉后公开炫耀成就,引发对高校程序正义和道德审查机制的广泛质疑。香港浸会大学虽于7月31日发出道德核查函并启动独立审查程序,但8月6日流传的"撤销录取资格"消息被证实为谣言,校方仅表示按纪律程序处理而未公布具体决定,这种处理方式与公众对学术不端"零容忍"的期待形成鲜明落差。事件已对武汉大学校誉和公信力造成重创,成为反思中国高等教育学术诚信与制度监管的典型案例。值得注意的是,杨景媛在调查期间曾试图通过百度网盘上传论文修正文件为自己辩解,但根据中国学术管理规定,已归档学位论文原则上不允许修改,这一行为进一步引发公众对学术规范执行力的质疑。目前校方对论文修改争议仍保持沉默,武汉大学和香港浸会大学的最终处理结果仍悬而未决,公众持续关注事件进展。
## 舆情应对策略与措施
武汉大学在舆情管理方面展现出多层次的应对策略,但近年来的危机事件暴露了其机制中的挑战。以2023年图书馆诬告案为例,学校初期基于单方指控快速处分学生,试图通过‘先处理为敬’的方式平息舆情,却在法院判决反转后引发更严重的舆论反噬,凸显了危机处理中调查不足、反应滞后和急于问责的问题。官方回应方面,校长张平文的‘等上级安排’言论反映了内部决策迟缓,导致‘高度重视’仅停留在内部层面,形成悬殊的公众感知温差,损害了信任;校方在事件中未提供具体调查依据,信息空窗期过长,加剧了隐瞒印象。学校通过保卫部发布通报澄清谣言(如2025年机动车逼停事件),并采取报案等法律行动,体现了沟通和行动结合的策略,但学用脱节问题(如依赖‘落地劝删’等落后手段和缺乏动态舆情监测机制)表明需加强实战能力,避免理论知识与实际处置脱节。舆情研判不足导致未能预判风向反转,应对话术机械被动,错失修复信任窗口。此外,事件还揭示了学校对‘极端女权’等社会舆论现象的应对不足,需更深入理解民意背景。总体而言,武大需优化响应速度、确保调查公正性、建立透明沟通机制(如及时发布‘一对一’式核查回应和补救措施),并通过动态跟踪和预警体系缓解负面舆情,以修复声誉和提升舆情管理效能。
## 舆情对武汉大学声誉的影响
武汉大学近年来面临多起舆情事件,对学校声誉、招生、学术合作和社会形象产生了显著影响。根据知微数据分析,2021年武汉大学'和服赏樱'冲突事件影响力指数达65.4,高于同类事件均值10.8%,引发广泛舆论关注。事件初期,负面观点占比较高,质疑学校狭隘和保安暴力行为。但通过及时公关回应,武汉大学发布情况说明,强调游客未预约和言语挑衅,舆论风向逆转,支持学校决定的比例从26%升至48%。这体现了正面回应在舆情管理中的有效性,有助于维护社会形象。
然而,其他事件如2025年的校园交通冲突,部分自媒体传播'教职工子女蛮横别停学生'等不实信息,导致负面讨论滋生,网民质疑校园管理特权问题,例如取消车辆通行授权三个月的惩罚措施细节和校外人员校园行驶权限。尽管校方迅速澄清涉事驾驶员为校外退休职工子女、无特权行为,并报案处理谣言,事件仍暴露了谣言对声誉的潜在危害。此外,学术相关争议如肖某某纪律处分和杨某某论文调查,通过媒体和社交平台扩散,影响学术合作信任度。
招生方面,2024年武汉大学招生总人数有所增加,面向全国招生7215人,强基计划招生专业从8个增加到9个,显示学校在扩大招生规模上的努力。然而,校长张平文在宣传片中不当言论曾引发网络质疑,需警惕对招生的潜在负面影响。值得注意的是,全国高等教育性别格局变化显著,2023年本科在校生女性占比达52.22%,招生中女生占比高达63%,但顶尖高校如C9联盟女性占比仅37.7%,武汉大学作为综合性大学,需关注专业性别分化(如计算机学院男女比4.88:1,新闻传播学院女生超80%)对招生多样性和社会形象的影响。
总体而言,舆情事件对招生和合作可能带来短期波动,但武汉大学的应对策略——如快速响应和透明沟通——在一定程度上 mitigates 负面影响,凸显高校需加强舆情监测和公关智慧以保护声誉。新华社等媒体评论指出舆情应对应避免'唯上不唯实',强调高校需提升行政敏感度,防止事件处理失能进一步损害形象。
## 未来舆情趋势与建议
武汉大学未来可能面临的舆情挑战主要集中在透明度不足、沟通效率低下和预防机制缺失等方面。基于搜索结果,武汉大学在图书馆事件中暴露出反应滞后、信息空窗期过长的问题,导致公众信任流失。未来需加强舆情监测和预警体系,采用人工智能技术(如BERT模型、情感分析)实时跟踪网络舆论动态,提升研判能力。建议建立快速响应机制,确保调查流程公开透明,避免模糊策略;优化内部沟通流程,减少层层汇报导致的延误;同时引入区块链技术或深度学习模型(如CNN)加强校园舆情分析,实现多维度事件处理。此外,应定期复盘舆情案例,完善危机公关预案,通过专业、善意的沟通回应公众关切,修复声誉损害。结合前沿技术趋势,武汉大学可借鉴大数据、云计算和人工智能在智慧城市管理中的创新应用,推动舆情管理手段和模式升级,如利用人工智能和区块链提升供应链韧性和协同效益,加强2024年后的业务效率改善。通过整合5G、半导体等新一代信息技术,加快智能化的舆情预警和响应系统建设,提升透明度和沟通效率,预防潜在危机。值得注意的是,在2024年金融科技创新大赛中,武汉大学团队展示了区块链和人工智能的应用潜力,如“区块链大战供应链融碳生金”项目,这为舆情管理提供了技术参考,可探索区块链用于数据透明存证和AI驱动的情感分析,以增强舆情应对的实时性和可信度。同时,关注AI伦理和治理框架,如避免算法偏见和隐私风险,确保技术应用符合国际标准,提升整体舆情管理的可持续性和普惠性。
## 结论
综合以上分析,武汉大学舆情管理面临显著挑战,尤其在学术诚信、透明度和响应机制方面。近期事件如杨景媛学术不端和图书馆诬告案暴露了系统性漏洞,对学校声誉造成冲击。未来,武汉大学需加强技术应用(如AI和区块链)、优化沟通策略,并建立预防性机制,以提升舆情应对能力,维护长期声誉和发展。
@@ -1,208 +0,0 @@
# 【舆情洞察】“珞珈裂痕”——武汉大学2023-2024年度高烈度舆情全景深度民意分析报告
(版本号:V3.2|字数≈1.4万|数据截止:2024-08-05 24:00
---
## 执行摘要
### 核心舆情发现
| 指标 | 数值 | 同比/环比 | 备注 |
|---|---|---|---|
| 事件总频次 | 5起重大+12起衍生 | ↑186%/五年同期 | 甲醛事件为“临界点” |
| 全平台阅读量 | 28.7亿次 | ↑190%/五年总和 | 短视频占比67% |
| 正向情感谷底 | 6.4% | 创985高校最低纪录 | 72h内暴跌31%→6.4% |
| 二次舆情反弹概率 | 58% | ↑27个百分点 | “官方通报—学生实锤—舆论反噬”剧本固化 |
| 学生组织化程度 | 1937人在线协同文档 | 首次出现“四方联动” | 校友律师团+家长后援会+第三方检测+央媒 |
### 民意热点概览
- **沸点议题**:甲醛宿舍(TVOC超标7.4倍)、樱花黄牛票(200元/人“VIP通道”)、选调生“萝卜坑”(嘉峪关备注栏空白)。
- **跨平台情绪温差**:微博“嘲讽48.3%”🔥、抖音“愤怒54.3%”😡、小红书“维权68.9%”📢、B站“解构鬼畜”🤡。
- **群体撕裂图谱**:本地家长“护校49.7%” vs 外省学生“维权71.8%”;女性愤怒61.2% vs 男性45.7%。
---
## 一、事件全景回溯:从“鼠患”到“甲醛”,五幕舆情连续剧
### 1.1 民意数据画像
| 事件 | 首曝平台 | 峰值阅读量 | 校方首次回应时长 | 二次爆发触发点 | 情感主色调 |
|---|---|---|---|---|---|
| 珞珈山鼠患 | 小红书 | 7.6亿 | 1天4h | 后勤摆拍捕鼠笼 | 😱恐惧→😠愤怒 |
| 樱花黄牛 | 抖音 | 12.3亿 | 1天1h | 学生直播抓现行 | 😤愤怒 |
| 选调生公示 | 知乎 | 9.1亿 | 2天7h | “备注空白”质疑萝卜坑 | 🙄嘲讽 |
| 雷军捐赠 | B站 | 11.2亿 | 9h | 63%“其他费用”未列明细 | 🤔质疑 |
| 甲醛超标 | QQ群文档 | 18.4亿 | 15h | 校方“合格”被第三方推翻 | 😡愤怒 |
### 1.2 代表性民声
**支持校方(9.1%**
> “樱花节一年创收2.7亿,这些钱变成了我们的奖学金。”——抖音@珞珈山导游(点赞5.6万)
**质疑校方(67.4%**
> “用命读研,学校说我们矫情。”——甲醛互助文档匿名留言(制成表情包30万次传播)
**解构狂欢(23.5%**
> “建议把行政楼改成‘珞珈山主题乐园’,门票补贴学生。”——B站鬼畜弹幕(8.2万条)
### 1.3 深度舆情解读
- **资源错配悖论**:2023年招生宣传片制作费400万 vs 生均宿舍维修432元/年(985倒数第3)。
- **算法放大困境**:校方“快而浅”回应(平均1.9天)触发平台“冲突加权”机制,二次爆发概率58%。
- **议题升级路径**:生活服务→制度性质疑(41%帖子要求公开后勤招标、学生代表提案权)。
### 1.4 情感演变轨迹(甲醛事件为例)
T01月5日):恐惧58%+焦虑24%
T1(1月7日校方“合格”):愤怒45%陡升
T2(1月12日第三方实锤):恐惧52%回潮
T3(1月28日临时搬迁):焦虑39%超越愤怒36%
---
## 二、热度与传播路径:注意力通货的“通胀”与“操控”
### 2.1 平台扩散矩阵
| 平台 | 首发占比 | 视觉再生产延迟 | 热搜通胀表现 | 购买痕迹验证 |
|---|---|---|---|---|
| 微博 | 68% | 0h(首发) | 43个“热搜第一”↑27个 | 7个“热搜包”12万/h |
| 抖音 | 24% | 2.1h | 14个破亿播放 | 3个“热推+KOL”35万/次 |
| 小红书 | 18% | 3.4h | 点赞均值↓38% | 1个“关键词霸屏”5万/次 |
| B站 | 10% | 6h | 弹幕语义漂移 | 无直接购买,UP主自发 |
### 2.2 多元声音(公众对“热搜造假”的元认知)
> “热搜像通胀的货币,越来越不值钱,可大家还得抢。”——@社媒观察员(微信公众号精选)
> “我妈都学会说‘这又是买的吧’,全民反诈从反热搜开始。”——@人间观察(抖音点赞8.7万)
### 2.3 深层洞察
- **格雷欣法则**:极端修辞“爆燃”“史诗级”驱逐“火了”,社会议题突围成本↑183%。
- **双层合谋结构**:平台流量券→MCN二次采购→用户接盘,知乎78%回答贴出“报价表”截图。
- **无语化抵抗**:“无语”成最安全表达,互动率↓42%但发布量↑3倍,算法更难捕捉真实情绪。
---
## 三、情感光谱与观点冲突:极化、摇摆与“认知战”升级
### 3.1 情感时间序列(4月15日-25日)
| 阶段 | 正向 | 嘲讽 | 愤怒 | 失望 | 新情感 |
|---|---|---|---|---|---|
| T0 (0h) | 31.2% | 12.1% | 18.7% | - | - |
| T1 (72h) | 9.1% | 48.3% | 35.4% | - | - |
| T2 (144h) | 7.8% | 41.2% | 52.7% | +11.3% | “羞耻”7.8% |
| T3 (240h) | 6.4% | 35.7% | 58.9% | 持续↑ | - |
### 3.2 群体极化对比
| 群体 | 主要立场 | 情感浓度 | 影响力 | 活跃时段 |
|---|---|---|---|---|
| 湖北家长 | 护校49.7% | 焦虑+防御 | 高(学区房溢价↓4.7%) | 21-23点 |
| 外省学生 | 维权71.8% | 愤怒+失望 | 极高(#985滤镜碎了#3.2亿) | 全天 |
| 海外校友 | 理客中35.9% | 羞耻+调和 | 中(Reddit点赞1.2万) | 北美时差 |
| 教育从业者 | 维权72.3% | 制度焦虑 | 高(同行声援) | 工作日 |
### 3.3 认知战升级
- **护校派新武器**:引用《武大章程》第23条“珍惜声誉”,将维权定义为“违约”。
- **维权派历史叙事**:制作“武大维权时间轴”(21级传播,覆盖2347万用户)。
- **性别差异**:女性“愤怒+失望”双高,男性“制度+程序”论证,但女性点赞数↑40%。
---
## 四、群体与平台画像:Z世代、校友、家长的“身份政治”
### 4.1 年龄-平台矩阵
| 年龄层 | 主阵地 | 叙事风格 | 代表话题 | 组织化案例 |
|---|---|---|---|---|
| 18-22岁Z世代 | 抖音/B站 | 剧本杀+鬼畜 | #图书馆副本攻略# | RPG攻略神贴50万点击 |
| 23-27岁硕博 | 知乎/小红书 | 论文化+数据党 | 学位撤销程序 | 2,700条高质量回答 |
| 28-35岁校友 | LinkedIn/推特 | 全球联署 | #DefendWHU# | 312人实名公开信 |
| 36-45岁家长 | 小红书/抖音 | 避坑指南 | “高考慎填武大” | 模板文案8.7倍搜索量 |
### 4.2 地域叙事“三城演义”
- **武汉本地**#保卫珞珈#超话签到118万人次,政务号被@次数↑14倍。
- **北上广深**:“别让985蒙尘”3.8亿阅读,地域公平议题升级。
- **海外战场**Reddit r/China单帖1.2万点赞,出现“系统性迫害”叙事。
### 4.3 官方-自媒体“真相断层”
- 校方9字回应“已关注到法院判决”→评论41万“9个字就想打发?”
- 湖北发布长图《武大需要交代》→自媒体转发61%,政务号仅3%。
---
## 五、深层机制:财政失衡→品牌崩塌→治理现代性危机
### 5.1 财政结构性失衡
| 项目 | 武大2024预算 | 华科对比 | 全国985均值 |
|---|---|---|---|
| 宿舍维修/总支出 | 1.9% (0.47亿) | 7.2% (1.83亿) | 5.1% |
| 生均宿舍维修 | 432元/年 | 1,847元/年 | 1,200元/年 |
| 三公经费/宿舍维修 | 2.14倍 | 0.31倍 | 0.5倍 |
| 捐赠收入占比 | 9.4% | 14.7% | 12.3% |
### 5.2 品牌溢价崩塌链
1. 硬件落差→“985滤镜碎了”3.2亿阅读
2. 选调黑幕→“精致利己985”5.7亿阅读
3. 国际对比→QS“学生设施”5星→3星
### 5.3 治理现代性缺口
- **程序正义赤字**:教育部要求“重大财政事项学生听证”,武大未召开。
- **透明度缺口**:财务处“商业机密”说违反《政府信息公开条例》第20条。
- **对话机制缺位**:对比伯克利“学生监察员”制度,武大仍“辅导员约喝茶”。
---
## 舆情态势综合分析
### 整体民意倾向
“愤怒-失望”双高(58.9%+24.1%),“羞耻”首次成为校友主流情感(67.3%)。民意已从“求补偿”升级为“求制度变革”——41%帖子要求第三方审计、学生代表列席校务会。
### 平台差异化
| 平台 | 主情绪 | 核心梗 | 风险点 |
|---|---|---|---|
| 微博 | 嘲讽48.3% | “护校蛆”表情包 | 地域撕裂 |
| 知乎 | 愤怒58.9% | 万字制度分析 | 学术圈震荡 |
| 小红书 | 维权68.9% | 宿舍改造模板 | 家长焦虑外溢 |
| B站 | 解构鬼畜 | “图书馆副本” | 国际声誉风险 |
### 舆情预判(2024Q3-Q4
- **招生端**:湖北考生第一志愿率预计从31%→19%(低于华科35%)。
- **经济端**:校友捐赠2024-2026年或减少2.3-3.7亿元(=1个国家重点实验室年投入)。
- **师资端**:国家杰青候选人流出↑210%,华科针对性挖角“提供人才公寓”。
- **政策端**:湖北省教育厅已暗示“影响双一流资金分配”。
---
## 深层洞察与建议
### 社会心理分析
- **相对剥夺感→制度性羞辱**:学费↑28%、住宿费↑35%,但宿舍维修仅↑2.1%。
- **身份政治升级**:Z世代“反被代表”愤怒(63%“其他费用”不透明),家长“品牌贬值”焦虑(学区房溢价↓4.7%)。
- **算法困境**:校方“快回应”被算法识别为“高冲突”→二次发酵58%。
### 舆情管理建议
1. **即时响应**:48小时内发布交互式宿舍地图(斯坦福模式),精确到房间维修记录。
2. **制度修复**
- 设立“宿舍事务学生理事会”,拥有10%预算否决权(剑桥模式)。
- 发行“宿舍改善债券”,5年期,利率高于国债30bp。
3. **国际对标**:与新加坡国立大学签署“宿舍自治协议”,3年内生均维修经费→2000元/年。
4. **舆情前置**:建立“宿舍舆情指数”三级预警(黄15%/橙30%/红50%负面)。
5. **品牌修复**:校长直播“宿舍改造工地开放日”,每周一次,持续至2025樱花节。
---
## 数据附录
### 关键舆情指标汇总
- 总阅读量:28.7亿
- 总讨论量:117万条
- 负面情感峰值:72.8%2024-04-20
- 海外传播:#WHUScandal 推特趋势榜第7位(1,800万阅读)
### 重要用户评论合集(TOP20
> 见各章节“代表性民声”及“多元声音汇聚”。
### 情感分析详细数据
- SnowNLP+BosonNLP联合追踪:120万条文本,12维度情感细颗粒度。
- LDA主题模型:新增“代际冲突”权重0.34,“体制想象”权重0.29。
---
**结语**
从“鼠患”到“甲醛”,武汉大学舆情已非单一后勤危机,而是中国高教资源稀缺性、品牌溢价透支、治理现代化滞后的集中爆发。能否将“珞珈裂痕”转化为“治理契机”,取决于校方是否愿意放弃“控制-回应”旧脚本,走向“透明-共治”新范式。毕竟,**没有哪所大学的樱花,值得学生用健康去交换**。
📊 **报告撰写团队**:珞珈舆情实验室×Z世代观察站
📅 **数据截止**2024-08-05 24:00
📧 **反馈邮箱**opinion@luojialab.org
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -1,43 +0,0 @@
# 深度研究报告
好的,这是根据您提供的数据格式化的Markdown报告。
# 武汉大学:历史、地位与综合概览
武汉大学,简称“武大”,坐落于湖北省武汉市,是一所中华人民共和国教育部直属的综合性全国重点大学。作为国家“985工程”、“211工程”重点建设高校,武汉大学亦是首批“双一流”建设高校之一,体现了其在中国高等教育体系中的卓越地位与重要影响力。学校的办学历史源远流长,可追溯至1893年由晚清湖广总督张之洞创办的自强学堂,历经传承演变,于1928年定名为国立武汉大学,是近代中国首批国立综合性大学之一。2000年,武汉大学与武汉水利电力大学、武汉测绘科技大学、湖北医科大学合并组建新的武汉大学,学科优势进一步整合,综合实力显著增强。学校学科实力雄厚,在教育部第四轮学科评估中,其马克思主义理论、地球物理学、测绘科学与技术、图书情报与档案管理4个学科获评顶尖的A+等级,A类学科总数达19个,位居全国高校前列。在全球范围内,其遥感技术学科高居世界第一,水资源工程、图书情报科学等学科也处于世界领先地位。凭借卓越的学科建设成就,学校共有11个学科入选国家“双一流”建设名单。校园依山傍水,坐拥珞珈山,环绕东湖,自然风光与人文景观交相辉映,被誉为中国最美的大学校园之一。
## 学术实力与优势学科
武汉大学学术实力雄厚,科研成果卓著。根据科睿唯安2024年数据,学校有18个学科进入ESI(基本科学指标数据库)全球排名前1%,彰显了其强大的国际学术影响力。在国内学科评估中,武汉大学同样表现卓越。根据教育部第四轮学科评估结果,其马克思主义理论、地球物理学、测绘科学与技术、图书情报与档案管理4个一级学科获评A+(位列全国前2%),达到全国顶尖水平。此外,法学、生物学、软件工程、公共管理等多个学科也获评A(位列全国前2%-5%),学科优势全面。值得一提的是,其遥感技术学科更是高居全球排名榜首,在软科世界大学学术排名中,武汉大学也连续多年稳居世界百强。学校的科研创新能力依托于世界一流的科研平台,拥有测绘遥感信息工程、水资源与水电工程科学、病毒学、杂交水稻和软件工程等5个国家重点实验室,以及2个国家工程技术研究中心和多个教育部重点实验室。凭借这些平台,武汉大学不仅在国际核心期刊上产出大量高水平学术成果,还深度参与了三峡工程、南水北调等国家重大工程项目的科研攻关,展现了其服务国家战略需求的强大能力。
## 校园环境与人文底蕴
武汉大学素有“中国最美大学”之称,其独特的校园风光源于自然山水与人文建筑的完美交融。校园坐落于长江之畔,东湖之滨,以珞珈山为屏,东湖水为镜。其早期校园规划是中国近代大学校园建设的佳作与典范,开创性地实行了功能分区,并巧妙运用对景、借景等中国传统园林手法,依山就势,将建筑群有机融入自然,造就了湖光山色、钟灵毓秀的整体环境。春日里的樱花大道是武大最负盛名的景观,当樱花盛开,花影与琅琅晨读声交织,“历史与青春在此共书一页”。
校园的标志性建筑群始建于1930年代,是中国近代唯一完整规划并于短时间内建成的大学建筑群。校门口庄重的牌坊正面刻有“国立武汉大学”,背面则题写着“文法理工农医”,彰显了其作为综合性大学的宏大学术视野。这些建筑的设计理念以“中国固有之形式”为基调,实现了中西合璧的完美融合。其外观呈现飞檐斗拱、琉璃瓦顶等中式古典特征,整体布局遵循“轴线对称、主从有序”的传统原则;内部则采用了钢筋混凝土等当时先进的西方建筑技术与结构,并融入了罗马式、拜占庭式等西方古典元素。沿着樱花大道拾级而上,便是以老斋舍为代表的建筑群,其依山而建,形似布达拉宫,气势磅礴。而位于狮子山顶、作为校园至高点和精神象征的老图书馆,以其巍峨的八角飞檐,让人心生敬仰。此外,“珞珈山十八栋”别墅群则是当年为吸引顶尖名师鸿儒而建,见证了学校对人才的尊崇。
正是这种山水与建筑、自然与人文的和谐统一,孕育了武汉大学独特的人文精神。庄严厚重的历史建筑群沉淀了自1893年“自强学堂”以来的百年风骨,而秀美的自然景色则赋予了校园自由开放的灵气,从而形成了武大特有的“珞珈文化”和校园精神。
## 杰出校友与社会贡献
武汉大学自创立以来,始终以培养顶尖人才为己任,为中国乃至世界的社会发展输送了无数栋梁之才。这些杰出校友遍布各行各业,以其卓越成就彰显了母校“自强、弘毅、求是、拓新”的精神。
在政界,武汉大学培养了对中国近现代史产生深远影响的杰出人物。中国共产党的创始人之一、中华人民共和国开国元勋董必武,以及同为中共创始人的陈潭秋和党的重要领导人罗荣桓等革命先驱,都与武汉大学有着深厚的渊源。他们为中国的革命事业和新中国的建立与发展作出了不可磨灭的贡献,是武大校友中的光辉典范。
在商界,武大校友引领着时代的浪潮。小米公司的创始人、同时担任全国人大代表的雷军,以其创新精神和技术远见,深刻改变了全球智能手机和物联网行业的格局。泰康保险集团创始人陈东升、卓尔控股有限公司董事长阎志以及华人实业家与慈善家黄彰任等企业家,都在各自的领域取得了非凡的商业成就,是中国经济发展的重要推动力量。
在学术殿堂,武大学子同样星光璀璨。中国现代地质学之父李四光,为中国石油勘探事业立下了不朽功勋。曾在武汉大学任教的诺贝尔物理学奖得主杨振宁,其提出的“杨-米尔斯理论”是现代物理学的基石之一。新晋杰出校友、免疫学家董晨,则在细胞免疫研究领域取得了世界级的成果。
在文化与法律界,著名学者易中天以其独特的视角和生动的语言,极大地推动了中国历史文化的普及。艺术大师吴冠中被誉为中国现代艺术的泰斗,其作品融合中西,影响深远。著名法学家端木正曾任最高人民法院副院长,为中国的法治建设作出了卓越贡献。
从政界元勋到商界巨擘,从科学泰斗到文化大家,武汉大学培养的杰出人才不胜枚举。他们的奋斗与贡献,不仅是个人价值的实现,更是武汉大学卓越人才培养成果的生动证明,深刻体现了其对社会进步和人类发展的深远影响。
## 国际合作与未来展望
武汉大学坚定不移地推进国际化办学战略,致力于提升全球学术声誉与国际影响力。截至2024年底,学校已与全球53个国家和地区的374所大学及科研机构建立了紧密的合作关系,其中包括哈佛大学、耶鲁大学、牛津大学、剑桥大学、东京大学、多伦多大学等世界名校。学校积极开展覆盖美洲、欧洲、亚洲多地的学生交换与联合培养项目,例如获国家留学基金委批准的“中法医工交叉创新型人才培养项目”等创新型人才国际合作培养项目。在中外合作办学方面,与美国杜克大学强强联手创办的昆山杜克大学,开创了中国“985工程”高校与世界顶尖大学合作办学的成功典范。此外,武汉大学自2006年起便积极参与汉语国际推广,在海外合作建立了多所孔子学院。
展望未来,在“双一流”建设的背景下,武汉大学将继续以更高水平的开放姿态,加强全方位国际交流。最新的“软科世界大学学术排名”显示,武汉大学已连续三年稳居世界百强,这为其全球声誉的提升提供了有力证明。学校将继续围绕国家重大战略需求,通过加强学科建设、提升师资水平与科研实力,深化与世界知名高校的合作,持续增强其在全球高等教育领域的竞争力,向着建成中国特色、世界一流大学的宏伟目标不断迈进。
## 结论
综上所述,武汉大学不仅是中国高等教育体系中的璀璨明珠,更是一所历史底蕴、学术实力与人文精神交相辉映的顶尖学府。从晚清自强学堂的百年传承,到“985工程”与“双一流”建设的核心成员,其卓越地位毋庸置疑。学校凭借在遥感、测绘等领域的全球领先优势和雄厚的综合科研实力,为国家重大战略需求提供了强有力的智力支持。其独一无二的珞珈山水与中西合璧的古典建筑群,共同构筑了“中国最美大学”的诗意画卷,孕育了独特的珞珈文化。遍布全球各行各业的杰出校友,更是其“自强、弘毅、求是、拓新”校训精神的生动体现。展望未来,武汉大学正以更加开放的姿态深化国际合作,朝着建成中国特色、世界一流大学的宏伟目标稳步前行,续写其辉煌篇章。
@@ -1,21 +0,0 @@
# 深度研究报告
好的,作为一名资深的多媒体内容分析专家和融合报告编辑,我将为您呈现一份关于“武汉大学图书馆事件”的立体化、多维度的全景式多媒体分析报告。本报告将严格遵循您提供的创新架构和格式化要求,力求信息的深度融合与分析的立体呈现。
***
# 【全景解析】武汉大学图书馆事件:一场后真相时代的舆论、司法与伦理多维度融合分析报告
## 🌟 全景概览
本报告旨在对“武汉大学图书馆事件”进行一次前所未有的全景式解构。我们将超越传统的线性叙事,将散落在网络空间、官方文件与司法文书中的文字、视觉(描述性)、数据及情感等多维信息碎片,重新拼合与熔炼,构建一个能够反映事件全貌及其深层社会肌理的立体信息模型。此事件不仅是一宗校园纠纷,更是一个绝佳的棱镜,折射出后真相时代下,个人叙事、机构反应、司法裁决与网络舆论之间复杂的博弈与张力。
### 多维信息摘要
- **文字信息核心发现**:事件的叙事主线经历了从“个人情感控诉”(杨某某的社交媒体长文)到“机构模糊定性”(武汉大学的“不雅行为”通报),最终到“司法理性裁决”(法院判决书认定“不构成性骚扰”)的戏剧性三级反转。文本信息的权威性与情感色彩在不同阶段扮演了截然不同的舆论引导角色。
- **视觉内容关键洞察**:事件的核心视觉证据——由杨某某拍摄的手机视频——其本质具有高度的“罗夏墨迹效应”。视频画面本身是模糊且缺乏上下文的,其解读完全依赖于观看者接收到的先验信息(文字叙事)。它从最初被视为性骚扰的“铁证”,到后来被理解为抓痒的“误会”,生动展示了视觉信息在舆论场中的不确定性与可塑性。
- **数据趋势重要指标**:虽然缺乏精确的量化数据,但可从事件的时间线与舆论热度变化中提取关键指标。数据显示,从事件发生(7月11日)到网络曝光(10月11日)存在长达**三个月**的“静默期”。网络曝光后,舆论热度在**48小时**内达到顶峰,校方在舆论压力下迅速发布处分决定。司法判决后,舆论风向在**24小时**内发生显著逆转,讨论焦点从“性骚扰”转向“诬告”与“网络暴力”。
- **跨媒体关联分析**:本事件是文字与视觉信息高度捆绑、相互赋能的典型案例。杨某某的控诉文字为模糊的视频画面提供了唯一的、具有道德谴责意味的“字幕”,二者结合形成了极具传播力的“情绪炸弹”。而法院判决书这一权威文本,则通过提供医学证据(特
@@ -1,11 +0,0 @@
# 深度研究报告
好的,这是根据您提供的数据格式化的研究报告。
# 武汉大学舆情分析报告
## 武汉大学舆情概述
武汉大学作为中国顶尖的“985”和“双一流”高校,因其卓越的学术声誉、深厚的历史底蕴和“最美大学”的校园环境而备受社会关注。这种高关注度使其一举一动都可能成为公众讨论的焦点,从而形成复杂的舆情生态。武汉大学的舆情不仅反映了公众对高等教育的期待与监督,也直接影响着学校的品牌形象、招生质量和社会声誉。
近年来,一系列具体事件凸显了武汉大学所面临的舆情挑战。其中,影响最为深远的
@@ -1,31 +0,0 @@
# 武汉大学舆情分析报告
## 武汉大学舆情概述与定义
武汉大学舆情是指在高等教育和社会背景下,围绕武汉大学及其相关事件产生的公众意见、情绪和态度的总和。舆情监测范围涵盖学术活动、校园管理、师生行为、社会服务等多个方面,常见类型包括正面热点(如学术成就、学生善行)、负面事件(如管理争议、安全事件)以及周期性事件(如招生、毕业季)。在高等教育领域,舆情管理至关重要,因为积极的舆情能提升学校声誉和社会影响力,而消极舆情可能冲击学生价值观和学校形象,甚至引发公关危机。有效的舆情监测需借助专业系统(如乐思、蚁坊软件),这些系统为政府和教育部门提供专业的互联网舆情监测服务,包括舆情分析、预警和疏导,实现全网络舆论实时采集和快速发现。例如,蚁坊软件舆情监测系统平台通过大数据技术为舆情监测提供先机,支持舆情监测、全网络舆论分析和预警工作;乐思舆情监测则强调信息全面性和定向搜索能力,共同支持高校舆情管理中的预警机制和应对策略,以维护学校稳定和发展。此外,舆情监测技术还可辅助教学建模分析,提升品牌营销能力,体现了其在教育领域的多维应用价值。技术应用案例包括AI驱动的智能体,通过调用API和数据训练,更好地发挥数据价值,以及自适应噪声抵消等关键技术研究,这些创新进一步增强了舆情监测的精度和实用性。
## 近期武汉大学舆情事件分析
武汉大学近期舆情事件主要集中在杨景媛学术不端事件和图书馆诬告案两大核心问题上。2025年7月,武汉大学硕士毕业生杨景媛因长期诬告肖姓学弟性骚扰败诉后,其硕士学位论文《中印生育行为影响家庭暴力的经济学分析》被曝光存在严重学术造假问题,包括虚构不存在的《离婚法》、数据来源伪造(将世卫组织公布的36.1%数据篡改为28.3%)、历史常识错误(如将1949年误写为1049年)以及逻辑错误、预设结论、大量抄袭、变量操纵等系统性学术不端行为。这一事件不仅暴露了杨景媛个人学术诚信的缺失,更揭示了武汉大学在研究生培养、论文审核机制以及学术伦理建设方面的系统性漏洞。公众反应强烈,质疑导师指导责任和答辩委员会审查失效,同时批评校方在事件曝光后的迟缓应对态度——直到8月1日央媒关注后才宣布组建工作专班进行全面调查复核。此外,该事件与图书馆诬告案交织:肖同学因被诬告遭受记过处分,丧失保研与法考资格,其家庭更因网暴陷入长期创伤(爷爷受刺激去世、外公成植物人);而杨景媛却获得保研资格并被香港浸会大学录取(后证实为研究助理而非博士录取),甚至在败诉后公开炫耀成就,引发对高校程序正义和道德审查机制的广泛质疑。香港浸会大学虽于7月31日发出道德核查函并启动独立审查程序,但8月6日流传的"撤销录取资格"消息被证实为谣言,校方仅表示按纪律程序处理而未公布具体决定,这种处理方式与公众对学术不端"零容忍"的期待形成鲜明落差。事件已对武汉大学校誉和公信力造成重创,成为反思中国高等教育学术诚信与制度监管的典型案例。值得注意的是,杨景媛在调查期间曾试图通过百度网盘上传论文修正文件为自己辩解,但根据中国学术管理规定,已归档学位论文原则上不允许修改,这一行为进一步引发公众对学术规范执行力的质疑。目前校方对论文修改争议仍保持沉默,武汉大学和香港浸会大学的最终处理结果仍悬而未决,公众持续关注事件进展。
## 舆情应对策略与措施
武汉大学在舆情管理方面展现出多层次的应对策略,但近年来的危机事件暴露了其机制中的挑战。以2023年图书馆诬告案为例,学校初期基于单方指控快速处分学生,试图通过‘先处理为敬’的方式平息舆情,却在法院判决反转后引发更严重的舆论反噬,凸显了危机处理中调查不足、反应滞后和急于问责的问题。官方回应方面,校长张平文的‘等上级安排’言论反映了内部决策迟缓,导致‘高度重视’仅停留在内部层面,形成悬殊的公众感知温差,损害了信任;校方在事件中未提供具体调查依据,信息空窗期过长,加剧了隐瞒印象。学校通过保卫部发布通报澄清谣言(如2025年机动车逼停事件),并采取报案等法律行动,体现了沟通和行动结合的策略,但学用脱节问题(如依赖‘落地劝删’等落后手段和缺乏动态舆情监测机制)表明需加强实战能力,避免理论知识与实际处置脱节。舆情研判不足导致未能预判风向反转,应对话术机械被动,错失修复信任窗口。此外,事件还揭示了学校对‘极端女权’等社会舆论现象的应对不足,需更深入理解民意背景。总体而言,武大需优化响应速度、确保调查公正性、建立透明沟通机制(如及时发布‘一对一’式核查回应和补救措施),并通过动态跟踪和预警体系缓解负面舆情,以修复声誉和提升舆情管理效能。
## 舆情对武汉大学声誉的影响
武汉大学近年来面临多起舆情事件,对学校声誉、招生、学术合作和社会形象产生了显著影响。根据知微数据分析,2021年武汉大学'和服赏樱'冲突事件影响力指数达65.4,高于同类事件均值10.8%,引发广泛舆论关注。事件初期,负面观点占比较高,质疑学校狭隘和保安暴力行为。但通过及时公关回应,武汉大学发布情况说明,强调游客未预约和言语挑衅,舆论风向逆转,支持学校决定的比例从26%升至48%。这体现了正面回应在舆情管理中的有效性,有助于维护社会形象。
然而,其他事件如2025年的校园交通冲突,部分自媒体传播'教职工子女蛮横别停学生'等不实信息,导致负面讨论滋生,网民质疑校园管理特权问题,例如取消车辆通行授权三个月的惩罚措施细节和校外人员校园行驶权限。尽管校方迅速澄清涉事驾驶员为校外退休职工子女、无特权行为,并报案处理谣言,事件仍暴露了谣言对声誉的潜在危害。此外,学术相关争议如肖某某纪律处分和杨某某论文调查,通过媒体和社交平台扩散,影响学术合作信任度。
招生方面,2024年武汉大学招生总人数有所增加,面向全国招生7215人,强基计划招生专业从8个增加到9个,显示学校在扩大招生规模上的努力。然而,校长张平文在宣传片中不当言论曾引发网络质疑,需警惕对招生的潜在负面影响。值得注意的是,全国高等教育性别格局变化显著,2023年本科在校生女性占比达52.22%,招生中女生占比高达63%,但顶尖高校如C9联盟女性占比仅37.7%,武汉大学作为综合性大学,需关注专业性别分化(如计算机学院男女比4.88:1,新闻传播学院女生超80%)对招生多样性和社会形象的影响。
总体而言,舆情事件对招生和合作可能带来短期波动,但武汉大学的应对策略——如快速响应和透明沟通——在一定程度上 mitigates 负面影响,凸显高校需加强舆情监测和公关智慧以保护声誉。新华社等媒体评论指出舆情应对应避免'唯上不唯实',强调高校需提升行政敏感度,防止事件处理失能进一步损害形象。
## 未来舆情趋势与建议
武汉大学未来可能面临的舆情挑战主要集中在透明度不足、沟通效率低下和预防机制缺失等方面。基于搜索结果,武汉大学在图书馆事件中暴露出反应滞后、信息空窗期过长的问题,导致公众信任流失。未来需加强舆情监测和预警体系,采用人工智能技术(如BERT模型、情感分析)实时跟踪网络舆论动态,提升研判能力。建议建立快速响应机制,确保调查流程公开透明,避免模糊策略;优化内部沟通流程,减少层层汇报导致的延误;同时引入区块链技术或深度学习模型(如CNN)加强校园舆情分析,实现多维度事件处理。此外,应定期复盘舆情案例,完善危机公关预案,通过专业、善意的沟通回应公众关切,修复声誉损害。结合前沿技术趋势,武汉大学可借鉴大数据、云计算和人工智能在智慧城市管理中的创新应用,推动舆情管理手段和模式升级,如利用人工智能和区块链提升供应链韧性和协同效益,加强2024年后的业务效率改善。通过整合5G、半导体等新一代信息技术,加快智能化的舆情预警和响应系统建设,提升透明度和沟通效率,预防潜在危机。值得注意的是,在2024年金融科技创新大赛中,武汉大学团队展示了区块链和人工智能的应用潜力,如“区块链大战供应链融碳生金”项目,这为舆情管理提供了技术参考,可探索区块链用于数据透明存证和AI驱动的情感分析,以增强舆情应对的实时性和可信度。同时,关注AI伦理和治理框架,如避免算法偏见和隐私风险,确保技术应用符合国际标准,提升整体舆情管理的可持续性和普惠性。
## 结论
综合以上分析,武汉大学舆情管理面临显著挑战,尤其在学术诚信、透明度和响应机制方面。近期事件如杨景媛学术不端和图书馆诬告案暴露了系统性漏洞,对学校声誉造成冲击。未来,武汉大学需加强技术应用(如AI和区块链)、优化沟通策略,并建立预防性机制,以提升舆情应对能力,维护长期声誉和发展。
@@ -1,31 +0,0 @@
# 武汉大学舆情分析报告
## 武汉大学舆情概述与定义
武汉大学舆情是指在高等教育和社会背景下,围绕武汉大学及其相关事件产生的公众意见、情绪和态度的总和。舆情监测范围涵盖学术活动、校园管理、师生行为、社会服务等多个方面,常见类型包括正面热点(如学术成就、学生善行)、负面事件(如管理争议、安全事件)以及周期性事件(如招生、毕业季)。在高等教育领域,舆情管理至关重要,因为积极的舆情能提升学校声誉和社会影响力,而消极舆情可能冲击学生价值观和学校形象,甚至引发公关危机。有效的舆情监测需借助专业系统(如乐思、蚁坊软件),这些系统为政府和教育部门提供专业的互联网舆情监测服务,包括舆情分析、预警和疏导,实现全网络舆论实时采集和快速发现。例如,蚁坊软件舆情监测系统平台通过大数据技术为舆情监测提供先机,支持舆情监测、全网络舆论分析和预警工作;乐思舆情监测则强调信息全面性和定向搜索能力,共同支持高校舆情管理中的预警机制和应对策略,以维护学校稳定和发展。此外,舆情监测技术还可辅助教学建模分析,提升品牌营销能力,体现了其在教育领域的多维应用价值。技术应用案例包括AI驱动的智能体,通过调用API和数据训练,更好地发挥数据价值,以及自适应噪声抵消等关键技术研究,这些创新进一步增强了舆情监测的精度和实用性。
## 近期武汉大学舆情事件分析
武汉大学近期舆情事件主要集中在杨景媛学术不端事件和图书馆诬告案两大核心问题上。2025年7月,武汉大学硕士毕业生杨景媛因长期诬告肖姓学弟性骚扰败诉后,其硕士学位论文《中印生育行为影响家庭暴力的经济学分析》被曝光存在严重学术造假问题,包括虚构不存在的《离婚法》、数据来源伪造(将世卫组织公布的36.1%数据篡改为28.3%)、历史常识错误(如将1949年误写为1049年)以及逻辑错误、预设结论、大量抄袭、变量操纵等系统性学术不端行为。这一事件不仅暴露了杨景媛个人学术诚信的缺失,更揭示了武汉大学在研究生培养、论文审核机制以及学术伦理建设方面的系统性漏洞。公众反应强烈,质疑导师指导责任和答辩委员会审查失效,同时批评校方在事件曝光后的迟缓应对态度——直到8月1日央媒关注后才宣布组建工作专班进行全面调查复核。此外,该事件与图书馆诬告案交织:肖同学因被诬告遭受记过处分,丧失保研与法考资格,其家庭更因网暴陷入长期创伤(爷爷受刺激去世、外公成植物人);而杨景媛却获得保研资格并被香港浸会大学录取(后证实为研究助理而非博士录取),甚至在败诉后公开炫耀成就,引发对高校程序正义和道德审查机制的广泛质疑。香港浸会大学虽于7月31日发出道德核查函并启动独立审查程序,但8月6日流传的"撤销录取资格"消息被证实为谣言,校方仅表示按纪律程序处理而未公布具体决定,这种处理方式与公众对学术不端"零容忍"的期待形成鲜明落差。事件已对武汉大学校誉和公信力造成重创,成为反思中国高等教育学术诚信与制度监管的典型案例。值得注意的是,杨景媛在调查期间曾试图通过百度网盘上传论文修正文件为自己辩解,但根据中国学术管理规定,已归档学位论文原则上不允许修改,这一行为进一步引发公众对学术规范执行力的质疑。目前校方对论文修改争议仍保持沉默,武汉大学和香港浸会大学的最终处理结果仍悬而未决,公众持续关注事件进展。
## 舆情应对策略与措施
武汉大学在舆情管理方面展现出多层次的应对策略,但近年来的危机事件暴露了其机制中的挑战。以2023年图书馆诬告案为例,学校初期基于单方指控快速处分学生,试图通过‘先处理为敬’的方式平息舆情,却在法院判决反转后引发更严重的舆论反噬,凸显了危机处理中调查不足、反应滞后和急于问责的问题。官方回应方面,校长张平文的‘等上级安排’言论反映了内部决策迟缓,导致‘高度重视’仅停留在内部层面,形成悬殊的公众感知温差,损害了信任;校方在事件中未提供具体调查依据,信息空窗期过长,加剧了隐瞒印象。学校通过保卫部发布通报澄清谣言(如2025年机动车逼停事件),并采取报案等法律行动,体现了沟通和行动结合的策略,但学用脱节问题(如依赖‘落地劝删’等落后手段和缺乏动态舆情监测机制)表明需加强实战能力,避免理论知识与实际处置脱节。舆情研判不足导致未能预判风向反转,应对话术机械被动,错失修复信任窗口。此外,事件还揭示了学校对‘极端女权’等社会舆论现象的应对不足,需更深入理解民意背景。总体而言,武大需优化响应速度、确保调查公正性、建立透明沟通机制(如及时发布‘一对一’式核查回应和补救措施),并通过动态跟踪和预警体系缓解负面舆情,以修复声誉和提升舆情管理效能。
## 舆情对武汉大学声誉的影响
武汉大学近年来面临多起舆情事件,对学校声誉、招生、学术合作和社会形象产生了显著影响。根据知微数据分析,2021年武汉大学'和服赏樱'冲突事件影响力指数达65.4,高于同类事件均值10.8%,引发广泛舆论关注。事件初期,负面观点占比较高,质疑学校狭隘和保安暴力行为。但通过及时公关回应,武汉大学发布情况说明,强调游客未预约和言语挑衅,舆论风向逆转,支持学校决定的比例从26%升至48%。这体现了正面回应在舆情管理中的有效性,有助于维护社会形象。
然而,其他事件如2025年的校园交通冲突,部分自媒体传播'教职工子女蛮横别停学生'等不实信息,导致负面讨论滋生,网民质疑校园管理特权问题,例如取消车辆通行授权三个月的惩罚措施细节和校外人员校园行驶权限。尽管校方迅速澄清涉事驾驶员为校外退休职工子女、无特权行为,并报案处理谣言,事件仍暴露了谣言对声誉的潜在危害。此外,学术相关争议如肖某某纪律处分和杨某某论文调查,通过媒体和社交平台扩散,影响学术合作信任度。
招生方面,2024年武汉大学招生总人数有所增加,面向全国招生7215人,强基计划招生专业从8个增加到9个,显示学校在扩大招生规模上的努力。然而,校长张平文在宣传片中不当言论曾引发网络质疑,需警惕对招生的潜在负面影响。值得注意的是,全国高等教育性别格局变化显著,2023年本科在校生女性占比达52.22%,招生中女生占比高达63%,但顶尖高校如C9联盟女性占比仅37.7%,武汉大学作为综合性大学,需关注专业性别分化(如计算机学院男女比4.88:1,新闻传播学院女生超80%)对招生多样性和社会形象的影响。
总体而言,舆情事件对招生和合作可能带来短期波动,但武汉大学的应对策略——如快速响应和透明沟通——在一定程度上 mitigates 负面影响,凸显高校需加强舆情监测和公关智慧以保护声誉。新华社等媒体评论指出舆情应对应避免'唯上不唯实',强调高校需提升行政敏感度,防止事件处理失能进一步损害形象。
## 未来舆情趋势与建议
武汉大学未来可能面临的舆情挑战主要集中在透明度不足、沟通效率低下和预防机制缺失等方面。基于搜索结果,武汉大学在图书馆事件中暴露出反应滞后、信息空窗期过长的问题,导致公众信任流失。未来需加强舆情监测和预警体系,采用人工智能技术(如BERT模型、情感分析)实时跟踪网络舆论动态,提升研判能力。建议建立快速响应机制,确保调查流程公开透明,避免模糊策略;优化内部沟通流程,减少层层汇报导致的延误;同时引入区块链技术或深度学习模型(如CNN)加强校园舆情分析,实现多维度事件处理。此外,应定期复盘舆情案例,完善危机公关预案,通过专业、善意的沟通回应公众关切,修复声誉损害。结合前沿技术趋势,武汉大学可借鉴大数据、云计算和人工智能在智慧城市管理中的创新应用,推动舆情管理手段和模式升级,如利用人工智能和区块链提升供应链韧性和协同效益,加强2024年后的业务效率改善。通过整合5G、半导体等新一代信息技术,加快智能化的舆情预警和响应系统建设,提升透明度和沟通效率,预防潜在危机。值得注意的是,在2024年金融科技创新大赛中,武汉大学团队展示了区块链和人工智能的应用潜力,如“区块链大战供应链融碳生金”项目,这为舆情管理提供了技术参考,可探索区块链用于数据透明存证和AI驱动的情感分析,以增强舆情应对的实时性和可信度。同时,关注AI伦理和治理框架,如避免算法偏见和隐私风险,确保技术应用符合国际标准,提升整体舆情管理的可持续性和普惠性。
## 结论
综合以上分析,武汉大学舆情管理面临显著挑战,尤其在学术诚信、透明度和响应机制方面。近期事件如杨景媛学术不端和图书馆诬告案暴露了系统性漏洞,对学校声誉造成冲击。未来,武汉大学需加强技术应用(如AI和区块链)、优化沟通策略,并建立预防性机制,以提升舆情应对能力,维护长期声誉和发展。
@@ -1,14 +0,0 @@
# 深度研究报告
[数据]节点(如排名、论文数)和[文字]节点(如科研突破报道),并配以[图片]节点(如实验室照片)。
- **文化魅力**分支将主要连接到[图片]节点(海量樱花UGC),并由[文字]节点(“最美大学”叙事)和[数据]节点(“1000株”)提供支撑。
- **公共治理**分支将连接到多个负面事件的[文字]节点(如图书馆事件、樱花季冲突),每个事件都将关联一个关键的[图片/视频]引爆点,并由[数据]节点(如响应时间线)标示其处理效率。
- **校友网络**分支将连接到[文字]节点(雷军等校友故事)和[数据]节点(13亿捐款),并与“雷军班”事件的[图片]节点(标签截图)产生关联,显示其双刃剑效应。
图中将用**绿色箭头**表示正面协同效应(如学术数据支撑文化魅力),用**红色箭头**表示负面触发关系(如争议视频引爆治理危机),清晰地展现出多媒体信息流如何共同塑造了武汉大学复杂而动态的公共形象。
### AI分析结果汇总(模拟)
- **情感分析**:对近一年涉“武汉大学”社交媒体文本进行情感分析,结果显示:正面情感占45%(主要集中在樱花季、学术成就),负面情感占35%(集中在几次重大舆情事件期间),中性情感占20%。情感分布呈现明显的“周期性波动”和“事件驱动”特征。
- **主题建模**:通过LDA主题模型,识别出五大核心舆论议题簇:1)**樱花与校园生活**(关键词:樱花、预约、游客、最美);2)**学术与排名**(关键词:科研、论文、排名、病毒);3)**图书馆事件与公正**(关键词:图书馆、诬告、判决、回应、公平);4)**雷军与精英教育**(关键词:雷军、捐款、雷军班、公平);5)**校园管理与冲突**(关键词:保安、游客、规定、和服)。
- **传播路径分析**:模拟显示,负面舆情事件的典型传播路径为:个体社交账号(引爆点)-> 领域意见领袖(KOL)转发(第一波放大)-> 多个自媒体平台跟进(形成舆论场)-> 官方媒体介入(事件升级)。整个过程在24小时内即可完成,视觉内容的传播速度比纯文本快3-5倍。
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,189 +0,0 @@
# 【深度调查】武汉大学图书馆争议事件全面新闻分析报告
## 🎯 核心要点摘要
### 关键事实发现
- 事件始于2023年7月11日武汉大学图书馆,涉及性骚扰指控与反指控的复杂校园纠纷
- 法院一审判决(2025年7月25日)认定不构成性骚扰,与校方前期处分决定形成明显矛盾
- 事件持续25个月,经历网络发酵、司法诉讼、学术审查等多重发展阶段
- 香港浸会大学于2025年8月6日撤销杨景媛博士录取资格,引发连锁反应
### 信息来源概览
- 主流媒体报道:南方都市报、经济观察报、澎湃新闻、潇湘晨报等深度报道
- 官方信息发布:武汉大学三次官方通报(2023年10月、2025年8月)
- 司法文书:武汉市经开区人民法院一审判决书
- 学术机构声明:香港浸会大学录取撤销决定
## 📊 一、事件起源与初期发展脉络
### 1.1 事件脉络梳理
| 时间 | 事件 | 信息来源 | 可信度 | 影响程度 |
|------|------|----------|--------|----------|
| 2023年7月11日18:30 | 图书馆行为争议发生 | 多方证实 | 高 | 重大 |
| 2023年7月13日 | 杨某要求重新书面道歉 | 百度百科记录 | 中 | 中等 |
| 2023年10月11日 | 杨某网络发文曝光 | 社交媒体证据 | 高 | 重大 |
| 2023年10月13日 | 武大给予肖某某记过处分 | 武大官方通报 | 极高 | 重大 |
### 1.2 多方报道对比
**主流媒体观点**
- 《南方都市报》:"涉事双方双双沦为网暴的靶心:一方被贴上诬告者的标签,另一方被冠上性骚扰的罪名" (发布时间:2024年)
- 《经济观察报》:"武汉大学校长张平文回应了记者的电话求询" (发布时间:2025年7月31日)
**官方声明**
- 武汉大学:"对涉及我校学生的网上举报,经调查核实,根据相关规定,学校研究决定,给予2022级本科生肖某某记过处分" (发布时间:2023年10月13日)
- 武汉市经开区法院:"现有证据无法达到证明肖某某在自慰的目的" (发布时间:2025年7月25日)
### 1.3 关键数据分析
事件时间跨度达25个月,从2023年7月11日至2025年8月,涉及5段总时长4分50秒的视频证据和16分钟对话录音。网络关注度持续高涨,知乎相关问题的回答数量达到2,244个,最高赞回答获得5,178人赞同,微博相关话题"7418人参与534评论"。
### 1.4 事实核查与验证
根据法院一审判决书认定,视频证据"经过严重剪辑"可能误导公众,同时肖某某提供的湿疹病史证据(2018-2023年多次就诊记录)经公证具有较高可信度。双方在道歉信中的表述存在语义模糊,无法直接证明性骚扰行为。
## 📈 二、校方处理与司法判决对比分析
### 2.1 处理脉络梳理
| 时间 | 事件 | 信息来源 | 可信度 | 影响程度 |
|------|------|----------|--------|----------|
| 2023年10月 | 校方初期调查存疑 | 南方都市报 | 高 | 重大 |
| 2023年10月13日 | 迫于舆论压力处分 | 武大通报 | 极高 | 重大 |
| 2024年6月 | 杨某提起民事诉讼 | 法院记录 | 极高 | 重大 |
| 2025年7月25日 | 法院一审驳回指控 | 判决书 | 极高 | 重大 |
### 2.2 多方报道分析
**校方立场变化**
初期(2023年10月):"学校历来对违规违纪行为零容忍,一经查实,绝不姑息"
后期(2025年8月):"已组建工作专班,正在全面调查复核"
**司法认定**
法院明确认定:"性骚扰需满足针对特定对象、具有性暗示或不当意图的要件,而肖某某的行为仅为抓挠身体,无针对性"
### 2.3 关键矛盾点分析
1. **证据认定差异**:校方初期依据视频证据作出处分,法院认定视频经过剪辑且医学证据支持抓痒可能性
2. **处理程序问题**:校方在舆论压力下快速处分,但后续医学证据出现后未及时重新评估
3. **标准不统一**:校纪处分与司法认定出现明显分歧,反映高校处理机制与法律标准的衔接问题
### 2.4 事实核查与验证
根据肖母提供的证据,包括5份与医护咨询湿疹治疗购药的微信聊天记录公证书、事发当天购药记录,经专业医学专家论证"行为不符合自慰特征",该证据链具有较高可信度。
## 📊 三、网络舆论与社会影响深度分析
### 3.1 舆论发展脉络
| 时间 | 事件 | 信息来源 | 可信度 | 影响程度 |
|------|------|----------|--------|----------|
| 2023年10月 | 网络发酵初期 | 社交媒体数据 | 高 | 重大 |
| 2024年2月 | 肖母公开证据舆论反转 | 网络平台 | 中 | 重大 |
| 2025年7月 | 判决后新一轮网暴 | 平台数据 | 高 | 重大 |
| 2025年8月 | 学术诚信问题曝光 | 多方报道 | 高 | 重大 |
### 3.2 多方报道分析
**媒体评论视角**
- 大象新闻:"没有胜利者,都是受害者"的框架分析
- 胡锡进分析:"武大图书馆事件直到现在没有明确结果,有关方面一直在推动善后"
**平台治理反应**
豆瓣专项治理公告:"清理删除违规内容2547条,处置违规账号162个"
### 3.3 网络影响数据
- 肖某某遭受网暴后果:个人信息泄露,照片被制作成花圈、遗像传播,确诊严重创伤后应激障碍
- 杨某方面:手机号、家庭住址、社交媒体账号等被"开盒"
- 家庭影响:肖家称年迈祖父受刺激后离世,父母失业
### 3.4 事实核查与验证
网络暴力证据经多个平台确认,但具体伤害程度难以量化。平台治理数据来自豆瓣官方公告,具有较高可信度。个人遭遇描述来自家属陈述,需谨慎采信。
## 📈 四、学术维度与制度反思
### 4.1 学术问题脉络
| 时间 | 事件 | 信息来源 | 可信度 | 影响程度 |
|------|------|----------|--------|----------|
| 2025年7月 | 论文问题首次曝光 | 网络爆料 | 中 | 重大 |
| 2025年8月6日 | 港浸大撤销录取 | 官方声明 | 极高 | 重大 |
| 2025年8月 | 武大启动论文复核 | 官方通报 | 极高 | 重大 |
### 4.2 学术问题分析
**论文具体问题**
- 虚构中国的离婚法(中国不存在《离婚法》)
- 篡改印度的家暴率数据
- 将新中国成立的年份写成1049年
- 模型造假等学术不端嫌疑
**制度回应**
香港浸会大学纪律委员会仅用6天时间完成调查并做出决定,体现高效处理
### 4.3 深度背景分析
**高校处理机制缺陷**
- 性骚扰认定标准模糊
- 应急处理缺乏充分调查
- 舆论压力影响决策独立性
- 学术监管体系存在漏洞
**系统性风险**
导师郭汝飞及其他学生论文被知网下架,显示问题可能具有系统性特征
### 4.4 事实核查与验证
论文错误事实经多个来源交叉验证,具有较高可信度。香港浸会大学的决定经过正式程序,具有权威性。导师论文下架情况需要进一步官方确认。
## 🔍 综合事实分析
### 事件全貌还原
基于多源信息重构,事件本质是一起由主观误解引发的校园纠纷,在舆论放大下演变为复杂的法律、道德和学术复合型事件。核心事实包括:肖某某确实存在肢体行为,但医学证据支持抓痒可能性;杨某主观认定性骚扰但缺乏直接证据;校方在舆论压力下仓促处分;司法判决基于完整证据链否定性骚扰指控。
### 信息可信度评估
| 信息类型 | 来源数量 | 可信度 | 一致性 | 时效性 |
|----------|----------|--------|--------|--------|
| 司法判决 | 1个 | 极高 | 高 | 最新 |
| 医学证据 | 多份 | 高 | 高 | 及时 |
| 校方通报 | 3次 | 高 | 中 | 分阶段 |
| 网络爆料 | 大量 | 中 | 低 | 持续 |
### 发展趋势研判
1. **短期**:武汉大学工作专班复核结果将成为关注焦点,可能涉及处分调整和学术处理
2. **中期**:高校性骚扰处理机制将面临修订压力,建立更严谨的调查程序
3. **长期**:此案可能成为校园争议处理的典型案例,影响类似事件的处理标准
### 影响评估
**个体层面**:双方均遭受严重身心伤害和网络暴力
**制度层面**:暴露高校危机管理、学术监管、投诉处理等多重机制缺陷
**社会层面**:引发对网络舆论、司法公正、性别议题的广泛讨论
## 📋 专业结论
### 核心事实总结
1. 肖某某图书馆行为经司法认定不构成性骚扰,存在医学合理解释
2. 校方前期处分决定在证据不足情况下做出,与司法认定存在矛盾
3. 杨某学术诚信问题经香港浸会大学调查确认,导致录取资格撤销
4. 网络暴力对双方造成严重伤害,平台治理机制亟待完善
### 专业观察
1. **证据认定重要性**:此案凸显完整证据链和专业评估在敏感事件中的关键作用
2. **制度衔接问题**:校纪处分与司法认定需要更好的衔接机制
3. **舆论管理挑战**:高校在应对网络舆论时需要更专业的危机管理能力
4. **学术诚信底线**:事件表明学术不端问题会产生连锁反应,影响个人发展
## 📎 信息附录
### 重要数据汇总
- 事件时间跨度:25个月(2023年7月-2025年8月)
- 视频证据:5段,总时长4分50秒
- 录音证据:16分钟
- 司法审理时间:700多天
- 网络参与度:知乎2244个回答,微博7418人参与
### 关键报道时间线
- 2023年10月11日:杨某网络发文曝光
- 2023年10月13日:武大处分通报
- 2024年2月:肖母公开医学证据
- 2025年7月25日:法院一审判决
- 2025年8月1日:武大组建工作专班
- 2025年8月6日:港浸大撤销录取
### 权威来源清单
1. 武汉市经开区人民法院一审判决书
2. 武汉大学官方通报(三次)
3. 香港浸会大学官方声明
4. 南方都市报深度调查
5. 经济观察报报道
---
**报告说明**:本报告基于截至2025年8月的公开信息编制,力求客观准确反映事件全貌。随着事态发展,部分信息可能更新,建议读者关注权威渠道的最新通报。所有事实陈述均经过多源验证,观点分析基于新闻专业准则保持中立客观。
@@ -1,32 +0,0 @@
# 武汉大学舆情分析报告
## 武汉大学舆情概述与背景介绍
武汉大学作为中国著名高等学府,其舆情监测工作涵盖校园事件、学术声誉和学生活动等多个关键领域。近年来,该校经历了多起引发广泛关注的舆情事件,凸显了舆情管理的重要性。例如,2025年的“职工子女逼停学生”事件中,一名校外人员驾驶机动车在校园内危险驾驶,引发公众对校园安全和特权行为的质疑。尽管校方迅速发布通报澄清事实,但部分自媒体传播不实信息,导致舆情发酵,舆情在6月14日和18日分别达到峰值和次峰值,媒体如新华网、央广网等广泛报道,网民质疑处罚细节和校园管理漏洞。此外,2023年的“图书馆诬告案”涉及学生杨景媛指控同学性骚扰,后经法院判决不成立,但事件暴露了学术诚信和道德规范问题,杨景媛的硕士论文被指存在编造法律条文(如虚构《离婚法》)、数据造假(如未注明来源的中国社科院和WHO数据)等学术不端行为,引发对武汉大学学术声誉和监管机制的批评,校方被指在事件处理中存在程序瑕疵和响应延迟。这些案例表明,舆情监测有助于及时发现负面信息,但校方在回应时效性、信息透明度和处理公正性方面仍有改进空间,需加强危机公关和风险防范,以维护大学形象和社会信任。值得注意的是,相关研究成果已广泛应用于武汉市网络舆情中心等机构的监测系统,涉及可信人工智能等方向,这为武汉大学舆情监测提供了技术支撑。同时,校园安全监测可通过智能摄像头、传感器等设备实现实时监控,提升风险防范能力,而学术声誉管理需结合环境、健康与安全(EHS)等标准,确保合规运营和可持续发展。
## 近期武汉大学舆情热点事件分析
武汉大学图书馆性骚扰争议事件(2023-2024年)是该校近期最受关注的舆情热点之一。事件始于2023年7月,女生杨景媛在社交媒体指控男生肖明滔在图书馆实施性骚扰,校方迅速对肖明滔处以记过处分。然而,2025年7月法院一审判决认定肖明滔行为不构成性骚扰,驳回杨景媛诉求,引发舆论反转。公众强烈质疑杨景媛涉嫌诬告和学术不端(其硕士论文被指造假),同时批评武汉大学处分草率、处理不透明。校方回应滞后,仅表示将全面调查复核处分和论文问题,但未提供具体时间表或依据,导致舆情持续发酵。媒体覆盖广泛,包括新华社、《南方周末》、《半岛晨报》、《齐鲁晚报》等主流媒体均报道此事,知乎等平台讨论热度高,焦点集中于高校治理能力、舆情处置失当(如反应延迟、话术机械)和学术诚信问题。该事件历时两年多,涉及舆论、高校管理及司法判决等多方面,暴露了武汉大学在危机管理、透明度和社会信任维护方面的不足,对学校声誉造成显著负面影响。值得注意的是,维基百科已创建“武汉大学图书馆争议事件”条目,显示其影响深远,但条目标题暂定为可能原创或不准确,需进一步共识。此外,一周舆情热点报告将此事件列为重点,凸显其在公共讨论中的持续热度。
## 舆情监测与应对机制
武汉大学在舆情监测与应对机制方面展现出系统化的管理策略,结合先进技术工具和危机公关措施,以应对校园舆情事件。例如,在2025年“职工子女逼停学生”事件中,校方通过保卫部发布官方通报,使用舆情监测机制实时跟踪舆论动态,并针对谣言迅速报案,体现了主动应对和透明度原则。技术工具上,武汉大学采用大数据分析和AI监测系统,如时空大数据分析与AI技术融合的时空信息工程专业(2025年新设),通过动态感知和智能分析能力识别舆情热点和传播趋势,这有助于早期预警和精准干预。具体而言,武汉大学部署了WHU-POMS网络舆情监控系统,该系统全面跟踪互联网上与学校相关的新闻报道,实时识别新闻热点和敏感信息,确保舆情监测的全面性和及时性。此外,武汉大学在舆情研究领域深化产学研合作,例如与湖北省舆情研究中心等机构开展学术交流,提升舆情监测的专业能力,包括网络舆情监测与分析、传播学原理等课程内容。应对策略包括“冷处理”方式,避免过早回应激化矛盾,同时坚持公开公正的信息发布,以消弭公众猜疑。危机公关措施强调责任担当,如校方在事件中主动澄清事实,维护校园秩序和公信力。整体上,武汉大学的舆情处理表现显示出对舆情风险的敏感性和结构化应对能力,但事件也暴露出通报范围和方式需优化,以避免衍生问题,未来应持续完善监测机制和响应时效性,以提升舆情管理效果。
## 舆情对武汉大学的影响评估
武汉大学近年来面临多起舆情事件的冲击,对学校声誉、招生、学术合作及社会形象产生了显著影响。根据舆情数据分析,2019年“和服赏樱”冲突事件影响力指数达65.4,高于同类事件均值10.8%,短期内引发公众对校园管理政策的质疑,但校方通过及时回应和澄清,使支持率从26%升至48%,有效缓解了负面舆论。然而,2025年的“职工子女逼停学生”事件中,尽管校方迅速通报并报案处理谣言,但自媒体传播仍导致特权质疑等衍生舆情,暴露了危机沟通中的漏洞。更为严重的是2025年杨景媛事件,涉及诬告行为和学术造假,法院判决驳回其诉讼请求,但校方初期仓促处分受害者肖某某(记过处分),未充分调查即回应舆情,导致程序正义缺失;事后未撤销错误处分或追究杨景媛责任(因其已毕业“学籍自动结束”),引发公众对高校治理和学术诚信的广泛批评,损害了学校公信力。招生方面,近期虚假研学夏令营事件(如“魔豆梦工厂”等机构利用虚假信息招生)促使武汉大学招生办公室发布严正声明,凸显舆情对招生诚信的直接影响;杨景媛事件中,其保研资格和香港浸会大学录取引发道德审查争议,长期可能削弱国际学生吸引力,因为舆情波动会损害潜在申请者的信任,尤其在国际传播层面(如2025年全球AI设备博览会等国际活动中的形象关联)。学术合作上,武汉大学持续参与国际学术交流,如师生参加国际媒介与传播研究学会2025年会,以及举办跨学科论坛如“新时代中国国际传播创新”,但2025年性骚扰案涉及的论文争议(如虚构《离婚法》、数据造假)若处理不当,可能损害学术诚信形象,影响研究伙伴关系;校方学术不端处理机制响应延迟,暴露了论文审核和导师指导的漏洞。校方应从这些事件中学习改进,包括加强舆情监测、提升回应透明度(避免“重舆轻法”的应急处理)、平衡纪律处分与法律裁决,建立更有效的谣言应对机制,强化国际传播以维护长期声誉和社会形象,并完善道德审查和学术监管体系,防止类似事件重演。
## 未来舆情趋势与建议
武汉大学未来可能面临的舆情挑战主要集中在学术诚信危机、管理透明度不足以及沟通机制失效等方面。近期杨景媛事件暴露了学校在舆情管理中的多重问题:学术论文造假(如虚构《离婚法》、数据来源伪造)未被及时查处,损害了学术公信力;图书馆诬告事件中,校方仓促处分受害者肖某某以“平息舆论”,却未在法院判决后撤销处分或追究诬告者责任,凸显程序正义缺失。这些案例反映了高校在危机预防和响应上的滞后性,易引发公众对学校形象和治理能力的质疑。此外,搜索结果揭示的知网论文修改事件和清退留学生争议,表明武汉大学在平衡学术声誉与网络舆论压力时面临困境,可能进一步削弱公众信任。
为改进舆情管理,建议武汉大学采取以下措施:
1. 增强透明度:建立公开的学术不端调查流程,及时发布处理结果,避免“学籍自动结束”等逃避责任的机制。参考搜索结果中香港浸会大学的做法,明确招生和行为守则,并对外回应公众关切。同时,借鉴美国大学“宽进严出”模式,加强招生和毕业环节的道德审查,确保学术与伦理标准统一。
2. 加强沟通策略:优化舆情监测工具,主动收集和分析网络需求清单,在危机初期进行事实核查而非仓促行动。例如,在类似事件中,应优先基于医学和法律证据决策,而非屈服于舆论压力。强化与媒体和校友的沟通,避免公关策略失误导致形象损害。
3. 强化预防措施:实施风险管理策略,包括定期风险评估和潜在危机检测,建立防诬告机制和学术审查强化体系,确保研究生培养质量监控不被民间替代。加强法学等专业的伦理教育,培育法治信仰,防止道德异化现象,并完善司法体系准入资格审查。
通过这些改进,武汉大学可维护学校形象,提升舆情应对水平,避免类似事件重演,并有效应对未来可能的跨境舆情挑战(如香港高校介入引发的关注)。
## 结论
基于以上分析,武汉大学在舆情管理方面展现出一定的技术能力和结构化策略,但近期事件如杨景媛案和“职工子女逼停学生”事件暴露了在透明度、响应时效性和程序公正性上的不足。这些舆情挑战已对学校声誉、招生和国际合作产生负面影响。未来,武汉大学应聚焦于增强学术诚信监管、优化危机沟通机制和强化预防措施,以提升舆情应对效果和维护长期社会信任。通过实施透明度提升、沟通策略优化和风险管理强化等建议,学校可有效 mitigate 未来舆情风险,巩固其作为顶尖高校的形象。
@@ -1,25 +0,0 @@
# 武汉大学舆情分析报告
## 武汉大学舆情概述与背景
武汉大学舆情作为高等教育机构与社会舆论互动的重要体现,具有深厚的历史背景和复杂的现实意义。舆情概念可追溯至古代,如《全唐诗》中已有记载,其内涵随时代演变,涉及民心表达、舆论监督等维度。在高等教育领域,武汉大学舆情常反映学术诚信、校园治理等议题,例如1993年校史争议和近年图书馆事件,凸显了高校在舆论场中的敏感地位。全媒体时代,舆情传播载体从传统论坛演进至社交媒体,信息速度加快、多元平台融合,导致舆情热点频发且复杂化,涉及社会结构变迁、群体利益冲突等因素。武汉大学舆情工作需结合监测、预警和研判方法,应对潜性与显性舆情,促进民意表达和社会改革,同时舆情产业在中国独特发展,涉及媒体、高校等多方参与,成为观察社会变化的重要工具。具体案例中,2023年武汉大学图书馆事件成为舆情处置的典型反面教材,涉及学术诚信和校园治理问题:事件初期校方反应滞后、信息空窗期过长,仅以模糊策略处理,导致公众信任流失;舆情研判不足,缺乏动态跟踪机制,未能预判舆论反转和声誉风险;应对话术机械,暴露推责式公关惯性;最终以沉默代替透明,拖延回应,严重损害高校公信力。该事件还引发对研究生培养质量、学术不端处理机制及网络实名举报治理效能的深度反思,凸显舆情在高校治理中的关键作用。2025年7月25日法院一审判决肖某某行为不构成性骚扰后,舆情焦点转向校方公信力危机,舆论要求撤销错误处分并质疑校方“重舆轻法”的处置模式,同时杨某某的学术不端问题(如虚构《离婚法》、数据造假)进一步暴露了高校在学术伦理监管和危机应对机制上的系统性缺陷。
## 近期武汉大学舆情事件分析
武汉大学近期因图书馆“性骚扰”争议事件陷入舆论漩涡,事件源于2023年10月,学生杨某某指控2022级本科生肖某某在图书馆性骚扰,学校于2023年10月对肖某某给予记过处分。然而,2025年7月25日武汉市经济技术开发区人民法院一审判决认定肖某某行为不构成性骚扰,其动作存在“抓痒的高度可能”,且事发场景开放、双方无交流,无法证明存在性暗示或性挑逗行为,故驳回杨某某的全部诉讼请求。这一判决引发公众广泛质疑学校最初的处分决定,舆情迅速发酵。事件发展过程中,肖某某遭受严重网暴,个人信息被泄露,本人罹患创伤后应激障碍,家人也受牵连;杨某某则在判决后宣布保研成功、通过法考并将赴香港浸会大学读博,并表示继续投诉肖某某,引发舆论反弹。2025年7月31日晚,武汉大学校长张平文回应称学校正在处理中,具体结果需等待上级安排,但官网处分通报仍未删除(点击量超17万),其回应被指卸责,加剧信任危机。舆情涉及学术诚信、校园管理和特权问题,网民批评学校处理不当、回应迟缓。武汉大学已启动对肖某某处分和杨某某学位论文的复核程序,但尚未撤销处分。事件暴露了校风学风建设不足、意识形态风险防控薄弱等问题,与中央巡视组2021年指出的整改不到位相呼应。舆情高峰出现在2025年7月底至8月初,媒体和网民呼吁透明和公正处理,以避免次生舆情和品牌形象损害。2025年8月1日,武汉大学发布通报,表示已组建工作专班对肖某某纪律处分和杨某某学位论文进行全面调查复核,将以事实为依据,严格按照校纪校规和学术规范作出处理。公众反应强烈,部分网民质疑学校缺乏主见,呼吁尽快公布公正处理结果。
## 舆情监测与管理机制
武汉大学在舆情监测与管理方面建立了较为完善的机制,通过官方通报、危机公关和社交媒体互动等多渠道应对舆情事件。以2025年“职工子女逼停学生”事件为例,学校保卫部及时发布通报澄清事实,强调涉事人员为校外退休职工子女(其父已去世多年),不存在特权行为,并对造谣者采取法律手段,向公安机关报案。同时,学校通过新华网、央广网、央视网等央级媒体以及网易、今日头条等平台扩散正面信息,遏制谣言传播。在社交媒体管理上,武汉大学注重实时监测舆情动态,采用“冷处理”策略避免过度回应刺激负面舆情,并通过学生意见领袖引导舆论走向。此外,学校还参考了舆情管理最佳实践,如建立48小时黄金回应机制和舆情缓冲带,确保信息公开透明,维护校园公信力。然而,在2023-2025年的杨景媛事件中,学校暴露出危机应对的双轨制困境:初期为平息舆论仓促对肖某某记过处分(取消保研资格),未充分调查事实(监控显示无交流);法院判决反转后,因杨景媛已毕业“学籍自动结束”,未对其学术不端(如论文将新中国成立年份误写为“1049年”、虚构“2001年《离婚法》”)和诬告行为追责,引发程序正义争议。学校采用7×24小时AI智能监测系统(如A公司研发的P系统)实时抓取全网舆情,但在响应机制上存在延迟,依赖新热点转移注意力(如防城港奔驰女事件),而非彻底解决问题。舆情分析显示,网民主要质疑处罚细节(如取消校园通行授权三个月)和谣言滋生原因,部分极端观点借机攻击学校整体声誉。高校网络舆情具有突发性、广泛性和低可控性特点,需平衡法理与人情,强化学术伦理审查(如香港浸会大学发函核查杨景媛事件)和防诬告机制,以提升治理效能。教训表明,舆情处置应避免“未调查先问责”的惯性思维,注重线下事实全面呈现,减少内部汇报链条导致的响应滞后。
## 舆情对武汉大学的影响
武汉大学近年来因多起舆情事件面临严峻的声誉挑战,对招生、学术合作和公共形象产生了显著影响。负面案例如2023年的杨景媛诬告事件和2025年的知网论文修改争议,引发了全国性舆论风波,导致公众对学校学术诚信和管理能力的质疑,可能影响考生报考意愿和学术合作伙伴的信任。正面案例包括学校办学声誉的明显提升,国内排名上升到前百,以及重大科研项目与经费的显著增长(项目经费从2.36亿上升到3.74亿,SCI论文从405篇增长到534篇),这些成就得益于人才培养工作的实际成效和多学科交叉培养策略,支持博士生参与学术交流和国际合作研究,从而拓宽学术视野并激发创新思维,促进了学术合作的成功和声誉提升。学术研究表明,高校可通过建立舆情协同治理机制(如基于结构方程模型的实证研究所示)、加强透明度(如及时发布调查通报)和强化学术道德教育来缓解负面影响,从而提升声誉和促进招生增长。例如,武汉大学在论文事件中试图快速平息事态,但策略不完善,反而加剧形象损害,突显了高校在平衡学术声誉与舆论压力时的普遍困境。总体而言,舆情管理需从被动应对转向主动预防,通过实证研究和协同机制优化,以维护长期声誉并支持招生增长。
## 未来舆情趋势与建议
基于武汉大学图书馆事件的舆情处置教训和学术研究分析,未来武汉大学舆情管理应重点关注以下趋势和建议:首先,舆情发展呈现快速扩散和多次反转的特点,高校需建立实时监测和动态预警机制,提前识别潜在风险点,并借鉴中南大学等机构在深度学习构建舆情事件演化图方面的研究成果,提升预测准确性。其次,新媒体环境下舆情传播具有突发性、广泛性和互动性,高校应构建包括危机公关策略在内的全方位应对体系,确保快速响应和信息透明,避免2023年事件中出现的反应滞后、信息空窗期过长问题。第三,2024年趋势显示,舆情管理需从管理思维转向客户思维,强化基层和前台力量,注重公众沟通。建议武汉大学:1)完善舆情管理协同机制,加强部门间协作,避免反应滞后和责任模糊,杜绝“推责式公关”和“唯上不唯实”的倾向;2)提升舆情研判能力,定期进行模拟演练和流程复盘,建立动态跟踪机制以应对舆论风向反转;3)强化信息公开和沟通话术,以透明化操作替代沉默拖延,主动修复公众信任,明确解释事件原委和判断依据;4)加强舆情监管队伍建设,培训专业人员处理敏感问题,引入第三方评估或专家智库支持;5)借鉴SCCIhub等先进技术框架,运用AWP聚类、Tri-training等算法和指令微调后的大语言模型,提升舆情监测、分析和预警能力;6)关注网络舆情涉及内容的广泛性,包括民生诉求、科教文卫等多元领域,建立针对不同主题的应对预案。这些措施有助于维护学校形象,避免类似事件重演,并应对学术诚信、采购管理等多元舆情挑战。
## 结论
综合分析武汉大学舆情事件,可见其舆情管理面临多重挑战,包括反应滞后、信息不透明、研判不足等问题,尤其在2023-2025年图书馆事件中暴露了系统性缺陷。然而,学校在舆情监测机制建设、正面声誉提升(如科研经费增长和排名上升)方面也展现出积极进展。未来,武汉大学应强化实时监测、动态预警和协同治理,注重透明沟通和学术伦理审查,以主动预防替代被动应对,从而有效维护公信力、促进招生和学术合作,实现可持续发展。
@@ -1,31 +0,0 @@
# 武汉大学舆情分析报告
## 武汉大学舆情概述与定义
武汉大学舆情是指在高等教育和社会背景下,围绕武汉大学及其相关事件产生的公众意见、情绪和态度的总和。舆情监测范围涵盖学术活动、校园管理、师生行为、社会服务等多个方面,常见类型包括正面热点(如学术成就、学生善行)、负面事件(如管理争议、安全事件)以及周期性事件(如招生、毕业季)。在高等教育领域,舆情管理至关重要,因为积极的舆情能提升学校声誉和社会影响力,而消极舆情可能冲击学生价值观和学校形象,甚至引发公关危机。有效的舆情监测需借助专业系统(如乐思、蚁坊软件),这些系统为政府和教育部门提供专业的互联网舆情监测服务,包括舆情分析、预警和疏导,实现全网络舆论实时采集和快速发现。例如,蚁坊软件舆情监测系统平台通过大数据技术为舆情监测提供先机,支持舆情监测、全网络舆论分析和预警工作;乐思舆情监测则强调信息全面性和定向搜索能力,共同支持高校舆情管理中的预警机制和应对策略,以维护学校稳定和发展。此外,舆情监测技术还可辅助教学建模分析,提升品牌营销能力,体现了其在教育领域的多维应用价值。技术应用案例包括AI驱动的智能体,通过调用API和数据训练,更好地发挥数据价值,以及自适应噪声抵消等关键技术研究,这些创新进一步增强了舆情监测的精度和实用性。
## 近期武汉大学舆情事件分析
武汉大学近期舆情事件主要集中在杨景媛学术不端事件和图书馆诬告案两大核心问题上。2025年7月,武汉大学硕士毕业生杨景媛因长期诬告肖姓学弟性骚扰败诉后,其硕士学位论文《中印生育行为影响家庭暴力的经济学分析》被曝光存在严重学术造假问题,包括虚构不存在的《离婚法》、数据来源伪造(将世卫组织公布的36.1%数据篡改为28.3%)、历史常识错误(如将1949年误写为1049年)以及逻辑错误、预设结论、大量抄袭、变量操纵等系统性学术不端行为。这一事件不仅暴露了杨景媛个人学术诚信的缺失,更揭示了武汉大学在研究生培养、论文审核机制以及学术伦理建设方面的系统性漏洞。公众反应强烈,质疑导师指导责任和答辩委员会审查失效,同时批评校方在事件曝光后的迟缓应对态度——直到8月1日央媒关注后才宣布组建工作专班进行全面调查复核。此外,该事件与图书馆诬告案交织:肖同学因被诬告遭受记过处分,丧失保研与法考资格,其家庭更因网暴陷入长期创伤(爷爷受刺激去世、外公成植物人);而杨景媛却获得保研资格并被香港浸会大学录取(后证实为研究助理而非博士录取),甚至在败诉后公开炫耀成就,引发对高校程序正义和道德审查机制的广泛质疑。香港浸会大学虽于7月31日发出道德核查函并启动独立审查程序,但8月6日流传的"撤销录取资格"消息被证实为谣言,校方仅表示按纪律程序处理而未公布具体决定,这种处理方式与公众对学术不端"零容忍"的期待形成鲜明落差。事件已对武汉大学校誉和公信力造成重创,成为反思中国高等教育学术诚信与制度监管的典型案例。值得注意的是,杨景媛在调查期间曾试图通过百度网盘上传论文修正文件为自己辩解,但根据中国学术管理规定,已归档学位论文原则上不允许修改,这一行为进一步引发公众对学术规范执行力的质疑。目前校方对论文修改争议仍保持沉默,武汉大学和香港浸会大学的最终处理结果仍悬而未决,公众持续关注事件进展。
## 舆情应对策略与措施
武汉大学在舆情管理方面展现出多层次的应对策略,但近年来的危机事件暴露了其机制中的挑战。以2023年图书馆诬告案为例,学校初期基于单方指控快速处分学生,试图通过‘先处理为敬’的方式平息舆情,却在法院判决反转后引发更严重的舆论反噬,凸显了危机处理中调查不足、反应滞后和急于问责的问题。官方回应方面,校长张平文的‘等上级安排’言论反映了内部决策迟缓,导致‘高度重视’仅停留在内部层面,形成悬殊的公众感知温差,损害了信任;校方在事件中未提供具体调查依据,信息空窗期过长,加剧了隐瞒印象。学校通过保卫部发布通报澄清谣言(如2025年机动车逼停事件),并采取报案等法律行动,体现了沟通和行动结合的策略,但学用脱节问题(如依赖‘落地劝删’等落后手段和缺乏动态舆情监测机制)表明需加强实战能力,避免理论知识与实际处置脱节。舆情研判不足导致未能预判风向反转,应对话术机械被动,错失修复信任窗口。此外,事件还揭示了学校对‘极端女权’等社会舆论现象的应对不足,需更深入理解民意背景。总体而言,武大需优化响应速度、确保调查公正性、建立透明沟通机制(如及时发布‘一对一’式核查回应和补救措施),并通过动态跟踪和预警体系缓解负面舆情,以修复声誉和提升舆情管理效能。
## 舆情对武汉大学声誉的影响
武汉大学近年来面临多起舆情事件,对学校声誉、招生、学术合作和社会形象产生了显著影响。根据知微数据分析,2021年武汉大学'和服赏樱'冲突事件影响力指数达65.4,高于同类事件均值10.8%,引发广泛舆论关注。事件初期,负面观点占比较高,质疑学校狭隘和保安暴力行为。但通过及时公关回应,武汉大学发布情况说明,强调游客未预约和言语挑衅,舆论风向逆转,支持学校决定的比例从26%升至48%。这体现了正面回应在舆情管理中的有效性,有助于维护社会形象。
然而,其他事件如2025年的校园交通冲突,部分自媒体传播'教职工子女蛮横别停学生'等不实信息,导致负面讨论滋生,网民质疑校园管理特权问题,例如取消车辆通行授权三个月的惩罚措施细节和校外人员校园行驶权限。尽管校方迅速澄清涉事驾驶员为校外退休职工子女、无特权行为,并报案处理谣言,事件仍暴露了谣言对声誉的潜在危害。此外,学术相关争议如肖某某纪律处分和杨某某论文调查,通过媒体和社交平台扩散,影响学术合作信任度。
招生方面,2024年武汉大学招生总人数有所增加,面向全国招生7215人,强基计划招生专业从8个增加到9个,显示学校在扩大招生规模上的努力。然而,校长张平文在宣传片中不当言论曾引发网络质疑,需警惕对招生的潜在负面影响。值得注意的是,全国高等教育性别格局变化显著,2023年本科在校生女性占比达52.22%,招生中女生占比高达63%,但顶尖高校如C9联盟女性占比仅37.7%,武汉大学作为综合性大学,需关注专业性别分化(如计算机学院男女比4.88:1,新闻传播学院女生超80%)对招生多样性和社会形象的影响。
总体而言,舆情事件对招生和合作可能带来短期波动,但武汉大学的应对策略——如快速响应和透明沟通——在一定程度上 mitigates 负面影响,凸显高校需加强舆情监测和公关智慧以保护声誉。新华社等媒体评论指出舆情应对应避免'唯上不唯实',强调高校需提升行政敏感度,防止事件处理失能进一步损害形象。
## 未来舆情趋势与建议
武汉大学未来可能面临的舆情挑战主要集中在透明度不足、沟通效率低下和预防机制缺失等方面。基于搜索结果,武汉大学在图书馆事件中暴露出反应滞后、信息空窗期过长的问题,导致公众信任流失。未来需加强舆情监测和预警体系,采用人工智能技术(如BERT模型、情感分析)实时跟踪网络舆论动态,提升研判能力。建议建立快速响应机制,确保调查流程公开透明,避免模糊策略;优化内部沟通流程,减少层层汇报导致的延误;同时引入区块链技术或深度学习模型(如CNN)加强校园舆情分析,实现多维度事件处理。此外,应定期复盘舆情案例,完善危机公关预案,通过专业、善意的沟通回应公众关切,修复声誉损害。结合前沿技术趋势,武汉大学可借鉴大数据、云计算和人工智能在智慧城市管理中的创新应用,推动舆情管理手段和模式升级,如利用人工智能和区块链提升供应链韧性和协同效益,加强2024年后的业务效率改善。通过整合5G、半导体等新一代信息技术,加快智能化的舆情预警和响应系统建设,提升透明度和沟通效率,预防潜在危机。值得注意的是,在2024年金融科技创新大赛中,武汉大学团队展示了区块链和人工智能的应用潜力,如“区块链大战供应链融碳生金”项目,这为舆情管理提供了技术参考,可探索区块链用于数据透明存证和AI驱动的情感分析,以增强舆情应对的实时性和可信度。同时,关注AI伦理和治理框架,如避免算法偏见和隐私风险,确保技术应用符合国际标准,提升整体舆情管理的可持续性和普惠性。
## 结论
综合以上分析,武汉大学舆情管理面临显著挑战,尤其在学术诚信、透明度和响应机制方面。近期事件如杨景媛学术不端和图书馆诬告案暴露了系统性漏洞,对学校声誉造成冲击。未来,武汉大学需加强技术应用(如AI和区块链)、优化沟通策略,并建立预防性机制,以提升舆情应对能力,维护长期声誉和发展。
@@ -1,31 +0,0 @@
# 武汉大学舆情分析报告
## 武汉大学舆情概述与定义
武汉大学舆情是指在高等教育和社会背景下,围绕武汉大学及其相关事件产生的公众意见、情绪和态度的总和。舆情监测范围涵盖学术活动、校园管理、师生行为、社会服务等多个方面,常见类型包括正面热点(如学术成就、学生善行)、负面事件(如管理争议、安全事件)以及周期性事件(如招生、毕业季)。在高等教育领域,舆情管理至关重要,因为积极的舆情能提升学校声誉和社会影响力,而消极舆情可能冲击学生价值观和学校形象,甚至引发公关危机。有效的舆情监测需借助专业系统(如乐思、蚁坊软件),这些系统为政府和教育部门提供专业的互联网舆情监测服务,包括舆情分析、预警和疏导,实现全网络舆论实时采集和快速发现。例如,蚁坊软件舆情监测系统平台通过大数据技术为舆情监测提供先机,支持舆情监测、全网络舆论分析和预警工作;乐思舆情监测则强调信息全面性和定向搜索能力,共同支持高校舆情管理中的预警机制和应对策略,以维护学校稳定和发展。此外,舆情监测技术还可辅助教学建模分析,提升品牌营销能力,体现了其在教育领域的多维应用价值。技术应用案例包括AI驱动的智能体,通过调用API和数据训练,更好地发挥数据价值,以及自适应噪声抵消等关键技术研究,这些创新进一步增强了舆情监测的精度和实用性。
## 近期武汉大学舆情事件分析
武汉大学近期舆情事件主要集中在杨景媛学术不端事件和图书馆诬告案两大核心问题上。2025年7月,武汉大学硕士毕业生杨景媛因长期诬告肖姓学弟性骚扰败诉后,其硕士学位论文《中印生育行为影响家庭暴力的经济学分析》被曝光存在严重学术造假问题,包括虚构不存在的《离婚法》、数据来源伪造(将世卫组织公布的36.1%数据篡改为28.3%)、历史常识错误(如将1949年误写为1049年)以及逻辑错误、预设结论、大量抄袭、变量操纵等系统性学术不端行为。这一事件不仅暴露了杨景媛个人学术诚信的缺失,更揭示了武汉大学在研究生培养、论文审核机制以及学术伦理建设方面的系统性漏洞。公众反应强烈,质疑导师指导责任和答辩委员会审查失效,同时批评校方在事件曝光后的迟缓应对态度——直到8月1日央媒关注后才宣布组建工作专班进行全面调查复核。此外,该事件与图书馆诬告案交织:肖同学因被诬告遭受记过处分,丧失保研与法考资格,其家庭更因网暴陷入长期创伤(爷爷受刺激去世、外公成植物人);而杨景媛却获得保研资格并被香港浸会大学录取(后证实为研究助理而非博士录取),甚至在败诉后公开炫耀成就,引发对高校程序正义和道德审查机制的广泛质疑。香港浸会大学虽于7月31日发出道德核查函并启动独立审查程序,但8月6日流传的"撤销录取资格"消息被证实为谣言,校方仅表示按纪律程序处理而未公布具体决定,这种处理方式与公众对学术不端"零容忍"的期待形成鲜明落差。事件已对武汉大学校誉和公信力造成重创,成为反思中国高等教育学术诚信与制度监管的典型案例。值得注意的是,杨景媛在调查期间曾试图通过百度网盘上传论文修正文件为自己辩解,但根据中国学术管理规定,已归档学位论文原则上不允许修改,这一行为进一步引发公众对学术规范执行力的质疑。目前校方对论文修改争议仍保持沉默,武汉大学和香港浸会大学的最终处理结果仍悬而未决,公众持续关注事件进展。
## 舆情应对策略与措施
武汉大学在舆情管理方面展现出多层次的应对策略,但近年来的危机事件暴露了其机制中的挑战。以2023年图书馆诬告案为例,学校初期基于单方指控快速处分学生,试图通过‘先处理为敬’的方式平息舆情,却在法院判决反转后引发更严重的舆论反噬,凸显了危机处理中调查不足、反应滞后和急于问责的问题。官方回应方面,校长张平文的‘等上级安排’言论反映了内部决策迟缓,导致‘高度重视’仅停留在内部层面,形成悬殊的公众感知温差,损害了信任;校方在事件中未提供具体调查依据,信息空窗期过长,加剧了隐瞒印象。学校通过保卫部发布通报澄清谣言(如2025年机动车逼停事件),并采取报案等法律行动,体现了沟通和行动结合的策略,但学用脱节问题(如依赖‘落地劝删’等落后手段和缺乏动态舆情监测机制)表明需加强实战能力,避免理论知识与实际处置脱节。舆情研判不足导致未能预判风向反转,应对话术机械被动,错失修复信任窗口。此外,事件还揭示了学校对‘极端女权’等社会舆论现象的应对不足,需更深入理解民意背景。总体而言,武大需优化响应速度、确保调查公正性、建立透明沟通机制(如及时发布‘一对一’式核查回应和补救措施),并通过动态跟踪和预警体系缓解负面舆情,以修复声誉和提升舆情管理效能。
## 舆情对武汉大学声誉的影响
武汉大学近年来面临多起舆情事件,对学校声誉、招生、学术合作和社会形象产生了显著影响。根据知微数据分析,2021年武汉大学'和服赏樱'冲突事件影响力指数达65.4,高于同类事件均值10.8%,引发广泛舆论关注。事件初期,负面观点占比较高,质疑学校狭隘和保安暴力行为。但通过及时公关回应,武汉大学发布情况说明,强调游客未预约和言语挑衅,舆论风向逆转,支持学校决定的比例从26%升至48%。这体现了正面回应在舆情管理中的有效性,有助于维护社会形象。
然而,其他事件如2025年的校园交通冲突,部分自媒体传播'教职工子女蛮横别停学生'等不实信息,导致负面讨论滋生,网民质疑校园管理特权问题,例如取消车辆通行授权三个月的惩罚措施细节和校外人员校园行驶权限。尽管校方迅速澄清涉事驾驶员为校外退休职工子女、无特权行为,并报案处理谣言,事件仍暴露了谣言对声誉的潜在危害。此外,学术相关争议如肖某某纪律处分和杨某某论文调查,通过媒体和社交平台扩散,影响学术合作信任度。
招生方面,2024年武汉大学招生总人数有所增加,面向全国招生7215人,强基计划招生专业从8个增加到9个,显示学校在扩大招生规模上的努力。然而,校长张平文在宣传片中不当言论曾引发网络质疑,需警惕对招生的潜在负面影响。值得注意的是,全国高等教育性别格局变化显著,2023年本科在校生女性占比达52.22%,招生中女生占比高达63%,但顶尖高校如C9联盟女性占比仅37.7%,武汉大学作为综合性大学,需关注专业性别分化(如计算机学院男女比4.88:1,新闻传播学院女生超80%)对招生多样性和社会形象的影响。
总体而言,舆情事件对招生和合作可能带来短期波动,但武汉大学的应对策略——如快速响应和透明沟通——在一定程度上 mitigates 负面影响,凸显高校需加强舆情监测和公关智慧以保护声誉。新华社等媒体评论指出舆情应对应避免'唯上不唯实',强调高校需提升行政敏感度,防止事件处理失能进一步损害形象。
## 未来舆情趋势与建议
武汉大学未来可能面临的舆情挑战主要集中在透明度不足、沟通效率低下和预防机制缺失等方面。基于搜索结果,武汉大学在图书馆事件中暴露出反应滞后、信息空窗期过长的问题,导致公众信任流失。未来需加强舆情监测和预警体系,采用人工智能技术(如BERT模型、情感分析)实时跟踪网络舆论动态,提升研判能力。建议建立快速响应机制,确保调查流程公开透明,避免模糊策略;优化内部沟通流程,减少层层汇报导致的延误;同时引入区块链技术或深度学习模型(如CNN)加强校园舆情分析,实现多维度事件处理。此外,应定期复盘舆情案例,完善危机公关预案,通过专业、善意的沟通回应公众关切,修复声誉损害。结合前沿技术趋势,武汉大学可借鉴大数据、云计算和人工智能在智慧城市管理中的创新应用,推动舆情管理手段和模式升级,如利用人工智能和区块链提升供应链韧性和协同效益,加强2024年后的业务效率改善。通过整合5G、半导体等新一代信息技术,加快智能化的舆情预警和响应系统建设,提升透明度和沟通效率,预防潜在危机。值得注意的是,在2024年金融科技创新大赛中,武汉大学团队展示了区块链和人工智能的应用潜力,如“区块链大战供应链融碳生金”项目,这为舆情管理提供了技术参考,可探索区块链用于数据透明存证和AI驱动的情感分析,以增强舆情应对的实时性和可信度。同时,关注AI伦理和治理框架,如避免算法偏见和隐私风险,确保技术应用符合国际标准,提升整体舆情管理的可持续性和普惠性。
## 结论
综合以上分析,武汉大学舆情管理面临显著挑战,尤其在学术诚信、透明度和响应机制方面。近期事件如杨景媛学术不端和图书馆诬告案暴露了系统性漏洞,对学校声誉造成冲击。未来,武汉大学需加强技术应用(如AI和区块链)、优化沟通策略,并建立预防性机制,以提升舆情应对能力,维护长期声誉和发展。
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4 -4
View File
@@ -230,10 +230,10 @@ def make_retryable_request(
# 预定义一些常用的重试配置
LLM_RETRY_CONFIG = RetryConfig(
max_retries=5, # 增加到5次重试
initial_delay=3.0, # 增加初始延迟到3秒
backoff_factor=1.8, # 调整退避因子
max_delay=45.0 # 增加最大延迟
max_retries=6, # 保持额外重试次数
initial_delay=60.0, # 首次等待至少 1 分钟
backoff_factor=2.0, # 继续使用指数退避
max_delay=600.0 # 单次等待最长 10 分钟
)
SEARCH_API_RETRY_CONFIG = RetryConfig(