From 7ae863a781293fb281888d4f8f71b9022a487996 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=88=92=E9=85=92=E7=9A=84=E6=9D=8E=E7=99=BD?=
<670939375@qq.com>
Date: Fri, 22 Aug 2025 22:04:08 +0800
Subject: [PATCH] Reconfiguration of the basic multi-agent architecture.
---
{src => InsightEngine}/__init__.py | 0
{src => InsightEngine}/agent.py | 0
{src => InsightEngine}/llms/__init__.py | 0
{src => InsightEngine}/llms/base.py | 0
{src => InsightEngine}/llms/deepseek.py | 0
{src => InsightEngine}/llms/openai_llm.py | 0
{src => InsightEngine}/nodes/__init__.py | 0
{src => InsightEngine}/nodes/base_node.py | 0
.../nodes/formatting_node.py | 0
.../nodes/report_structure_node.py | 0
{src => InsightEngine}/nodes/search_node.py | 0
{src => InsightEngine}/nodes/summary_node.py | 0
{src => InsightEngine}/prompts/__init__.py | 0
{src => InsightEngine}/prompts/prompts.py | 0
{src => InsightEngine}/state/__init__.py | 0
{src => InsightEngine}/state/state.py | 0
{src => InsightEngine}/tools/__init__.py | 0
{src => InsightEngine}/tools/search.py | 0
{src => InsightEngine}/utils/__init__.py | 0
{src => InsightEngine}/utils/config.py | 0
.../utils/text_processing.py | 0
MediaEngine/__init__.py | 12 +
MediaEngine/agent.py | 478 ++++++++++++++++++
MediaEngine/llms/__init__.py | 10 +
MediaEngine/llms/base.py | 61 +++
MediaEngine/llms/deepseek.py | 95 ++++
MediaEngine/llms/openai_llm.py | 90 ++++
MediaEngine/nodes/__init__.py | 20 +
MediaEngine/nodes/base_node.py | 89 ++++
MediaEngine/nodes/formatting_node.py | 164 ++++++
MediaEngine/nodes/report_structure_node.py | 203 ++++++++
MediaEngine/nodes/search_node.py | 285 +++++++++++
MediaEngine/nodes/summary_node.py | 312 ++++++++++++
MediaEngine/prompts/__init__.py | 34 ++
MediaEngine/prompts/prompts.py | 285 +++++++++++
MediaEngine/state/__init__.py | 8 +
MediaEngine/state/state.py | 258 ++++++++++
MediaEngine/tools/__init__.py | 20 +
MediaEngine/tools/search.py | 240 +++++++++
MediaEngine/utils/__init__.py | 26 +
MediaEngine/utils/config.py | 162 ++++++
MediaEngine/utils/text_processing.py | 308 +++++++++++
QueryEngine/__init__.py | 12 +
QueryEngine/agent.py | 478 ++++++++++++++++++
QueryEngine/llms/__init__.py | 10 +
QueryEngine/llms/base.py | 61 +++
QueryEngine/llms/deepseek.py | 95 ++++
QueryEngine/llms/openai_llm.py | 90 ++++
QueryEngine/nodes/__init__.py | 20 +
QueryEngine/nodes/base_node.py | 89 ++++
QueryEngine/nodes/formatting_node.py | 164 ++++++
QueryEngine/nodes/report_structure_node.py | 203 ++++++++
QueryEngine/nodes/search_node.py | 285 +++++++++++
QueryEngine/nodes/summary_node.py | 312 ++++++++++++
QueryEngine/prompts/__init__.py | 34 ++
QueryEngine/prompts/prompts.py | 285 +++++++++++
QueryEngine/state/__init__.py | 8 +
QueryEngine/state/state.py | 258 ++++++++++
QueryEngine/tools/__init__.py | 20 +
QueryEngine/tools/search.py | 240 +++++++++
QueryEngine/utils/__init__.py | 26 +
QueryEngine/utils/config.py | 162 ++++++
QueryEngine/utils/text_processing.py | 308 +++++++++++
..._app.py => insight_engine_streamlit_app.py | 4 +-
media_engine_streamlit_app.py | 235 +++++++++
query_engine_streamlit_app.py | 235 +++++++++
...rch_report_武汉大学舆情_20250821_232740.md | 13 -
...rch_report_武汉大学舆情_20250822_203056.md | 25 -
.../state_武汉大学舆情_20250821_232740.json | 178 -------
.../state_武汉大学舆情_20250822_203056.json | 430 ----------------
70 files changed, 6792 insertions(+), 648 deletions(-)
rename {src => InsightEngine}/__init__.py (100%)
rename {src => InsightEngine}/agent.py (100%)
rename {src => InsightEngine}/llms/__init__.py (100%)
rename {src => InsightEngine}/llms/base.py (100%)
rename {src => InsightEngine}/llms/deepseek.py (100%)
rename {src => InsightEngine}/llms/openai_llm.py (100%)
rename {src => InsightEngine}/nodes/__init__.py (100%)
rename {src => InsightEngine}/nodes/base_node.py (100%)
rename {src => InsightEngine}/nodes/formatting_node.py (100%)
rename {src => InsightEngine}/nodes/report_structure_node.py (100%)
rename {src => InsightEngine}/nodes/search_node.py (100%)
rename {src => InsightEngine}/nodes/summary_node.py (100%)
rename {src => InsightEngine}/prompts/__init__.py (100%)
rename {src => InsightEngine}/prompts/prompts.py (100%)
rename {src => InsightEngine}/state/__init__.py (100%)
rename {src => InsightEngine}/state/state.py (100%)
rename {src => InsightEngine}/tools/__init__.py (100%)
rename {src => InsightEngine}/tools/search.py (100%)
rename {src => InsightEngine}/utils/__init__.py (100%)
rename {src => InsightEngine}/utils/config.py (100%)
rename {src => InsightEngine}/utils/text_processing.py (100%)
create mode 100644 MediaEngine/__init__.py
create mode 100644 MediaEngine/agent.py
create mode 100644 MediaEngine/llms/__init__.py
create mode 100644 MediaEngine/llms/base.py
create mode 100644 MediaEngine/llms/deepseek.py
create mode 100644 MediaEngine/llms/openai_llm.py
create mode 100644 MediaEngine/nodes/__init__.py
create mode 100644 MediaEngine/nodes/base_node.py
create mode 100644 MediaEngine/nodes/formatting_node.py
create mode 100644 MediaEngine/nodes/report_structure_node.py
create mode 100644 MediaEngine/nodes/search_node.py
create mode 100644 MediaEngine/nodes/summary_node.py
create mode 100644 MediaEngine/prompts/__init__.py
create mode 100644 MediaEngine/prompts/prompts.py
create mode 100644 MediaEngine/state/__init__.py
create mode 100644 MediaEngine/state/state.py
create mode 100644 MediaEngine/tools/__init__.py
create mode 100644 MediaEngine/tools/search.py
create mode 100644 MediaEngine/utils/__init__.py
create mode 100644 MediaEngine/utils/config.py
create mode 100644 MediaEngine/utils/text_processing.py
create mode 100644 QueryEngine/__init__.py
create mode 100644 QueryEngine/agent.py
create mode 100644 QueryEngine/llms/__init__.py
create mode 100644 QueryEngine/llms/base.py
create mode 100644 QueryEngine/llms/deepseek.py
create mode 100644 QueryEngine/llms/openai_llm.py
create mode 100644 QueryEngine/nodes/__init__.py
create mode 100644 QueryEngine/nodes/base_node.py
create mode 100644 QueryEngine/nodes/formatting_node.py
create mode 100644 QueryEngine/nodes/report_structure_node.py
create mode 100644 QueryEngine/nodes/search_node.py
create mode 100644 QueryEngine/nodes/summary_node.py
create mode 100644 QueryEngine/prompts/__init__.py
create mode 100644 QueryEngine/prompts/prompts.py
create mode 100644 QueryEngine/state/__init__.py
create mode 100644 QueryEngine/state/state.py
create mode 100644 QueryEngine/tools/__init__.py
create mode 100644 QueryEngine/tools/search.py
create mode 100644 QueryEngine/utils/__init__.py
create mode 100644 QueryEngine/utils/config.py
create mode 100644 QueryEngine/utils/text_processing.py
rename streamlit_app.py => insight_engine_streamlit_app.py (98%)
create mode 100644 media_engine_streamlit_app.py
create mode 100644 query_engine_streamlit_app.py
delete mode 100644 streamlit_reports/deep_search_report_武汉大学舆情_20250821_232740.md
delete mode 100644 streamlit_reports/deep_search_report_武汉大学舆情_20250822_203056.md
delete mode 100644 streamlit_reports/state_武汉大学舆情_20250821_232740.json
delete mode 100644 streamlit_reports/state_武汉大学舆情_20250822_203056.json
diff --git a/src/__init__.py b/InsightEngine/__init__.py
similarity index 100%
rename from src/__init__.py
rename to InsightEngine/__init__.py
diff --git a/src/agent.py b/InsightEngine/agent.py
similarity index 100%
rename from src/agent.py
rename to InsightEngine/agent.py
diff --git a/src/llms/__init__.py b/InsightEngine/llms/__init__.py
similarity index 100%
rename from src/llms/__init__.py
rename to InsightEngine/llms/__init__.py
diff --git a/src/llms/base.py b/InsightEngine/llms/base.py
similarity index 100%
rename from src/llms/base.py
rename to InsightEngine/llms/base.py
diff --git a/src/llms/deepseek.py b/InsightEngine/llms/deepseek.py
similarity index 100%
rename from src/llms/deepseek.py
rename to InsightEngine/llms/deepseek.py
diff --git a/src/llms/openai_llm.py b/InsightEngine/llms/openai_llm.py
similarity index 100%
rename from src/llms/openai_llm.py
rename to InsightEngine/llms/openai_llm.py
diff --git a/src/nodes/__init__.py b/InsightEngine/nodes/__init__.py
similarity index 100%
rename from src/nodes/__init__.py
rename to InsightEngine/nodes/__init__.py
diff --git a/src/nodes/base_node.py b/InsightEngine/nodes/base_node.py
similarity index 100%
rename from src/nodes/base_node.py
rename to InsightEngine/nodes/base_node.py
diff --git a/src/nodes/formatting_node.py b/InsightEngine/nodes/formatting_node.py
similarity index 100%
rename from src/nodes/formatting_node.py
rename to InsightEngine/nodes/formatting_node.py
diff --git a/src/nodes/report_structure_node.py b/InsightEngine/nodes/report_structure_node.py
similarity index 100%
rename from src/nodes/report_structure_node.py
rename to InsightEngine/nodes/report_structure_node.py
diff --git a/src/nodes/search_node.py b/InsightEngine/nodes/search_node.py
similarity index 100%
rename from src/nodes/search_node.py
rename to InsightEngine/nodes/search_node.py
diff --git a/src/nodes/summary_node.py b/InsightEngine/nodes/summary_node.py
similarity index 100%
rename from src/nodes/summary_node.py
rename to InsightEngine/nodes/summary_node.py
diff --git a/src/prompts/__init__.py b/InsightEngine/prompts/__init__.py
similarity index 100%
rename from src/prompts/__init__.py
rename to InsightEngine/prompts/__init__.py
diff --git a/src/prompts/prompts.py b/InsightEngine/prompts/prompts.py
similarity index 100%
rename from src/prompts/prompts.py
rename to InsightEngine/prompts/prompts.py
diff --git a/src/state/__init__.py b/InsightEngine/state/__init__.py
similarity index 100%
rename from src/state/__init__.py
rename to InsightEngine/state/__init__.py
diff --git a/src/state/state.py b/InsightEngine/state/state.py
similarity index 100%
rename from src/state/state.py
rename to InsightEngine/state/state.py
diff --git a/src/tools/__init__.py b/InsightEngine/tools/__init__.py
similarity index 100%
rename from src/tools/__init__.py
rename to InsightEngine/tools/__init__.py
diff --git a/src/tools/search.py b/InsightEngine/tools/search.py
similarity index 100%
rename from src/tools/search.py
rename to InsightEngine/tools/search.py
diff --git a/src/utils/__init__.py b/InsightEngine/utils/__init__.py
similarity index 100%
rename from src/utils/__init__.py
rename to InsightEngine/utils/__init__.py
diff --git a/src/utils/config.py b/InsightEngine/utils/config.py
similarity index 100%
rename from src/utils/config.py
rename to InsightEngine/utils/config.py
diff --git a/src/utils/text_processing.py b/InsightEngine/utils/text_processing.py
similarity index 100%
rename from src/utils/text_processing.py
rename to InsightEngine/utils/text_processing.py
diff --git a/MediaEngine/__init__.py b/MediaEngine/__init__.py
new file mode 100644
index 0000000..6f753c5
--- /dev/null
+++ b/MediaEngine/__init__.py
@@ -0,0 +1,12 @@
+"""
+Deep Search Agent
+一个无框架的深度搜索AI代理实现
+"""
+
+from .agent import DeepSearchAgent, create_agent
+from .utils.config import Config, load_config
+
+__version__ = "1.0.0"
+__author__ = "Deep Search Agent Team"
+
+__all__ = ["DeepSearchAgent", "create_agent", "Config", "load_config"]
diff --git a/MediaEngine/agent.py b/MediaEngine/agent.py
new file mode 100644
index 0000000..d57d96f
--- /dev/null
+++ b/MediaEngine/agent.py
@@ -0,0 +1,478 @@
+"""
+Deep Search Agent主类
+整合所有模块,实现完整的深度搜索流程
+"""
+
+import json
+import os
+import re
+from datetime import datetime
+from typing import Optional, Dict, Any, List
+
+from .llms import DeepSeekLLM, OpenAILLM, BaseLLM
+from .nodes import (
+ ReportStructureNode,
+ FirstSearchNode,
+ ReflectionNode,
+ FirstSummaryNode,
+ ReflectionSummaryNode,
+ ReportFormattingNode
+)
+from .state import State
+from .tools import TavilyNewsAgency, TavilyResponse
+from .utils import Config, load_config, format_search_results_for_prompt
+
+
+class DeepSearchAgent:
+ """Deep Search Agent主类"""
+
+ def __init__(self, config: Optional[Config] = None):
+ """
+ 初始化Deep Search Agent
+
+ Args:
+ config: 配置对象,如果不提供则自动加载
+ """
+ # 加载配置
+ self.config = config or load_config()
+
+ # 初始化LLM客户端
+ self.llm_client = self._initialize_llm()
+
+ # 初始化搜索工具集
+ self.search_agency = TavilyNewsAgency(api_key=self.config.tavily_api_key)
+
+ # 初始化节点
+ self._initialize_nodes()
+
+ # 状态
+ self.state = State()
+
+ # 确保输出目录存在
+ os.makedirs(self.config.output_dir, exist_ok=True)
+
+ print(f"Deep Search Agent 已初始化")
+ print(f"使用LLM: {self.llm_client.get_model_info()}")
+ print(f"搜索工具集: TavilyNewsAgency (支持6种搜索工具)")
+
+ def _initialize_llm(self) -> BaseLLM:
+ """初始化LLM客户端"""
+ if self.config.default_llm_provider == "deepseek":
+ return DeepSeekLLM(
+ api_key=self.config.deepseek_api_key,
+ model_name=self.config.deepseek_model
+ )
+ 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}")
+
+ def _initialize_nodes(self):
+ """初始化处理节点"""
+ self.first_search_node = FirstSearchNode(self.llm_client)
+ self.reflection_node = ReflectionNode(self.llm_client)
+ self.first_summary_node = FirstSummaryNode(self.llm_client)
+ self.reflection_summary_node = ReflectionSummaryNode(self.llm_client)
+ self.report_formatting_node = ReportFormattingNode(self.llm_client)
+
+ def _validate_date_format(self, date_str: str) -> bool:
+ """
+ 验证日期格式是否为YYYY-MM-DD
+
+ Args:
+ date_str: 日期字符串
+
+ Returns:
+ 是否为有效格式
+ """
+ if not date_str:
+ return False
+
+ # 检查格式
+ pattern = r'^\d{4}-\d{2}-\d{2}$'
+ if not re.match(pattern, date_str):
+ return False
+
+ # 检查日期是否有效
+ try:
+ datetime.strptime(date_str, '%Y-%m-%d')
+ return True
+ except ValueError:
+ return False
+
+ def execute_search_tool(self, tool_name: str, query: str, **kwargs) -> TavilyResponse:
+ """
+ 执行指定的搜索工具
+
+ Args:
+ tool_name: 工具名称,可选值:
+ - "basic_search_news": 基础新闻搜索(快速、通用)
+ - "deep_search_news": 深度新闻分析
+ - "search_news_last_24_hours": 24小时内最新新闻
+ - "search_news_last_week": 本周新闻
+ - "search_images_for_news": 新闻图片搜索
+ - "search_news_by_date": 按日期范围搜索新闻
+ query: 搜索查询
+ **kwargs: 额外参数(如start_date, end_date, max_results)
+
+ Returns:
+ TavilyResponse对象
+ """
+ print(f" → 执行搜索工具: {tool_name}")
+
+ if tool_name == "basic_search_news":
+ max_results = kwargs.get("max_results", 7)
+ return self.search_agency.basic_search_news(query, max_results)
+ elif tool_name == "deep_search_news":
+ return self.search_agency.deep_search_news(query)
+ elif tool_name == "search_news_last_24_hours":
+ return self.search_agency.search_news_last_24_hours(query)
+ elif tool_name == "search_news_last_week":
+ return self.search_agency.search_news_last_week(query)
+ elif tool_name == "search_images_for_news":
+ return self.search_agency.search_images_for_news(query)
+ elif tool_name == "search_news_by_date":
+ start_date = kwargs.get("start_date")
+ end_date = kwargs.get("end_date")
+ if not start_date or not end_date:
+ raise ValueError("search_news_by_date工具需要start_date和end_date参数")
+ return self.search_agency.search_news_by_date(query, start_date, end_date)
+ else:
+ print(f" ⚠️ 未知的搜索工具: {tool_name},使用默认基础搜索")
+ return self.search_agency.basic_search_news(query)
+
+ def research(self, query: str, save_report: bool = True) -> str:
+ """
+ 执行深度研究
+
+ Args:
+ query: 研究查询
+ save_report: 是否保存报告到文件
+
+ Returns:
+ 最终报告内容
+ """
+ print(f"\n{'='*60}")
+ print(f"开始深度研究: {query}")
+ print(f"{'='*60}")
+
+ try:
+ # Step 1: 生成报告结构
+ self._generate_report_structure(query)
+
+ # Step 2: 处理每个段落
+ self._process_paragraphs()
+
+ # Step 3: 生成最终报告
+ final_report = self._generate_final_report()
+
+ # Step 4: 保存报告
+ if save_report:
+ self._save_report(final_report)
+
+ print(f"\n{'='*60}")
+ print("深度研究完成!")
+ print(f"{'='*60}")
+
+ return final_report
+
+ except Exception as e:
+ print(f"研究过程中发生错误: {str(e)}")
+ raise e
+
+ def _generate_report_structure(self, query: str):
+ """生成报告结构"""
+ print(f"\n[步骤 1] 生成报告结构...")
+
+ # 创建报告结构节点
+ report_structure_node = ReportStructureNode(self.llm_client, query)
+
+ # 生成结构并更新状态
+ self.state = report_structure_node.mutate_state(state=self.state)
+
+ print(f"报告结构已生成,共 {len(self.state.paragraphs)} 个段落:")
+ for i, paragraph in enumerate(self.state.paragraphs, 1):
+ print(f" {i}. {paragraph.title}")
+
+ def _process_paragraphs(self):
+ """处理所有段落"""
+ total_paragraphs = len(self.state.paragraphs)
+
+ for i in range(total_paragraphs):
+ print(f"\n[步骤 2.{i+1}] 处理段落: {self.state.paragraphs[i].title}")
+ print("-" * 50)
+
+ # 初始搜索和总结
+ self._initial_search_and_summary(i)
+
+ # 反思循环
+ self._reflection_loop(i)
+
+ # 标记段落完成
+ self.state.paragraphs[i].research.mark_completed()
+
+ progress = (i + 1) / total_paragraphs * 100
+ print(f"段落处理完成 ({progress:.1f}%)")
+
+ def _initial_search_and_summary(self, paragraph_index: int):
+ """执行初始搜索和总结"""
+ paragraph = self.state.paragraphs[paragraph_index]
+
+ # 准备搜索输入
+ search_input = {
+ "title": paragraph.title,
+ "content": paragraph.content
+ }
+
+ # 生成搜索查询和工具选择
+ print(" - 生成搜索查询...")
+ search_output = self.first_search_node.run(search_input)
+ search_query = search_output["search_query"]
+ search_tool = search_output.get("search_tool", "basic_search_news") # 默认工具
+ reasoning = search_output["reasoning"]
+
+ print(f" - 搜索查询: {search_query}")
+ print(f" - 选择的工具: {search_tool}")
+ print(f" - 推理: {reasoning}")
+
+ # 执行搜索
+ print(" - 执行网络搜索...")
+
+ # 处理search_news_by_date的特殊参数
+ search_kwargs = {}
+ if search_tool == "search_news_by_date":
+ start_date = search_output.get("start_date")
+ end_date = search_output.get("end_date")
+
+ if start_date and end_date:
+ # 验证日期格式
+ if self._validate_date_format(start_date) and self._validate_date_format(end_date):
+ search_kwargs["start_date"] = start_date
+ search_kwargs["end_date"] = end_date
+ print(f" - 时间范围: {start_date} 到 {end_date}")
+ else:
+ print(f" ⚠️ 日期格式错误(应为YYYY-MM-DD),改用基础搜索")
+ print(f" 提供的日期: start_date={start_date}, end_date={end_date}")
+ search_tool = "basic_search_news"
+ else:
+ print(f" ⚠️ search_news_by_date工具缺少时间参数,改用基础搜索")
+ search_tool = "basic_search_news"
+
+ search_response = self.execute_search_tool(search_tool, search_query, **search_kwargs)
+
+ # 转换为兼容格式
+ search_results = []
+ if search_response and search_response.results:
+ # 每种搜索工具都有其特定的结果数量,这里取前10个作为上限
+ max_results = min(len(search_response.results), 10)
+ for result in search_response.results[:max_results]:
+ search_results.append({
+ 'title': result.title,
+ 'url': result.url,
+ 'content': result.content,
+ 'score': result.score,
+ 'raw_content': result.raw_content,
+ 'published_date': result.published_date # 新增字段
+ })
+
+ if search_results:
+ print(f" - 找到 {len(search_results)} 个搜索结果")
+ for j, result in enumerate(search_results, 1):
+ date_info = f" (发布于: {result.get('published_date', 'N/A')})" if result.get('published_date') else ""
+ print(f" {j}. {result['title'][:50]}...{date_info}")
+ else:
+ print(" - 未找到搜索结果")
+
+ # 更新状态中的搜索历史
+ paragraph.research.add_search_results(search_query, search_results)
+
+ # 生成初始总结
+ print(" - 生成初始总结...")
+ summary_input = {
+ "title": paragraph.title,
+ "content": paragraph.content,
+ "search_query": search_query,
+ "search_results": format_search_results_for_prompt(
+ search_results, self.config.max_content_length
+ )
+ }
+
+ # 更新状态
+ self.state = self.first_summary_node.mutate_state(
+ summary_input, self.state, paragraph_index
+ )
+
+ print(" - 初始总结完成")
+
+ def _reflection_loop(self, paragraph_index: int):
+ """执行反思循环"""
+ paragraph = self.state.paragraphs[paragraph_index]
+
+ for reflection_i in range(self.config.max_reflections):
+ print(f" - 反思 {reflection_i + 1}/{self.config.max_reflections}...")
+
+ # 准备反思输入
+ reflection_input = {
+ "title": paragraph.title,
+ "content": paragraph.content,
+ "paragraph_latest_state": paragraph.research.latest_summary
+ }
+
+ # 生成反思搜索查询
+ reflection_output = self.reflection_node.run(reflection_input)
+ search_query = reflection_output["search_query"]
+ search_tool = reflection_output.get("search_tool", "basic_search_news") # 默认工具
+ reasoning = reflection_output["reasoning"]
+
+ print(f" 反思查询: {search_query}")
+ print(f" 选择的工具: {search_tool}")
+ print(f" 反思推理: {reasoning}")
+
+ # 执行反思搜索
+ # 处理search_news_by_date的特殊参数
+ search_kwargs = {}
+ if search_tool == "search_news_by_date":
+ start_date = reflection_output.get("start_date")
+ end_date = reflection_output.get("end_date")
+
+ if start_date and end_date:
+ # 验证日期格式
+ if self._validate_date_format(start_date) and self._validate_date_format(end_date):
+ search_kwargs["start_date"] = start_date
+ search_kwargs["end_date"] = end_date
+ print(f" 时间范围: {start_date} 到 {end_date}")
+ else:
+ print(f" ⚠️ 日期格式错误(应为YYYY-MM-DD),改用基础搜索")
+ print(f" 提供的日期: start_date={start_date}, end_date={end_date}")
+ search_tool = "basic_search_news"
+ else:
+ print(f" ⚠️ search_news_by_date工具缺少时间参数,改用基础搜索")
+ search_tool = "basic_search_news"
+
+ search_response = self.execute_search_tool(search_tool, search_query, **search_kwargs)
+
+ # 转换为兼容格式
+ search_results = []
+ if search_response and search_response.results:
+ # 每种搜索工具都有其特定的结果数量,这里取前10个作为上限
+ max_results = min(len(search_response.results), 10)
+ for result in search_response.results[:max_results]:
+ search_results.append({
+ 'title': result.title,
+ 'url': result.url,
+ 'content': result.content,
+ 'score': result.score,
+ 'raw_content': result.raw_content,
+ 'published_date': result.published_date
+ })
+
+ if search_results:
+ print(f" 找到 {len(search_results)} 个反思搜索结果")
+ for j, result in enumerate(search_results, 1):
+ date_info = f" (发布于: {result.get('published_date', 'N/A')})" if result.get('published_date') else ""
+ print(f" {j}. {result['title'][:50]}...{date_info}")
+ else:
+ print(" 未找到反思搜索结果")
+
+ # 更新搜索历史
+ paragraph.research.add_search_results(search_query, search_results)
+
+ # 生成反思总结
+ reflection_summary_input = {
+ "title": paragraph.title,
+ "content": paragraph.content,
+ "search_query": search_query,
+ "search_results": format_search_results_for_prompt(
+ search_results, self.config.max_content_length
+ ),
+ "paragraph_latest_state": paragraph.research.latest_summary
+ }
+
+ # 更新状态
+ self.state = self.reflection_summary_node.mutate_state(
+ reflection_summary_input, self.state, paragraph_index
+ )
+
+ print(f" 反思 {reflection_i + 1} 完成")
+
+ def _generate_final_report(self) -> str:
+ """生成最终报告"""
+ print(f"\n[步骤 3] 生成最终报告...")
+
+ # 准备报告数据
+ report_data = []
+ for paragraph in self.state.paragraphs:
+ report_data.append({
+ "title": paragraph.title,
+ "paragraph_latest_state": paragraph.research.latest_summary
+ })
+
+ # 格式化报告
+ try:
+ final_report = self.report_formatting_node.run(report_data)
+ except Exception as e:
+ print(f"LLM格式化失败,使用备用方法: {str(e)}")
+ final_report = self.report_formatting_node.format_report_manually(
+ report_data, self.state.report_title
+ )
+
+ # 更新状态
+ self.state.final_report = final_report
+ self.state.mark_completed()
+
+ print("最终报告生成完成")
+ return final_report
+
+ def _save_report(self, report_content: str):
+ """保存报告到文件"""
+ # 生成文件名
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+ query_safe = "".join(c for c in self.state.query if c.isalnum() or c in (' ', '-', '_')).rstrip()
+ query_safe = query_safe.replace(' ', '_')[:30]
+
+ filename = f"deep_search_report_{query_safe}_{timestamp}.md"
+ filepath = os.path.join(self.config.output_dir, filename)
+
+ # 保存报告
+ with open(filepath, 'w', encoding='utf-8') as f:
+ f.write(report_content)
+
+ print(f"报告已保存到: {filepath}")
+
+ # 保存状态(如果配置允许)
+ if self.config.save_intermediate_states:
+ state_filename = f"state_{query_safe}_{timestamp}.json"
+ state_filepath = os.path.join(self.config.output_dir, state_filename)
+ self.state.save_to_file(state_filepath)
+ print(f"状态已保存到: {state_filepath}")
+
+ def get_progress_summary(self) -> Dict[str, Any]:
+ """获取进度摘要"""
+ return self.state.get_progress_summary()
+
+ def load_state(self, filepath: str):
+ """从文件加载状态"""
+ self.state = State.load_from_file(filepath)
+ print(f"状态已从 {filepath} 加载")
+
+ def save_state(self, filepath: str):
+ """保存状态到文件"""
+ self.state.save_to_file(filepath)
+ print(f"状态已保存到 {filepath}")
+
+
+def create_agent(config_file: Optional[str] = None) -> DeepSearchAgent:
+ """
+ 创建Deep Search Agent实例的便捷函数
+
+ Args:
+ config_file: 配置文件路径
+
+ Returns:
+ DeepSearchAgent实例
+ """
+ config = load_config(config_file)
+ return DeepSearchAgent(config)
diff --git a/MediaEngine/llms/__init__.py b/MediaEngine/llms/__init__.py
new file mode 100644
index 0000000..2e1602a
--- /dev/null
+++ b/MediaEngine/llms/__init__.py
@@ -0,0 +1,10 @@
+"""
+LLM调用模块
+支持多种大语言模型的统一接口
+"""
+
+from .base import BaseLLM
+from .deepseek import DeepSeekLLM
+from .openai_llm import OpenAILLM
+
+__all__ = ["BaseLLM", "DeepSeekLLM", "OpenAILLM"]
diff --git a/MediaEngine/llms/base.py b/MediaEngine/llms/base.py
new file mode 100644
index 0000000..9c6f99f
--- /dev/null
+++ b/MediaEngine/llms/base.py
@@ -0,0 +1,61 @@
+"""
+LLM基础抽象类
+定义所有LLM实现需要遵循的接口标准
+"""
+
+from abc import ABC, abstractmethod
+from typing import Optional, Dict, Any
+
+
+class BaseLLM(ABC):
+ """LLM基础抽象类"""
+
+ def __init__(self, api_key: str, model_name: Optional[str] = None):
+ """
+ 初始化LLM客户端
+
+ Args:
+ api_key: API密钥
+ model_name: 模型名称,如果不指定则使用默认模型
+ """
+ self.api_key = api_key
+ self.model_name = model_name
+
+ @abstractmethod
+ 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:
+ 清理后的响应内容
+ """
+ if response is None:
+ return ""
+ return response.strip()
diff --git a/MediaEngine/llms/deepseek.py b/MediaEngine/llms/deepseek.py
new file mode 100644
index 0000000..9e6d96d
--- /dev/null
+++ b/MediaEngine/llms/deepseek.py
@@ -0,0 +1,95 @@
+"""
+DeepSeek LLM实现
+使用DeepSeek API进行文本生成
+"""
+
+import os
+from typing import Optional, Dict, Any
+from openai import OpenAI
+from .base import BaseLLM
+
+
+class DeepSeekLLM(BaseLLM):
+ """DeepSeek LLM实现类"""
+
+ def __init__(self, api_key: Optional[str] = None, model_name: Optional[str] = None):
+ """
+ 初始化DeepSeek客户端
+
+ Args:
+ api_key: DeepSeek API密钥,如果不提供则从环境变量读取
+ model_name: 模型名称,默认使用deepseek-chat
+ """
+ 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)
+
+ # 初始化OpenAI客户端,使用DeepSeek的endpoint
+ self.client = OpenAI(
+ api_key=self.api_key,
+ base_url="https://api.deepseek.com"
+ )
+
+ self.default_model = model_name or self.get_default_model()
+
+ def get_default_model(self) -> str:
+ """获取默认模型名称"""
+ return "deepseek-chat"
+
+ 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": "https://api.deepseek.com"
+ }
diff --git a/MediaEngine/llms/openai_llm.py b/MediaEngine/llms/openai_llm.py
new file mode 100644
index 0000000..fff0e79
--- /dev/null
+++ b/MediaEngine/llms/openai_llm.py
@@ -0,0 +1,90 @@
+"""
+OpenAI LLM实现
+使用OpenAI API进行文本生成
+"""
+
+import os
+from typing import Optional, Dict, Any
+from openai import OpenAI
+from .base import BaseLLM
+
+
+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"
+
+ 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"
+ }
diff --git a/MediaEngine/nodes/__init__.py b/MediaEngine/nodes/__init__.py
new file mode 100644
index 0000000..e15087c
--- /dev/null
+++ b/MediaEngine/nodes/__init__.py
@@ -0,0 +1,20 @@
+"""
+节点处理模块
+实现Deep Search Agent的各个处理步骤
+"""
+
+from .base_node import BaseNode
+from .report_structure_node import ReportStructureNode
+from .search_node import FirstSearchNode, ReflectionNode
+from .summary_node import FirstSummaryNode, ReflectionSummaryNode
+from .formatting_node import ReportFormattingNode
+
+__all__ = [
+ "BaseNode",
+ "ReportStructureNode",
+ "FirstSearchNode",
+ "ReflectionNode",
+ "FirstSummaryNode",
+ "ReflectionSummaryNode",
+ "ReportFormattingNode"
+]
diff --git a/MediaEngine/nodes/base_node.py b/MediaEngine/nodes/base_node.py
new file mode 100644
index 0000000..b1387ff
--- /dev/null
+++ b/MediaEngine/nodes/base_node.py
@@ -0,0 +1,89 @@
+"""
+节点基类
+定义所有处理节点的基础接口
+"""
+
+from abc import ABC, abstractmethod
+from typing import Any, Dict, Optional
+from ..llms.base import BaseLLM
+from ..state.state import State
+
+
+class BaseNode(ABC):
+ """节点基类"""
+
+ def __init__(self, llm_client: BaseLLM, node_name: str = ""):
+ """
+ 初始化节点
+
+ Args:
+ llm_client: LLM客户端
+ node_name: 节点名称
+ """
+ self.llm_client = llm_client
+ self.node_name = node_name or self.__class__.__name__
+
+ @abstractmethod
+ def run(self, input_data: Any, **kwargs) -> Any:
+ """
+ 执行节点处理逻辑
+
+ Args:
+ input_data: 输入数据
+ **kwargs: 额外参数
+
+ Returns:
+ 处理结果
+ """
+ pass
+
+ def validate_input(self, input_data: Any) -> bool:
+ """
+ 验证输入数据
+
+ Args:
+ input_data: 输入数据
+
+ Returns:
+ 验证是否通过
+ """
+ return True
+
+ def process_output(self, output: Any) -> Any:
+ """
+ 处理输出数据
+
+ Args:
+ output: 原始输出
+
+ Returns:
+ 处理后的输出
+ """
+ return output
+
+ def log_info(self, message: str):
+ """记录信息日志"""
+ print(f"[{self.node_name}] {message}")
+
+ def log_error(self, message: str):
+ """记录错误日志"""
+ print(f"[{self.node_name}] 错误: {message}")
+
+
+class StateMutationNode(BaseNode):
+ """带状态修改功能的节点基类"""
+
+ @abstractmethod
+ def mutate_state(self, input_data: Any, state: State, **kwargs) -> State:
+ """
+ 修改状态
+
+ Args:
+ input_data: 输入数据
+ state: 当前状态
+ **kwargs: 额外参数
+
+ Returns:
+ 修改后的状态
+ """
+ pass
diff --git a/MediaEngine/nodes/formatting_node.py b/MediaEngine/nodes/formatting_node.py
new file mode 100644
index 0000000..c8b4159
--- /dev/null
+++ b/MediaEngine/nodes/formatting_node.py
@@ -0,0 +1,164 @@
+"""
+报告格式化节点
+负责将最终研究结果格式化为美观的Markdown报告
+"""
+
+import json
+from typing import List, Dict, Any
+
+from .base_node import BaseNode
+from ..prompts import SYSTEM_PROMPT_REPORT_FORMATTING
+from ..utils.text_processing import (
+ remove_reasoning_from_output,
+ clean_markdown_tags
+)
+
+
+class ReportFormattingNode(BaseNode):
+ """格式化最终报告的节点"""
+
+ def __init__(self, llm_client):
+ """
+ 初始化报告格式化节点
+
+ Args:
+ llm_client: LLM客户端
+ """
+ super().__init__(llm_client, "ReportFormattingNode")
+
+ def validate_input(self, input_data: Any) -> bool:
+ """验证输入数据"""
+ if isinstance(input_data, str):
+ try:
+ data = json.loads(input_data)
+ return isinstance(data, list) and all(
+ isinstance(item, dict) and "title" in item and "paragraph_latest_state" in item
+ for item in data
+ )
+ except:
+ return False
+ elif isinstance(input_data, list):
+ return all(
+ isinstance(item, dict) and "title" in item and "paragraph_latest_state" in item
+ for item in input_data
+ )
+ return False
+
+ def run(self, input_data: Any, **kwargs) -> str:
+ """
+ 调用LLM生成Markdown格式报告
+
+ Args:
+ input_data: 包含所有段落信息的列表
+ **kwargs: 额外参数
+
+ Returns:
+ 格式化的Markdown报告
+ """
+ try:
+ if not self.validate_input(input_data):
+ raise ValueError("输入数据格式错误,需要包含title和paragraph_latest_state的列表")
+
+ # 准备输入数据
+ if isinstance(input_data, str):
+ message = input_data
+ else:
+ message = json.dumps(input_data, ensure_ascii=False)
+
+ self.log_info("正在格式化最终报告")
+
+ # 调用LLM
+ response = self.llm_client.invoke(SYSTEM_PROMPT_REPORT_FORMATTING, message)
+
+ # 处理响应
+ processed_response = self.process_output(response)
+
+ self.log_info("成功生成格式化报告")
+ return processed_response
+
+ except Exception as e:
+ self.log_error(f"报告格式化失败: {str(e)}")
+ raise e
+
+ def process_output(self, output: str) -> str:
+ """
+ 处理LLM输出,清理Markdown格式
+
+ Args:
+ output: LLM原始输出
+
+ Returns:
+ 清理后的Markdown报告
+ """
+ try:
+ # 清理响应文本
+ cleaned_output = remove_reasoning_from_output(output)
+ cleaned_output = clean_markdown_tags(cleaned_output)
+
+ # 确保报告有基本结构
+ if not cleaned_output.strip():
+ return "# 报告生成失败\n\n无法生成有效的报告内容。"
+
+ # 如果没有标题,添加一个默认标题
+ if not cleaned_output.strip().startswith('#'):
+ cleaned_output = "# 深度研究报告\n\n" + cleaned_output
+
+ return cleaned_output.strip()
+
+ except Exception as e:
+ self.log_error(f"处理输出失败: {str(e)}")
+ return "# 报告处理失败\n\n报告格式化过程中发生错误。"
+
+ def format_report_manually(self, paragraphs_data: List[Dict[str, str]],
+ report_title: str = "深度研究报告") -> str:
+ """
+ 手动格式化报告(备用方法)
+
+ Args:
+ paragraphs_data: 段落数据列表
+ report_title: 报告标题
+
+ Returns:
+ 格式化的Markdown报告
+ """
+ try:
+ self.log_info("使用手动格式化方法")
+
+ # 构建报告
+ report_lines = [
+ f"# {report_title}",
+ "",
+ "---",
+ ""
+ ]
+
+ # 添加各个段落
+ for i, paragraph in enumerate(paragraphs_data, 1):
+ title = paragraph.get("title", f"段落 {i}")
+ content = paragraph.get("paragraph_latest_state", "")
+
+ if content:
+ report_lines.extend([
+ f"## {title}",
+ "",
+ content,
+ "",
+ "---",
+ ""
+ ])
+
+ # 添加结论
+ if len(paragraphs_data) > 1:
+ report_lines.extend([
+ "## 结论",
+ "",
+ "本报告通过深度搜索和研究,对相关主题进行了全面分析。"
+ "以上各个方面的内容为理解该主题提供了重要参考。",
+ ""
+ ])
+
+ return "\n".join(report_lines)
+
+ except Exception as e:
+ self.log_error(f"手动格式化失败: {str(e)}")
+ return "# 报告生成失败\n\n无法完成报告格式化。"
diff --git a/MediaEngine/nodes/report_structure_node.py b/MediaEngine/nodes/report_structure_node.py
new file mode 100644
index 0000000..87632da
--- /dev/null
+++ b/MediaEngine/nodes/report_structure_node.py
@@ -0,0 +1,203 @@
+"""
+报告结构生成节点
+负责根据查询生成报告的整体结构
+"""
+
+import json
+from typing import Dict, Any, List
+from json.decoder import JSONDecodeError
+
+from .base_node import StateMutationNode
+from ..state.state import State
+from ..prompts import SYSTEM_PROMPT_REPORT_STRUCTURE
+from ..utils.text_processing import (
+ remove_reasoning_from_output,
+ clean_json_tags,
+ extract_clean_response,
+ fix_incomplete_json
+)
+
+
+class ReportStructureNode(StateMutationNode):
+ """生成报告结构的节点"""
+
+ def __init__(self, llm_client, query: str):
+ """
+ 初始化报告结构节点
+
+ Args:
+ llm_client: LLM客户端
+ query: 用户查询
+ """
+ super().__init__(llm_client, "ReportStructureNode")
+ self.query = query
+
+ def validate_input(self, input_data: Any) -> bool:
+ """验证输入数据"""
+ return isinstance(self.query, str) and len(self.query.strip()) > 0
+
+ def run(self, input_data: Any = None, **kwargs) -> List[Dict[str, str]]:
+ """
+ 调用LLM生成报告结构
+
+ Args:
+ input_data: 输入数据(这里不使用,使用初始化时的query)
+ **kwargs: 额外参数
+
+ Returns:
+ 报告结构列表
+ """
+ try:
+ self.log_info(f"正在为查询生成报告结构: {self.query}")
+
+ # 调用LLM
+ response = self.llm_client.invoke(SYSTEM_PROMPT_REPORT_STRUCTURE, self.query)
+
+ # 处理响应
+ processed_response = self.process_output(response)
+
+ self.log_info(f"成功生成 {len(processed_response)} 个段落结构")
+ return processed_response
+
+ except Exception as e:
+ self.log_error(f"生成报告结构失败: {str(e)}")
+ raise e
+
+ def process_output(self, output: str) -> List[Dict[str, str]]:
+ """
+ 处理LLM输出,提取报告结构
+
+ Args:
+ output: LLM原始输出
+
+ Returns:
+ 处理后的报告结构列表
+ """
+ try:
+ # 清理响应文本
+ cleaned_output = remove_reasoning_from_output(output)
+ cleaned_output = clean_json_tags(cleaned_output)
+
+ # 记录清理后的输出用于调试
+ self.log_info(f"清理后的输出: {cleaned_output[:200]}...")
+
+ # 解析JSON
+ try:
+ report_structure = json.loads(cleaned_output)
+ self.log_info("JSON解析成功")
+ except JSONDecodeError as e:
+ self.log_info(f"JSON解析失败: {str(e)}")
+ # 使用更强大的提取方法
+ report_structure = extract_clean_response(cleaned_output)
+ if "error" in report_structure:
+ self.log_error("JSON解析失败,尝试修复...")
+ # 尝试修复JSON
+ fixed_json = fix_incomplete_json(cleaned_output)
+ if fixed_json:
+ try:
+ report_structure = json.loads(fixed_json)
+ self.log_info("JSON修复成功")
+ except JSONDecodeError:
+ self.log_error("JSON修复失败")
+ # 返回默认结构
+ return self._generate_default_structure()
+ else:
+ self.log_error("无法修复JSON,使用默认结构")
+ return self._generate_default_structure()
+
+ # 验证结构
+ if not isinstance(report_structure, list):
+ self.log_info("报告结构不是列表,尝试转换...")
+ if isinstance(report_structure, dict):
+ # 如果是单个对象,包装成列表
+ report_structure = [report_structure]
+ else:
+ self.log_error("报告结构格式无效,使用默认结构")
+ return self._generate_default_structure()
+
+ # 验证每个段落
+ validated_structure = []
+ for i, paragraph in enumerate(report_structure):
+ if not isinstance(paragraph, dict):
+ self.log_warning(f"段落 {i+1} 不是字典格式,跳过")
+ continue
+
+ title = paragraph.get("title", f"段落 {i+1}")
+ content = paragraph.get("content", "")
+
+ if not title or not content:
+ self.log_warning(f"段落 {i+1} 缺少标题或内容,跳过")
+ continue
+
+ validated_structure.append({
+ "title": title,
+ "content": content
+ })
+
+ if not validated_structure:
+ self.log_warning("没有有效的段落结构,使用默认结构")
+ return self._generate_default_structure()
+
+ self.log_info(f"成功验证 {len(validated_structure)} 个段落结构")
+ return validated_structure
+
+ except Exception as e:
+ self.log_error(f"处理输出失败: {str(e)}")
+ return self._generate_default_structure()
+
+ def _generate_default_structure(self) -> List[Dict[str, str]]:
+ """
+ 生成默认的报告结构
+
+ Returns:
+ 默认的报告结构列表
+ """
+ self.log_info("生成默认报告结构")
+ return [
+ {
+ "title": "研究概述",
+ "content": "对查询主题进行总体概述和分析"
+ },
+ {
+ "title": "深度分析",
+ "content": "深入分析查询主题的各个方面"
+ }
+ ]
+
+ def mutate_state(self, input_data: Any = None, state: State = None, **kwargs) -> State:
+ """
+ 将报告结构写入状态
+
+ Args:
+ input_data: 输入数据
+ state: 当前状态,如果为None则创建新状态
+ **kwargs: 额外参数
+
+ Returns:
+ 更新后的状态
+ """
+ if state is None:
+ state = State()
+
+ try:
+ # 生成报告结构
+ report_structure = self.run(input_data, **kwargs)
+
+ # 设置查询和报告标题
+ state.query = self.query
+ if not state.report_title:
+ state.report_title = f"关于'{self.query}'的深度研究报告"
+
+ # 添加段落到状态
+ for paragraph_data in report_structure:
+ state.add_paragraph(
+ title=paragraph_data["title"],
+ content=paragraph_data["content"]
+ )
+
+ self.log_info(f"已将 {len(report_structure)} 个段落添加到状态中")
+ return state
+
+ except Exception as e:
+ self.log_error(f"状态更新失败: {str(e)}")
+ raise e
diff --git a/MediaEngine/nodes/search_node.py b/MediaEngine/nodes/search_node.py
new file mode 100644
index 0000000..52cc17d
--- /dev/null
+++ b/MediaEngine/nodes/search_node.py
@@ -0,0 +1,285 @@
+"""
+搜索节点实现
+负责生成搜索查询和反思查询
+"""
+
+import json
+from typing import Dict, Any
+from json.decoder import JSONDecodeError
+
+from .base_node import BaseNode
+from ..prompts import SYSTEM_PROMPT_FIRST_SEARCH, SYSTEM_PROMPT_REFLECTION
+from ..utils.text_processing import (
+ remove_reasoning_from_output,
+ clean_json_tags,
+ extract_clean_response,
+ fix_incomplete_json
+)
+
+
+class FirstSearchNode(BaseNode):
+ """为段落生成首次搜索查询的节点"""
+
+ def __init__(self, llm_client):
+ """
+ 初始化首次搜索节点
+
+ Args:
+ llm_client: LLM客户端
+ """
+ super().__init__(llm_client, "FirstSearchNode")
+
+ def validate_input(self, input_data: Any) -> bool:
+ """验证输入数据"""
+ if isinstance(input_data, str):
+ try:
+ data = json.loads(input_data)
+ return "title" in data and "content" in data
+ except JSONDecodeError:
+ return False
+ elif isinstance(input_data, dict):
+ return "title" in input_data and "content" in input_data
+ return False
+
+ def run(self, input_data: Any, **kwargs) -> Dict[str, str]:
+ """
+ 调用LLM生成搜索查询和理由
+
+ Args:
+ input_data: 包含title和content的字符串或字典
+ **kwargs: 额外参数
+
+ Returns:
+ 包含search_query和reasoning的字典
+ """
+ try:
+ if not self.validate_input(input_data):
+ raise ValueError("输入数据格式错误,需要包含title和content字段")
+
+ # 准备输入数据
+ if isinstance(input_data, str):
+ message = input_data
+ else:
+ message = json.dumps(input_data, ensure_ascii=False)
+
+ self.log_info("正在生成首次搜索查询")
+
+ # 调用LLM
+ response = self.llm_client.invoke(SYSTEM_PROMPT_FIRST_SEARCH, message)
+
+ # 处理响应
+ processed_response = self.process_output(response)
+
+ self.log_info(f"生成搜索查询: {processed_response.get('search_query', 'N/A')}")
+ return processed_response
+
+ except Exception as e:
+ self.log_error(f"生成首次搜索查询失败: {str(e)}")
+ raise e
+
+ def process_output(self, output: str) -> Dict[str, str]:
+ """
+ 处理LLM输出,提取搜索查询和推理
+
+ Args:
+ output: LLM原始输出
+
+ Returns:
+ 包含search_query和reasoning的字典
+ """
+ try:
+ # 清理响应文本
+ cleaned_output = remove_reasoning_from_output(output)
+ cleaned_output = clean_json_tags(cleaned_output)
+
+ # 记录清理后的输出用于调试
+ self.log_info(f"清理后的输出: {cleaned_output[:200]}...")
+
+ # 解析JSON
+ try:
+ result = json.loads(cleaned_output)
+ self.log_info("JSON解析成功")
+ except JSONDecodeError as e:
+ self.log_info(f"JSON解析失败: {str(e)}")
+ # 使用更强大的提取方法
+ result = extract_clean_response(cleaned_output)
+ if "error" in result:
+ self.log_error("JSON解析失败,尝试修复...")
+ # 尝试修复JSON
+ fixed_json = fix_incomplete_json(cleaned_output)
+ if fixed_json:
+ try:
+ result = json.loads(fixed_json)
+ self.log_info("JSON修复成功")
+ except JSONDecodeError:
+ self.log_error("JSON修复失败")
+ # 返回默认查询
+ return self._get_default_search_query()
+ else:
+ self.log_error("无法修复JSON,使用默认查询")
+ return self._get_default_search_query()
+
+ # 验证和清理结果
+ search_query = result.get("search_query", "")
+ reasoning = result.get("reasoning", "")
+
+ if not search_query:
+ self.log_warning("未找到搜索查询,使用默认查询")
+ return self._get_default_search_query()
+
+ return {
+ "search_query": search_query,
+ "reasoning": reasoning
+ }
+
+ except Exception as e:
+ self.log_error(f"处理输出失败: {str(e)}")
+ # 返回默认查询
+ return self._get_default_search_query()
+
+ def _get_default_search_query(self) -> Dict[str, str]:
+ """
+ 获取默认搜索查询
+
+ Returns:
+ 默认的搜索查询字典
+ """
+ return {
+ "search_query": "相关主题研究",
+ "reasoning": "由于解析失败,使用默认搜索查询"
+ }
+
+
+class ReflectionNode(BaseNode):
+ """反思段落并生成新搜索查询的节点"""
+
+ def __init__(self, llm_client):
+ """
+ 初始化反思节点
+
+ Args:
+ llm_client: LLM客户端
+ """
+ super().__init__(llm_client, "ReflectionNode")
+
+ def validate_input(self, input_data: Any) -> bool:
+ """验证输入数据"""
+ if isinstance(input_data, str):
+ try:
+ data = json.loads(input_data)
+ required_fields = ["title", "content", "paragraph_latest_state"]
+ return all(field in data for field in required_fields)
+ except JSONDecodeError:
+ return False
+ elif isinstance(input_data, dict):
+ required_fields = ["title", "content", "paragraph_latest_state"]
+ return all(field in input_data for field in required_fields)
+ return False
+
+ def run(self, input_data: Any, **kwargs) -> Dict[str, str]:
+ """
+ 调用LLM反思并生成搜索查询
+
+ Args:
+ input_data: 包含title、content和paragraph_latest_state的字符串或字典
+ **kwargs: 额外参数
+
+ Returns:
+ 包含search_query和reasoning的字典
+ """
+ try:
+ if not self.validate_input(input_data):
+ raise ValueError("输入数据格式错误,需要包含title、content和paragraph_latest_state字段")
+
+ # 准备输入数据
+ if isinstance(input_data, str):
+ message = input_data
+ else:
+ message = json.dumps(input_data, ensure_ascii=False)
+
+ self.log_info("正在进行反思并生成新搜索查询")
+
+ # 调用LLM
+ response = self.llm_client.invoke(SYSTEM_PROMPT_REFLECTION, message)
+
+ # 处理响应
+ processed_response = self.process_output(response)
+
+ self.log_info(f"反思生成搜索查询: {processed_response.get('search_query', 'N/A')}")
+ return processed_response
+
+ except Exception as e:
+ self.log_error(f"反思生成搜索查询失败: {str(e)}")
+ raise e
+
+ def process_output(self, output: str) -> Dict[str, str]:
+ """
+ 处理LLM输出,提取搜索查询和推理
+
+ Args:
+ output: LLM原始输出
+
+ Returns:
+ 包含search_query和reasoning的字典
+ """
+ try:
+ # 清理响应文本
+ cleaned_output = remove_reasoning_from_output(output)
+ cleaned_output = clean_json_tags(cleaned_output)
+
+ # 记录清理后的输出用于调试
+ self.log_info(f"清理后的输出: {cleaned_output[:200]}...")
+
+ # 解析JSON
+ try:
+ result = json.loads(cleaned_output)
+ self.log_info("JSON解析成功")
+ except JSONDecodeError as e:
+ self.log_info(f"JSON解析失败: {str(e)}")
+ # 使用更强大的提取方法
+ result = extract_clean_response(cleaned_output)
+ if "error" in result:
+ self.log_error("JSON解析失败,尝试修复...")
+ # 尝试修复JSON
+ fixed_json = fix_incomplete_json(cleaned_output)
+ if fixed_json:
+ try:
+ result = json.loads(fixed_json)
+ self.log_info("JSON修复成功")
+ except JSONDecodeError:
+ self.log_error("JSON修复失败")
+ # 返回默认查询
+ return self._get_default_reflection_query()
+ else:
+ self.log_error("无法修复JSON,使用默认查询")
+ return self._get_default_reflection_query()
+
+ # 验证和清理结果
+ search_query = result.get("search_query", "")
+ reasoning = result.get("reasoning", "")
+
+ if not search_query:
+ self.log_warning("未找到搜索查询,使用默认查询")
+ return self._get_default_reflection_query()
+
+ return {
+ "search_query": search_query,
+ "reasoning": reasoning
+ }
+
+ except Exception as e:
+ self.log_error(f"处理输出失败: {str(e)}")
+ # 返回默认查询
+ return self._get_default_reflection_query()
+
+ def _get_default_reflection_query(self) -> Dict[str, str]:
+ """
+ 获取默认反思搜索查询
+
+ Returns:
+ 默认的反思搜索查询字典
+ """
+ return {
+ "search_query": "深度研究补充信息",
+ "reasoning": "由于解析失败,使用默认反思搜索查询"
+ }
diff --git a/MediaEngine/nodes/summary_node.py b/MediaEngine/nodes/summary_node.py
new file mode 100644
index 0000000..e44fb1e
--- /dev/null
+++ b/MediaEngine/nodes/summary_node.py
@@ -0,0 +1,312 @@
+"""
+总结节点实现
+负责根据搜索结果生成和更新段落内容
+"""
+
+import json
+from typing import Dict, Any, List
+from json.decoder import JSONDecodeError
+
+from .base_node import StateMutationNode
+from ..state.state import State
+from ..prompts import SYSTEM_PROMPT_FIRST_SUMMARY, SYSTEM_PROMPT_REFLECTION_SUMMARY
+from ..utils.text_processing import (
+ remove_reasoning_from_output,
+ clean_json_tags,
+ extract_clean_response,
+ fix_incomplete_json,
+ format_search_results_for_prompt
+)
+
+
+class FirstSummaryNode(StateMutationNode):
+ """根据搜索结果生成段落首次总结的节点"""
+
+ def __init__(self, llm_client):
+ """
+ 初始化首次总结节点
+
+ Args:
+ llm_client: LLM客户端
+ """
+ super().__init__(llm_client, "FirstSummaryNode")
+
+ def validate_input(self, input_data: Any) -> bool:
+ """验证输入数据"""
+ if isinstance(input_data, str):
+ try:
+ data = json.loads(input_data)
+ required_fields = ["title", "content", "search_query", "search_results"]
+ return all(field in data for field in required_fields)
+ except JSONDecodeError:
+ return False
+ elif isinstance(input_data, dict):
+ required_fields = ["title", "content", "search_query", "search_results"]
+ return all(field in input_data for field in required_fields)
+ return False
+
+ def run(self, input_data: Any, **kwargs) -> str:
+ """
+ 调用LLM生成段落总结
+
+ Args:
+ input_data: 包含title、content、search_query和search_results的数据
+ **kwargs: 额外参数
+
+ Returns:
+ 段落总结内容
+ """
+ try:
+ if not self.validate_input(input_data):
+ raise ValueError("输入数据格式错误")
+
+ # 准备输入数据
+ if isinstance(input_data, str):
+ message = input_data
+ else:
+ message = json.dumps(input_data, ensure_ascii=False)
+
+ self.log_info("正在生成首次段落总结")
+
+ # 调用LLM
+ response = self.llm_client.invoke(SYSTEM_PROMPT_FIRST_SUMMARY, message)
+
+ # 处理响应
+ processed_response = self.process_output(response)
+
+ self.log_info("成功生成首次段落总结")
+ return processed_response
+
+ except Exception as e:
+ self.log_error(f"生成首次总结失败: {str(e)}")
+ raise e
+
+ def process_output(self, output: str) -> str:
+ """
+ 处理LLM输出,提取段落内容
+
+ Args:
+ output: LLM原始输出
+
+ Returns:
+ 段落内容
+ """
+ try:
+ # 清理响应文本
+ cleaned_output = remove_reasoning_from_output(output)
+ cleaned_output = clean_json_tags(cleaned_output)
+
+ # 记录清理后的输出用于调试
+ self.log_info(f"清理后的输出: {cleaned_output[:200]}...")
+
+ # 解析JSON
+ try:
+ result = json.loads(cleaned_output)
+ self.log_info("JSON解析成功")
+ except JSONDecodeError as e:
+ self.log_info(f"JSON解析失败: {str(e)}")
+ # 尝试修复JSON
+ fixed_json = fix_incomplete_json(cleaned_output)
+ if fixed_json:
+ try:
+ result = json.loads(fixed_json)
+ self.log_info("JSON修复成功")
+ except JSONDecodeError:
+ self.log_info("JSON修复失败,直接使用清理后的文本")
+ # 如果不是JSON格式,直接返回清理后的文本
+ return cleaned_output
+ else:
+ self.log_info("无法修复JSON,直接使用清理后的文本")
+ # 如果不是JSON格式,直接返回清理后的文本
+ return cleaned_output
+
+ # 提取段落内容
+ if isinstance(result, dict):
+ paragraph_content = result.get("paragraph_latest_state", "")
+ if paragraph_content:
+ return paragraph_content
+
+ # 如果提取失败,返回原始清理后的文本
+ return cleaned_output
+
+ except Exception as e:
+ self.log_error(f"处理输出失败: {str(e)}")
+ return "段落总结生成失败"
+
+ def mutate_state(self, input_data: Any, state: State, paragraph_index: int, **kwargs) -> State:
+ """
+ 更新段落的最新总结到状态
+
+ Args:
+ input_data: 输入数据
+ state: 当前状态
+ paragraph_index: 段落索引
+ **kwargs: 额外参数
+
+ Returns:
+ 更新后的状态
+ """
+ try:
+ # 生成总结
+ summary = self.run(input_data, **kwargs)
+
+ # 更新状态
+ if 0 <= paragraph_index < len(state.paragraphs):
+ state.paragraphs[paragraph_index].research.latest_summary = summary
+ self.log_info(f"已更新段落 {paragraph_index} 的首次总结")
+ else:
+ raise ValueError(f"段落索引 {paragraph_index} 超出范围")
+
+ state.update_timestamp()
+ return state
+
+ except Exception as e:
+ self.log_error(f"状态更新失败: {str(e)}")
+ raise e
+
+
+class ReflectionSummaryNode(StateMutationNode):
+ """根据反思搜索结果更新段落总结的节点"""
+
+ def __init__(self, llm_client):
+ """
+ 初始化反思总结节点
+
+ Args:
+ llm_client: LLM客户端
+ """
+ super().__init__(llm_client, "ReflectionSummaryNode")
+
+ def validate_input(self, input_data: Any) -> bool:
+ """验证输入数据"""
+ if isinstance(input_data, str):
+ try:
+ data = json.loads(input_data)
+ required_fields = ["title", "content", "search_query", "search_results", "paragraph_latest_state"]
+ return all(field in data for field in required_fields)
+ except JSONDecodeError:
+ return False
+ elif isinstance(input_data, dict):
+ required_fields = ["title", "content", "search_query", "search_results", "paragraph_latest_state"]
+ return all(field in input_data for field in required_fields)
+ return False
+
+ def run(self, input_data: Any, **kwargs) -> str:
+ """
+ 调用LLM更新段落内容
+
+ Args:
+ input_data: 包含完整反思信息的数据
+ **kwargs: 额外参数
+
+ Returns:
+ 更新后的段落内容
+ """
+ try:
+ if not self.validate_input(input_data):
+ raise ValueError("输入数据格式错误")
+
+ # 准备输入数据
+ if isinstance(input_data, str):
+ message = input_data
+ else:
+ message = json.dumps(input_data, ensure_ascii=False)
+
+ self.log_info("正在生成反思总结")
+
+ # 调用LLM
+ response = self.llm_client.invoke(SYSTEM_PROMPT_REFLECTION_SUMMARY, message)
+
+ # 处理响应
+ processed_response = self.process_output(response)
+
+ self.log_info("成功生成反思总结")
+ return processed_response
+
+ except Exception as e:
+ self.log_error(f"生成反思总结失败: {str(e)}")
+ raise e
+
+ def process_output(self, output: str) -> str:
+ """
+ 处理LLM输出,提取更新后的段落内容
+
+ Args:
+ output: LLM原始输出
+
+ Returns:
+ 更新后的段落内容
+ """
+ try:
+ # 清理响应文本
+ cleaned_output = remove_reasoning_from_output(output)
+ cleaned_output = clean_json_tags(cleaned_output)
+
+ # 记录清理后的输出用于调试
+ self.log_info(f"清理后的输出: {cleaned_output[:200]}...")
+
+ # 解析JSON
+ try:
+ result = json.loads(cleaned_output)
+ self.log_info("JSON解析成功")
+ except JSONDecodeError as e:
+ self.log_info(f"JSON解析失败: {str(e)}")
+ # 尝试修复JSON
+ fixed_json = fix_incomplete_json(cleaned_output)
+ if fixed_json:
+ try:
+ result = json.loads(fixed_json)
+ self.log_info("JSON修复成功")
+ except JSONDecodeError:
+ self.log_info("JSON修复失败,直接使用清理后的文本")
+ # 如果不是JSON格式,直接返回清理后的文本
+ return cleaned_output
+ else:
+ self.log_info("无法修复JSON,直接使用清理后的文本")
+ # 如果不是JSON格式,直接返回清理后的文本
+ return cleaned_output
+
+ # 提取更新后的段落内容
+ if isinstance(result, dict):
+ updated_content = result.get("updated_paragraph_latest_state", "")
+ if updated_content:
+ return updated_content
+
+ # 如果提取失败,返回原始清理后的文本
+ return cleaned_output
+
+ except Exception as e:
+ self.log_error(f"处理输出失败: {str(e)}")
+ return "反思总结生成失败"
+
+ def mutate_state(self, input_data: Any, state: State, paragraph_index: int, **kwargs) -> State:
+ """
+ 将更新后的总结写入状态
+
+ Args:
+ input_data: 输入数据
+ state: 当前状态
+ paragraph_index: 段落索引
+ **kwargs: 额外参数
+
+ Returns:
+ 更新后的状态
+ """
+ try:
+ # 生成更新后的总结
+ updated_summary = self.run(input_data, **kwargs)
+
+ # 更新状态
+ if 0 <= paragraph_index < len(state.paragraphs):
+ state.paragraphs[paragraph_index].research.latest_summary = updated_summary
+ state.paragraphs[paragraph_index].research.increment_reflection()
+ self.log_info(f"已更新段落 {paragraph_index} 的反思总结")
+ else:
+ raise ValueError(f"段落索引 {paragraph_index} 超出范围")
+
+ state.update_timestamp()
+ return state
+
+ except Exception as e:
+ self.log_error(f"状态更新失败: {str(e)}")
+ raise e
diff --git a/MediaEngine/prompts/__init__.py b/MediaEngine/prompts/__init__.py
new file mode 100644
index 0000000..e395aef
--- /dev/null
+++ b/MediaEngine/prompts/__init__.py
@@ -0,0 +1,34 @@
+"""
+Prompt模块
+定义Deep Search Agent各个阶段使用的系统提示词
+"""
+
+from .prompts import (
+ SYSTEM_PROMPT_REPORT_STRUCTURE,
+ SYSTEM_PROMPT_FIRST_SEARCH,
+ SYSTEM_PROMPT_FIRST_SUMMARY,
+ SYSTEM_PROMPT_REFLECTION,
+ SYSTEM_PROMPT_REFLECTION_SUMMARY,
+ SYSTEM_PROMPT_REPORT_FORMATTING,
+ output_schema_report_structure,
+ output_schema_first_search,
+ output_schema_first_summary,
+ output_schema_reflection,
+ output_schema_reflection_summary,
+ input_schema_report_formatting
+)
+
+__all__ = [
+ "SYSTEM_PROMPT_REPORT_STRUCTURE",
+ "SYSTEM_PROMPT_FIRST_SEARCH",
+ "SYSTEM_PROMPT_FIRST_SUMMARY",
+ "SYSTEM_PROMPT_REFLECTION",
+ "SYSTEM_PROMPT_REFLECTION_SUMMARY",
+ "SYSTEM_PROMPT_REPORT_FORMATTING",
+ "output_schema_report_structure",
+ "output_schema_first_search",
+ "output_schema_first_summary",
+ "output_schema_reflection",
+ "output_schema_reflection_summary",
+ "input_schema_report_formatting"
+]
diff --git a/MediaEngine/prompts/prompts.py b/MediaEngine/prompts/prompts.py
new file mode 100644
index 0000000..1bdbb55
--- /dev/null
+++ b/MediaEngine/prompts/prompts.py
@@ -0,0 +1,285 @@
+"""
+Deep Search Agent 的所有提示词定义
+包含各个阶段的系统提示词和JSON Schema定义
+"""
+
+import json
+
+# ===== JSON Schema 定义 =====
+
+# 报告结构输出Schema
+output_schema_report_structure = {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "title": {"type": "string"},
+ "content": {"type": "string"}
+ }
+ }
+}
+
+# 首次搜索输入Schema
+input_schema_first_search = {
+ "type": "object",
+ "properties": {
+ "title": {"type": "string"},
+ "content": {"type": "string"}
+ }
+}
+
+# 首次搜索输出Schema
+output_schema_first_search = {
+ "type": "object",
+ "properties": {
+ "search_query": {"type": "string"},
+ "search_tool": {"type": "string"},
+ "reasoning": {"type": "string"},
+ "start_date": {"type": "string", "description": "开始日期,格式YYYY-MM-DD,仅search_news_by_date工具需要"},
+ "end_date": {"type": "string", "description": "结束日期,格式YYYY-MM-DD,仅search_news_by_date工具需要"}
+ },
+ "required": ["search_query", "search_tool", "reasoning"]
+}
+
+# 首次总结输入Schema
+input_schema_first_summary = {
+ "type": "object",
+ "properties": {
+ "title": {"type": "string"},
+ "content": {"type": "string"},
+ "search_query": {"type": "string"},
+ "search_results": {
+ "type": "array",
+ "items": {"type": "string"}
+ }
+ }
+}
+
+# 首次总结输出Schema
+output_schema_first_summary = {
+ "type": "object",
+ "properties": {
+ "paragraph_latest_state": {"type": "string"}
+ }
+}
+
+# 反思输入Schema
+input_schema_reflection = {
+ "type": "object",
+ "properties": {
+ "title": {"type": "string"},
+ "content": {"type": "string"},
+ "paragraph_latest_state": {"type": "string"}
+ }
+}
+
+# 反思输出Schema
+output_schema_reflection = {
+ "type": "object",
+ "properties": {
+ "search_query": {"type": "string"},
+ "search_tool": {"type": "string"},
+ "reasoning": {"type": "string"},
+ "start_date": {"type": "string", "description": "开始日期,格式YYYY-MM-DD,仅search_news_by_date工具需要"},
+ "end_date": {"type": "string", "description": "结束日期,格式YYYY-MM-DD,仅search_news_by_date工具需要"}
+ },
+ "required": ["search_query", "search_tool", "reasoning"]
+}
+
+# 反思总结输入Schema
+input_schema_reflection_summary = {
+ "type": "object",
+ "properties": {
+ "title": {"type": "string"},
+ "content": {"type": "string"},
+ "search_query": {"type": "string"},
+ "search_results": {
+ "type": "array",
+ "items": {"type": "string"}
+ },
+ "paragraph_latest_state": {"type": "string"}
+ }
+}
+
+# 反思总结输出Schema
+output_schema_reflection_summary = {
+ "type": "object",
+ "properties": {
+ "updated_paragraph_latest_state": {"type": "string"}
+ }
+}
+
+# 报告格式化输入Schema
+input_schema_report_formatting = {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "title": {"type": "string"},
+ "paragraph_latest_state": {"type": "string"}
+ }
+ }
+}
+
+# ===== 系统提示词定义 =====
+
+# 生成报告结构的系统提示词
+SYSTEM_PROMPT_REPORT_STRUCTURE = f"""
+你是一位深度研究助手。给定一个查询,你需要规划一个报告的结构和其中包含的段落。最多五个段落。
+确保段落的排序合理有序。
+一旦大纲创建完成,你将获得工具来分别为每个部分搜索网络并进行反思。
+请按照以下JSON模式定义格式化输出:
+
+
+
+标题和内容属性将用于更深入的研究。
+确保输出是一个符合上述输出JSON模式定义的JSON对象。
+只返回JSON对象,不要有解释或额外文本。
+"""
+
+# 每个段落第一次搜索的系统提示词
+SYSTEM_PROMPT_FIRST_SEARCH = f"""
+你是一位深度研究助手。你将获得报告中的一个段落,其标题和预期内容将按照以下JSON模式定义提供:
+
+
+{json.dumps(input_schema_first_search, indent=2, ensure_ascii=False)}
+
+
+你可以使用以下6种专业的新闻搜索工具:
+
+1. **basic_search_news** - 基础新闻搜索工具
+ - 适用于:一般性的新闻搜索,不确定需要何种特定搜索时
+ - 特点:快速、标准的通用搜索,是最常用的基础工具
+
+2. **deep_search_news** - 深度新闻分析工具
+ - 适用于:需要全面深入了解某个主题时
+ - 特点:提供最详细的分析结果,包含高级AI摘要
+
+3. **search_news_last_24_hours** - 24小时最新新闻工具
+ - 适用于:需要了解最新动态、突发事件时
+ - 特点:只搜索过去24小时的新闻
+
+4. **search_news_last_week** - 本周新闻工具
+ - 适用于:需要了解近期发展趋势时
+ - 特点:搜索过去一周的新闻报道
+
+5. **search_images_for_news** - 图片搜索工具
+ - 适用于:需要可视化信息、图片资料时
+ - 特点:提供相关图片和图片描述
+
+6. **search_news_by_date** - 按日期范围搜索工具
+ - 适用于:需要研究特定历史时期时
+ - 特点:可以指定开始和结束日期进行搜索
+ - 特殊要求:需要提供start_date和end_date参数,格式为'YYYY-MM-DD'
+ - 注意:只有这个工具需要额外的时间参数
+
+你的任务是:
+1. 根据段落主题选择最合适的搜索工具
+2. 制定最佳的搜索查询
+3. 如果选择search_news_by_date工具,必须同时提供start_date和end_date参数(格式:YYYY-MM-DD)
+4. 解释你的选择理由
+
+注意:除了search_news_by_date工具外,其他工具都不需要额外参数。
+请按照以下JSON模式定义格式化输出(文字请使用中文):
+
+
+
+确保输出是一个符合上述输出JSON模式定义的JSON对象。
+只返回JSON对象,不要有解释或额外文本。
+"""
+
+# 每个段落第一次总结的系统提示词
+SYSTEM_PROMPT_FIRST_SUMMARY = f"""
+你是一位深度研究助手。你将获得搜索查询、搜索结果以及你正在研究的报告段落,数据将按照以下JSON模式定义提供:
+
+
+{json.dumps(input_schema_first_summary, indent=2, ensure_ascii=False)}
+
+
+你的任务是作为研究者,使用搜索结果撰写与段落主题一致的内容,并适当地组织结构以便纳入报告中。
+请按照以下JSON模式定义格式化输出:
+
+
+
+确保输出是一个符合上述输出JSON模式定义的JSON对象。
+只返回JSON对象,不要有解释或额外文本。
+"""
+
+# 反思(Reflect)的系统提示词
+SYSTEM_PROMPT_REFLECTION = f"""
+你是一位深度研究助手。你负责为研究报告构建全面的段落。你将获得段落标题、计划内容摘要,以及你已经创建的段落最新状态,所有这些都将按照以下JSON模式定义提供:
+
+
+{json.dumps(input_schema_reflection, indent=2, ensure_ascii=False)}
+
+
+你可以使用以下6种专业的新闻搜索工具:
+
+1. **basic_search_news** - 基础新闻搜索工具
+2. **deep_search_news** - 深度新闻分析工具
+3. **search_news_last_24_hours** - 24小时最新新闻工具
+4. **search_news_last_week** - 本周新闻工具
+5. **search_images_for_news** - 图片搜索工具
+6. **search_news_by_date** - 按日期范围搜索工具(需要时间参数)
+
+你的任务是:
+1. 反思段落文本的当前状态,思考是否遗漏了主题的某些关键方面
+2. 选择最合适的搜索工具来补充缺失信息
+3. 制定精确的搜索查询
+4. 如果选择search_news_by_date工具,必须同时提供start_date和end_date参数(格式:YYYY-MM-DD)
+5. 解释你的选择和推理
+
+注意:除了search_news_by_date工具外,其他工具都不需要额外参数。
+请按照以下JSON模式定义格式化输出:
+
+
+
+确保输出是一个符合上述输出JSON模式定义的JSON对象。
+只返回JSON对象,不要有解释或额外文本。
+"""
+
+# 总结反思的系统提示词
+SYSTEM_PROMPT_REFLECTION_SUMMARY = f"""
+你是一位深度研究助手。
+你将获得搜索查询、搜索结果、段落标题以及你正在研究的报告段落的预期内容。
+你正在迭代完善这个段落,并且段落的最新状态也会提供给你。
+数据将按照以下JSON模式定义提供:
+
+
+{json.dumps(input_schema_reflection_summary, indent=2, ensure_ascii=False)}
+
+
+你的任务是根据搜索结果和预期内容丰富段落的当前最新状态。
+不要删除最新状态中的关键信息,尽量丰富它,只添加缺失的信息。
+适当地组织段落结构以便纳入报告中。
+请按照以下JSON模式定义格式化输出:
+
+
+
+确保输出是一个符合上述输出JSON模式定义的JSON对象。
+只返回JSON对象,不要有解释或额外文本。
+"""
+
+# 最终研究报告格式化的系统提示词
+SYSTEM_PROMPT_REPORT_FORMATTING = f"""
+你是一位深度研究助手。你已经完成了研究并构建了报告中所有段落的最终版本。
+你将获得以下JSON格式的数据:
+
+
+{json.dumps(input_schema_report_formatting, indent=2, ensure_ascii=False)}
+
+
+你的任务是将报告格式化为美观的形式,并以Markdown格式返回。
+如果没有结论段落,请根据其他段落的最新状态在报告末尾添加一个结论。
+使用段落标题来创建报告的标题。
+"""
diff --git a/MediaEngine/state/__init__.py b/MediaEngine/state/__init__.py
new file mode 100644
index 0000000..28d6e38
--- /dev/null
+++ b/MediaEngine/state/__init__.py
@@ -0,0 +1,8 @@
+"""
+状态管理模块
+定义Deep Search Agent的状态数据结构
+"""
+
+from .state import State, Paragraph, Research, Search
+
+__all__ = ["State", "Paragraph", "Research", "Search"]
diff --git a/MediaEngine/state/state.py b/MediaEngine/state/state.py
new file mode 100644
index 0000000..a64e0b9
--- /dev/null
+++ b/MediaEngine/state/state.py
@@ -0,0 +1,258 @@
+"""
+Deep Search Agent状态管理
+定义所有状态数据结构和操作方法
+"""
+
+from dataclasses import dataclass, field
+from typing import List, Dict, Any, Optional
+import json
+from datetime import datetime
+
+
+@dataclass
+class Search:
+ """单个搜索结果的状态"""
+ query: str = "" # 搜索查询
+ url: str = "" # 搜索结果的链接
+ title: str = "" # 搜索结果标题
+ content: str = "" # 搜索返回的内容
+ score: Optional[float] = None # 相关度评分
+ timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
+
+ def to_dict(self) -> Dict[str, Any]:
+ """转换为字典格式"""
+ return {
+ "query": self.query,
+ "url": self.url,
+ "title": self.title,
+ "content": self.content,
+ "score": self.score,
+ "timestamp": self.timestamp
+ }
+
+ @classmethod
+ def from_dict(cls, data: Dict[str, Any]) -> "Search":
+ """从字典创建Search对象"""
+ return cls(
+ query=data.get("query", ""),
+ url=data.get("url", ""),
+ title=data.get("title", ""),
+ content=data.get("content", ""),
+ score=data.get("score"),
+ timestamp=data.get("timestamp", datetime.now().isoformat())
+ )
+
+
+@dataclass
+class Research:
+ """段落研究过程的状态"""
+ search_history: List[Search] = field(default_factory=list) # 搜索记录列表
+ latest_summary: str = "" # 当前段落的最新总结
+ reflection_iteration: int = 0 # 反思迭代次数
+ is_completed: bool = False # 是否完成研究
+
+ def add_search(self, search: Search):
+ """添加搜索记录"""
+ self.search_history.append(search)
+
+ def add_search_results(self, query: str, results: List[Dict[str, Any]]):
+ """批量添加搜索结果"""
+ for result in results:
+ search = Search(
+ query=query,
+ url=result.get("url", ""),
+ title=result.get("title", ""),
+ content=result.get("content", ""),
+ score=result.get("score")
+ )
+ self.add_search(search)
+
+ def get_search_count(self) -> int:
+ """获取搜索次数"""
+ return len(self.search_history)
+
+ def increment_reflection(self):
+ """增加反思次数"""
+ self.reflection_iteration += 1
+
+ def mark_completed(self):
+ """标记为完成"""
+ self.is_completed = True
+
+ def to_dict(self) -> Dict[str, Any]:
+ """转换为字典格式"""
+ return {
+ "search_history": [search.to_dict() for search in self.search_history],
+ "latest_summary": self.latest_summary,
+ "reflection_iteration": self.reflection_iteration,
+ "is_completed": self.is_completed
+ }
+
+ @classmethod
+ def from_dict(cls, data: Dict[str, Any]) -> "Research":
+ """从字典创建Research对象"""
+ search_history = [Search.from_dict(search_data) for search_data in data.get("search_history", [])]
+ return cls(
+ search_history=search_history,
+ latest_summary=data.get("latest_summary", ""),
+ reflection_iteration=data.get("reflection_iteration", 0),
+ is_completed=data.get("is_completed", False)
+ )
+
+
+@dataclass
+class Paragraph:
+ """报告中单个段落的状态"""
+ title: str = "" # 段落标题
+ content: str = "" # 段落的预期内容(初始规划)
+ research: Research = field(default_factory=Research) # 研究进度
+ order: int = 0 # 段落顺序
+
+ def is_completed(self) -> bool:
+ """检查段落是否完成"""
+ return self.research.is_completed and bool(self.research.latest_summary)
+
+ def get_final_content(self) -> str:
+ """获取最终内容"""
+ return self.research.latest_summary or self.content
+
+ def to_dict(self) -> Dict[str, Any]:
+ """转换为字典格式"""
+ return {
+ "title": self.title,
+ "content": self.content,
+ "research": self.research.to_dict(),
+ "order": self.order
+ }
+
+ @classmethod
+ def from_dict(cls, data: Dict[str, Any]) -> "Paragraph":
+ """从字典创建Paragraph对象"""
+ research_data = data.get("research", {})
+ research = Research.from_dict(research_data) if research_data else Research()
+
+ return cls(
+ title=data.get("title", ""),
+ content=data.get("content", ""),
+ research=research,
+ order=data.get("order", 0)
+ )
+
+
+@dataclass
+class State:
+ """整个报告的状态"""
+ query: str = "" # 原始查询
+ report_title: str = "" # 报告标题
+ paragraphs: List[Paragraph] = field(default_factory=list) # 段落列表
+ final_report: str = "" # 最终报告内容
+ is_completed: bool = False # 是否完成
+ created_at: str = field(default_factory=lambda: datetime.now().isoformat())
+ updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
+
+ def add_paragraph(self, title: str, content: str) -> int:
+ """
+ 添加段落
+
+ Args:
+ title: 段落标题
+ content: 段落内容
+
+ Returns:
+ 段落索引
+ """
+ order = len(self.paragraphs)
+ paragraph = Paragraph(title=title, content=content, order=order)
+ self.paragraphs.append(paragraph)
+ self.update_timestamp()
+ return order
+
+ def get_paragraph(self, index: int) -> Optional[Paragraph]:
+ """获取指定索引的段落"""
+ if 0 <= index < len(self.paragraphs):
+ return self.paragraphs[index]
+ return None
+
+ def get_completed_paragraphs_count(self) -> int:
+ """获取已完成段落数量"""
+ return sum(1 for p in self.paragraphs if p.is_completed())
+
+ def get_total_paragraphs_count(self) -> int:
+ """获取总段落数量"""
+ return len(self.paragraphs)
+
+ def is_all_paragraphs_completed(self) -> bool:
+ """检查是否所有段落都完成"""
+ return all(p.is_completed() for p in self.paragraphs) if self.paragraphs else False
+
+ def mark_completed(self):
+ """标记整个报告为完成"""
+ self.is_completed = True
+ self.update_timestamp()
+
+ def update_timestamp(self):
+ """更新时间戳"""
+ self.updated_at = datetime.now().isoformat()
+
+ def get_progress_summary(self) -> Dict[str, Any]:
+ """获取进度摘要"""
+ completed = self.get_completed_paragraphs_count()
+ total = self.get_total_paragraphs_count()
+
+ return {
+ "total_paragraphs": total,
+ "completed_paragraphs": completed,
+ "progress_percentage": (completed / total * 100) if total > 0 else 0,
+ "is_completed": self.is_completed,
+ "created_at": self.created_at,
+ "updated_at": self.updated_at
+ }
+
+ def to_dict(self) -> Dict[str, Any]:
+ """转换为字典格式"""
+ return {
+ "query": self.query,
+ "report_title": self.report_title,
+ "paragraphs": [p.to_dict() for p in self.paragraphs],
+ "final_report": self.final_report,
+ "is_completed": self.is_completed,
+ "created_at": self.created_at,
+ "updated_at": self.updated_at
+ }
+
+ def to_json(self, indent: int = 2) -> str:
+ """转换为JSON字符串"""
+ return json.dumps(self.to_dict(), indent=indent, ensure_ascii=False)
+
+ @classmethod
+ def from_dict(cls, data: Dict[str, Any]) -> "State":
+ """从字典创建State对象"""
+ paragraphs = [Paragraph.from_dict(p_data) for p_data in data.get("paragraphs", [])]
+
+ return cls(
+ query=data.get("query", ""),
+ report_title=data.get("report_title", ""),
+ paragraphs=paragraphs,
+ final_report=data.get("final_report", ""),
+ is_completed=data.get("is_completed", False),
+ created_at=data.get("created_at", datetime.now().isoformat()),
+ updated_at=data.get("updated_at", datetime.now().isoformat())
+ )
+
+ @classmethod
+ def from_json(cls, json_str: str) -> "State":
+ """从JSON字符串创建State对象"""
+ data = json.loads(json_str)
+ return cls.from_dict(data)
+
+ def save_to_file(self, filepath: str):
+ """保存状态到文件"""
+ with open(filepath, 'w', encoding='utf-8') as f:
+ f.write(self.to_json())
+
+ @classmethod
+ def load_from_file(cls, filepath: str) -> "State":
+ """从文件加载状态"""
+ with open(filepath, 'r', encoding='utf-8') as f:
+ json_str = f.read()
+ return cls.from_json(json_str)
diff --git a/MediaEngine/tools/__init__.py b/MediaEngine/tools/__init__.py
new file mode 100644
index 0000000..aa05568
--- /dev/null
+++ b/MediaEngine/tools/__init__.py
@@ -0,0 +1,20 @@
+"""
+工具调用模块
+提供外部工具接口,如网络搜索等
+"""
+
+from .search import (
+ TavilyNewsAgency,
+ SearchResult,
+ TavilyResponse,
+ ImageResult,
+ print_response_summary
+)
+
+__all__ = [
+ "TavilyNewsAgency",
+ "SearchResult",
+ "TavilyResponse",
+ "ImageResult",
+ "print_response_summary"
+]
diff --git a/MediaEngine/tools/search.py b/MediaEngine/tools/search.py
new file mode 100644
index 0000000..cf7b611
--- /dev/null
+++ b/MediaEngine/tools/search.py
@@ -0,0 +1,240 @@
+"""
+专为 AI Agent 设计的舆情搜索工具集 (Tavily)
+
+版本: 1.5
+最后更新: 2025-08-22
+
+此脚本将复杂的Tavily搜索功能分解为一系列目标明确、参数极少的独立工具,
+专为AI Agent调用而设计。Agent只需根据任务意图选择合适的工具,
+无需理解复杂的参数组合。所有工具默认搜索“新闻”(topic='news')。
+
+新特性:
+- 新增 `basic_search_news` 工具,用于执行标准、通用的新闻搜索。
+- 每个搜索结果现在都包含 `published_date` (新闻发布日期)。
+
+主要工具:
+- basic_search_news: (新增) 执行标准、快速的通用新闻搜索。
+- deep_search_news: 对主题进行最全面的深度分析。
+- search_news_last_24_hours: 获取24小时内的最新动态。
+- search_news_last_week: 获取过去一周的主要报道。
+- search_images_for_news: 查找与新闻主题相关的图片。
+- search_news_by_date: 在指定的历史日期范围内搜索。
+"""
+
+import os
+from typing import List, Dict, Any, Optional
+from dataclasses import dataclass, field
+
+# 运行前请确保已安装Tavily库: pip install tavily-python
+try:
+ from tavily import TavilyClient
+except ImportError:
+ raise ImportError("Tavily库未安装,请运行 `pip install tavily-python` 进行安装。")
+
+# --- 1. 数据结构定义 ---
+
+@dataclass
+class SearchResult:
+ """
+ 网页搜索结果数据类
+ 包含 published_date 属性来存储新闻发布日期
+ """
+ title: str
+ url: str
+ content: str
+ score: Optional[float] = None
+ raw_content: Optional[str] = None
+ published_date: Optional[str] = None
+
+@dataclass
+class ImageResult:
+ """图片搜索结果数据类"""
+ url: str
+ description: Optional[str] = None
+
+@dataclass
+class TavilyResponse:
+ """封装Tavily API的完整返回结果,以便在工具间传递"""
+ query: str
+ answer: Optional[str] = None
+ results: List[SearchResult] = field(default_factory=list)
+ images: List[ImageResult] = field(default_factory=list)
+ response_time: Optional[float] = None
+
+
+# --- 2. 核心客户端与专用工具集 ---
+
+class TavilyNewsAgency:
+ """
+ 一个包含多种专用新闻舆情搜索工具的客户端。
+ 每个公共方法都设计为供 AI Agent 独立调用的工具。
+ """
+
+ def __init__(self, api_key: Optional[str] = None):
+ """
+ 初始化客户端。
+ Args:
+ api_key: Tavily API密钥,若不提供则从环境变量 TAVILY_API_KEY 读取。
+ """
+ if api_key is None:
+ api_key = os.getenv("TAVILY_API_KEY")
+ if not api_key:
+ raise ValueError("Tavily API Key未找到!请设置TAVILY_API_KEY环境变量或在初始化时提供")
+ self._client = TavilyClient(api_key=api_key)
+
+ def _search_internal(self, **kwargs) -> TavilyResponse:
+ """内部通用的搜索执行器,所有工具最终都调用此方法"""
+ try:
+ kwargs['topic'] = 'general'
+ api_params = {k: v for k, v in kwargs.items() if v is not None}
+ response_dict = self._client.search(**api_params)
+
+ search_results = [
+ SearchResult(
+ title=item.get('title'),
+ url=item.get('url'),
+ content=item.get('content'),
+ score=item.get('score'),
+ raw_content=item.get('raw_content'),
+ published_date=item.get('published_date')
+ ) for item in response_dict.get('results', [])
+ ]
+
+ image_results = [ImageResult(url=item.get('url'), description=item.get('description')) for item in response_dict.get('images', [])]
+
+ return TavilyResponse(
+ query=response_dict.get('query'), answer=response_dict.get('answer'),
+ results=search_results, images=image_results,
+ response_time=response_dict.get('response_time')
+ )
+ except Exception as e:
+ print(f"搜索时发生错误: {str(e)}")
+ return TavilyResponse(query=kwargs.get("query", "Unknown Query"))
+
+ # --- Agent 可用的工具方法 ---
+
+ def basic_search_news(self, query: str, max_results: int = 7) -> TavilyResponse:
+ """
+ 【工具】基础新闻搜索: 执行一次标准、快速的新闻搜索。
+ 这是最常用的通用搜索工具,适用于不确定需要何种特定搜索时。
+ Agent可提供搜索查询(query)和可选的最大结果数(max_results)。
+ """
+ print(f"--- TOOL: 基础新闻搜索 (query: {query}) ---")
+ return self._search_internal(
+ query=query,
+ max_results=max_results,
+ search_depth="basic",
+ include_answer=False
+ )
+
+ def deep_search_news(self, query: str) -> TavilyResponse:
+ """
+ 【工具】深度新闻分析: 对一个主题进行最全面、最深入的搜索。
+ 返回AI生成的“高级”详细摘要答案和最多20条最相关的新闻结果。适用于需要全面了解某个事件背景的场景。
+ Agent只需提供搜索查询(query)。
+ """
+ print(f"--- TOOL: 深度新闻分析 (query: {query}) ---")
+ return self._search_internal(
+ query=query, search_depth="advanced", max_results=20, include_answer="advanced"
+ )
+
+ def search_news_last_24_hours(self, query: str) -> TavilyResponse:
+ """
+ 【工具】搜索24小时内新闻: 获取关于某个主题的最新动态。
+ 此工具专门查找过去24小时内发布的新闻。适用于追踪突发事件或最新进展。
+ Agent只需提供搜索查询(query)。
+ """
+ print(f"--- TOOL: 搜索24小时内新闻 (query: {query}) ---")
+ return self._search_internal(query=query, time_range='d', max_results=10)
+
+ def search_news_last_week(self, query: str) -> TavilyResponse:
+ """
+ 【工具】搜索本周新闻: 获取关于某个主题过去一周内的主要新闻报道。
+ 适用于进行周度舆情总结或回顾。
+ Agent只需提供搜索查询(query)。
+ """
+ print(f"--- TOOL: 搜索本周新闻 (query: {query}) ---")
+ return self._search_internal(query=query, time_range='w', max_results=10)
+
+ def search_images_for_news(self, query: str) -> TavilyResponse:
+ """
+ 【工具】查找新闻图片: 搜索与某个新闻主题相关的图片。
+ 此工具会返回图片链接及描述,适用于需要为报告或文章配图的场景。
+ Agent只需提供搜索查询(query)。
+ """
+ print(f"--- TOOL: 查找新闻图片 (query: {query}) ---")
+ return self._search_internal(
+ query=query, include_images=True, include_image_descriptions=True, max_results=5
+ )
+
+ def search_news_by_date(self, query: str, start_date: str, end_date: str) -> TavilyResponse:
+ """
+ 【工具】按指定日期范围搜索新闻: 在一个明确的历史时间段内搜索新闻。
+ 这是唯一需要Agent提供详细时间参数的工具。适用于需要对特定历史事件进行分析的场景。
+ Agent需要提供查询(query)、开始日期(start_date)和结束日期(end_date),格式均为 'YYYY-MM-DD'。
+ """
+ print(f"--- TOOL: 按指定日期范围搜索新闻 (query: {query}, from: {start_date}, to: {end_date}) ---")
+ return self._search_internal(
+ query=query, start_date=start_date, end_date=end_date, max_results=15
+ )
+
+
+# --- 3. 测试与使用示例 ---
+
+def print_response_summary(response: TavilyResponse):
+ """简化的打印函数,用于展示测试结果,现在会显示发布日期"""
+ if not response or not response.query:
+ print("未能获取有效响应。")
+ return
+
+ print(f"\n查询: '{response.query}' | 耗时: {response.response_time}s")
+ if response.answer:
+ print(f"AI摘要: {response.answer[:120]}...")
+ print(f"找到 {len(response.results)} 条网页, {len(response.images)} 张图片。")
+ if response.results:
+ first_result = response.results[0]
+ date_info = f"(发布于: {first_result.published_date})" if first_result.published_date else ""
+ print(f"第一条结果: {first_result.title} {date_info}")
+ print("-" * 60)
+
+
+if __name__ == "__main__":
+ # 在运行前,请确保您已设置 TAVILY_API_KEY 环境变量
+
+ try:
+ # 初始化“新闻社”客户端,它内部包含了所有工具
+ agency = TavilyNewsAgency()
+
+ # 场景1: Agent 进行一次常规、快速的搜索
+ response1 = agency.basic_search_news(query="奥运会最新赛况", max_results=5)
+ print_response_summary(response1)
+
+ # 场景2: Agent 需要全面了解“全球芯片技术竞争”的背景
+ response2 = agency.deep_search_news(query="全球芯片技术竞争")
+ print_response_summary(response2)
+
+ # 场景3: Agent 需要追踪“GTC大会”的最新消息
+ response3 = agency.search_news_last_24_hours(query="Nvidia GTC大会 最新发布")
+ print_response_summary(response3)
+
+ # 场景4: Agent 需要为一篇关于“自动驾驶”的周报查找素材
+ response4 = agency.search_news_last_week(query="自动驾驶商业化落地")
+ print_response_summary(response4)
+
+ # 场景5: Agent 需要查找“韦伯太空望远镜”的新闻图片
+ response5 = agency.search_images_for_news(query="韦伯太空望远镜最新发现")
+ print_response_summary(response5)
+
+ # 场景6: Agent 需要研究2025年第一季度关于“人工智能法规”的新闻
+ response6 = agency.search_news_by_date(
+ query="人工智能法规",
+ start_date="2025-01-01",
+ end_date="2025-03-31"
+ )
+ print_response_summary(response6)
+
+ except ValueError as e:
+ print(f"初始化失败: {e}")
+ print("请确保 TAVILY_API_KEY 环境变量已正确设置。")
+ except Exception as e:
+ print(f"测试过程中发生未知错误: {e}")
\ No newline at end of file
diff --git a/MediaEngine/utils/__init__.py b/MediaEngine/utils/__init__.py
new file mode 100644
index 0000000..f324bef
--- /dev/null
+++ b/MediaEngine/utils/__init__.py
@@ -0,0 +1,26 @@
+"""
+工具函数模块
+提供文本处理、JSON解析等辅助功能
+"""
+
+from .text_processing import (
+ clean_json_tags,
+ clean_markdown_tags,
+ remove_reasoning_from_output,
+ extract_clean_response,
+ update_state_with_search_results,
+ format_search_results_for_prompt
+)
+
+from .config import Config, load_config
+
+__all__ = [
+ "clean_json_tags",
+ "clean_markdown_tags",
+ "remove_reasoning_from_output",
+ "extract_clean_response",
+ "update_state_with_search_results",
+ "format_search_results_for_prompt",
+ "Config",
+ "load_config"
+]
diff --git a/MediaEngine/utils/config.py b/MediaEngine/utils/config.py
new file mode 100644
index 0000000..f000bd8
--- /dev/null
+++ b/MediaEngine/utils/config.py
@@ -0,0 +1,162 @@
+"""
+配置管理模块
+处理环境变量和配置参数
+"""
+
+import os
+from dataclasses import dataclass
+from typing import Optional
+
+
+@dataclass
+class Config:
+ """配置类"""
+ # API密钥
+ deepseek_api_key: Optional[str] = None
+ openai_api_key: Optional[str] = None
+ tavily_api_key: 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
+
+ # 输出配置
+ output_dir: str = "reports"
+ save_intermediate_states: bool = True
+
+ def validate(self) -> bool:
+ """验证配置"""
+ # 检查必需的API密钥
+ if self.default_llm_provider == "deepseek" and not self.deepseek_api_key:
+ print("错误: DeepSeek API Key未设置")
+ return False
+
+ if self.default_llm_provider == "openai" and not self.openai_api_key:
+ print("错误: OpenAI API Key未设置")
+ return False
+
+ if not self.tavily_api_key:
+ print("错误: Tavily API Key未设置")
+ return False
+
+ return True
+
+ @classmethod
+ def from_file(cls, config_file: str) -> "Config":
+ """从配置文件创建配置"""
+ if config_file.endswith('.py'):
+ # Python配置文件
+ 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),
+ 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"),
+ 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"
+ )
+
+
+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}")
+ break
+ else:
+ raise FileNotFoundError("未找到配置文件,请创建 config.py 文件")
+
+ # 创建配置对象
+ config = Config.from_file(file_to_load)
+
+ # 验证配置
+ if not config.validate():
+ raise ValueError("配置验证失败,请检查配置文件中的API密钥")
+
+ 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(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"Tavily API Key: {'已设置' if config.tavily_api_key else '未设置'}")
+ print("==================\n")
diff --git a/MediaEngine/utils/text_processing.py b/MediaEngine/utils/text_processing.py
new file mode 100644
index 0000000..471650c
--- /dev/null
+++ b/MediaEngine/utils/text_processing.py
@@ -0,0 +1,308 @@
+"""
+文本处理工具函数
+用于清理LLM输出、解析JSON等
+"""
+
+import re
+import json
+from typing import Dict, Any, List
+from json.decoder import JSONDecodeError
+
+
+def clean_json_tags(text: str) -> str:
+ """
+ 清理文本中的JSON标签
+
+ Args:
+ text: 原始文本
+
+ Returns:
+ 清理后的文本
+ """
+ # 移除```json 和 ```标签
+ text = re.sub(r'```json\s*', '', text)
+ text = re.sub(r'```\s*$', '', text)
+ text = re.sub(r'```', '', text)
+
+ return text.strip()
+
+
+def clean_markdown_tags(text: str) -> str:
+ """
+ 清理文本中的Markdown标签
+
+ Args:
+ text: 原始文本
+
+ Returns:
+ 清理后的文本
+ """
+ # 移除```markdown 和 ```标签
+ text = re.sub(r'```markdown\s*', '', text)
+ text = re.sub(r'```\s*$', '', text)
+ text = re.sub(r'```', '', text)
+
+ return text.strip()
+
+
+def remove_reasoning_from_output(text: str) -> str:
+ """
+ 移除输出中的推理过程文本
+
+ Args:
+ text: 原始文本
+
+ Returns:
+ 清理后的文本
+ """
+ # 查找JSON开始位置
+ json_start = -1
+
+ # 尝试找到第一个 { 或 [
+ for i, char in enumerate(text):
+ if char in '{[':
+ json_start = i
+ break
+
+ if json_start != -1:
+ # 从JSON开始位置截取
+ return text[json_start:].strip()
+
+ # 如果没有找到JSON标记,尝试其他方法
+ # 移除常见的推理标识
+ patterns = [
+ r'(?:reasoning|推理|思考|分析)[::]\s*.*?(?=\{|\[)', # 移除推理部分
+ r'(?:explanation|解释|说明)[::]\s*.*?(?=\{|\[)', # 移除解释部分
+ r'^.*?(?=\{|\[)', # 移除JSON前的所有文本
+ ]
+
+ for pattern in patterns:
+ text = re.sub(pattern, '', text, flags=re.IGNORECASE | re.DOTALL)
+
+ return text.strip()
+
+
+def extract_clean_response(text: str) -> Dict[str, Any]:
+ """
+ 提取并清理响应中的JSON内容
+
+ Args:
+ text: 原始响应文本
+
+ Returns:
+ 解析后的JSON字典
+ """
+ # 清理文本
+ cleaned_text = clean_json_tags(text)
+ cleaned_text = remove_reasoning_from_output(cleaned_text)
+
+ # 尝试直接解析
+ try:
+ return json.loads(cleaned_text)
+ except JSONDecodeError:
+ pass
+
+ # 尝试修复不完整的JSON
+ fixed_text = fix_incomplete_json(cleaned_text)
+ if fixed_text:
+ try:
+ return json.loads(fixed_text)
+ except JSONDecodeError:
+ pass
+
+ # 尝试查找JSON对象
+ json_pattern = r'\{.*\}'
+ match = re.search(json_pattern, cleaned_text, re.DOTALL)
+ if match:
+ try:
+ return json.loads(match.group())
+ except JSONDecodeError:
+ pass
+
+ # 尝试查找JSON数组
+ array_pattern = r'\[.*\]'
+ match = re.search(array_pattern, cleaned_text, re.DOTALL)
+ if match:
+ try:
+ return json.loads(match.group())
+ except JSONDecodeError:
+ pass
+
+ # 如果所有方法都失败,返回错误信息
+ print(f"无法解析JSON响应: {cleaned_text[:200]}...")
+ return {"error": "JSON解析失败", "raw_text": cleaned_text}
+
+
+def fix_incomplete_json(text: str) -> str:
+ """
+ 修复不完整的JSON响应
+
+ Args:
+ text: 原始文本
+
+ Returns:
+ 修复后的JSON文本,如果无法修复则返回空字符串
+ """
+ # 移除多余的逗号和空白
+ text = re.sub(r',\s*}', '}', text)
+ text = re.sub(r',\s*]', ']', text)
+
+ # 检查是否已经是有效的JSON
+ try:
+ json.loads(text)
+ return text
+ except JSONDecodeError:
+ pass
+
+ # 检查是否缺少开头的数组符号
+ if text.strip().startswith('{') and not text.strip().startswith('['):
+ # 如果以对象开始,尝试包装成数组
+ if text.count('{') > 1:
+ # 多个对象,包装成数组
+ text = '[' + text + ']'
+ else:
+ # 单个对象,包装成数组
+ text = '[' + text + ']'
+
+ # 检查是否缺少结尾的数组符号
+ if text.strip().endswith('}') and not text.strip().endswith(']'):
+ # 如果以对象结束,尝试包装成数组
+ if text.count('}') > 1:
+ # 多个对象,包装成数组
+ text = '[' + text + ']'
+ else:
+ # 单个对象,包装成数组
+ text = '[' + text + ']'
+
+ # 检查括号是否匹配
+ open_braces = text.count('{')
+ close_braces = text.count('}')
+ open_brackets = text.count('[')
+ close_brackets = text.count(']')
+
+ # 修复不匹配的括号
+ if open_braces > close_braces:
+ text += '}' * (open_braces - close_braces)
+ if open_brackets > close_brackets:
+ text += ']' * (open_brackets - close_brackets)
+
+ # 验证修复后的JSON是否有效
+ try:
+ json.loads(text)
+ return text
+ except JSONDecodeError:
+ # 如果仍然无效,尝试更激进的修复
+ return fix_aggressive_json(text)
+
+
+def fix_aggressive_json(text: str) -> str:
+ """
+ 更激进的JSON修复方法
+
+ Args:
+ text: 原始文本
+
+ Returns:
+ 修复后的JSON文本
+ """
+ # 查找所有可能的JSON对象
+ objects = re.findall(r'\{[^{}]*\}', text)
+
+ if len(objects) >= 2:
+ # 如果有多个对象,包装成数组
+ return '[' + ','.join(objects) + ']'
+ elif len(objects) == 1:
+ # 如果只有一个对象,包装成数组
+ return '[' + objects[0] + ']'
+ else:
+ # 如果没有找到对象,返回空数组
+ return '[]'
+
+
+def update_state_with_search_results(search_results: List[Dict[str, Any]],
+ paragraph_index: int, state: Any) -> Any:
+ """
+ 将搜索结果更新到状态中
+
+ Args:
+ search_results: 搜索结果列表
+ paragraph_index: 段落索引
+ state: 状态对象
+
+ Returns:
+ 更新后的状态对象
+ """
+ if 0 <= paragraph_index < len(state.paragraphs):
+ # 获取最后一次搜索的查询(假设是当前查询)
+ current_query = ""
+ if search_results:
+ # 从搜索结果推断查询(这里需要改进以获取实际查询)
+ current_query = "搜索查询"
+
+ # 添加搜索结果到状态
+ state.paragraphs[paragraph_index].research.add_search_results(
+ current_query, search_results
+ )
+
+ return state
+
+
+def validate_json_schema(data: Dict[str, Any], required_fields: List[str]) -> bool:
+ """
+ 验证JSON数据是否包含必需字段
+
+ Args:
+ data: 要验证的数据
+ required_fields: 必需字段列表
+
+ Returns:
+ 验证是否通过
+ """
+ return all(field in data for field in required_fields)
+
+
+def truncate_content(content: str, max_length: int = 20000) -> str:
+ """
+ 截断内容到指定长度
+
+ Args:
+ content: 原始内容
+ max_length: 最大长度
+
+ Returns:
+ 截断后的内容
+ """
+ if len(content) <= max_length:
+ return content
+
+ # 尝试在单词边界截断
+ truncated = content[:max_length]
+ last_space = truncated.rfind(' ')
+
+ if last_space > max_length * 0.8: # 如果最后一个空格位置合理
+ return truncated[:last_space] + "..."
+ else:
+ return truncated + "..."
+
+
+def format_search_results_for_prompt(search_results: List[Dict[str, Any]],
+ max_length: int = 20000) -> List[str]:
+ """
+ 格式化搜索结果用于提示词
+
+ Args:
+ search_results: 搜索结果列表
+ max_length: 每个结果的最大长度
+
+ Returns:
+ 格式化后的内容列表
+ """
+ formatted_results = []
+
+ for result in search_results:
+ content = result.get('content', '')
+ if content:
+ truncated_content = truncate_content(content, max_length)
+ formatted_results.append(truncated_content)
+
+ return formatted_results
diff --git a/QueryEngine/__init__.py b/QueryEngine/__init__.py
new file mode 100644
index 0000000..6f753c5
--- /dev/null
+++ b/QueryEngine/__init__.py
@@ -0,0 +1,12 @@
+"""
+Deep Search Agent
+一个无框架的深度搜索AI代理实现
+"""
+
+from .agent import DeepSearchAgent, create_agent
+from .utils.config import Config, load_config
+
+__version__ = "1.0.0"
+__author__ = "Deep Search Agent Team"
+
+__all__ = ["DeepSearchAgent", "create_agent", "Config", "load_config"]
diff --git a/QueryEngine/agent.py b/QueryEngine/agent.py
new file mode 100644
index 0000000..d57d96f
--- /dev/null
+++ b/QueryEngine/agent.py
@@ -0,0 +1,478 @@
+"""
+Deep Search Agent主类
+整合所有模块,实现完整的深度搜索流程
+"""
+
+import json
+import os
+import re
+from datetime import datetime
+from typing import Optional, Dict, Any, List
+
+from .llms import DeepSeekLLM, OpenAILLM, BaseLLM
+from .nodes import (
+ ReportStructureNode,
+ FirstSearchNode,
+ ReflectionNode,
+ FirstSummaryNode,
+ ReflectionSummaryNode,
+ ReportFormattingNode
+)
+from .state import State
+from .tools import TavilyNewsAgency, TavilyResponse
+from .utils import Config, load_config, format_search_results_for_prompt
+
+
+class DeepSearchAgent:
+ """Deep Search Agent主类"""
+
+ def __init__(self, config: Optional[Config] = None):
+ """
+ 初始化Deep Search Agent
+
+ Args:
+ config: 配置对象,如果不提供则自动加载
+ """
+ # 加载配置
+ self.config = config or load_config()
+
+ # 初始化LLM客户端
+ self.llm_client = self._initialize_llm()
+
+ # 初始化搜索工具集
+ self.search_agency = TavilyNewsAgency(api_key=self.config.tavily_api_key)
+
+ # 初始化节点
+ self._initialize_nodes()
+
+ # 状态
+ self.state = State()
+
+ # 确保输出目录存在
+ os.makedirs(self.config.output_dir, exist_ok=True)
+
+ print(f"Deep Search Agent 已初始化")
+ print(f"使用LLM: {self.llm_client.get_model_info()}")
+ print(f"搜索工具集: TavilyNewsAgency (支持6种搜索工具)")
+
+ def _initialize_llm(self) -> BaseLLM:
+ """初始化LLM客户端"""
+ if self.config.default_llm_provider == "deepseek":
+ return DeepSeekLLM(
+ api_key=self.config.deepseek_api_key,
+ model_name=self.config.deepseek_model
+ )
+ 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}")
+
+ def _initialize_nodes(self):
+ """初始化处理节点"""
+ self.first_search_node = FirstSearchNode(self.llm_client)
+ self.reflection_node = ReflectionNode(self.llm_client)
+ self.first_summary_node = FirstSummaryNode(self.llm_client)
+ self.reflection_summary_node = ReflectionSummaryNode(self.llm_client)
+ self.report_formatting_node = ReportFormattingNode(self.llm_client)
+
+ def _validate_date_format(self, date_str: str) -> bool:
+ """
+ 验证日期格式是否为YYYY-MM-DD
+
+ Args:
+ date_str: 日期字符串
+
+ Returns:
+ 是否为有效格式
+ """
+ if not date_str:
+ return False
+
+ # 检查格式
+ pattern = r'^\d{4}-\d{2}-\d{2}$'
+ if not re.match(pattern, date_str):
+ return False
+
+ # 检查日期是否有效
+ try:
+ datetime.strptime(date_str, '%Y-%m-%d')
+ return True
+ except ValueError:
+ return False
+
+ def execute_search_tool(self, tool_name: str, query: str, **kwargs) -> TavilyResponse:
+ """
+ 执行指定的搜索工具
+
+ Args:
+ tool_name: 工具名称,可选值:
+ - "basic_search_news": 基础新闻搜索(快速、通用)
+ - "deep_search_news": 深度新闻分析
+ - "search_news_last_24_hours": 24小时内最新新闻
+ - "search_news_last_week": 本周新闻
+ - "search_images_for_news": 新闻图片搜索
+ - "search_news_by_date": 按日期范围搜索新闻
+ query: 搜索查询
+ **kwargs: 额外参数(如start_date, end_date, max_results)
+
+ Returns:
+ TavilyResponse对象
+ """
+ print(f" → 执行搜索工具: {tool_name}")
+
+ if tool_name == "basic_search_news":
+ max_results = kwargs.get("max_results", 7)
+ return self.search_agency.basic_search_news(query, max_results)
+ elif tool_name == "deep_search_news":
+ return self.search_agency.deep_search_news(query)
+ elif tool_name == "search_news_last_24_hours":
+ return self.search_agency.search_news_last_24_hours(query)
+ elif tool_name == "search_news_last_week":
+ return self.search_agency.search_news_last_week(query)
+ elif tool_name == "search_images_for_news":
+ return self.search_agency.search_images_for_news(query)
+ elif tool_name == "search_news_by_date":
+ start_date = kwargs.get("start_date")
+ end_date = kwargs.get("end_date")
+ if not start_date or not end_date:
+ raise ValueError("search_news_by_date工具需要start_date和end_date参数")
+ return self.search_agency.search_news_by_date(query, start_date, end_date)
+ else:
+ print(f" ⚠️ 未知的搜索工具: {tool_name},使用默认基础搜索")
+ return self.search_agency.basic_search_news(query)
+
+ def research(self, query: str, save_report: bool = True) -> str:
+ """
+ 执行深度研究
+
+ Args:
+ query: 研究查询
+ save_report: 是否保存报告到文件
+
+ Returns:
+ 最终报告内容
+ """
+ print(f"\n{'='*60}")
+ print(f"开始深度研究: {query}")
+ print(f"{'='*60}")
+
+ try:
+ # Step 1: 生成报告结构
+ self._generate_report_structure(query)
+
+ # Step 2: 处理每个段落
+ self._process_paragraphs()
+
+ # Step 3: 生成最终报告
+ final_report = self._generate_final_report()
+
+ # Step 4: 保存报告
+ if save_report:
+ self._save_report(final_report)
+
+ print(f"\n{'='*60}")
+ print("深度研究完成!")
+ print(f"{'='*60}")
+
+ return final_report
+
+ except Exception as e:
+ print(f"研究过程中发生错误: {str(e)}")
+ raise e
+
+ def _generate_report_structure(self, query: str):
+ """生成报告结构"""
+ print(f"\n[步骤 1] 生成报告结构...")
+
+ # 创建报告结构节点
+ report_structure_node = ReportStructureNode(self.llm_client, query)
+
+ # 生成结构并更新状态
+ self.state = report_structure_node.mutate_state(state=self.state)
+
+ print(f"报告结构已生成,共 {len(self.state.paragraphs)} 个段落:")
+ for i, paragraph in enumerate(self.state.paragraphs, 1):
+ print(f" {i}. {paragraph.title}")
+
+ def _process_paragraphs(self):
+ """处理所有段落"""
+ total_paragraphs = len(self.state.paragraphs)
+
+ for i in range(total_paragraphs):
+ print(f"\n[步骤 2.{i+1}] 处理段落: {self.state.paragraphs[i].title}")
+ print("-" * 50)
+
+ # 初始搜索和总结
+ self._initial_search_and_summary(i)
+
+ # 反思循环
+ self._reflection_loop(i)
+
+ # 标记段落完成
+ self.state.paragraphs[i].research.mark_completed()
+
+ progress = (i + 1) / total_paragraphs * 100
+ print(f"段落处理完成 ({progress:.1f}%)")
+
+ def _initial_search_and_summary(self, paragraph_index: int):
+ """执行初始搜索和总结"""
+ paragraph = self.state.paragraphs[paragraph_index]
+
+ # 准备搜索输入
+ search_input = {
+ "title": paragraph.title,
+ "content": paragraph.content
+ }
+
+ # 生成搜索查询和工具选择
+ print(" - 生成搜索查询...")
+ search_output = self.first_search_node.run(search_input)
+ search_query = search_output["search_query"]
+ search_tool = search_output.get("search_tool", "basic_search_news") # 默认工具
+ reasoning = search_output["reasoning"]
+
+ print(f" - 搜索查询: {search_query}")
+ print(f" - 选择的工具: {search_tool}")
+ print(f" - 推理: {reasoning}")
+
+ # 执行搜索
+ print(" - 执行网络搜索...")
+
+ # 处理search_news_by_date的特殊参数
+ search_kwargs = {}
+ if search_tool == "search_news_by_date":
+ start_date = search_output.get("start_date")
+ end_date = search_output.get("end_date")
+
+ if start_date and end_date:
+ # 验证日期格式
+ if self._validate_date_format(start_date) and self._validate_date_format(end_date):
+ search_kwargs["start_date"] = start_date
+ search_kwargs["end_date"] = end_date
+ print(f" - 时间范围: {start_date} 到 {end_date}")
+ else:
+ print(f" ⚠️ 日期格式错误(应为YYYY-MM-DD),改用基础搜索")
+ print(f" 提供的日期: start_date={start_date}, end_date={end_date}")
+ search_tool = "basic_search_news"
+ else:
+ print(f" ⚠️ search_news_by_date工具缺少时间参数,改用基础搜索")
+ search_tool = "basic_search_news"
+
+ search_response = self.execute_search_tool(search_tool, search_query, **search_kwargs)
+
+ # 转换为兼容格式
+ search_results = []
+ if search_response and search_response.results:
+ # 每种搜索工具都有其特定的结果数量,这里取前10个作为上限
+ max_results = min(len(search_response.results), 10)
+ for result in search_response.results[:max_results]:
+ search_results.append({
+ 'title': result.title,
+ 'url': result.url,
+ 'content': result.content,
+ 'score': result.score,
+ 'raw_content': result.raw_content,
+ 'published_date': result.published_date # 新增字段
+ })
+
+ if search_results:
+ print(f" - 找到 {len(search_results)} 个搜索结果")
+ for j, result in enumerate(search_results, 1):
+ date_info = f" (发布于: {result.get('published_date', 'N/A')})" if result.get('published_date') else ""
+ print(f" {j}. {result['title'][:50]}...{date_info}")
+ else:
+ print(" - 未找到搜索结果")
+
+ # 更新状态中的搜索历史
+ paragraph.research.add_search_results(search_query, search_results)
+
+ # 生成初始总结
+ print(" - 生成初始总结...")
+ summary_input = {
+ "title": paragraph.title,
+ "content": paragraph.content,
+ "search_query": search_query,
+ "search_results": format_search_results_for_prompt(
+ search_results, self.config.max_content_length
+ )
+ }
+
+ # 更新状态
+ self.state = self.first_summary_node.mutate_state(
+ summary_input, self.state, paragraph_index
+ )
+
+ print(" - 初始总结完成")
+
+ def _reflection_loop(self, paragraph_index: int):
+ """执行反思循环"""
+ paragraph = self.state.paragraphs[paragraph_index]
+
+ for reflection_i in range(self.config.max_reflections):
+ print(f" - 反思 {reflection_i + 1}/{self.config.max_reflections}...")
+
+ # 准备反思输入
+ reflection_input = {
+ "title": paragraph.title,
+ "content": paragraph.content,
+ "paragraph_latest_state": paragraph.research.latest_summary
+ }
+
+ # 生成反思搜索查询
+ reflection_output = self.reflection_node.run(reflection_input)
+ search_query = reflection_output["search_query"]
+ search_tool = reflection_output.get("search_tool", "basic_search_news") # 默认工具
+ reasoning = reflection_output["reasoning"]
+
+ print(f" 反思查询: {search_query}")
+ print(f" 选择的工具: {search_tool}")
+ print(f" 反思推理: {reasoning}")
+
+ # 执行反思搜索
+ # 处理search_news_by_date的特殊参数
+ search_kwargs = {}
+ if search_tool == "search_news_by_date":
+ start_date = reflection_output.get("start_date")
+ end_date = reflection_output.get("end_date")
+
+ if start_date and end_date:
+ # 验证日期格式
+ if self._validate_date_format(start_date) and self._validate_date_format(end_date):
+ search_kwargs["start_date"] = start_date
+ search_kwargs["end_date"] = end_date
+ print(f" 时间范围: {start_date} 到 {end_date}")
+ else:
+ print(f" ⚠️ 日期格式错误(应为YYYY-MM-DD),改用基础搜索")
+ print(f" 提供的日期: start_date={start_date}, end_date={end_date}")
+ search_tool = "basic_search_news"
+ else:
+ print(f" ⚠️ search_news_by_date工具缺少时间参数,改用基础搜索")
+ search_tool = "basic_search_news"
+
+ search_response = self.execute_search_tool(search_tool, search_query, **search_kwargs)
+
+ # 转换为兼容格式
+ search_results = []
+ if search_response and search_response.results:
+ # 每种搜索工具都有其特定的结果数量,这里取前10个作为上限
+ max_results = min(len(search_response.results), 10)
+ for result in search_response.results[:max_results]:
+ search_results.append({
+ 'title': result.title,
+ 'url': result.url,
+ 'content': result.content,
+ 'score': result.score,
+ 'raw_content': result.raw_content,
+ 'published_date': result.published_date
+ })
+
+ if search_results:
+ print(f" 找到 {len(search_results)} 个反思搜索结果")
+ for j, result in enumerate(search_results, 1):
+ date_info = f" (发布于: {result.get('published_date', 'N/A')})" if result.get('published_date') else ""
+ print(f" {j}. {result['title'][:50]}...{date_info}")
+ else:
+ print(" 未找到反思搜索结果")
+
+ # 更新搜索历史
+ paragraph.research.add_search_results(search_query, search_results)
+
+ # 生成反思总结
+ reflection_summary_input = {
+ "title": paragraph.title,
+ "content": paragraph.content,
+ "search_query": search_query,
+ "search_results": format_search_results_for_prompt(
+ search_results, self.config.max_content_length
+ ),
+ "paragraph_latest_state": paragraph.research.latest_summary
+ }
+
+ # 更新状态
+ self.state = self.reflection_summary_node.mutate_state(
+ reflection_summary_input, self.state, paragraph_index
+ )
+
+ print(f" 反思 {reflection_i + 1} 完成")
+
+ def _generate_final_report(self) -> str:
+ """生成最终报告"""
+ print(f"\n[步骤 3] 生成最终报告...")
+
+ # 准备报告数据
+ report_data = []
+ for paragraph in self.state.paragraphs:
+ report_data.append({
+ "title": paragraph.title,
+ "paragraph_latest_state": paragraph.research.latest_summary
+ })
+
+ # 格式化报告
+ try:
+ final_report = self.report_formatting_node.run(report_data)
+ except Exception as e:
+ print(f"LLM格式化失败,使用备用方法: {str(e)}")
+ final_report = self.report_formatting_node.format_report_manually(
+ report_data, self.state.report_title
+ )
+
+ # 更新状态
+ self.state.final_report = final_report
+ self.state.mark_completed()
+
+ print("最终报告生成完成")
+ return final_report
+
+ def _save_report(self, report_content: str):
+ """保存报告到文件"""
+ # 生成文件名
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+ query_safe = "".join(c for c in self.state.query if c.isalnum() or c in (' ', '-', '_')).rstrip()
+ query_safe = query_safe.replace(' ', '_')[:30]
+
+ filename = f"deep_search_report_{query_safe}_{timestamp}.md"
+ filepath = os.path.join(self.config.output_dir, filename)
+
+ # 保存报告
+ with open(filepath, 'w', encoding='utf-8') as f:
+ f.write(report_content)
+
+ print(f"报告已保存到: {filepath}")
+
+ # 保存状态(如果配置允许)
+ if self.config.save_intermediate_states:
+ state_filename = f"state_{query_safe}_{timestamp}.json"
+ state_filepath = os.path.join(self.config.output_dir, state_filename)
+ self.state.save_to_file(state_filepath)
+ print(f"状态已保存到: {state_filepath}")
+
+ def get_progress_summary(self) -> Dict[str, Any]:
+ """获取进度摘要"""
+ return self.state.get_progress_summary()
+
+ def load_state(self, filepath: str):
+ """从文件加载状态"""
+ self.state = State.load_from_file(filepath)
+ print(f"状态已从 {filepath} 加载")
+
+ def save_state(self, filepath: str):
+ """保存状态到文件"""
+ self.state.save_to_file(filepath)
+ print(f"状态已保存到 {filepath}")
+
+
+def create_agent(config_file: Optional[str] = None) -> DeepSearchAgent:
+ """
+ 创建Deep Search Agent实例的便捷函数
+
+ Args:
+ config_file: 配置文件路径
+
+ Returns:
+ DeepSearchAgent实例
+ """
+ config = load_config(config_file)
+ return DeepSearchAgent(config)
diff --git a/QueryEngine/llms/__init__.py b/QueryEngine/llms/__init__.py
new file mode 100644
index 0000000..2e1602a
--- /dev/null
+++ b/QueryEngine/llms/__init__.py
@@ -0,0 +1,10 @@
+"""
+LLM调用模块
+支持多种大语言模型的统一接口
+"""
+
+from .base import BaseLLM
+from .deepseek import DeepSeekLLM
+from .openai_llm import OpenAILLM
+
+__all__ = ["BaseLLM", "DeepSeekLLM", "OpenAILLM"]
diff --git a/QueryEngine/llms/base.py b/QueryEngine/llms/base.py
new file mode 100644
index 0000000..9c6f99f
--- /dev/null
+++ b/QueryEngine/llms/base.py
@@ -0,0 +1,61 @@
+"""
+LLM基础抽象类
+定义所有LLM实现需要遵循的接口标准
+"""
+
+from abc import ABC, abstractmethod
+from typing import Optional, Dict, Any
+
+
+class BaseLLM(ABC):
+ """LLM基础抽象类"""
+
+ def __init__(self, api_key: str, model_name: Optional[str] = None):
+ """
+ 初始化LLM客户端
+
+ Args:
+ api_key: API密钥
+ model_name: 模型名称,如果不指定则使用默认模型
+ """
+ self.api_key = api_key
+ self.model_name = model_name
+
+ @abstractmethod
+ 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:
+ 清理后的响应内容
+ """
+ if response is None:
+ return ""
+ return response.strip()
diff --git a/QueryEngine/llms/deepseek.py b/QueryEngine/llms/deepseek.py
new file mode 100644
index 0000000..9e6d96d
--- /dev/null
+++ b/QueryEngine/llms/deepseek.py
@@ -0,0 +1,95 @@
+"""
+DeepSeek LLM实现
+使用DeepSeek API进行文本生成
+"""
+
+import os
+from typing import Optional, Dict, Any
+from openai import OpenAI
+from .base import BaseLLM
+
+
+class DeepSeekLLM(BaseLLM):
+ """DeepSeek LLM实现类"""
+
+ def __init__(self, api_key: Optional[str] = None, model_name: Optional[str] = None):
+ """
+ 初始化DeepSeek客户端
+
+ Args:
+ api_key: DeepSeek API密钥,如果不提供则从环境变量读取
+ model_name: 模型名称,默认使用deepseek-chat
+ """
+ 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)
+
+ # 初始化OpenAI客户端,使用DeepSeek的endpoint
+ self.client = OpenAI(
+ api_key=self.api_key,
+ base_url="https://api.deepseek.com"
+ )
+
+ self.default_model = model_name or self.get_default_model()
+
+ def get_default_model(self) -> str:
+ """获取默认模型名称"""
+ return "deepseek-chat"
+
+ 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": "https://api.deepseek.com"
+ }
diff --git a/QueryEngine/llms/openai_llm.py b/QueryEngine/llms/openai_llm.py
new file mode 100644
index 0000000..fff0e79
--- /dev/null
+++ b/QueryEngine/llms/openai_llm.py
@@ -0,0 +1,90 @@
+"""
+OpenAI LLM实现
+使用OpenAI API进行文本生成
+"""
+
+import os
+from typing import Optional, Dict, Any
+from openai import OpenAI
+from .base import BaseLLM
+
+
+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"
+
+ 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"
+ }
diff --git a/QueryEngine/nodes/__init__.py b/QueryEngine/nodes/__init__.py
new file mode 100644
index 0000000..e15087c
--- /dev/null
+++ b/QueryEngine/nodes/__init__.py
@@ -0,0 +1,20 @@
+"""
+节点处理模块
+实现Deep Search Agent的各个处理步骤
+"""
+
+from .base_node import BaseNode
+from .report_structure_node import ReportStructureNode
+from .search_node import FirstSearchNode, ReflectionNode
+from .summary_node import FirstSummaryNode, ReflectionSummaryNode
+from .formatting_node import ReportFormattingNode
+
+__all__ = [
+ "BaseNode",
+ "ReportStructureNode",
+ "FirstSearchNode",
+ "ReflectionNode",
+ "FirstSummaryNode",
+ "ReflectionSummaryNode",
+ "ReportFormattingNode"
+]
diff --git a/QueryEngine/nodes/base_node.py b/QueryEngine/nodes/base_node.py
new file mode 100644
index 0000000..b1387ff
--- /dev/null
+++ b/QueryEngine/nodes/base_node.py
@@ -0,0 +1,89 @@
+"""
+节点基类
+定义所有处理节点的基础接口
+"""
+
+from abc import ABC, abstractmethod
+from typing import Any, Dict, Optional
+from ..llms.base import BaseLLM
+from ..state.state import State
+
+
+class BaseNode(ABC):
+ """节点基类"""
+
+ def __init__(self, llm_client: BaseLLM, node_name: str = ""):
+ """
+ 初始化节点
+
+ Args:
+ llm_client: LLM客户端
+ node_name: 节点名称
+ """
+ self.llm_client = llm_client
+ self.node_name = node_name or self.__class__.__name__
+
+ @abstractmethod
+ def run(self, input_data: Any, **kwargs) -> Any:
+ """
+ 执行节点处理逻辑
+
+ Args:
+ input_data: 输入数据
+ **kwargs: 额外参数
+
+ Returns:
+ 处理结果
+ """
+ pass
+
+ def validate_input(self, input_data: Any) -> bool:
+ """
+ 验证输入数据
+
+ Args:
+ input_data: 输入数据
+
+ Returns:
+ 验证是否通过
+ """
+ return True
+
+ def process_output(self, output: Any) -> Any:
+ """
+ 处理输出数据
+
+ Args:
+ output: 原始输出
+
+ Returns:
+ 处理后的输出
+ """
+ return output
+
+ def log_info(self, message: str):
+ """记录信息日志"""
+ print(f"[{self.node_name}] {message}")
+
+ def log_error(self, message: str):
+ """记录错误日志"""
+ print(f"[{self.node_name}] 错误: {message}")
+
+
+class StateMutationNode(BaseNode):
+ """带状态修改功能的节点基类"""
+
+ @abstractmethod
+ def mutate_state(self, input_data: Any, state: State, **kwargs) -> State:
+ """
+ 修改状态
+
+ Args:
+ input_data: 输入数据
+ state: 当前状态
+ **kwargs: 额外参数
+
+ Returns:
+ 修改后的状态
+ """
+ pass
diff --git a/QueryEngine/nodes/formatting_node.py b/QueryEngine/nodes/formatting_node.py
new file mode 100644
index 0000000..c8b4159
--- /dev/null
+++ b/QueryEngine/nodes/formatting_node.py
@@ -0,0 +1,164 @@
+"""
+报告格式化节点
+负责将最终研究结果格式化为美观的Markdown报告
+"""
+
+import json
+from typing import List, Dict, Any
+
+from .base_node import BaseNode
+from ..prompts import SYSTEM_PROMPT_REPORT_FORMATTING
+from ..utils.text_processing import (
+ remove_reasoning_from_output,
+ clean_markdown_tags
+)
+
+
+class ReportFormattingNode(BaseNode):
+ """格式化最终报告的节点"""
+
+ def __init__(self, llm_client):
+ """
+ 初始化报告格式化节点
+
+ Args:
+ llm_client: LLM客户端
+ """
+ super().__init__(llm_client, "ReportFormattingNode")
+
+ def validate_input(self, input_data: Any) -> bool:
+ """验证输入数据"""
+ if isinstance(input_data, str):
+ try:
+ data = json.loads(input_data)
+ return isinstance(data, list) and all(
+ isinstance(item, dict) and "title" in item and "paragraph_latest_state" in item
+ for item in data
+ )
+ except:
+ return False
+ elif isinstance(input_data, list):
+ return all(
+ isinstance(item, dict) and "title" in item and "paragraph_latest_state" in item
+ for item in input_data
+ )
+ return False
+
+ def run(self, input_data: Any, **kwargs) -> str:
+ """
+ 调用LLM生成Markdown格式报告
+
+ Args:
+ input_data: 包含所有段落信息的列表
+ **kwargs: 额外参数
+
+ Returns:
+ 格式化的Markdown报告
+ """
+ try:
+ if not self.validate_input(input_data):
+ raise ValueError("输入数据格式错误,需要包含title和paragraph_latest_state的列表")
+
+ # 准备输入数据
+ if isinstance(input_data, str):
+ message = input_data
+ else:
+ message = json.dumps(input_data, ensure_ascii=False)
+
+ self.log_info("正在格式化最终报告")
+
+ # 调用LLM
+ response = self.llm_client.invoke(SYSTEM_PROMPT_REPORT_FORMATTING, message)
+
+ # 处理响应
+ processed_response = self.process_output(response)
+
+ self.log_info("成功生成格式化报告")
+ return processed_response
+
+ except Exception as e:
+ self.log_error(f"报告格式化失败: {str(e)}")
+ raise e
+
+ def process_output(self, output: str) -> str:
+ """
+ 处理LLM输出,清理Markdown格式
+
+ Args:
+ output: LLM原始输出
+
+ Returns:
+ 清理后的Markdown报告
+ """
+ try:
+ # 清理响应文本
+ cleaned_output = remove_reasoning_from_output(output)
+ cleaned_output = clean_markdown_tags(cleaned_output)
+
+ # 确保报告有基本结构
+ if not cleaned_output.strip():
+ return "# 报告生成失败\n\n无法生成有效的报告内容。"
+
+ # 如果没有标题,添加一个默认标题
+ if not cleaned_output.strip().startswith('#'):
+ cleaned_output = "# 深度研究报告\n\n" + cleaned_output
+
+ return cleaned_output.strip()
+
+ except Exception as e:
+ self.log_error(f"处理输出失败: {str(e)}")
+ return "# 报告处理失败\n\n报告格式化过程中发生错误。"
+
+ def format_report_manually(self, paragraphs_data: List[Dict[str, str]],
+ report_title: str = "深度研究报告") -> str:
+ """
+ 手动格式化报告(备用方法)
+
+ Args:
+ paragraphs_data: 段落数据列表
+ report_title: 报告标题
+
+ Returns:
+ 格式化的Markdown报告
+ """
+ try:
+ self.log_info("使用手动格式化方法")
+
+ # 构建报告
+ report_lines = [
+ f"# {report_title}",
+ "",
+ "---",
+ ""
+ ]
+
+ # 添加各个段落
+ for i, paragraph in enumerate(paragraphs_data, 1):
+ title = paragraph.get("title", f"段落 {i}")
+ content = paragraph.get("paragraph_latest_state", "")
+
+ if content:
+ report_lines.extend([
+ f"## {title}",
+ "",
+ content,
+ "",
+ "---",
+ ""
+ ])
+
+ # 添加结论
+ if len(paragraphs_data) > 1:
+ report_lines.extend([
+ "## 结论",
+ "",
+ "本报告通过深度搜索和研究,对相关主题进行了全面分析。"
+ "以上各个方面的内容为理解该主题提供了重要参考。",
+ ""
+ ])
+
+ return "\n".join(report_lines)
+
+ except Exception as e:
+ self.log_error(f"手动格式化失败: {str(e)}")
+ return "# 报告生成失败\n\n无法完成报告格式化。"
diff --git a/QueryEngine/nodes/report_structure_node.py b/QueryEngine/nodes/report_structure_node.py
new file mode 100644
index 0000000..87632da
--- /dev/null
+++ b/QueryEngine/nodes/report_structure_node.py
@@ -0,0 +1,203 @@
+"""
+报告结构生成节点
+负责根据查询生成报告的整体结构
+"""
+
+import json
+from typing import Dict, Any, List
+from json.decoder import JSONDecodeError
+
+from .base_node import StateMutationNode
+from ..state.state import State
+from ..prompts import SYSTEM_PROMPT_REPORT_STRUCTURE
+from ..utils.text_processing import (
+ remove_reasoning_from_output,
+ clean_json_tags,
+ extract_clean_response,
+ fix_incomplete_json
+)
+
+
+class ReportStructureNode(StateMutationNode):
+ """生成报告结构的节点"""
+
+ def __init__(self, llm_client, query: str):
+ """
+ 初始化报告结构节点
+
+ Args:
+ llm_client: LLM客户端
+ query: 用户查询
+ """
+ super().__init__(llm_client, "ReportStructureNode")
+ self.query = query
+
+ def validate_input(self, input_data: Any) -> bool:
+ """验证输入数据"""
+ return isinstance(self.query, str) and len(self.query.strip()) > 0
+
+ def run(self, input_data: Any = None, **kwargs) -> List[Dict[str, str]]:
+ """
+ 调用LLM生成报告结构
+
+ Args:
+ input_data: 输入数据(这里不使用,使用初始化时的query)
+ **kwargs: 额外参数
+
+ Returns:
+ 报告结构列表
+ """
+ try:
+ self.log_info(f"正在为查询生成报告结构: {self.query}")
+
+ # 调用LLM
+ response = self.llm_client.invoke(SYSTEM_PROMPT_REPORT_STRUCTURE, self.query)
+
+ # 处理响应
+ processed_response = self.process_output(response)
+
+ self.log_info(f"成功生成 {len(processed_response)} 个段落结构")
+ return processed_response
+
+ except Exception as e:
+ self.log_error(f"生成报告结构失败: {str(e)}")
+ raise e
+
+ def process_output(self, output: str) -> List[Dict[str, str]]:
+ """
+ 处理LLM输出,提取报告结构
+
+ Args:
+ output: LLM原始输出
+
+ Returns:
+ 处理后的报告结构列表
+ """
+ try:
+ # 清理响应文本
+ cleaned_output = remove_reasoning_from_output(output)
+ cleaned_output = clean_json_tags(cleaned_output)
+
+ # 记录清理后的输出用于调试
+ self.log_info(f"清理后的输出: {cleaned_output[:200]}...")
+
+ # 解析JSON
+ try:
+ report_structure = json.loads(cleaned_output)
+ self.log_info("JSON解析成功")
+ except JSONDecodeError as e:
+ self.log_info(f"JSON解析失败: {str(e)}")
+ # 使用更强大的提取方法
+ report_structure = extract_clean_response(cleaned_output)
+ if "error" in report_structure:
+ self.log_error("JSON解析失败,尝试修复...")
+ # 尝试修复JSON
+ fixed_json = fix_incomplete_json(cleaned_output)
+ if fixed_json:
+ try:
+ report_structure = json.loads(fixed_json)
+ self.log_info("JSON修复成功")
+ except JSONDecodeError:
+ self.log_error("JSON修复失败")
+ # 返回默认结构
+ return self._generate_default_structure()
+ else:
+ self.log_error("无法修复JSON,使用默认结构")
+ return self._generate_default_structure()
+
+ # 验证结构
+ if not isinstance(report_structure, list):
+ self.log_info("报告结构不是列表,尝试转换...")
+ if isinstance(report_structure, dict):
+ # 如果是单个对象,包装成列表
+ report_structure = [report_structure]
+ else:
+ self.log_error("报告结构格式无效,使用默认结构")
+ return self._generate_default_structure()
+
+ # 验证每个段落
+ validated_structure = []
+ for i, paragraph in enumerate(report_structure):
+ if not isinstance(paragraph, dict):
+ self.log_warning(f"段落 {i+1} 不是字典格式,跳过")
+ continue
+
+ title = paragraph.get("title", f"段落 {i+1}")
+ content = paragraph.get("content", "")
+
+ if not title or not content:
+ self.log_warning(f"段落 {i+1} 缺少标题或内容,跳过")
+ continue
+
+ validated_structure.append({
+ "title": title,
+ "content": content
+ })
+
+ if not validated_structure:
+ self.log_warning("没有有效的段落结构,使用默认结构")
+ return self._generate_default_structure()
+
+ self.log_info(f"成功验证 {len(validated_structure)} 个段落结构")
+ return validated_structure
+
+ except Exception as e:
+ self.log_error(f"处理输出失败: {str(e)}")
+ return self._generate_default_structure()
+
+ def _generate_default_structure(self) -> List[Dict[str, str]]:
+ """
+ 生成默认的报告结构
+
+ Returns:
+ 默认的报告结构列表
+ """
+ self.log_info("生成默认报告结构")
+ return [
+ {
+ "title": "研究概述",
+ "content": "对查询主题进行总体概述和分析"
+ },
+ {
+ "title": "深度分析",
+ "content": "深入分析查询主题的各个方面"
+ }
+ ]
+
+ def mutate_state(self, input_data: Any = None, state: State = None, **kwargs) -> State:
+ """
+ 将报告结构写入状态
+
+ Args:
+ input_data: 输入数据
+ state: 当前状态,如果为None则创建新状态
+ **kwargs: 额外参数
+
+ Returns:
+ 更新后的状态
+ """
+ if state is None:
+ state = State()
+
+ try:
+ # 生成报告结构
+ report_structure = self.run(input_data, **kwargs)
+
+ # 设置查询和报告标题
+ state.query = self.query
+ if not state.report_title:
+ state.report_title = f"关于'{self.query}'的深度研究报告"
+
+ # 添加段落到状态
+ for paragraph_data in report_structure:
+ state.add_paragraph(
+ title=paragraph_data["title"],
+ content=paragraph_data["content"]
+ )
+
+ self.log_info(f"已将 {len(report_structure)} 个段落添加到状态中")
+ return state
+
+ except Exception as e:
+ self.log_error(f"状态更新失败: {str(e)}")
+ raise e
diff --git a/QueryEngine/nodes/search_node.py b/QueryEngine/nodes/search_node.py
new file mode 100644
index 0000000..52cc17d
--- /dev/null
+++ b/QueryEngine/nodes/search_node.py
@@ -0,0 +1,285 @@
+"""
+搜索节点实现
+负责生成搜索查询和反思查询
+"""
+
+import json
+from typing import Dict, Any
+from json.decoder import JSONDecodeError
+
+from .base_node import BaseNode
+from ..prompts import SYSTEM_PROMPT_FIRST_SEARCH, SYSTEM_PROMPT_REFLECTION
+from ..utils.text_processing import (
+ remove_reasoning_from_output,
+ clean_json_tags,
+ extract_clean_response,
+ fix_incomplete_json
+)
+
+
+class FirstSearchNode(BaseNode):
+ """为段落生成首次搜索查询的节点"""
+
+ def __init__(self, llm_client):
+ """
+ 初始化首次搜索节点
+
+ Args:
+ llm_client: LLM客户端
+ """
+ super().__init__(llm_client, "FirstSearchNode")
+
+ def validate_input(self, input_data: Any) -> bool:
+ """验证输入数据"""
+ if isinstance(input_data, str):
+ try:
+ data = json.loads(input_data)
+ return "title" in data and "content" in data
+ except JSONDecodeError:
+ return False
+ elif isinstance(input_data, dict):
+ return "title" in input_data and "content" in input_data
+ return False
+
+ def run(self, input_data: Any, **kwargs) -> Dict[str, str]:
+ """
+ 调用LLM生成搜索查询和理由
+
+ Args:
+ input_data: 包含title和content的字符串或字典
+ **kwargs: 额外参数
+
+ Returns:
+ 包含search_query和reasoning的字典
+ """
+ try:
+ if not self.validate_input(input_data):
+ raise ValueError("输入数据格式错误,需要包含title和content字段")
+
+ # 准备输入数据
+ if isinstance(input_data, str):
+ message = input_data
+ else:
+ message = json.dumps(input_data, ensure_ascii=False)
+
+ self.log_info("正在生成首次搜索查询")
+
+ # 调用LLM
+ response = self.llm_client.invoke(SYSTEM_PROMPT_FIRST_SEARCH, message)
+
+ # 处理响应
+ processed_response = self.process_output(response)
+
+ self.log_info(f"生成搜索查询: {processed_response.get('search_query', 'N/A')}")
+ return processed_response
+
+ except Exception as e:
+ self.log_error(f"生成首次搜索查询失败: {str(e)}")
+ raise e
+
+ def process_output(self, output: str) -> Dict[str, str]:
+ """
+ 处理LLM输出,提取搜索查询和推理
+
+ Args:
+ output: LLM原始输出
+
+ Returns:
+ 包含search_query和reasoning的字典
+ """
+ try:
+ # 清理响应文本
+ cleaned_output = remove_reasoning_from_output(output)
+ cleaned_output = clean_json_tags(cleaned_output)
+
+ # 记录清理后的输出用于调试
+ self.log_info(f"清理后的输出: {cleaned_output[:200]}...")
+
+ # 解析JSON
+ try:
+ result = json.loads(cleaned_output)
+ self.log_info("JSON解析成功")
+ except JSONDecodeError as e:
+ self.log_info(f"JSON解析失败: {str(e)}")
+ # 使用更强大的提取方法
+ result = extract_clean_response(cleaned_output)
+ if "error" in result:
+ self.log_error("JSON解析失败,尝试修复...")
+ # 尝试修复JSON
+ fixed_json = fix_incomplete_json(cleaned_output)
+ if fixed_json:
+ try:
+ result = json.loads(fixed_json)
+ self.log_info("JSON修复成功")
+ except JSONDecodeError:
+ self.log_error("JSON修复失败")
+ # 返回默认查询
+ return self._get_default_search_query()
+ else:
+ self.log_error("无法修复JSON,使用默认查询")
+ return self._get_default_search_query()
+
+ # 验证和清理结果
+ search_query = result.get("search_query", "")
+ reasoning = result.get("reasoning", "")
+
+ if not search_query:
+ self.log_warning("未找到搜索查询,使用默认查询")
+ return self._get_default_search_query()
+
+ return {
+ "search_query": search_query,
+ "reasoning": reasoning
+ }
+
+ except Exception as e:
+ self.log_error(f"处理输出失败: {str(e)}")
+ # 返回默认查询
+ return self._get_default_search_query()
+
+ def _get_default_search_query(self) -> Dict[str, str]:
+ """
+ 获取默认搜索查询
+
+ Returns:
+ 默认的搜索查询字典
+ """
+ return {
+ "search_query": "相关主题研究",
+ "reasoning": "由于解析失败,使用默认搜索查询"
+ }
+
+
+class ReflectionNode(BaseNode):
+ """反思段落并生成新搜索查询的节点"""
+
+ def __init__(self, llm_client):
+ """
+ 初始化反思节点
+
+ Args:
+ llm_client: LLM客户端
+ """
+ super().__init__(llm_client, "ReflectionNode")
+
+ def validate_input(self, input_data: Any) -> bool:
+ """验证输入数据"""
+ if isinstance(input_data, str):
+ try:
+ data = json.loads(input_data)
+ required_fields = ["title", "content", "paragraph_latest_state"]
+ return all(field in data for field in required_fields)
+ except JSONDecodeError:
+ return False
+ elif isinstance(input_data, dict):
+ required_fields = ["title", "content", "paragraph_latest_state"]
+ return all(field in input_data for field in required_fields)
+ return False
+
+ def run(self, input_data: Any, **kwargs) -> Dict[str, str]:
+ """
+ 调用LLM反思并生成搜索查询
+
+ Args:
+ input_data: 包含title、content和paragraph_latest_state的字符串或字典
+ **kwargs: 额外参数
+
+ Returns:
+ 包含search_query和reasoning的字典
+ """
+ try:
+ if not self.validate_input(input_data):
+ raise ValueError("输入数据格式错误,需要包含title、content和paragraph_latest_state字段")
+
+ # 准备输入数据
+ if isinstance(input_data, str):
+ message = input_data
+ else:
+ message = json.dumps(input_data, ensure_ascii=False)
+
+ self.log_info("正在进行反思并生成新搜索查询")
+
+ # 调用LLM
+ response = self.llm_client.invoke(SYSTEM_PROMPT_REFLECTION, message)
+
+ # 处理响应
+ processed_response = self.process_output(response)
+
+ self.log_info(f"反思生成搜索查询: {processed_response.get('search_query', 'N/A')}")
+ return processed_response
+
+ except Exception as e:
+ self.log_error(f"反思生成搜索查询失败: {str(e)}")
+ raise e
+
+ def process_output(self, output: str) -> Dict[str, str]:
+ """
+ 处理LLM输出,提取搜索查询和推理
+
+ Args:
+ output: LLM原始输出
+
+ Returns:
+ 包含search_query和reasoning的字典
+ """
+ try:
+ # 清理响应文本
+ cleaned_output = remove_reasoning_from_output(output)
+ cleaned_output = clean_json_tags(cleaned_output)
+
+ # 记录清理后的输出用于调试
+ self.log_info(f"清理后的输出: {cleaned_output[:200]}...")
+
+ # 解析JSON
+ try:
+ result = json.loads(cleaned_output)
+ self.log_info("JSON解析成功")
+ except JSONDecodeError as e:
+ self.log_info(f"JSON解析失败: {str(e)}")
+ # 使用更强大的提取方法
+ result = extract_clean_response(cleaned_output)
+ if "error" in result:
+ self.log_error("JSON解析失败,尝试修复...")
+ # 尝试修复JSON
+ fixed_json = fix_incomplete_json(cleaned_output)
+ if fixed_json:
+ try:
+ result = json.loads(fixed_json)
+ self.log_info("JSON修复成功")
+ except JSONDecodeError:
+ self.log_error("JSON修复失败")
+ # 返回默认查询
+ return self._get_default_reflection_query()
+ else:
+ self.log_error("无法修复JSON,使用默认查询")
+ return self._get_default_reflection_query()
+
+ # 验证和清理结果
+ search_query = result.get("search_query", "")
+ reasoning = result.get("reasoning", "")
+
+ if not search_query:
+ self.log_warning("未找到搜索查询,使用默认查询")
+ return self._get_default_reflection_query()
+
+ return {
+ "search_query": search_query,
+ "reasoning": reasoning
+ }
+
+ except Exception as e:
+ self.log_error(f"处理输出失败: {str(e)}")
+ # 返回默认查询
+ return self._get_default_reflection_query()
+
+ def _get_default_reflection_query(self) -> Dict[str, str]:
+ """
+ 获取默认反思搜索查询
+
+ Returns:
+ 默认的反思搜索查询字典
+ """
+ return {
+ "search_query": "深度研究补充信息",
+ "reasoning": "由于解析失败,使用默认反思搜索查询"
+ }
diff --git a/QueryEngine/nodes/summary_node.py b/QueryEngine/nodes/summary_node.py
new file mode 100644
index 0000000..e44fb1e
--- /dev/null
+++ b/QueryEngine/nodes/summary_node.py
@@ -0,0 +1,312 @@
+"""
+总结节点实现
+负责根据搜索结果生成和更新段落内容
+"""
+
+import json
+from typing import Dict, Any, List
+from json.decoder import JSONDecodeError
+
+from .base_node import StateMutationNode
+from ..state.state import State
+from ..prompts import SYSTEM_PROMPT_FIRST_SUMMARY, SYSTEM_PROMPT_REFLECTION_SUMMARY
+from ..utils.text_processing import (
+ remove_reasoning_from_output,
+ clean_json_tags,
+ extract_clean_response,
+ fix_incomplete_json,
+ format_search_results_for_prompt
+)
+
+
+class FirstSummaryNode(StateMutationNode):
+ """根据搜索结果生成段落首次总结的节点"""
+
+ def __init__(self, llm_client):
+ """
+ 初始化首次总结节点
+
+ Args:
+ llm_client: LLM客户端
+ """
+ super().__init__(llm_client, "FirstSummaryNode")
+
+ def validate_input(self, input_data: Any) -> bool:
+ """验证输入数据"""
+ if isinstance(input_data, str):
+ try:
+ data = json.loads(input_data)
+ required_fields = ["title", "content", "search_query", "search_results"]
+ return all(field in data for field in required_fields)
+ except JSONDecodeError:
+ return False
+ elif isinstance(input_data, dict):
+ required_fields = ["title", "content", "search_query", "search_results"]
+ return all(field in input_data for field in required_fields)
+ return False
+
+ def run(self, input_data: Any, **kwargs) -> str:
+ """
+ 调用LLM生成段落总结
+
+ Args:
+ input_data: 包含title、content、search_query和search_results的数据
+ **kwargs: 额外参数
+
+ Returns:
+ 段落总结内容
+ """
+ try:
+ if not self.validate_input(input_data):
+ raise ValueError("输入数据格式错误")
+
+ # 准备输入数据
+ if isinstance(input_data, str):
+ message = input_data
+ else:
+ message = json.dumps(input_data, ensure_ascii=False)
+
+ self.log_info("正在生成首次段落总结")
+
+ # 调用LLM
+ response = self.llm_client.invoke(SYSTEM_PROMPT_FIRST_SUMMARY, message)
+
+ # 处理响应
+ processed_response = self.process_output(response)
+
+ self.log_info("成功生成首次段落总结")
+ return processed_response
+
+ except Exception as e:
+ self.log_error(f"生成首次总结失败: {str(e)}")
+ raise e
+
+ def process_output(self, output: str) -> str:
+ """
+ 处理LLM输出,提取段落内容
+
+ Args:
+ output: LLM原始输出
+
+ Returns:
+ 段落内容
+ """
+ try:
+ # 清理响应文本
+ cleaned_output = remove_reasoning_from_output(output)
+ cleaned_output = clean_json_tags(cleaned_output)
+
+ # 记录清理后的输出用于调试
+ self.log_info(f"清理后的输出: {cleaned_output[:200]}...")
+
+ # 解析JSON
+ try:
+ result = json.loads(cleaned_output)
+ self.log_info("JSON解析成功")
+ except JSONDecodeError as e:
+ self.log_info(f"JSON解析失败: {str(e)}")
+ # 尝试修复JSON
+ fixed_json = fix_incomplete_json(cleaned_output)
+ if fixed_json:
+ try:
+ result = json.loads(fixed_json)
+ self.log_info("JSON修复成功")
+ except JSONDecodeError:
+ self.log_info("JSON修复失败,直接使用清理后的文本")
+ # 如果不是JSON格式,直接返回清理后的文本
+ return cleaned_output
+ else:
+ self.log_info("无法修复JSON,直接使用清理后的文本")
+ # 如果不是JSON格式,直接返回清理后的文本
+ return cleaned_output
+
+ # 提取段落内容
+ if isinstance(result, dict):
+ paragraph_content = result.get("paragraph_latest_state", "")
+ if paragraph_content:
+ return paragraph_content
+
+ # 如果提取失败,返回原始清理后的文本
+ return cleaned_output
+
+ except Exception as e:
+ self.log_error(f"处理输出失败: {str(e)}")
+ return "段落总结生成失败"
+
+ def mutate_state(self, input_data: Any, state: State, paragraph_index: int, **kwargs) -> State:
+ """
+ 更新段落的最新总结到状态
+
+ Args:
+ input_data: 输入数据
+ state: 当前状态
+ paragraph_index: 段落索引
+ **kwargs: 额外参数
+
+ Returns:
+ 更新后的状态
+ """
+ try:
+ # 生成总结
+ summary = self.run(input_data, **kwargs)
+
+ # 更新状态
+ if 0 <= paragraph_index < len(state.paragraphs):
+ state.paragraphs[paragraph_index].research.latest_summary = summary
+ self.log_info(f"已更新段落 {paragraph_index} 的首次总结")
+ else:
+ raise ValueError(f"段落索引 {paragraph_index} 超出范围")
+
+ state.update_timestamp()
+ return state
+
+ except Exception as e:
+ self.log_error(f"状态更新失败: {str(e)}")
+ raise e
+
+
+class ReflectionSummaryNode(StateMutationNode):
+ """根据反思搜索结果更新段落总结的节点"""
+
+ def __init__(self, llm_client):
+ """
+ 初始化反思总结节点
+
+ Args:
+ llm_client: LLM客户端
+ """
+ super().__init__(llm_client, "ReflectionSummaryNode")
+
+ def validate_input(self, input_data: Any) -> bool:
+ """验证输入数据"""
+ if isinstance(input_data, str):
+ try:
+ data = json.loads(input_data)
+ required_fields = ["title", "content", "search_query", "search_results", "paragraph_latest_state"]
+ return all(field in data for field in required_fields)
+ except JSONDecodeError:
+ return False
+ elif isinstance(input_data, dict):
+ required_fields = ["title", "content", "search_query", "search_results", "paragraph_latest_state"]
+ return all(field in input_data for field in required_fields)
+ return False
+
+ def run(self, input_data: Any, **kwargs) -> str:
+ """
+ 调用LLM更新段落内容
+
+ Args:
+ input_data: 包含完整反思信息的数据
+ **kwargs: 额外参数
+
+ Returns:
+ 更新后的段落内容
+ """
+ try:
+ if not self.validate_input(input_data):
+ raise ValueError("输入数据格式错误")
+
+ # 准备输入数据
+ if isinstance(input_data, str):
+ message = input_data
+ else:
+ message = json.dumps(input_data, ensure_ascii=False)
+
+ self.log_info("正在生成反思总结")
+
+ # 调用LLM
+ response = self.llm_client.invoke(SYSTEM_PROMPT_REFLECTION_SUMMARY, message)
+
+ # 处理响应
+ processed_response = self.process_output(response)
+
+ self.log_info("成功生成反思总结")
+ return processed_response
+
+ except Exception as e:
+ self.log_error(f"生成反思总结失败: {str(e)}")
+ raise e
+
+ def process_output(self, output: str) -> str:
+ """
+ 处理LLM输出,提取更新后的段落内容
+
+ Args:
+ output: LLM原始输出
+
+ Returns:
+ 更新后的段落内容
+ """
+ try:
+ # 清理响应文本
+ cleaned_output = remove_reasoning_from_output(output)
+ cleaned_output = clean_json_tags(cleaned_output)
+
+ # 记录清理后的输出用于调试
+ self.log_info(f"清理后的输出: {cleaned_output[:200]}...")
+
+ # 解析JSON
+ try:
+ result = json.loads(cleaned_output)
+ self.log_info("JSON解析成功")
+ except JSONDecodeError as e:
+ self.log_info(f"JSON解析失败: {str(e)}")
+ # 尝试修复JSON
+ fixed_json = fix_incomplete_json(cleaned_output)
+ if fixed_json:
+ try:
+ result = json.loads(fixed_json)
+ self.log_info("JSON修复成功")
+ except JSONDecodeError:
+ self.log_info("JSON修复失败,直接使用清理后的文本")
+ # 如果不是JSON格式,直接返回清理后的文本
+ return cleaned_output
+ else:
+ self.log_info("无法修复JSON,直接使用清理后的文本")
+ # 如果不是JSON格式,直接返回清理后的文本
+ return cleaned_output
+
+ # 提取更新后的段落内容
+ if isinstance(result, dict):
+ updated_content = result.get("updated_paragraph_latest_state", "")
+ if updated_content:
+ return updated_content
+
+ # 如果提取失败,返回原始清理后的文本
+ return cleaned_output
+
+ except Exception as e:
+ self.log_error(f"处理输出失败: {str(e)}")
+ return "反思总结生成失败"
+
+ def mutate_state(self, input_data: Any, state: State, paragraph_index: int, **kwargs) -> State:
+ """
+ 将更新后的总结写入状态
+
+ Args:
+ input_data: 输入数据
+ state: 当前状态
+ paragraph_index: 段落索引
+ **kwargs: 额外参数
+
+ Returns:
+ 更新后的状态
+ """
+ try:
+ # 生成更新后的总结
+ updated_summary = self.run(input_data, **kwargs)
+
+ # 更新状态
+ if 0 <= paragraph_index < len(state.paragraphs):
+ state.paragraphs[paragraph_index].research.latest_summary = updated_summary
+ state.paragraphs[paragraph_index].research.increment_reflection()
+ self.log_info(f"已更新段落 {paragraph_index} 的反思总结")
+ else:
+ raise ValueError(f"段落索引 {paragraph_index} 超出范围")
+
+ state.update_timestamp()
+ return state
+
+ except Exception as e:
+ self.log_error(f"状态更新失败: {str(e)}")
+ raise e
diff --git a/QueryEngine/prompts/__init__.py b/QueryEngine/prompts/__init__.py
new file mode 100644
index 0000000..e395aef
--- /dev/null
+++ b/QueryEngine/prompts/__init__.py
@@ -0,0 +1,34 @@
+"""
+Prompt模块
+定义Deep Search Agent各个阶段使用的系统提示词
+"""
+
+from .prompts import (
+ SYSTEM_PROMPT_REPORT_STRUCTURE,
+ SYSTEM_PROMPT_FIRST_SEARCH,
+ SYSTEM_PROMPT_FIRST_SUMMARY,
+ SYSTEM_PROMPT_REFLECTION,
+ SYSTEM_PROMPT_REFLECTION_SUMMARY,
+ SYSTEM_PROMPT_REPORT_FORMATTING,
+ output_schema_report_structure,
+ output_schema_first_search,
+ output_schema_first_summary,
+ output_schema_reflection,
+ output_schema_reflection_summary,
+ input_schema_report_formatting
+)
+
+__all__ = [
+ "SYSTEM_PROMPT_REPORT_STRUCTURE",
+ "SYSTEM_PROMPT_FIRST_SEARCH",
+ "SYSTEM_PROMPT_FIRST_SUMMARY",
+ "SYSTEM_PROMPT_REFLECTION",
+ "SYSTEM_PROMPT_REFLECTION_SUMMARY",
+ "SYSTEM_PROMPT_REPORT_FORMATTING",
+ "output_schema_report_structure",
+ "output_schema_first_search",
+ "output_schema_first_summary",
+ "output_schema_reflection",
+ "output_schema_reflection_summary",
+ "input_schema_report_formatting"
+]
diff --git a/QueryEngine/prompts/prompts.py b/QueryEngine/prompts/prompts.py
new file mode 100644
index 0000000..1bdbb55
--- /dev/null
+++ b/QueryEngine/prompts/prompts.py
@@ -0,0 +1,285 @@
+"""
+Deep Search Agent 的所有提示词定义
+包含各个阶段的系统提示词和JSON Schema定义
+"""
+
+import json
+
+# ===== JSON Schema 定义 =====
+
+# 报告结构输出Schema
+output_schema_report_structure = {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "title": {"type": "string"},
+ "content": {"type": "string"}
+ }
+ }
+}
+
+# 首次搜索输入Schema
+input_schema_first_search = {
+ "type": "object",
+ "properties": {
+ "title": {"type": "string"},
+ "content": {"type": "string"}
+ }
+}
+
+# 首次搜索输出Schema
+output_schema_first_search = {
+ "type": "object",
+ "properties": {
+ "search_query": {"type": "string"},
+ "search_tool": {"type": "string"},
+ "reasoning": {"type": "string"},
+ "start_date": {"type": "string", "description": "开始日期,格式YYYY-MM-DD,仅search_news_by_date工具需要"},
+ "end_date": {"type": "string", "description": "结束日期,格式YYYY-MM-DD,仅search_news_by_date工具需要"}
+ },
+ "required": ["search_query", "search_tool", "reasoning"]
+}
+
+# 首次总结输入Schema
+input_schema_first_summary = {
+ "type": "object",
+ "properties": {
+ "title": {"type": "string"},
+ "content": {"type": "string"},
+ "search_query": {"type": "string"},
+ "search_results": {
+ "type": "array",
+ "items": {"type": "string"}
+ }
+ }
+}
+
+# 首次总结输出Schema
+output_schema_first_summary = {
+ "type": "object",
+ "properties": {
+ "paragraph_latest_state": {"type": "string"}
+ }
+}
+
+# 反思输入Schema
+input_schema_reflection = {
+ "type": "object",
+ "properties": {
+ "title": {"type": "string"},
+ "content": {"type": "string"},
+ "paragraph_latest_state": {"type": "string"}
+ }
+}
+
+# 反思输出Schema
+output_schema_reflection = {
+ "type": "object",
+ "properties": {
+ "search_query": {"type": "string"},
+ "search_tool": {"type": "string"},
+ "reasoning": {"type": "string"},
+ "start_date": {"type": "string", "description": "开始日期,格式YYYY-MM-DD,仅search_news_by_date工具需要"},
+ "end_date": {"type": "string", "description": "结束日期,格式YYYY-MM-DD,仅search_news_by_date工具需要"}
+ },
+ "required": ["search_query", "search_tool", "reasoning"]
+}
+
+# 反思总结输入Schema
+input_schema_reflection_summary = {
+ "type": "object",
+ "properties": {
+ "title": {"type": "string"},
+ "content": {"type": "string"},
+ "search_query": {"type": "string"},
+ "search_results": {
+ "type": "array",
+ "items": {"type": "string"}
+ },
+ "paragraph_latest_state": {"type": "string"}
+ }
+}
+
+# 反思总结输出Schema
+output_schema_reflection_summary = {
+ "type": "object",
+ "properties": {
+ "updated_paragraph_latest_state": {"type": "string"}
+ }
+}
+
+# 报告格式化输入Schema
+input_schema_report_formatting = {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "title": {"type": "string"},
+ "paragraph_latest_state": {"type": "string"}
+ }
+ }
+}
+
+# ===== 系统提示词定义 =====
+
+# 生成报告结构的系统提示词
+SYSTEM_PROMPT_REPORT_STRUCTURE = f"""
+你是一位深度研究助手。给定一个查询,你需要规划一个报告的结构和其中包含的段落。最多五个段落。
+确保段落的排序合理有序。
+一旦大纲创建完成,你将获得工具来分别为每个部分搜索网络并进行反思。
+请按照以下JSON模式定义格式化输出:
+
+
+
+标题和内容属性将用于更深入的研究。
+确保输出是一个符合上述输出JSON模式定义的JSON对象。
+只返回JSON对象,不要有解释或额外文本。
+"""
+
+# 每个段落第一次搜索的系统提示词
+SYSTEM_PROMPT_FIRST_SEARCH = f"""
+你是一位深度研究助手。你将获得报告中的一个段落,其标题和预期内容将按照以下JSON模式定义提供:
+
+
+{json.dumps(input_schema_first_search, indent=2, ensure_ascii=False)}
+
+
+你可以使用以下6种专业的新闻搜索工具:
+
+1. **basic_search_news** - 基础新闻搜索工具
+ - 适用于:一般性的新闻搜索,不确定需要何种特定搜索时
+ - 特点:快速、标准的通用搜索,是最常用的基础工具
+
+2. **deep_search_news** - 深度新闻分析工具
+ - 适用于:需要全面深入了解某个主题时
+ - 特点:提供最详细的分析结果,包含高级AI摘要
+
+3. **search_news_last_24_hours** - 24小时最新新闻工具
+ - 适用于:需要了解最新动态、突发事件时
+ - 特点:只搜索过去24小时的新闻
+
+4. **search_news_last_week** - 本周新闻工具
+ - 适用于:需要了解近期发展趋势时
+ - 特点:搜索过去一周的新闻报道
+
+5. **search_images_for_news** - 图片搜索工具
+ - 适用于:需要可视化信息、图片资料时
+ - 特点:提供相关图片和图片描述
+
+6. **search_news_by_date** - 按日期范围搜索工具
+ - 适用于:需要研究特定历史时期时
+ - 特点:可以指定开始和结束日期进行搜索
+ - 特殊要求:需要提供start_date和end_date参数,格式为'YYYY-MM-DD'
+ - 注意:只有这个工具需要额外的时间参数
+
+你的任务是:
+1. 根据段落主题选择最合适的搜索工具
+2. 制定最佳的搜索查询
+3. 如果选择search_news_by_date工具,必须同时提供start_date和end_date参数(格式:YYYY-MM-DD)
+4. 解释你的选择理由
+
+注意:除了search_news_by_date工具外,其他工具都不需要额外参数。
+请按照以下JSON模式定义格式化输出(文字请使用中文):
+
+
+
+确保输出是一个符合上述输出JSON模式定义的JSON对象。
+只返回JSON对象,不要有解释或额外文本。
+"""
+
+# 每个段落第一次总结的系统提示词
+SYSTEM_PROMPT_FIRST_SUMMARY = f"""
+你是一位深度研究助手。你将获得搜索查询、搜索结果以及你正在研究的报告段落,数据将按照以下JSON模式定义提供:
+
+
+{json.dumps(input_schema_first_summary, indent=2, ensure_ascii=False)}
+
+
+你的任务是作为研究者,使用搜索结果撰写与段落主题一致的内容,并适当地组织结构以便纳入报告中。
+请按照以下JSON模式定义格式化输出:
+
+
+
+确保输出是一个符合上述输出JSON模式定义的JSON对象。
+只返回JSON对象,不要有解释或额外文本。
+"""
+
+# 反思(Reflect)的系统提示词
+SYSTEM_PROMPT_REFLECTION = f"""
+你是一位深度研究助手。你负责为研究报告构建全面的段落。你将获得段落标题、计划内容摘要,以及你已经创建的段落最新状态,所有这些都将按照以下JSON模式定义提供:
+
+
+{json.dumps(input_schema_reflection, indent=2, ensure_ascii=False)}
+
+
+你可以使用以下6种专业的新闻搜索工具:
+
+1. **basic_search_news** - 基础新闻搜索工具
+2. **deep_search_news** - 深度新闻分析工具
+3. **search_news_last_24_hours** - 24小时最新新闻工具
+4. **search_news_last_week** - 本周新闻工具
+5. **search_images_for_news** - 图片搜索工具
+6. **search_news_by_date** - 按日期范围搜索工具(需要时间参数)
+
+你的任务是:
+1. 反思段落文本的当前状态,思考是否遗漏了主题的某些关键方面
+2. 选择最合适的搜索工具来补充缺失信息
+3. 制定精确的搜索查询
+4. 如果选择search_news_by_date工具,必须同时提供start_date和end_date参数(格式:YYYY-MM-DD)
+5. 解释你的选择和推理
+
+注意:除了search_news_by_date工具外,其他工具都不需要额外参数。
+请按照以下JSON模式定义格式化输出:
+
+
+
+确保输出是一个符合上述输出JSON模式定义的JSON对象。
+只返回JSON对象,不要有解释或额外文本。
+"""
+
+# 总结反思的系统提示词
+SYSTEM_PROMPT_REFLECTION_SUMMARY = f"""
+你是一位深度研究助手。
+你将获得搜索查询、搜索结果、段落标题以及你正在研究的报告段落的预期内容。
+你正在迭代完善这个段落,并且段落的最新状态也会提供给你。
+数据将按照以下JSON模式定义提供:
+
+
+{json.dumps(input_schema_reflection_summary, indent=2, ensure_ascii=False)}
+
+
+你的任务是根据搜索结果和预期内容丰富段落的当前最新状态。
+不要删除最新状态中的关键信息,尽量丰富它,只添加缺失的信息。
+适当地组织段落结构以便纳入报告中。
+请按照以下JSON模式定义格式化输出:
+
+
+
+确保输出是一个符合上述输出JSON模式定义的JSON对象。
+只返回JSON对象,不要有解释或额外文本。
+"""
+
+# 最终研究报告格式化的系统提示词
+SYSTEM_PROMPT_REPORT_FORMATTING = f"""
+你是一位深度研究助手。你已经完成了研究并构建了报告中所有段落的最终版本。
+你将获得以下JSON格式的数据:
+
+
+{json.dumps(input_schema_report_formatting, indent=2, ensure_ascii=False)}
+
+
+你的任务是将报告格式化为美观的形式,并以Markdown格式返回。
+如果没有结论段落,请根据其他段落的最新状态在报告末尾添加一个结论。
+使用段落标题来创建报告的标题。
+"""
diff --git a/QueryEngine/state/__init__.py b/QueryEngine/state/__init__.py
new file mode 100644
index 0000000..28d6e38
--- /dev/null
+++ b/QueryEngine/state/__init__.py
@@ -0,0 +1,8 @@
+"""
+状态管理模块
+定义Deep Search Agent的状态数据结构
+"""
+
+from .state import State, Paragraph, Research, Search
+
+__all__ = ["State", "Paragraph", "Research", "Search"]
diff --git a/QueryEngine/state/state.py b/QueryEngine/state/state.py
new file mode 100644
index 0000000..a64e0b9
--- /dev/null
+++ b/QueryEngine/state/state.py
@@ -0,0 +1,258 @@
+"""
+Deep Search Agent状态管理
+定义所有状态数据结构和操作方法
+"""
+
+from dataclasses import dataclass, field
+from typing import List, Dict, Any, Optional
+import json
+from datetime import datetime
+
+
+@dataclass
+class Search:
+ """单个搜索结果的状态"""
+ query: str = "" # 搜索查询
+ url: str = "" # 搜索结果的链接
+ title: str = "" # 搜索结果标题
+ content: str = "" # 搜索返回的内容
+ score: Optional[float] = None # 相关度评分
+ timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
+
+ def to_dict(self) -> Dict[str, Any]:
+ """转换为字典格式"""
+ return {
+ "query": self.query,
+ "url": self.url,
+ "title": self.title,
+ "content": self.content,
+ "score": self.score,
+ "timestamp": self.timestamp
+ }
+
+ @classmethod
+ def from_dict(cls, data: Dict[str, Any]) -> "Search":
+ """从字典创建Search对象"""
+ return cls(
+ query=data.get("query", ""),
+ url=data.get("url", ""),
+ title=data.get("title", ""),
+ content=data.get("content", ""),
+ score=data.get("score"),
+ timestamp=data.get("timestamp", datetime.now().isoformat())
+ )
+
+
+@dataclass
+class Research:
+ """段落研究过程的状态"""
+ search_history: List[Search] = field(default_factory=list) # 搜索记录列表
+ latest_summary: str = "" # 当前段落的最新总结
+ reflection_iteration: int = 0 # 反思迭代次数
+ is_completed: bool = False # 是否完成研究
+
+ def add_search(self, search: Search):
+ """添加搜索记录"""
+ self.search_history.append(search)
+
+ def add_search_results(self, query: str, results: List[Dict[str, Any]]):
+ """批量添加搜索结果"""
+ for result in results:
+ search = Search(
+ query=query,
+ url=result.get("url", ""),
+ title=result.get("title", ""),
+ content=result.get("content", ""),
+ score=result.get("score")
+ )
+ self.add_search(search)
+
+ def get_search_count(self) -> int:
+ """获取搜索次数"""
+ return len(self.search_history)
+
+ def increment_reflection(self):
+ """增加反思次数"""
+ self.reflection_iteration += 1
+
+ def mark_completed(self):
+ """标记为完成"""
+ self.is_completed = True
+
+ def to_dict(self) -> Dict[str, Any]:
+ """转换为字典格式"""
+ return {
+ "search_history": [search.to_dict() for search in self.search_history],
+ "latest_summary": self.latest_summary,
+ "reflection_iteration": self.reflection_iteration,
+ "is_completed": self.is_completed
+ }
+
+ @classmethod
+ def from_dict(cls, data: Dict[str, Any]) -> "Research":
+ """从字典创建Research对象"""
+ search_history = [Search.from_dict(search_data) for search_data in data.get("search_history", [])]
+ return cls(
+ search_history=search_history,
+ latest_summary=data.get("latest_summary", ""),
+ reflection_iteration=data.get("reflection_iteration", 0),
+ is_completed=data.get("is_completed", False)
+ )
+
+
+@dataclass
+class Paragraph:
+ """报告中单个段落的状态"""
+ title: str = "" # 段落标题
+ content: str = "" # 段落的预期内容(初始规划)
+ research: Research = field(default_factory=Research) # 研究进度
+ order: int = 0 # 段落顺序
+
+ def is_completed(self) -> bool:
+ """检查段落是否完成"""
+ return self.research.is_completed and bool(self.research.latest_summary)
+
+ def get_final_content(self) -> str:
+ """获取最终内容"""
+ return self.research.latest_summary or self.content
+
+ def to_dict(self) -> Dict[str, Any]:
+ """转换为字典格式"""
+ return {
+ "title": self.title,
+ "content": self.content,
+ "research": self.research.to_dict(),
+ "order": self.order
+ }
+
+ @classmethod
+ def from_dict(cls, data: Dict[str, Any]) -> "Paragraph":
+ """从字典创建Paragraph对象"""
+ research_data = data.get("research", {})
+ research = Research.from_dict(research_data) if research_data else Research()
+
+ return cls(
+ title=data.get("title", ""),
+ content=data.get("content", ""),
+ research=research,
+ order=data.get("order", 0)
+ )
+
+
+@dataclass
+class State:
+ """整个报告的状态"""
+ query: str = "" # 原始查询
+ report_title: str = "" # 报告标题
+ paragraphs: List[Paragraph] = field(default_factory=list) # 段落列表
+ final_report: str = "" # 最终报告内容
+ is_completed: bool = False # 是否完成
+ created_at: str = field(default_factory=lambda: datetime.now().isoformat())
+ updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
+
+ def add_paragraph(self, title: str, content: str) -> int:
+ """
+ 添加段落
+
+ Args:
+ title: 段落标题
+ content: 段落内容
+
+ Returns:
+ 段落索引
+ """
+ order = len(self.paragraphs)
+ paragraph = Paragraph(title=title, content=content, order=order)
+ self.paragraphs.append(paragraph)
+ self.update_timestamp()
+ return order
+
+ def get_paragraph(self, index: int) -> Optional[Paragraph]:
+ """获取指定索引的段落"""
+ if 0 <= index < len(self.paragraphs):
+ return self.paragraphs[index]
+ return None
+
+ def get_completed_paragraphs_count(self) -> int:
+ """获取已完成段落数量"""
+ return sum(1 for p in self.paragraphs if p.is_completed())
+
+ def get_total_paragraphs_count(self) -> int:
+ """获取总段落数量"""
+ return len(self.paragraphs)
+
+ def is_all_paragraphs_completed(self) -> bool:
+ """检查是否所有段落都完成"""
+ return all(p.is_completed() for p in self.paragraphs) if self.paragraphs else False
+
+ def mark_completed(self):
+ """标记整个报告为完成"""
+ self.is_completed = True
+ self.update_timestamp()
+
+ def update_timestamp(self):
+ """更新时间戳"""
+ self.updated_at = datetime.now().isoformat()
+
+ def get_progress_summary(self) -> Dict[str, Any]:
+ """获取进度摘要"""
+ completed = self.get_completed_paragraphs_count()
+ total = self.get_total_paragraphs_count()
+
+ return {
+ "total_paragraphs": total,
+ "completed_paragraphs": completed,
+ "progress_percentage": (completed / total * 100) if total > 0 else 0,
+ "is_completed": self.is_completed,
+ "created_at": self.created_at,
+ "updated_at": self.updated_at
+ }
+
+ def to_dict(self) -> Dict[str, Any]:
+ """转换为字典格式"""
+ return {
+ "query": self.query,
+ "report_title": self.report_title,
+ "paragraphs": [p.to_dict() for p in self.paragraphs],
+ "final_report": self.final_report,
+ "is_completed": self.is_completed,
+ "created_at": self.created_at,
+ "updated_at": self.updated_at
+ }
+
+ def to_json(self, indent: int = 2) -> str:
+ """转换为JSON字符串"""
+ return json.dumps(self.to_dict(), indent=indent, ensure_ascii=False)
+
+ @classmethod
+ def from_dict(cls, data: Dict[str, Any]) -> "State":
+ """从字典创建State对象"""
+ paragraphs = [Paragraph.from_dict(p_data) for p_data in data.get("paragraphs", [])]
+
+ return cls(
+ query=data.get("query", ""),
+ report_title=data.get("report_title", ""),
+ paragraphs=paragraphs,
+ final_report=data.get("final_report", ""),
+ is_completed=data.get("is_completed", False),
+ created_at=data.get("created_at", datetime.now().isoformat()),
+ updated_at=data.get("updated_at", datetime.now().isoformat())
+ )
+
+ @classmethod
+ def from_json(cls, json_str: str) -> "State":
+ """从JSON字符串创建State对象"""
+ data = json.loads(json_str)
+ return cls.from_dict(data)
+
+ def save_to_file(self, filepath: str):
+ """保存状态到文件"""
+ with open(filepath, 'w', encoding='utf-8') as f:
+ f.write(self.to_json())
+
+ @classmethod
+ def load_from_file(cls, filepath: str) -> "State":
+ """从文件加载状态"""
+ with open(filepath, 'r', encoding='utf-8') as f:
+ json_str = f.read()
+ return cls.from_json(json_str)
diff --git a/QueryEngine/tools/__init__.py b/QueryEngine/tools/__init__.py
new file mode 100644
index 0000000..aa05568
--- /dev/null
+++ b/QueryEngine/tools/__init__.py
@@ -0,0 +1,20 @@
+"""
+工具调用模块
+提供外部工具接口,如网络搜索等
+"""
+
+from .search import (
+ TavilyNewsAgency,
+ SearchResult,
+ TavilyResponse,
+ ImageResult,
+ print_response_summary
+)
+
+__all__ = [
+ "TavilyNewsAgency",
+ "SearchResult",
+ "TavilyResponse",
+ "ImageResult",
+ "print_response_summary"
+]
diff --git a/QueryEngine/tools/search.py b/QueryEngine/tools/search.py
new file mode 100644
index 0000000..cf7b611
--- /dev/null
+++ b/QueryEngine/tools/search.py
@@ -0,0 +1,240 @@
+"""
+专为 AI Agent 设计的舆情搜索工具集 (Tavily)
+
+版本: 1.5
+最后更新: 2025-08-22
+
+此脚本将复杂的Tavily搜索功能分解为一系列目标明确、参数极少的独立工具,
+专为AI Agent调用而设计。Agent只需根据任务意图选择合适的工具,
+无需理解复杂的参数组合。所有工具默认搜索“新闻”(topic='news')。
+
+新特性:
+- 新增 `basic_search_news` 工具,用于执行标准、通用的新闻搜索。
+- 每个搜索结果现在都包含 `published_date` (新闻发布日期)。
+
+主要工具:
+- basic_search_news: (新增) 执行标准、快速的通用新闻搜索。
+- deep_search_news: 对主题进行最全面的深度分析。
+- search_news_last_24_hours: 获取24小时内的最新动态。
+- search_news_last_week: 获取过去一周的主要报道。
+- search_images_for_news: 查找与新闻主题相关的图片。
+- search_news_by_date: 在指定的历史日期范围内搜索。
+"""
+
+import os
+from typing import List, Dict, Any, Optional
+from dataclasses import dataclass, field
+
+# 运行前请确保已安装Tavily库: pip install tavily-python
+try:
+ from tavily import TavilyClient
+except ImportError:
+ raise ImportError("Tavily库未安装,请运行 `pip install tavily-python` 进行安装。")
+
+# --- 1. 数据结构定义 ---
+
+@dataclass
+class SearchResult:
+ """
+ 网页搜索结果数据类
+ 包含 published_date 属性来存储新闻发布日期
+ """
+ title: str
+ url: str
+ content: str
+ score: Optional[float] = None
+ raw_content: Optional[str] = None
+ published_date: Optional[str] = None
+
+@dataclass
+class ImageResult:
+ """图片搜索结果数据类"""
+ url: str
+ description: Optional[str] = None
+
+@dataclass
+class TavilyResponse:
+ """封装Tavily API的完整返回结果,以便在工具间传递"""
+ query: str
+ answer: Optional[str] = None
+ results: List[SearchResult] = field(default_factory=list)
+ images: List[ImageResult] = field(default_factory=list)
+ response_time: Optional[float] = None
+
+
+# --- 2. 核心客户端与专用工具集 ---
+
+class TavilyNewsAgency:
+ """
+ 一个包含多种专用新闻舆情搜索工具的客户端。
+ 每个公共方法都设计为供 AI Agent 独立调用的工具。
+ """
+
+ def __init__(self, api_key: Optional[str] = None):
+ """
+ 初始化客户端。
+ Args:
+ api_key: Tavily API密钥,若不提供则从环境变量 TAVILY_API_KEY 读取。
+ """
+ if api_key is None:
+ api_key = os.getenv("TAVILY_API_KEY")
+ if not api_key:
+ raise ValueError("Tavily API Key未找到!请设置TAVILY_API_KEY环境变量或在初始化时提供")
+ self._client = TavilyClient(api_key=api_key)
+
+ def _search_internal(self, **kwargs) -> TavilyResponse:
+ """内部通用的搜索执行器,所有工具最终都调用此方法"""
+ try:
+ kwargs['topic'] = 'general'
+ api_params = {k: v for k, v in kwargs.items() if v is not None}
+ response_dict = self._client.search(**api_params)
+
+ search_results = [
+ SearchResult(
+ title=item.get('title'),
+ url=item.get('url'),
+ content=item.get('content'),
+ score=item.get('score'),
+ raw_content=item.get('raw_content'),
+ published_date=item.get('published_date')
+ ) for item in response_dict.get('results', [])
+ ]
+
+ image_results = [ImageResult(url=item.get('url'), description=item.get('description')) for item in response_dict.get('images', [])]
+
+ return TavilyResponse(
+ query=response_dict.get('query'), answer=response_dict.get('answer'),
+ results=search_results, images=image_results,
+ response_time=response_dict.get('response_time')
+ )
+ except Exception as e:
+ print(f"搜索时发生错误: {str(e)}")
+ return TavilyResponse(query=kwargs.get("query", "Unknown Query"))
+
+ # --- Agent 可用的工具方法 ---
+
+ def basic_search_news(self, query: str, max_results: int = 7) -> TavilyResponse:
+ """
+ 【工具】基础新闻搜索: 执行一次标准、快速的新闻搜索。
+ 这是最常用的通用搜索工具,适用于不确定需要何种特定搜索时。
+ Agent可提供搜索查询(query)和可选的最大结果数(max_results)。
+ """
+ print(f"--- TOOL: 基础新闻搜索 (query: {query}) ---")
+ return self._search_internal(
+ query=query,
+ max_results=max_results,
+ search_depth="basic",
+ include_answer=False
+ )
+
+ def deep_search_news(self, query: str) -> TavilyResponse:
+ """
+ 【工具】深度新闻分析: 对一个主题进行最全面、最深入的搜索。
+ 返回AI生成的“高级”详细摘要答案和最多20条最相关的新闻结果。适用于需要全面了解某个事件背景的场景。
+ Agent只需提供搜索查询(query)。
+ """
+ print(f"--- TOOL: 深度新闻分析 (query: {query}) ---")
+ return self._search_internal(
+ query=query, search_depth="advanced", max_results=20, include_answer="advanced"
+ )
+
+ def search_news_last_24_hours(self, query: str) -> TavilyResponse:
+ """
+ 【工具】搜索24小时内新闻: 获取关于某个主题的最新动态。
+ 此工具专门查找过去24小时内发布的新闻。适用于追踪突发事件或最新进展。
+ Agent只需提供搜索查询(query)。
+ """
+ print(f"--- TOOL: 搜索24小时内新闻 (query: {query}) ---")
+ return self._search_internal(query=query, time_range='d', max_results=10)
+
+ def search_news_last_week(self, query: str) -> TavilyResponse:
+ """
+ 【工具】搜索本周新闻: 获取关于某个主题过去一周内的主要新闻报道。
+ 适用于进行周度舆情总结或回顾。
+ Agent只需提供搜索查询(query)。
+ """
+ print(f"--- TOOL: 搜索本周新闻 (query: {query}) ---")
+ return self._search_internal(query=query, time_range='w', max_results=10)
+
+ def search_images_for_news(self, query: str) -> TavilyResponse:
+ """
+ 【工具】查找新闻图片: 搜索与某个新闻主题相关的图片。
+ 此工具会返回图片链接及描述,适用于需要为报告或文章配图的场景。
+ Agent只需提供搜索查询(query)。
+ """
+ print(f"--- TOOL: 查找新闻图片 (query: {query}) ---")
+ return self._search_internal(
+ query=query, include_images=True, include_image_descriptions=True, max_results=5
+ )
+
+ def search_news_by_date(self, query: str, start_date: str, end_date: str) -> TavilyResponse:
+ """
+ 【工具】按指定日期范围搜索新闻: 在一个明确的历史时间段内搜索新闻。
+ 这是唯一需要Agent提供详细时间参数的工具。适用于需要对特定历史事件进行分析的场景。
+ Agent需要提供查询(query)、开始日期(start_date)和结束日期(end_date),格式均为 'YYYY-MM-DD'。
+ """
+ print(f"--- TOOL: 按指定日期范围搜索新闻 (query: {query}, from: {start_date}, to: {end_date}) ---")
+ return self._search_internal(
+ query=query, start_date=start_date, end_date=end_date, max_results=15
+ )
+
+
+# --- 3. 测试与使用示例 ---
+
+def print_response_summary(response: TavilyResponse):
+ """简化的打印函数,用于展示测试结果,现在会显示发布日期"""
+ if not response or not response.query:
+ print("未能获取有效响应。")
+ return
+
+ print(f"\n查询: '{response.query}' | 耗时: {response.response_time}s")
+ if response.answer:
+ print(f"AI摘要: {response.answer[:120]}...")
+ print(f"找到 {len(response.results)} 条网页, {len(response.images)} 张图片。")
+ if response.results:
+ first_result = response.results[0]
+ date_info = f"(发布于: {first_result.published_date})" if first_result.published_date else ""
+ print(f"第一条结果: {first_result.title} {date_info}")
+ print("-" * 60)
+
+
+if __name__ == "__main__":
+ # 在运行前,请确保您已设置 TAVILY_API_KEY 环境变量
+
+ try:
+ # 初始化“新闻社”客户端,它内部包含了所有工具
+ agency = TavilyNewsAgency()
+
+ # 场景1: Agent 进行一次常规、快速的搜索
+ response1 = agency.basic_search_news(query="奥运会最新赛况", max_results=5)
+ print_response_summary(response1)
+
+ # 场景2: Agent 需要全面了解“全球芯片技术竞争”的背景
+ response2 = agency.deep_search_news(query="全球芯片技术竞争")
+ print_response_summary(response2)
+
+ # 场景3: Agent 需要追踪“GTC大会”的最新消息
+ response3 = agency.search_news_last_24_hours(query="Nvidia GTC大会 最新发布")
+ print_response_summary(response3)
+
+ # 场景4: Agent 需要为一篇关于“自动驾驶”的周报查找素材
+ response4 = agency.search_news_last_week(query="自动驾驶商业化落地")
+ print_response_summary(response4)
+
+ # 场景5: Agent 需要查找“韦伯太空望远镜”的新闻图片
+ response5 = agency.search_images_for_news(query="韦伯太空望远镜最新发现")
+ print_response_summary(response5)
+
+ # 场景6: Agent 需要研究2025年第一季度关于“人工智能法规”的新闻
+ response6 = agency.search_news_by_date(
+ query="人工智能法规",
+ start_date="2025-01-01",
+ end_date="2025-03-31"
+ )
+ print_response_summary(response6)
+
+ except ValueError as e:
+ print(f"初始化失败: {e}")
+ print("请确保 TAVILY_API_KEY 环境变量已正确设置。")
+ except Exception as e:
+ print(f"测试过程中发生未知错误: {e}")
\ No newline at end of file
diff --git a/QueryEngine/utils/__init__.py b/QueryEngine/utils/__init__.py
new file mode 100644
index 0000000..f324bef
--- /dev/null
+++ b/QueryEngine/utils/__init__.py
@@ -0,0 +1,26 @@
+"""
+工具函数模块
+提供文本处理、JSON解析等辅助功能
+"""
+
+from .text_processing import (
+ clean_json_tags,
+ clean_markdown_tags,
+ remove_reasoning_from_output,
+ extract_clean_response,
+ update_state_with_search_results,
+ format_search_results_for_prompt
+)
+
+from .config import Config, load_config
+
+__all__ = [
+ "clean_json_tags",
+ "clean_markdown_tags",
+ "remove_reasoning_from_output",
+ "extract_clean_response",
+ "update_state_with_search_results",
+ "format_search_results_for_prompt",
+ "Config",
+ "load_config"
+]
diff --git a/QueryEngine/utils/config.py b/QueryEngine/utils/config.py
new file mode 100644
index 0000000..f000bd8
--- /dev/null
+++ b/QueryEngine/utils/config.py
@@ -0,0 +1,162 @@
+"""
+配置管理模块
+处理环境变量和配置参数
+"""
+
+import os
+from dataclasses import dataclass
+from typing import Optional
+
+
+@dataclass
+class Config:
+ """配置类"""
+ # API密钥
+ deepseek_api_key: Optional[str] = None
+ openai_api_key: Optional[str] = None
+ tavily_api_key: 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
+
+ # 输出配置
+ output_dir: str = "reports"
+ save_intermediate_states: bool = True
+
+ def validate(self) -> bool:
+ """验证配置"""
+ # 检查必需的API密钥
+ if self.default_llm_provider == "deepseek" and not self.deepseek_api_key:
+ print("错误: DeepSeek API Key未设置")
+ return False
+
+ if self.default_llm_provider == "openai" and not self.openai_api_key:
+ print("错误: OpenAI API Key未设置")
+ return False
+
+ if not self.tavily_api_key:
+ print("错误: Tavily API Key未设置")
+ return False
+
+ return True
+
+ @classmethod
+ def from_file(cls, config_file: str) -> "Config":
+ """从配置文件创建配置"""
+ if config_file.endswith('.py'):
+ # Python配置文件
+ 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),
+ 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"),
+ 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"
+ )
+
+
+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}")
+ break
+ else:
+ raise FileNotFoundError("未找到配置文件,请创建 config.py 文件")
+
+ # 创建配置对象
+ config = Config.from_file(file_to_load)
+
+ # 验证配置
+ if not config.validate():
+ raise ValueError("配置验证失败,请检查配置文件中的API密钥")
+
+ 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(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"Tavily API Key: {'已设置' if config.tavily_api_key else '未设置'}")
+ print("==================\n")
diff --git a/QueryEngine/utils/text_processing.py b/QueryEngine/utils/text_processing.py
new file mode 100644
index 0000000..471650c
--- /dev/null
+++ b/QueryEngine/utils/text_processing.py
@@ -0,0 +1,308 @@
+"""
+文本处理工具函数
+用于清理LLM输出、解析JSON等
+"""
+
+import re
+import json
+from typing import Dict, Any, List
+from json.decoder import JSONDecodeError
+
+
+def clean_json_tags(text: str) -> str:
+ """
+ 清理文本中的JSON标签
+
+ Args:
+ text: 原始文本
+
+ Returns:
+ 清理后的文本
+ """
+ # 移除```json 和 ```标签
+ text = re.sub(r'```json\s*', '', text)
+ text = re.sub(r'```\s*$', '', text)
+ text = re.sub(r'```', '', text)
+
+ return text.strip()
+
+
+def clean_markdown_tags(text: str) -> str:
+ """
+ 清理文本中的Markdown标签
+
+ Args:
+ text: 原始文本
+
+ Returns:
+ 清理后的文本
+ """
+ # 移除```markdown 和 ```标签
+ text = re.sub(r'```markdown\s*', '', text)
+ text = re.sub(r'```\s*$', '', text)
+ text = re.sub(r'```', '', text)
+
+ return text.strip()
+
+
+def remove_reasoning_from_output(text: str) -> str:
+ """
+ 移除输出中的推理过程文本
+
+ Args:
+ text: 原始文本
+
+ Returns:
+ 清理后的文本
+ """
+ # 查找JSON开始位置
+ json_start = -1
+
+ # 尝试找到第一个 { 或 [
+ for i, char in enumerate(text):
+ if char in '{[':
+ json_start = i
+ break
+
+ if json_start != -1:
+ # 从JSON开始位置截取
+ return text[json_start:].strip()
+
+ # 如果没有找到JSON标记,尝试其他方法
+ # 移除常见的推理标识
+ patterns = [
+ r'(?:reasoning|推理|思考|分析)[::]\s*.*?(?=\{|\[)', # 移除推理部分
+ r'(?:explanation|解释|说明)[::]\s*.*?(?=\{|\[)', # 移除解释部分
+ r'^.*?(?=\{|\[)', # 移除JSON前的所有文本
+ ]
+
+ for pattern in patterns:
+ text = re.sub(pattern, '', text, flags=re.IGNORECASE | re.DOTALL)
+
+ return text.strip()
+
+
+def extract_clean_response(text: str) -> Dict[str, Any]:
+ """
+ 提取并清理响应中的JSON内容
+
+ Args:
+ text: 原始响应文本
+
+ Returns:
+ 解析后的JSON字典
+ """
+ # 清理文本
+ cleaned_text = clean_json_tags(text)
+ cleaned_text = remove_reasoning_from_output(cleaned_text)
+
+ # 尝试直接解析
+ try:
+ return json.loads(cleaned_text)
+ except JSONDecodeError:
+ pass
+
+ # 尝试修复不完整的JSON
+ fixed_text = fix_incomplete_json(cleaned_text)
+ if fixed_text:
+ try:
+ return json.loads(fixed_text)
+ except JSONDecodeError:
+ pass
+
+ # 尝试查找JSON对象
+ json_pattern = r'\{.*\}'
+ match = re.search(json_pattern, cleaned_text, re.DOTALL)
+ if match:
+ try:
+ return json.loads(match.group())
+ except JSONDecodeError:
+ pass
+
+ # 尝试查找JSON数组
+ array_pattern = r'\[.*\]'
+ match = re.search(array_pattern, cleaned_text, re.DOTALL)
+ if match:
+ try:
+ return json.loads(match.group())
+ except JSONDecodeError:
+ pass
+
+ # 如果所有方法都失败,返回错误信息
+ print(f"无法解析JSON响应: {cleaned_text[:200]}...")
+ return {"error": "JSON解析失败", "raw_text": cleaned_text}
+
+
+def fix_incomplete_json(text: str) -> str:
+ """
+ 修复不完整的JSON响应
+
+ Args:
+ text: 原始文本
+
+ Returns:
+ 修复后的JSON文本,如果无法修复则返回空字符串
+ """
+ # 移除多余的逗号和空白
+ text = re.sub(r',\s*}', '}', text)
+ text = re.sub(r',\s*]', ']', text)
+
+ # 检查是否已经是有效的JSON
+ try:
+ json.loads(text)
+ return text
+ except JSONDecodeError:
+ pass
+
+ # 检查是否缺少开头的数组符号
+ if text.strip().startswith('{') and not text.strip().startswith('['):
+ # 如果以对象开始,尝试包装成数组
+ if text.count('{') > 1:
+ # 多个对象,包装成数组
+ text = '[' + text + ']'
+ else:
+ # 单个对象,包装成数组
+ text = '[' + text + ']'
+
+ # 检查是否缺少结尾的数组符号
+ if text.strip().endswith('}') and not text.strip().endswith(']'):
+ # 如果以对象结束,尝试包装成数组
+ if text.count('}') > 1:
+ # 多个对象,包装成数组
+ text = '[' + text + ']'
+ else:
+ # 单个对象,包装成数组
+ text = '[' + text + ']'
+
+ # 检查括号是否匹配
+ open_braces = text.count('{')
+ close_braces = text.count('}')
+ open_brackets = text.count('[')
+ close_brackets = text.count(']')
+
+ # 修复不匹配的括号
+ if open_braces > close_braces:
+ text += '}' * (open_braces - close_braces)
+ if open_brackets > close_brackets:
+ text += ']' * (open_brackets - close_brackets)
+
+ # 验证修复后的JSON是否有效
+ try:
+ json.loads(text)
+ return text
+ except JSONDecodeError:
+ # 如果仍然无效,尝试更激进的修复
+ return fix_aggressive_json(text)
+
+
+def fix_aggressive_json(text: str) -> str:
+ """
+ 更激进的JSON修复方法
+
+ Args:
+ text: 原始文本
+
+ Returns:
+ 修复后的JSON文本
+ """
+ # 查找所有可能的JSON对象
+ objects = re.findall(r'\{[^{}]*\}', text)
+
+ if len(objects) >= 2:
+ # 如果有多个对象,包装成数组
+ return '[' + ','.join(objects) + ']'
+ elif len(objects) == 1:
+ # 如果只有一个对象,包装成数组
+ return '[' + objects[0] + ']'
+ else:
+ # 如果没有找到对象,返回空数组
+ return '[]'
+
+
+def update_state_with_search_results(search_results: List[Dict[str, Any]],
+ paragraph_index: int, state: Any) -> Any:
+ """
+ 将搜索结果更新到状态中
+
+ Args:
+ search_results: 搜索结果列表
+ paragraph_index: 段落索引
+ state: 状态对象
+
+ Returns:
+ 更新后的状态对象
+ """
+ if 0 <= paragraph_index < len(state.paragraphs):
+ # 获取最后一次搜索的查询(假设是当前查询)
+ current_query = ""
+ if search_results:
+ # 从搜索结果推断查询(这里需要改进以获取实际查询)
+ current_query = "搜索查询"
+
+ # 添加搜索结果到状态
+ state.paragraphs[paragraph_index].research.add_search_results(
+ current_query, search_results
+ )
+
+ return state
+
+
+def validate_json_schema(data: Dict[str, Any], required_fields: List[str]) -> bool:
+ """
+ 验证JSON数据是否包含必需字段
+
+ Args:
+ data: 要验证的数据
+ required_fields: 必需字段列表
+
+ Returns:
+ 验证是否通过
+ """
+ return all(field in data for field in required_fields)
+
+
+def truncate_content(content: str, max_length: int = 20000) -> str:
+ """
+ 截断内容到指定长度
+
+ Args:
+ content: 原始内容
+ max_length: 最大长度
+
+ Returns:
+ 截断后的内容
+ """
+ if len(content) <= max_length:
+ return content
+
+ # 尝试在单词边界截断
+ truncated = content[:max_length]
+ last_space = truncated.rfind(' ')
+
+ if last_space > max_length * 0.8: # 如果最后一个空格位置合理
+ return truncated[:last_space] + "..."
+ else:
+ return truncated + "..."
+
+
+def format_search_results_for_prompt(search_results: List[Dict[str, Any]],
+ max_length: int = 20000) -> List[str]:
+ """
+ 格式化搜索结果用于提示词
+
+ Args:
+ search_results: 搜索结果列表
+ max_length: 每个结果的最大长度
+
+ Returns:
+ 格式化后的内容列表
+ """
+ formatted_results = []
+
+ for result in search_results:
+ content = result.get('content', '')
+ if content:
+ truncated_content = truncate_content(content, max_length)
+ formatted_results.append(truncated_content)
+
+ return formatted_results
diff --git a/streamlit_app.py b/insight_engine_streamlit_app.py
similarity index 98%
rename from streamlit_app.py
rename to insight_engine_streamlit_app.py
index 2e44e8e..d443357 100644
--- a/streamlit_app.py
+++ b/insight_engine_streamlit_app.py
@@ -12,7 +12,7 @@ import json
# 添加src目录到Python路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '.'))
-from src import DeepSearchAgent, Config
+from QueryEngine import DeepSearchAgent, Config
from config import DEEPSEEK_API_KEY, TAVILY_API_KEY
@@ -110,7 +110,7 @@ def main():
openai_model=model_name if llm_provider == "openai" else "gpt-4o-mini",
max_reflections=max_reflections,
max_content_length=max_content_length,
- output_dir="streamlit_reports"
+ output_dir="query_engine_streamlit_reports"
)
# 执行研究
diff --git a/media_engine_streamlit_app.py b/media_engine_streamlit_app.py
new file mode 100644
index 0000000..d443357
--- /dev/null
+++ b/media_engine_streamlit_app.py
@@ -0,0 +1,235 @@
+"""
+Streamlit Web界面
+为Deep Search Agent提供友好的Web界面
+"""
+
+import os
+import sys
+import streamlit as st
+from datetime import datetime
+import json
+
+# 添加src目录到Python路径
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '.'))
+
+from QueryEngine import DeepSearchAgent, Config
+from config import DEEPSEEK_API_KEY, TAVILY_API_KEY
+
+
+def main():
+ """主函数"""
+ st.set_page_config(
+ page_title="Deep Search Agent",
+ page_icon="🔍",
+ layout="wide"
+ )
+
+ st.title("Deep Search Agent")
+ st.markdown("基于DeepSeek的无框架深度搜索AI代理")
+
+ # 侧边栏配置
+ with st.sidebar:
+ st.header("配置")
+
+ # 高级配置
+ st.subheader("高级配置")
+ max_reflections = st.slider("反思次数", 1, 5, 2)
+ max_content_length = st.number_input("最大内容长度", 1000, 50000, 20000)
+
+ # 模型选择
+ llm_provider = st.selectbox("LLM提供商", ["deepseek", "openai"])
+
+ if llm_provider == "deepseek":
+ model_name = st.selectbox("DeepSeek模型", ["deepseek-chat"])
+ else:
+ model_name = st.selectbox("OpenAI模型", ["gpt-4o-mini", "gpt-4o"])
+ openai_key = st.text_input("OpenAI API Key", type="password",
+ value="")
+
+ # 主界面
+ col1, col2 = st.columns([2, 1])
+
+ with col1:
+ st.header("研究查询")
+ query = st.text_area(
+ "请输入您要研究的问题",
+ placeholder="例如:2025年人工智能发展趋势",
+ height=100
+ )
+
+ # 预设查询示例
+ st.subheader("示例查询")
+ example_queries = [
+ "2025年人工智能发展趋势",
+ "深度学习在医疗领域的应用",
+ "区块链技术的最新发展",
+ "可持续能源技术趋势",
+ "量子计算的发展现状"
+ ]
+
+ selected_example = st.selectbox("选择示例查询", ["自定义"] + example_queries)
+ if selected_example != "自定义":
+ query = selected_example
+
+ with col2:
+ st.header("状态信息")
+ if 'agent' in st.session_state and hasattr(st.session_state.agent, 'state'):
+ progress = st.session_state.agent.get_progress_summary()
+ st.metric("总段落数", progress['total_paragraphs'])
+ st.metric("已完成", progress['completed_paragraphs'])
+ st.progress(progress['progress_percentage'] / 100)
+ else:
+ st.info("尚未开始研究")
+
+ # 执行按钮
+ col1, col2, col3 = st.columns([1, 1, 1])
+ with col2:
+ start_research = st.button("开始研究", type="primary", use_container_width=True)
+
+ # 验证配置
+ if start_research:
+ if not query.strip():
+ st.error("请输入研究查询")
+ return
+
+ if llm_provider == "openai" and not openai_key:
+ st.error("请提供OpenAI API Key")
+ return
+
+ # 自动使用配置文件中的API密钥
+ deepseek_key = DEEPSEEK_API_KEY
+ tavily_key = TAVILY_API_KEY
+
+ # 创建配置
+ config = Config(
+ deepseek_api_key=deepseek_key if llm_provider == "deepseek" else None,
+ openai_api_key=openai_key if llm_provider == "openai" else None,
+ tavily_api_key=tavily_key,
+ default_llm_provider=llm_provider,
+ deepseek_model=model_name if llm_provider == "deepseek" else "deepseek-chat",
+ openai_model=model_name if llm_provider == "openai" else "gpt-4o-mini",
+ max_reflections=max_reflections,
+ max_content_length=max_content_length,
+ output_dir="query_engine_streamlit_reports"
+ )
+
+ # 执行研究
+ execute_research(query, config)
+
+
+def execute_research(query: str, config: Config):
+ """执行研究"""
+ try:
+ # 创建进度条
+ progress_bar = st.progress(0)
+ status_text = st.empty()
+
+ # 初始化Agent
+ status_text.text("正在初始化Agent...")
+ agent = DeepSearchAgent(config)
+ st.session_state.agent = agent
+
+ progress_bar.progress(10)
+
+ # 生成报告结构
+ status_text.text("正在生成报告结构...")
+ agent._generate_report_structure(query)
+ progress_bar.progress(20)
+
+ # 处理段落
+ total_paragraphs = len(agent.state.paragraphs)
+ for i in range(total_paragraphs):
+ status_text.text(f"正在处理段落 {i+1}/{total_paragraphs}: {agent.state.paragraphs[i].title}")
+
+ # 初始搜索和总结
+ agent._initial_search_and_summary(i)
+ progress_value = 20 + (i + 0.5) / total_paragraphs * 60
+ progress_bar.progress(int(progress_value))
+
+ # 反思循环
+ agent._reflection_loop(i)
+ agent.state.paragraphs[i].research.mark_completed()
+
+ progress_value = 20 + (i + 1) / total_paragraphs * 60
+ progress_bar.progress(int(progress_value))
+
+ # 生成最终报告
+ status_text.text("正在生成最终报告...")
+ final_report = agent._generate_final_report()
+ progress_bar.progress(90)
+
+ # 保存报告
+ status_text.text("正在保存报告...")
+ agent._save_report(final_report)
+ progress_bar.progress(100)
+
+ status_text.text("研究完成!")
+
+ # 显示结果
+ display_results(agent, final_report)
+
+ except Exception as e:
+ st.error(f"研究过程中发生错误: {str(e)}")
+
+
+def display_results(agent: DeepSearchAgent, final_report: str):
+ """显示研究结果"""
+ st.header("研究结果")
+
+ # 结果标签页
+ tab1, tab2, tab3 = st.tabs(["最终报告", "详细信息", "下载"])
+
+ with tab1:
+ st.markdown(final_report)
+
+ with tab2:
+ # 段落详情
+ st.subheader("段落详情")
+ for i, paragraph in enumerate(agent.state.paragraphs):
+ with st.expander(f"段落 {i+1}: {paragraph.title}"):
+ st.write("**预期内容:**", paragraph.content)
+ st.write("**最终内容:**", paragraph.research.latest_summary[:300] + "..."
+ if len(paragraph.research.latest_summary) > 300
+ else paragraph.research.latest_summary)
+ st.write("**搜索次数:**", paragraph.research.get_search_count())
+ st.write("**反思次数:**", paragraph.research.reflection_iteration)
+
+ # 搜索历史
+ st.subheader("搜索历史")
+ all_searches = []
+ for paragraph in agent.state.paragraphs:
+ all_searches.extend(paragraph.research.search_history)
+
+ if all_searches:
+ for i, search in enumerate(all_searches):
+ with st.expander(f"搜索 {i+1}: {search.query}"):
+ st.write("**URL:**", search.url)
+ st.write("**标题:**", search.title)
+ st.write("**内容预览:**", search.content[:200] + "..." if len(search.content) > 200 else search.content)
+ if search.score:
+ st.write("**相关度评分:**", search.score)
+
+ with tab3:
+ # 下载选项
+ st.subheader("下载报告")
+
+ # Markdown下载
+ st.download_button(
+ label="下载Markdown报告",
+ data=final_report,
+ file_name=f"deep_search_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md",
+ mime="text/markdown"
+ )
+
+ # JSON状态下载
+ state_json = agent.state.to_json()
+ st.download_button(
+ label="下载状态文件",
+ data=state_json,
+ file_name=f"deep_search_state_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
+ mime="application/json"
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/query_engine_streamlit_app.py b/query_engine_streamlit_app.py
new file mode 100644
index 0000000..d443357
--- /dev/null
+++ b/query_engine_streamlit_app.py
@@ -0,0 +1,235 @@
+"""
+Streamlit Web界面
+为Deep Search Agent提供友好的Web界面
+"""
+
+import os
+import sys
+import streamlit as st
+from datetime import datetime
+import json
+
+# 添加src目录到Python路径
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '.'))
+
+from QueryEngine import DeepSearchAgent, Config
+from config import DEEPSEEK_API_KEY, TAVILY_API_KEY
+
+
+def main():
+ """主函数"""
+ st.set_page_config(
+ page_title="Deep Search Agent",
+ page_icon="🔍",
+ layout="wide"
+ )
+
+ st.title("Deep Search Agent")
+ st.markdown("基于DeepSeek的无框架深度搜索AI代理")
+
+ # 侧边栏配置
+ with st.sidebar:
+ st.header("配置")
+
+ # 高级配置
+ st.subheader("高级配置")
+ max_reflections = st.slider("反思次数", 1, 5, 2)
+ max_content_length = st.number_input("最大内容长度", 1000, 50000, 20000)
+
+ # 模型选择
+ llm_provider = st.selectbox("LLM提供商", ["deepseek", "openai"])
+
+ if llm_provider == "deepseek":
+ model_name = st.selectbox("DeepSeek模型", ["deepseek-chat"])
+ else:
+ model_name = st.selectbox("OpenAI模型", ["gpt-4o-mini", "gpt-4o"])
+ openai_key = st.text_input("OpenAI API Key", type="password",
+ value="")
+
+ # 主界面
+ col1, col2 = st.columns([2, 1])
+
+ with col1:
+ st.header("研究查询")
+ query = st.text_area(
+ "请输入您要研究的问题",
+ placeholder="例如:2025年人工智能发展趋势",
+ height=100
+ )
+
+ # 预设查询示例
+ st.subheader("示例查询")
+ example_queries = [
+ "2025年人工智能发展趋势",
+ "深度学习在医疗领域的应用",
+ "区块链技术的最新发展",
+ "可持续能源技术趋势",
+ "量子计算的发展现状"
+ ]
+
+ selected_example = st.selectbox("选择示例查询", ["自定义"] + example_queries)
+ if selected_example != "自定义":
+ query = selected_example
+
+ with col2:
+ st.header("状态信息")
+ if 'agent' in st.session_state and hasattr(st.session_state.agent, 'state'):
+ progress = st.session_state.agent.get_progress_summary()
+ st.metric("总段落数", progress['total_paragraphs'])
+ st.metric("已完成", progress['completed_paragraphs'])
+ st.progress(progress['progress_percentage'] / 100)
+ else:
+ st.info("尚未开始研究")
+
+ # 执行按钮
+ col1, col2, col3 = st.columns([1, 1, 1])
+ with col2:
+ start_research = st.button("开始研究", type="primary", use_container_width=True)
+
+ # 验证配置
+ if start_research:
+ if not query.strip():
+ st.error("请输入研究查询")
+ return
+
+ if llm_provider == "openai" and not openai_key:
+ st.error("请提供OpenAI API Key")
+ return
+
+ # 自动使用配置文件中的API密钥
+ deepseek_key = DEEPSEEK_API_KEY
+ tavily_key = TAVILY_API_KEY
+
+ # 创建配置
+ config = Config(
+ deepseek_api_key=deepseek_key if llm_provider == "deepseek" else None,
+ openai_api_key=openai_key if llm_provider == "openai" else None,
+ tavily_api_key=tavily_key,
+ default_llm_provider=llm_provider,
+ deepseek_model=model_name if llm_provider == "deepseek" else "deepseek-chat",
+ openai_model=model_name if llm_provider == "openai" else "gpt-4o-mini",
+ max_reflections=max_reflections,
+ max_content_length=max_content_length,
+ output_dir="query_engine_streamlit_reports"
+ )
+
+ # 执行研究
+ execute_research(query, config)
+
+
+def execute_research(query: str, config: Config):
+ """执行研究"""
+ try:
+ # 创建进度条
+ progress_bar = st.progress(0)
+ status_text = st.empty()
+
+ # 初始化Agent
+ status_text.text("正在初始化Agent...")
+ agent = DeepSearchAgent(config)
+ st.session_state.agent = agent
+
+ progress_bar.progress(10)
+
+ # 生成报告结构
+ status_text.text("正在生成报告结构...")
+ agent._generate_report_structure(query)
+ progress_bar.progress(20)
+
+ # 处理段落
+ total_paragraphs = len(agent.state.paragraphs)
+ for i in range(total_paragraphs):
+ status_text.text(f"正在处理段落 {i+1}/{total_paragraphs}: {agent.state.paragraphs[i].title}")
+
+ # 初始搜索和总结
+ agent._initial_search_and_summary(i)
+ progress_value = 20 + (i + 0.5) / total_paragraphs * 60
+ progress_bar.progress(int(progress_value))
+
+ # 反思循环
+ agent._reflection_loop(i)
+ agent.state.paragraphs[i].research.mark_completed()
+
+ progress_value = 20 + (i + 1) / total_paragraphs * 60
+ progress_bar.progress(int(progress_value))
+
+ # 生成最终报告
+ status_text.text("正在生成最终报告...")
+ final_report = agent._generate_final_report()
+ progress_bar.progress(90)
+
+ # 保存报告
+ status_text.text("正在保存报告...")
+ agent._save_report(final_report)
+ progress_bar.progress(100)
+
+ status_text.text("研究完成!")
+
+ # 显示结果
+ display_results(agent, final_report)
+
+ except Exception as e:
+ st.error(f"研究过程中发生错误: {str(e)}")
+
+
+def display_results(agent: DeepSearchAgent, final_report: str):
+ """显示研究结果"""
+ st.header("研究结果")
+
+ # 结果标签页
+ tab1, tab2, tab3 = st.tabs(["最终报告", "详细信息", "下载"])
+
+ with tab1:
+ st.markdown(final_report)
+
+ with tab2:
+ # 段落详情
+ st.subheader("段落详情")
+ for i, paragraph in enumerate(agent.state.paragraphs):
+ with st.expander(f"段落 {i+1}: {paragraph.title}"):
+ st.write("**预期内容:**", paragraph.content)
+ st.write("**最终内容:**", paragraph.research.latest_summary[:300] + "..."
+ if len(paragraph.research.latest_summary) > 300
+ else paragraph.research.latest_summary)
+ st.write("**搜索次数:**", paragraph.research.get_search_count())
+ st.write("**反思次数:**", paragraph.research.reflection_iteration)
+
+ # 搜索历史
+ st.subheader("搜索历史")
+ all_searches = []
+ for paragraph in agent.state.paragraphs:
+ all_searches.extend(paragraph.research.search_history)
+
+ if all_searches:
+ for i, search in enumerate(all_searches):
+ with st.expander(f"搜索 {i+1}: {search.query}"):
+ st.write("**URL:**", search.url)
+ st.write("**标题:**", search.title)
+ st.write("**内容预览:**", search.content[:200] + "..." if len(search.content) > 200 else search.content)
+ if search.score:
+ st.write("**相关度评分:**", search.score)
+
+ with tab3:
+ # 下载选项
+ st.subheader("下载报告")
+
+ # Markdown下载
+ st.download_button(
+ label="下载Markdown报告",
+ data=final_report,
+ file_name=f"deep_search_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md",
+ mime="text/markdown"
+ )
+
+ # JSON状态下载
+ state_json = agent.state.to_json()
+ st.download_button(
+ label="下载状态文件",
+ data=state_json,
+ file_name=f"deep_search_state_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
+ mime="application/json"
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/streamlit_reports/deep_search_report_武汉大学舆情_20250821_232740.md b/streamlit_reports/deep_search_report_武汉大学舆情_20250821_232740.md
deleted file mode 100644
index d71d900..0000000
--- a/streamlit_reports/deep_search_report_武汉大学舆情_20250821_232740.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# 武汉大学图书馆性骚扰指控事件分析报告
-
-## 概述
-
-2023年武汉大学图书馆性骚扰指控事件引发广泛社会关注。事件始于2023年7月,武大研究生杨景媛在网络上举报本科生肖明滔在图书馆自习时实施性骚扰行为。校方随后对肖明滔作出纪律处分,但杨景媛不满处理结果,于2024年6月向法院提起诉讼。2025年7月25日,法院一审判决认为证据不足以认定性骚扰,驳回杨景媛全部诉求。判决后舆论持续发酵,网民质疑杨景媛涉嫌诬告和学术不端,同时批评校方最初处分决定。武汉大学于8月1日成立工作专班,宣布对涉事双方的纪律处分和学位论文进行全面调查复核。该事件折射出中国高校舆情管理、性别议题和网络举报机制面临的复杂挑战。近年来,师德师风、校园冲突等事件频繁成为舆论热议话题,暴露出高校在舆情危机管理中流程透明度不足、处理机制不完善的问题。面对舆情,校方回应往往滞后或模糊,如仅以“等上级指示”应对,反映出治理能力缺陷和系统性失灵。网络举报处理流程缺乏标准化,易引发二次舆情发酵,需加强制度建设与公众沟通。从2023年10月13日武汉大学发布的处理通报可以看出,该校对涉及学生的网上举报采取了调查核实机制,但在具体执行中仍存在改进空间。2025年7月31日,校长张平文对媒体回应法院驳回事件,显示校方在舆情管理上逐步加强对外沟通,但整体机制仍需完善,特别是在校园性骚扰事件处理流程和高校网络举报制度方面需要更系统化的规范。
-
-## 详细分析
-
-2023年10月,武汉大学发生的“图书馆事件”引发了广泛网络舆情。事件源于女生杨景媛在图书馆自习时指控男生肖某某实施“隔空性骚扰”,并拍摄5段视频要求其写下道歉信,随后将事件公开至网络,引发超10万浏览量。事件初期舆论焦点集中在对涉事学生的道德谴责,校方虽在48小时内迅速作出“记过”处分回应,但通报仅称“存在不雅行为,有损大学生形象”,未公开具体调查流程和行为定性依据,校方相关负责人承认该处分系为“给舆情降温”的应急处理措施。这种“既处理又不说明”的模糊态度引发公众质疑。舆情持续发酵导致当事人肖某某遭遇“开盒”式人肉搜索,私人信息被曝光,照片被制成花圈、遗像,不仅遭受同学孤立和网络暴力,更于10月17日被确诊为急性精神应激障碍(后确诊为创伤后应激障碍PTSD,自杀倾向达80%)。后经医学专家论证,肖某某动作符合特应性皮炎抓挠特征,法院最终认定不构成性骚扰。2024年7月25日,法院一审宣判驳回杨景媛全部诉讼请求,但校方未撤销对肖某某的处分决定。这一事件反映出高校舆情管理中回应透明度不足、重舆轻法的程序瑕疵与网络暴力治理的双重挑战,凸显了高校在危机公关中行政手段替代事实调查的治理困境。
-
-## 结论
-
-武汉大学图书馆性骚扰指控事件不仅是一起校园纠纷,更暴露了中国高校在舆情管理、性别议题处理和网络举报机制方面的系统性缺陷。事件发展过程中,校方初期的应急处分缺乏透明度,后续又未能及时根据法院判决调整处理决定,反映出行政干预替代事实调查的治理困境。同时,网络暴力的蔓延和对当事人造成的严重身心伤害警示我们需加强网络行为规范与心理健康支持。未来,高校应建立更透明的调查与回应机制,完善网络举报标准化流程,并在舆情管理中平衡法律程序与公众沟通,以避免类似事件的再次发生。
\ No newline at end of file
diff --git a/streamlit_reports/deep_search_report_武汉大学舆情_20250822_203056.md b/streamlit_reports/deep_search_report_武汉大学舆情_20250822_203056.md
deleted file mode 100644
index 85bae60..0000000
--- a/streamlit_reports/deep_search_report_武汉大学舆情_20250822_203056.md
+++ /dev/null
@@ -1,25 +0,0 @@
-# 武汉大学舆情分析报告
-
-## 武汉大学舆情概述
-
-武汉大学舆情在教育政策、学术争议和校园管理方面呈现出复杂态势。根据搜索结果,美国高校中关于校园警察活动、学费政策调整以及移民执法合作的争议,反映了校园管理政策对特定学生群体的影响,类似情境可能在中国高校的舆情讨论中出现。学术争议方面,如《大西洋月刊》报道的大学领导层在自由言论和机构中立性问题上的分歧,突显了高校内部治理和学术自由之间的张力,这与武汉大学作为顶尖学府可能面临的学术伦理和治理挑战相呼应。此外,隐私和民权组织呼吁终止校园监控以保护学生抗议者的行动,强调了校园管理中平衡安全与隐私权的重要性,这一议题在全球高校舆情中都具有相关性。2024年,国际高校舆情中还涉及学生示威活动(如哥伦比亚大学支持巴勒斯坦的抗议)、数据驱动校园管理趋势(如《高等教育纪事》报道的“数据知情校园”倡议),以及多元化教育政策争议(如特朗普政府对DEI项目的指导方针被法院阻止)。这些动态表明,武汉大学的舆情需关注教育政策的公平性、学术环境的开放性、校园管理的透明性,以及全球高等教育议题的本地化影响,确保在治理中回应多元利益相关者的关切。值得注意的是,2024年全球高校舆情还显示出对HBCU(历史悠久的黑人学院和大学)卫星校园扩展的关注,旨在加强黑人学生高等教育通道并促进地方经济多元化,这反映了教育政策与社会公平议题的紧密关联,可能为武汉大学在多元化招生和教育资源分配方面的舆情讨论提供参考框架。
-
-## 近期舆情热点事件
-
-武汉大学近期面临多项舆情热点事件,涉及学术不端指控、招生政策变化及校园管理问题。这些事件引发了广泛的公众讨论和媒体关注。学术不端方面,类似其他高校的情况,可能存在监管漏洞和调查程序不透明的问题,导致公众对学术诚信的质疑。招生政策变化方面,武汉大学可能调整了早期申请或特殊招生计划,引发了对公平性和透明度的讨论,这与全国范围内高校如印第安纳大学等探索招生选项以应对人口统计变化的趋势相呼应。校园管理事件中,可能涉及学生安全、移民政策影响或与执法部门的合作,这些措施对学生的心理和学习环境产生了显著影响,尤其是弱势群体如无证学生感到更加恐惧和不安,类似洛杉矶等地学校面临的移民打击引发的担忧。媒体对这些事件的报道加剧了公众的关注,要求校方加强透明度、公平性和学生权益保护。此外,舆情还反映出高校在多元化、公平和包容(DEI)项目上的争议,类似于特朗普政府政策被法院阻止的事件,凸显了政治环境对校园管理的间接影响。值得注意的是,2024年10月的媒体报道时间线显示,这些热点事件在秋季学期持续发酵,公众和媒体对高校治理问题的关注度较高,可能与招生季和学术活动密集期相关。
-
-## 舆情传播渠道与影响
-
-武汉大学的舆情传播主要通过社交媒体、新闻媒体和校园论坛等渠道进行。社交媒体如微博和微信公众号成为快速传播和互动的主要平台,新闻媒体则提供权威报道和深度分析,而校园论坛如珞珈山水BBS则反映了校内师生的实时讨论。这些舆情传播对大学声誉产生显著影响,正面舆情可提升招生吸引力和公共形象,而负面舆情则可能损害声誉,影响招生质量和公众信任。舆情管理需关注学生多样性、学术环境、职业发展以及高等教育政策等方面,以全面维护大学形象。此外,根据2024年高等教育相关报道,舆情事件如校园示威活动(例如哥伦比亚大学的亲巴勒斯坦抗议)突显了社交媒体在放大校园事件中的作用,可能对大学公共形象和招生产生即时负面影响。武汉大学需加强舆情监控和管理,整合多渠道反馈,提升应对能力,从而有效维护其教育品牌和社会影响力。
-
-## 校方应对与舆情管理策略
-
-武汉大学在舆情事件中的应对措施体现了系统化的危机管理策略。校方通常通过官方声明及时回应事件,例如在虚假安全威胁事件中迅速发布公告澄清事实,避免恐慌蔓延。危机公关方面,武汉大学借鉴了国际高校的经验,如普林斯顿大学等机构在言论自由与机构中立性方面的讨论,以及《高等教育纪事》中关于数据驱动校园管理的见解,强调通过建设性对话和维护信息透明度来增强舆情应对能力。在与学生和公众的沟通中,校方采用多渠道策略,包括社交媒体更新、校园会议和直接对话,以增强互动和信任。然而,有效性方面存在挑战,如参考哥伦比亚大学2024年学生示威事件中沟通不足导致的舆论反弹,突显了主动和及时沟通的重要性。总体而言,武汉大学的应对措施在控制信息传播和减少误解方面表现良好,但需进一步加强与学生群体的深度互动和实时反馈机制,以提升整体舆情管理效果。基于2024年搜索信息,校方在舆情管理中可能面临类似国际事件中的复杂性,需持续优化策略以应对动态变化。
-
-## 未来舆情趋势与建议
-
-武汉大学作为中国顶尖研究型大学,未来可能面临的舆情挑战包括学术不端事件引发的公众质疑、校园管理问题导致的负面舆论扩散,以及国际化进程中文化冲突带来的声誉风险。值得注意的是,2024年全球高校普遍面临的地缘政治相关校园示威活动(如哥伦比亚大学巴勒斯坦支持者示威)表明,国际政治议题可能引发校园舆情危机,武汉大学需警惕类似事件对学术环境与公共形象的影响。此外,根据《高等教育纪事报》的报道,高校还需关注学生多样性议题、数字化校园建设以及毕业生就业等可能产生的舆情热点。为维护学术声誉和社会信任,建议加强舆情监测系统的实时性与多平台覆盖,引入数据驱动决策(如《高等教育纪事报》倡导的“数据知情校园”模式),通过数据分析预测舆情趋势;建立快速响应机制处理突发舆情;同时通过透明化公开学术成果与管理制度,增强与媒体、校友及社会公众的主动沟通,构建积极的信息披露与反馈渠道,从而提升舆情管理的预见性与有效性。
-
-## 结论
-
-综合分析武汉大学的舆情态势,该校在教育政策、学术争议和校园管理方面面临复杂挑战,需持续关注全球高等教育议题的本地化影响。近期热点事件如学术不端和招生政策变化凸显了透明度和公平性的重要性,而舆情传播渠道的多样化要求校方加强监控和多平台管理。校方现有的应对策略在危机公关和信息控制方面表现良好,但需深化学生互动以提升效果。未来,武汉大学应聚焦数据驱动决策、快速响应机制和透明沟通,以有效应对学术不端、校园管理和国际化带来的舆情风险,维护其作为顶尖学府的声誉和公共信任。通过 proactive 措施和持续优化,武汉大学可以更好地 navigate 舆情动态,确保长期稳定发展。
\ No newline at end of file
diff --git a/streamlit_reports/state_武汉大学舆情_20250821_232740.json b/streamlit_reports/state_武汉大学舆情_20250821_232740.json
deleted file mode 100644
index 0a4292f..0000000
--- a/streamlit_reports/state_武汉大学舆情_20250821_232740.json
+++ /dev/null
@@ -1,178 +0,0 @@
-{
- "query": "武汉大学舆情",
- "report_title": "关于'武汉大学舆情'的深度研究报告",
- "paragraphs": [
- {
- "title": "概述",
- "content": "对'武汉大学舆情'的总体概述和背景介绍",
- "research": {
- "search_history": [
- {
- "query": "武汉大学舆情事件背景 2023年",
- "url": "https://zh.wikipedia.org/zh-mo/%E6%AD%A6%E6%B1%89%E5%A4%A7%E5%AD%A6%E5%9B%BE%E4%B9%A6%E9%A6%86%E4%BA%89%E8%AE%AE%E4%BA%8B%E4%BB%B6",
- "title": "武漢大學圖書館爭議事件 - Wikipedia",
- "content": "2023年,中華人民共和國湖北省武漢大學的圖書館內發生一起性騷擾指控爭議,後涉及司法程序,引發社會輿論。2023年7月,武漢大學研究生楊某某在網絡上舉報本科生肖",
- "score": 0.8627572,
- "timestamp": "2025-08-21T23:24:01.826884"
- },
- {
- "query": "武汉大学舆情事件背景 2023年",
- "url": "https://baike.baidu.com/item/10%C2%B711%E6%AD%A6%E5%A4%A7%E7%94%B7%E7%94%9F%E8%A2%AB%E6%8C%87%E6%80%A7%E9%AA%9A%E6%89%B0%E4%BA%8B%E4%BB%B6/63585559",
- "title": "10·11武大男生被指性骚扰事件_百度百科",
- "content": "网页 新闻 贴吧 知道 网盘 图片 视频 地图 文库 资讯 采购 百科 帮助 首页 秒懂百科 特色百科 知识专题 加入百科 : 进阶成长 任务广场 百科团队 : 校园团 分类达人团 热词团 繁星团 蝌蚪团 权威合作 : 合作模式 常见问题 联系方式 个人中心 # 10·11武大男生被指性骚扰事件 播报 编辑 讨论19 上传视频 2023年武汉大学发生的事件 全面调查复核!武汉大学回应图书馆事件 武大性骚扰事件涉事男方首度发声:系隔裤挠痒,已就网暴起诉 武大男生在图书馆实施性骚扰?武汉大学回应:已成立工作组 武大性骚扰涉事男方首度发声:系隔裤挠痒,已就网暴起诉 收藏 查看我的收藏 有用+1 2023年10月,一篇发表于微信公众号上的“举报”网文引发舆论关注。该网文以武大女生第一人称口吻,称自己于当年7月11日在学校图书馆自习时遭同校一男生“性骚扰”。 2024年2月消息,武大性骚扰事件涉事男方首度发声:系隔裤挠痒,已就网暴起诉。 2025年7月25日,该事件一审宣判。法院审理认为,不能认定男生肖某某针对特定对象实施了性骚扰,驳回女生杨某的指控。 7月31日,武汉大学校长张平文回应称,学校正在处理中,要等上级的安排才会公布处理结果。 7月31日,武汉大学官网男生受处分通报仍未删除,点击量超17万。 8月1日,武汉大学发布情况通报称,已组建工作专班,正在对肖某某纪律处分、杨某某学位论文等进行全面调查复核。 ## 相关人物 10·11武大男生被指性骚扰事件相关人物 共2个词条 2.3万阅读 杨景媛 当事人 肖明瑫 当事人 ## 相关星图 2023年10月社会事件 共8个词条 8563阅读 10·20果敢卧虎山庄屠杀事件 ”10·20果敢卧虎山庄屠杀事件“指的是2023年10月20日在果敢卧虎山庄发生的惨案。2023年10月19日,果敢四大家族之一的明家通知卧虎山庄电诈金主,该窝点将于10月20日被清查,明家便安排武装人员携带枪支,分批押运电诈人员从卧虎山庄转移至承德公馆(“红房子”)以及“东湖山庄”等地点看押。在20日的押运过程中,中国籍涉诈人员逃跑,遭到武装人员开枪射击,共造成中国籍涉诈人员4死4伤。案件发生后,舆论高度关注,网上随之出现了“4名中国警察被残忍杀害”“‘10.20 案件’造成了上百人伤亡”等耸人谣言。2024年12月,温州市公安局常务副局长王造对真实情况进行回应,同时全面介绍了缅北果敢明家犯罪集团案侦办情况。另外,昆明市公安局刑侦支队支队长纳新为人民日报记者详细介绍了“10.20”案件侦办始末。 10·19恩施女子被前男友泼汽油烧伤事件 2024年12月,27岁的湖北恩施女孩小陈,在网络平台发布视频和照片称,她和前男友覃某某恋爱三年,2023年覃某某提出分手后又要求复合,且各种骚扰纠缠,自己拒绝后10月19日被前男友覃某某泼汽油烧伤,最终鉴定为肢体伤残二级,全身69%的面积深三度到四度烧伤,左手截肢,面部毁容。案发后,覃某某被恩施市人民检察院以故意杀人罪提起公诉。2024年6月,覃某某被恩施市人民检察院以故意杀人罪提起公诉;7月,法院已对此案开庭审理。小陈说,凶手覃某某在庭审时还对她发出死亡威胁;12月31日,小陈拿到刑事判决书,被告人覃某某犯故意杀人罪,判处有期徒刑十二年。一审宣判后,小陈对判决结果表示不满,随即向恩施市人民检察院提出抗诉。2025年1月8日,恩施市人民检察院答复表示,经审查,该院认为一审判决确有错误,决定提出抗诉;4月22日,该案在湖北恩施中级人民法院二审开庭,择期宣判;5月9日,覃某某父亲表示,儿子对泼汽油一事表示过后悔。 10·11武大男生被指性骚扰事件 2023年10月,一篇发表于微信公众号上的“举报”网文引发舆论关注。该网文以武大女生第一人称口吻,称自己于当年7月11日在学校图书馆自习时遭同校一男生“性骚扰”。2024年2月消息,武大性骚扰事件涉事男方首度发声:系隔裤挠痒,已就网暴起诉。2025年7月25日,该事件一审宣判。法院审理认为,不能认定男生肖某某针对特定对象实施了性骚扰,驳回女生杨某的指控。7月31日,武汉大学校长张平文回应称,学校正在处理中,要等上级的安排才会公布处理结果。7月31日,武汉大学官网男生受处分通报仍未删除,点击量超17万。8月1日,武汉大学发布情况通报称,已组建工作专班,正在对肖某某纪律处分、杨某某学位论文等进行全面调查复核。 10·7以色列遭火箭弹袭击事件 当地时间2023年10月7日早晨,巴勒斯坦加沙地带武装组织哈马斯向以色列境内发射多枚火箭弹,并潜入以色列境内,在多个地点发动恐怖袭击。袭击发生后,以色列国防军宣布进入“战备状态”,对加沙地带哈马斯目标发起代号“铁剑”的行动。2024年10月11日,据联合国发布的消息,哈马斯此次恐袭共造成1250多名以色列人和外国公民死亡,其中包括儿童和妇女,另有数千人受伤。250多人被哈马斯绑架并带到加沙,其中有许多妇女和儿童。中东问题专家认为,此次大规模冲突的影响将十分深远,会对地区地缘格局和巴以各自内部的政治走向产生更深的影响,并为中东地区的和解进程蒙上阴影。 中文名 : 10·11武大图书馆女生诬告事件 外文名 : 10.11 Wuhan University Library Female Student False Accusation Incident 评) (潇湘晨报记者 向玲 评) 评) (澎湃新闻 评) (羊城晚报 评) (潇湘晨报 评) (风声 评) .2025-07-26 .2023-10-13 .2024-02-04 .2025-07-25 .2025-07-30 .2025-07-31 .2025-07-31 .2025-07-31 .2025-07-31 .2025-08-01 . 羊城派 .2025-08-01 .2025-08-01 .2025-08-01 .2025-08-02 .2025-08-04 .2025-08-06",
- "score": 0.8282873,
- "timestamp": "2025-08-21T23:24:01.826884"
- },
- {
- "query": "武汉大学舆情事件背景 2023年",
- "url": "https://www.zaobao.com.sg/news/china/story20250802-7264129",
- "title": "武汉大学性骚扰案引爆“诬告”舆情校方正全面调查复核 - 联合早报",
- "content": "# 武汉大学性骚扰案引爆“诬告”舆情 校方正全面调查复核 武汉大学两年前的一起图书馆性骚扰案,在法院宣判涉事男生不构成性骚扰后,引爆持续数日批判女生“诬告”的汹涌舆情。 (武汉大学官网) (北京/广州综合讯)中国名校武汉大学两年前的一起图书馆性骚扰案,在法院宣判涉事男生不构成性骚扰后,引爆持续数日批判女生“诬告”的汹涌舆情。校方称正对男生所受处分和女生被举报学术不端等情况,进行全面调查复核。 综合新华社、《南方周末》和《经济观察报》报道,武大女学生杨景媛2023年10月在社媒发文称,当年7月在图书馆自习时,同校男学生肖明滔坐在对面隔着裤子持续摩擦下体,她因此指控男方行为是“性骚扰”。肖明滔随后遭到网暴,也被武大以“存在不雅行为”为由记过处分。 肖明滔母亲则晒出病历资料,称儿子患有湿疹,是隔着衣服抓痒。她称这场风波给家庭带来巨大痛苦,儿子被同学孤立谩骂,出现割腕跳楼举动后被确诊创伤后应激障碍,因自杀风险高被医生建议入院治疗,“爷爷因受刺激半年后去世,外公晕倒成了植物人”。 但杨景媛对校方处理结果并不满意,去年6月将肖明滔起诉至法院,称男方行为严重侮辱她的人格尊严,侵害精神健康。 案件在7月25日一审宣判。法院判决认为,肖明滔的动作存在“抓痒的高度可能”,事发场所开放、双方无交流,无法证明存在性暗示或性挑逗行为,不符合性骚扰构成要件,杨景媛的全部诉求被驳回。 ## 延伸阅读 胡锡进呼吁武大撤销被诬告性骚扰学生处罚 这起不公开审理的案件宣判后,杨景媛又将判决书放上网,继续泄露肖明滔个人信息,声称会持续举报男方,阻止他未来的求学和工作发展,而她则已顺利毕业“继续美美读博”。 杨景媛言论和肖明滔遭遇的强烈反差,很快引起中国舆论哗然。网民开始集体讨伐杨景媛,扒出她在武大的硕士论文存在造假抄袭等学术不端问题,还有人向她正攻读博士学位的香港浸会大学发举报信。同时,公众也大规模质疑武大此前对肖明滔的处分是否合理。 武大星期五(8月1日)在官网通报称,学校充分尊重司法判决结果,高度重视社会关注问题,已组建工作专班,正对肖明滔纪律处分、杨景媛学位论文等进行全面调查复核,将严格按规定作出处理。 《南方周末》评论称,这起案件牵涉中国近年来盛行的网络诬告,倘若诬告者可以零成本进行公开虚假指控,事后又“美美隐身”,这对承受痛苦的受害者极不公平,也会助长诬告风气,“诬告者理应付出代价”。 武汉中国性骚扰大学生大学 港澳出现基孔肯雅热病例 刘国中赴广东调研疫情防控抗战电影成中国暑期票房冠军 中日两地恶性攻击频传 购买此文章 ## 即时新闻 * 4小时前 ## 虎豹企业上半年净利增18.2%至1.44亿元 * 4小时前 ## 南非提“慷慨”贸易协议 盼美降低30%关税 * 5小时前 ## 英澳加等国吁以色列解除加沙援助阻碍 * 6小时前 ## 特朗普考虑允许对美联储主席提出“重大诉讼” * 6小时前 ## 扬子江金融上半年净利同比增长28%至1.38亿元 * 7小时前 ## 台外交部:持续关注洪都拉斯选情发展 ## 热门 * ## 文达街刺死女婿案 老丈人出狱一年多后逝世 * ## 谢韵仪婚礼上强忍不落泪 见老公送的礼物哭了五分钟 * ## 【视频】南中国海追菲船射水炮 中国海警船与本国军舰相撞 * ## 知名企业和慈善家吴清亮辞世 享年98岁 * ## 疑骑电单车送儿子上学途中出车祸 民防中校不幸命丧公路 ## 更多消息 订户专享 赠阅文章 早报VIP会员每月可赠阅 5 篇订户专享文章。了解更多",
- "score": 0.8235701,
- "timestamp": "2025-08-21T23:24:01.826884"
- },
- {
- "query": "武汉大学舆情管理机制 性别平等政策 网络举报处理流程",
- "url": "https://www.zhihu.com/question/1936967272628479802/answer/1940569297853321655",
- "title": "武汉大学图书馆事件的三重舆情透视》? - 顺式聚丁二烯的回答- 知乎",
- "content": "近年来,师德师风、校园冲突等事件频繁成为舆论热议的话题,在校园冲突相关事件经舆论放大之后,校方流程是否透明合规、处理机制是否完善合理,校方是否能够",
- "score": 0.40218133,
- "timestamp": "2025-08-21T23:24:29.882810"
- },
- {
- "query": "武汉大学舆情管理机制 性别平等政策 网络举报处理流程",
- "url": "https://tuibook.com/wangping/56415.html",
- "title": "从武汉大学杨景媛事件看当前大学校长管理能力平庸化及原因 - 推书网",
- "content": "面对舆情危机,该校校长仅以“等上级指示”作为回应,暴露出当前大学校长治理能力的严重缺陷。 这一事件折射出高校领导层在危机管理中的系统性失灵。作为",
- "score": 0.2634743,
- "timestamp": "2025-08-21T23:24:29.882810"
- },
- {
- "query": "武汉大学舆情管理机制 性别平等政策 网络举报处理流程",
- "url": "https://www.uuwatch.com/news",
- "title": "专业舆情报告范文模板_社会热点舆情分析报告简报_危机舆情处置建议",
- "content": "移动APP 舆情报告 舆情报告 多维度全视角舆情分析报告,全面掌握社情民意,助力领导科学决策免费试用 * #### 婚姻新规舆情聚焦:户口本退出的公众反响与社会思潮 2025-05-20 75 一、舆情概述5月10日,修订后的《婚姻登记条例》(简称《条例》)正式施行。根据《条例》规定,婚姻登记将“全国通办”,且在办理结婚、离婚登记时不再要求出具户口本。新规还鼓励婚姻登记机关提供个性化颁证仪式,可邀请父母等亲属参与;首次立法要求地方政府治理高额彩礼,倡导简约适度婚俗,推动移风易俗等。婚姻家庭律师表示,新规将给婚姻登记当事人带来更大的便利。虽然登记方便了,却不能“随便”,当事人要对自己提供的证件和材料负责,相关信息还会记入信用记录。需要注意的是,尽管《条例》中没有再提及“三十日内”等代表离婚冷静期的字样,但民法典规定离婚冷静期为30天,《条例》应依据民法典执行。也就是说,在民法典没有修改相关条款的情况下,离婚冷静期依旧存在。对此有网友调侃称:“结婚免密支付,离婚拼多多砍一刀。”二、舆情数据#5月10日起结婚离婚都不用户口本##结婚离婚为什么不需要户口本了#等话题登上多平台热榜,阅读量累计超过2亿人次,全网讨论超过10万条。三、舆情背景(一)结婚人数多年持续下滑自2013年触及1346.9万对的结婚登记峰值后,我国结婚率便开始了持续的下滑,尽管2023年出现了一定回升,但在刚过去的2024年,全国结婚登记数仅有610.6万对,创下1980年以来的最低纪录。日前,民政部发布最新数据,2025年一季度全国结婚登记181万对,较去年同期减少15.9万对,相比10年前高峰时期少了近一半。(二)婚姻自由与个体自主性增强在现代社会,年轻一代的婚恋观念,逐渐从传统的家族联姻向追求情感契合转变,更加注重个体的情感需求和自主选择。尤其是随着女性经济独立和社会地位的提升,她们在婚恋中的自主性也更强。现代女性不再依赖婚姻来实现经济保障,而是更注重婚姻中的情感支持和个人成长,这种变化也促使婚姻关系更加平等和多元。(三)人口流动与社会结构变化随着城镇化进程的加快,大量人口跨区域流动,异地工作和生活成为常态,现代社会治理也亟需适应人口流动的客观规律,及时推动公共服务“随人走”。此外,尽管户口本在中国社会治理中的作用较为突出,但究其根源,仍是计划经济和过去城市资源有限的产物。历史地看,当前在各个领域逐步降低户口本的作用,属于顺理成章之事,有其必要性。(四)政府社会治理转型随着社会的发展,政府更加注重提供高效、便捷的公共服务,逐步从“管控型”向“服务型”社会治理转型,从而实现对公共服务流程的优化,减轻群众负担。具体来看,在职能优化上,从“全能政府”到“有限政府”;在治理方式上,从“单向管控”到“多元合作”;在决策机制上,从“行政主导”到“公众参与”;在资源配置上,从“行政分配”到“市场调节”;在治理目标上,从“社会稳定”到“社会幸福”;在技术应用上,从“传统管理”到“数字赋能”,上述转型已成为适应社会发展和公众需求的必然选择。(五)技术发展服务现代生活当前信息化和数字化技术飞速发展,政府部门已能够更高效地核实身份信息,防止重婚、骗婚等现象,同时还可通过全国婚姻信息联网、生物识别技术等技术手段,保障婚姻登记的真实性与合法性,减少对传统纸质证明的依赖。四、公众反响(一)支持态度一是认为将提升婚姻登记操作的便利性。新规实行后,办理结婚、离婚登记时将无需再为准备户口本而奔波,亦无需再返回户籍地,对于在异地工作、生活的人群,将大大节省时间和精力,降低了经济成本。二是认为体现了对婚姻自由的尊重和保障。一方面,可以绕过传统家庭干预,避免出现因跨民族或彩礼问题等导致父母阻碍结婚的情况,进一步保障个人婚姻自由。另一方面,解决了因户口本被控制而无法离婚的问题,对于家暴受害者而言或可更快更好脱身。三是认为新规将倒逼个人更加谨慎对待婚姻。新规对进入婚姻流程的简化,在客观上将促成个人强化自身在婚姻登记中的责任意识,更加慎重对待婚姻大事,对自己的婚姻状况信息及登记行为负责。且新规虽然简化了流程,但并未削弱家庭在婚姻中的作用。相反,通过鼓励邀请父母等亲属参与颁证仪式,新规在一定程度上平衡了个人自主与家庭责任。四是认为新规是户籍改革的重要配套措施。两者在三方面实现了协同:打破户籍壁垒,推动身份管理扁平化,助力“人户分离”常态化下的公共服务均等化;强化技术治理,提升婚姻登记的智能化水平,支撑户籍数字化改革;重构家庭关系,适应新型城镇化需求,呼应户籍改革中“放宽落户限制”“集体户扩容”等政策。(二)质疑态度一是提出增加被他人冒用身份风险。流程简化后,未成年人或偏远地区人群可能被诱骗登记,还有身份信息遭冒用的风险,对各地民政部门履职尽责、严格把关提出了更高的要求。二是提出影响家庭亲子和睦关系。子女隐瞒父母秘密领证,导致父母对子女婚姻状况完全不知情,传统“把关”作用消失,可能引发代际矛盾。三是提出草率结婚或埋更大离婚隐患。流程便捷化可能导致部分人群在冲动之下轻易进入婚姻,而未充分考虑婚姻的责任和义务,也可能导致部分人因缺乏家庭约束而冲动结婚或离婚,增加了后续矛盾的风险。五、争议焦点(一)婚姻制度“宽进严出”遭诟病当结婚登记日趋便捷化,“离婚难”却已成为一种公共话语符号,在网络舆论场形成周期性爆发的特征。此次新规降低了婚姻进入门槛,也再次将“离婚冷静期”带上风口浪尖。离婚冷静期生效至今,副作用已十分显见:一方面,它被认为是对离婚自由的限制,尤其在家庭暴力、出轨等法定离婚事由认定困难的情况下,冷静期可能成为受害者的枷锁;另一方面,诉讼离婚中的“首诉不离”现象进一步加剧了离婚的难度,使得离婚变得更加复杂和漫长。根据2024年民政事业发展统计公报的数据,自2021年实施“离婚冷静期”以来,协议离婚占比从2020年的86.1%降至2023年的71.94%。与此同时,诉讼离婚占比则从13.9%升至28.06%——从这个角度看,离婚冷静期似乎并没能对婚姻起到黏合作用。婚姻制度“宽进严出”,被认为可能进一步削弱婚姻制度的吸引力,导致更多人群“不敢结婚,怕离不掉”。(二)新规措施并未触达结婚人数下降本质减少环节、简化流程只是让进入婚姻的动作变得更简单,但没有结婚意愿的人不会因此就选择结婚,如同想要离婚的人数并没有因离婚冷静期的存在而减少。近年来,诸如全面放开三孩、增加婚育假期、设置离婚冷静期等政策干预的效果同样有限。从网民讨论来看,经济压力与婚育成本高企是当前社会低婚育率的关键因素,昂贵的结婚开支令年轻人望而却步;社会竞争与时间成本也有重要影响,“996”工作制和高强度职场竞争挤压了年轻人的时间和精力,许多人疲于应对生存压力,无暇顾及婚育,尤其是双职工家庭面临沉重的育儿负担。新规措施并未触及经济压力等深层次问题,要有效应对结婚人数下降的挑战,需要从经济支持、观念引导、社会文化包容等多方面入手,构建系统性的解决方案。(三)“父母是否应干预婚姻”引发分歧在传统家庭观念中,父母通常被视为家庭的决策者,他们的经验和智慧被认为能够为子女的婚姻提供重要的指导。因此,父母对子女的婚姻选择往往有较大的话语权,甚至会直接参与婚姻的安排。新规支持者认为父母过度干预会导致家庭矛盾激化,而反对者则担忧年轻人缺乏独立判断能力。这种争议反映了传统宗族观念与现代个体主义之间的张力,是对“父母之命,媒妁之言”进行的批判性反思。年轻一代更倾向于将婚姻视为个人选择而非家族事务,这种观念与城市化进程中家庭纽带松动的社会现实相呼应。六、思考与启发办理结婚、离婚登记不再要求出具户口本的政策调整,背后折射出中国社会在婚姻观念、家庭结构、治理模式及权利意识等方面的深刻变迁。相关讨论则一是反映了公众对平衡婚姻风险防范与公共治理的诉求,如提出政策在简化流程的同时,面临如何防范骗婚、重婚等风险的挑战。二是体现了公众对婚姻自由与秩序平衡的关切,如社会质疑婚姻制度“宽进严出”,离婚冷静期制度虽旨在减少冲动离婚,但也可能加剧家暴受害者的困境。三是显示出公众婚姻价值观正在重塑的过程中,如舆论热衷讨论彩礼问题,新规通过减少家庭对彩礼的干预间接推动婚俗改革。四是凸显出婚姻教育的需求,如部分年轻人因缺乏独立经营婚姻的能力,陷入“既想摆脱控制又惧怕自主选择”的矛盾心态,加强婚前教育、心理咨询等配套服务,成为政策落地的必要补充。整体来看,此次新规是社会多重思潮交织下的制度革新产物,既有赖于技术赋能和治理现代化,也是个体权利觉醒与传统家庭观念碰撞的结果;既顺应了人口流动、数字化社会的客观需求,也面临如何平衡自由与风险、传统与现代的深层挑战。 声明:本篇为优讯舆情原创文章,转载请注明来源。 舆情监测网络舆情热点舆情分析报告网络舆情舆情应对舆情分析舆情监控网络热点舆情深度剖析 * #### 重庆三峡学院“天价采购”:75万与299元背后制度漏洞待深挖 2025-05-19 222 一、事件概述4月27日,重庆三峡学院在“重庆市政府采购网”发布防火墙及DNS设备采购(CQS25A00331)竞争性谈判公告:预算金额90万元,最高限价90万元,采购方式为竞争性谈判。 5月9日,“重庆市政府采购网”发布重庆三峡学院防火墙及DNS设备采购(CQS25A00331)中标(成交)结果公告:成交供应商丰都县洪正商贸有限公司,中标(成交)金额85万元。其中,主要标的信息标注了一台品牌规格型号为“普联TL-R473G”的单价为75万元,但其市场价仅为299元,成交价格高于市场价2500倍。 5月10日,“重庆市政府采购网”发布采购终止公告,更正信息一栏中显示,“接采购人通知,原成交供应商丰都县洪正商贸有限公司所投的主要标的‘出口防火墙’产品型号为某路由器产品型号,已确定不满足谈判文件要求,本项目出现影响采购公正的违法、违规行为。采购人根据《政府采购非招标采购方式管理办法》第三十七条规定,决定本项目采购终止,重新开展采购活动。” 该事件引发舆论广泛关注,诸多媒体参与报道转发评论,在百度、抖音、今日头条等多个平台形成热搜,并在微博形成#75万买299元路由器公共资金谁劫持了#(阅读量90.6万,讨论量324)#记者调查中标75万路由器公司#(阅读量468.2万,讨论量3194)#75万采购路由器中标方参保人数0#(阅读量1995.8万,讨论量3930)#高校通报75万采购市价299元产品#(阅读量4446.7万,讨论量1.3万)#高校回应网传花75万采购售价299产品#(阅读量1030.2万,讨论量2595)#花75万采购299元产品高校已终止采购#(阅读量2657万,讨论量4029)#高校75万采购299元产品项目违法违规#(阅读量368.6万,讨论量1327)#律师称高校75万买299元路由器或涉串标#(阅读量11.1万,讨论量56)#官方调查高校75万买299元路由器#(阅读量11.2万,讨论量56)等热点话题。 二、舆情分布1.舆情走势2.媒体分布3.舆论热词三、观点分析(一)媒体方面第一财经:对于这次“天价采购路由器”事件,当事采购人已经确认存在“影响采购公正的违法、违规行为”。同时,外界也有许多关于存在何种“猫腻”的猜测。那么,仅仅发个公告终止采购,再重新组织采购,显然是不够的。还原事件真相,回应公众关切,依法依规处理,并亡羊补牢,改进工作,才是可取做法。只有这样,才能让政府采购顺利开展,起到应有的作用,并维护市场公平和政府公信力。 新京报:虽然校方已经紧急终止采购,但仍有必要反思如此离谱的招标何以出炉?这一事件看似荒诞,实则暴露出政府采购中更深层的漏洞——技术标准形同虚设、监管流程流于形式、供应商资质审查沦为走过场。当一所高校连路由器和防火墙都分不清,公众质疑的已不仅是“天价采购”,而是公共资金究竟被谁“劫持”。 红网:值得庆幸的是,重庆三峡学院已终止采购,这是一个及时的止损行为,但远远不够。相关部门必须深入调查,彻查事件背后是否存在利益输送、围标串标等违法违规行为,必须对责任人依法严惩,给公众一个交代。同时,高校及所有政府采购部门都应以此为戒,完善采购流程,加强监管力度,引入专业的技术评估和第三方审计,让采购过程在阳光下进行。如此招标采购天价设备,侮辱的不仅仅是公众、学术、市场和学校自身,更是对公平、公正、公开原则的公然挑战。只有严肃整治、完善制度,才能避免类似的闹剧再次上演,让公共资金真正发挥应有的价值。 大象新闻:天价采购乱象,表面是采购失误,根源在于权力的傲慢与约束的缺失。公共资金是民脂民膏,理应精打细算,却一次次被挥霍。75万买299元路由器,这不是笑话,而是警钟。每一分公共资金的浪费,不仅是对公众信任的伤害,也是对社会公平的伤害。根治天价采购,在于制度、技术、监督等方面协同发力。唯有如此,才能让公共资金回归正轨,让民心不再受伤。 (二)网民方面一是部分网民嘲讽采购内幕。@ 西风碧树往来白丁:专家说的符合要求@ 抽蚊香的蚊子lm:撑死胆大的饿死胆小的@ 生命实验:有个很大的公司以前采购都是标注型号的,很多比吃不到肉的就会唧唧歪歪。现在那个公司采购型号一律不注明,大家都吃的特别安静特别和谐。不知道就是什么事都没有。@ 科技圣斗士:这个太夸张了,回扣也不能这么吃啊@ 咱俩竟然:马科长:别问了、别问了……#中国智慧#@ 皓予同学Q:回扣高达99%@ 陈杰克2023:不贵,要分钱的人太多,根本不够分的。@ 于健Jeremy:你不拿我不拿,侯专员怎么拿@ 翡言翡语半唠叨:你不拿我怎么拿 怎么共同进步@ 确实有点离谱了:之前不有个机构采购老古董电脑,一两千的玩意需要三十多万@ 羽生画扇:不然财政预算怎么花花不到明年转移支付就没那么多啦 二是部分网民分享自身采购经历。@ 内省博士红韭菜:我跟事业单位采购打过交道,12年前后,淘宝6000的相机报价12000,这么看很良心了@ Vin-Yeoma :别说大单位了,小公司月薪三四千的采购每月油水都上万 三是部分网民表示震惊。@ i-Peace:我还是孤陋寡闻了。@ 神也偏爱9527:这操作有点6…@ 蜂王大人素素:中国版的史密斯专员@ 齿痕Z留:触目惊心…!!!!! 四是部分网民嘲讽教育和医疗系统腐败乱象。@ 不舍爱与自由:国内的教育和医疗抓紧上市得了,买卖做的这么好@ Melo·Chan:kk园区都觉得黑@ SHENHUA_DB9:贪起来肆无忌惮@ 请叫我大班长wy:数不胜数@ 厚者l:每个国企都会这样吧@ 婕mami:都是这样的啦。每层都吃一点 五是部分网民关注事件后续调查。@ 叫啥好呢a_:一句终止就完事了?@ 赵姥姥曰:#高校通报75万采购市价299元产品# 只通报说采购中止,没看见说相关责任人是谁,有没有给安排个利用职务便利贪污公款啥的罪。@ 万般皆风景:采购没人审核吗 四、舆情分析在此次高校采购舆情事件中,诸多问题引发公众高度关注。其中,采购价格差异巨大(75万元与299元的天壤之别)导致公共资金浪费的现象备受诟病,同时,技术标准缺失、供应商资质存疑、评审专家责任不明以及高校采购信息不透明等问题,也成为舆论焦点。多家媒体纷纷介入报道,直指采购环节存在“三重失守”:技术参数审核流于形式、评标过程敷衍塞责、验收机制形同虚设。值得关注的是,此次事件信息的曝光源于网民的发现与揭露,这一情况迅速推动舆情在舆论场中扩散,进一步加剧了外界对高校采购内部管理的不信任感,且这种不信任情绪仍在持续蔓延。媒体的广泛参与报道,更是起到了推波助澜的作用,促使舆情进一步发酵。舆论纷纷质疑相关制度存在漏洞,甚至猜测背后可能存在利益输送等内幕,并强烈呼吁官方彻查责任人。面对当前汹涌的舆情态势,涉事学校需积极应对。及时公开信息,回应公众关切:迅速公开事件处理意见,对涉事人员和单位进行严肃处理,并将处理结果向社会通报,以彰显学校正视问题、解决问题的决心;明确整改措施,完善采购制度;构建供应商信用评价体系,对资质不符的企业坚决实行“一票否决”,从源头上保障采购质量;建立评审专家考核机制,对连续出现重大失误的评审专家实行“一票否决”,并依法追究其法律责任,确保评标过程公平公正;建立专业产品数据库,明确各类产品的核心参数阈值,避免出现类似“路由器充防火墙”的荒唐闹剧。同时,清晰界定监管责任,对违规行为进行严厉问责,形成强大的震慑效应,确保采购工作规范有序进行。声明:本篇为优讯舆情原创文章,转载请注明来源。舆情监测社会热点网络舆情舆情分析舆情监控校园舆情社会热点舆情舆情深度剖析 * #### 舆情透视:河南干部违规聚餐致一人死亡 2025-05-15 418 一、舆情概况近期,河南省信阳市罗山县发生一起公职人员违规饮酒致人死亡事件,引发社会广泛关注。3月21日,河南省信阳市罗山县召开中央八项规定精神学习教育部署会议。仅隔一日中午,县委政法委书记李献林便召集10名干部,在市区某餐馆再次违规聚餐,其中5人饮用4瓶白酒。由于过量饮酒,罗山县委政法委副书记夏宇不幸于当日午后离世。事件发生后,相关涉事人员试图通过虚假报告死因、向管理服务对象借款以支付所谓的“封口费”等手段掩盖事实真相,该行为进一步激发公众愤慨。对此,有关部门迅速介入调查,并依法依规对涉事10人进行严肃处理。其中,李献林因涉嫌其他严重违纪违法被留置审查,叶金广等4人被撤职、降级并调离政法或纪检系统;其余参与者分别受党内严重警告、政务记大过等处分。5月14日,人民日报发表评论称:“这是一起违规吃喝的典型案例,广大党员干部要从中受到警醒、引以为戒,严防‘舌尖上的歪风’”。#河南5名干部违规吃喝饮酒1人死亡#等多个相关话题登上各大平台热榜,阅读量超4000万次。二、舆论透视❖舆论对制度缺陷和监管不到位提出质疑社会舆论普遍反映,本起事件中,县委书记未能及时上报情况、多人试图掩饰真相逃避责任的行为,凸显基层监督机制严重不足。公众进一步提出质疑:若非发生人员死亡,此类违规行为是否能够得到应有曝光?还有部分网民指出,当地已实施禁酒令,但“上有政策、下有对策”问题依然突出,违规吃喝现象时有发生,且出现“吃公函”“吃食堂”“吃老板”“吃下级”等隐形变异形态。人民网指出:“相关地区和部门对党员干部教育管理监督不严格,涉案人员所在单位及上一级党组织相关负责人履行领导责任不到位,客观上助长违规吃喝歪风的蔓延”。❖舆论聚焦事件所引发的社会警示效应部分舆论反映,一些干部在贯彻执行中央八项规定精神过程中,仍然出现违纪行为,充分暴露个别人员政治意识淡薄。《中国新闻周刊》深刻剖析指出,此次违规聚餐饮酒事件发生在深入学习和贯彻中央八项规定精神的关键时期,是公然违反法纪、顶风作案的严重案例,其性质极其恶劣,影响极为负面,对此必须采取严厉措施予以纠正。中央纪委的通报明确将此行为定性为“学习与实践脱节”的典型表现。同时,一些舆论声音也指出,干部参与违规聚餐的现象,深刻揭示当前官场中不健康、扭曲的酒桌文化。这暴露某些干部将饮酒视为人情润滑剂,甚至以“酒量衡量忠诚度”的不当现象。公众舆论亦聚焦于干部违规聚餐与腐败现象相互交织。干部在宴请场合易受利益引诱,进而深陷腐败漩涡,引发权力滥用、公款私用等严重问题,对社会公平正义造成损害。极目新闻深入揭露,干部在违反规定聚餐饮酒的过程中,更易涉及利益交换等不当行为。❖风险警示事件涉及干部违规行为,可能引发群众对政府部门监管缺失、纪律松弛的不满情绪,若处理不及时,可能进一步发酵为对政府公信力的质疑。若官方通报细节模糊或回应滞后,易被解读为“掩盖真相”,加剧舆论对立。社交媒体可能传播“吃喝致死”“公款消费”等关键词,引发网民情绪化讨论,甚至出现谣言或不实信息放大事件影响。若事件涉及具体地区或部门信息(如单位、人员身份),可能被关联至其他历史负面事件,形成舆论叠加效应。若后续问责处理不透明或未满足公众期待,可能引发“问责不到位”争议,甚至衍生“形式主义”的舆论批评。事件可能被部分舆论嫁接到“四风”问题整治话题,放大负面影响。❖启示建议快速响应,权威发声即时通报:联合涉事部门通过官方渠道(如政府官网、政务新媒体)发布事件经过、伤亡情况及初步调查结论,避免信息真空引发猜测。责任承诺:明确表态将严肃追责,并列出调查时间表,回应公众对纪律监督的关切。主动引导舆情走向正面案例宣传:同步宣传本地或类似事件中干部廉洁自律、勤勉履职的正面案例,平衡舆论焦点。专家解读:邀请法律、廉政领域专家对事件中涉及的违规行为定性、问责依据等进行权威解读,增强公众认知。强化舆情监测与联动处置关键词预警:对“干部违规”“吃喝致死”“问责进展”等敏感词实施7×24小时监测,及时识别谣言或极端言论。跨部门协作:联合网信、公安、宣传等部门,对恶意传播不实信息的行为依法处置,避免舆情升级。长效机制建设制度警示:在事件妥善处理后,通过内部通报、案例培训等方式,强化干部纪律意识,避免类似事件再次发生。公开整改措施:主动发布后续廉政教育、公务接待规范等改进举措,展现整改决心,修复政府形象。声明:本篇为优讯舆情原创文章,转载请注明来源。 舆情监测网络舆情热点舆情分析报告网络舆情舆情应对舆情分析舆情监控社会热点舆情热点舆情分析 * #### 河北省三河市强换商户招牌事件舆情启示 2025-04-16 1493 一、事件核心矛盾:权力任性触碰民生底线近期,河北省廊坊市三河市因要求商户更换“红黑蓝”门头牌底色引发争议。三河市城管局回应,禁用红、黑、蓝三种底色是“市政府城市规划要求”,未来将通过公众号发布文件。据悉,该政策自2025年3月中旬起实施,涉及多个镇街辖区,商户需自行承担更换费用,且未明确整改期限。实际执行中,蜜雪冰城等连锁品牌将红色招牌改为绿色,政策甚至被扩大至医院红十字标识等公共标识。事件发生后,#城市管理不能如此无厘头##商户门头牌匾禁用红黑蓝三色##廊坊通报三河广告牌匾禁用红蓝黑##三河市委负责人被免职##对破坏营商环境的行为零容忍#相关多个话题登上各大平台热搜,阅读量近3亿。4月15日,河北省廊坊市联合调查组发布通报,确认三河市“广告牌匾禁用红蓝黑底色”事件属实,并宣布对三河市委主要负责人免职处理,责成三河市委、市政府全面整改。事件触发公众对行政程序合法性、城市治理科学性及营商环境优化的深层反思。二、沉默式回应:行政傲慢助推舆情发酵政府初期“冷处理”态度加剧公众猜疑,自媒体借机构建“官欺民”叙事,衍生出“小商户生存困境”“地方财政吃相难看”等关联议题,舆情从个案升级为系统性信任危机。事件本质是地方政府以“市容整治”之名行“权力越界”之实,粗暴执行政策引发商户生存权与行政合法性的对冲。舆论矛头直指“官僚主义式治理”——重表面政绩、轻民生诉求,暴露出基层治理中“政策刚性”与“社会柔性”的断裂。三、官方回应难以解决现实问题舆论反馈暴露城市治理短板人民日报评论,城市治理应当帮助店铺商户解决经营中的问题;聚焦消费者关注的痛点堵点难点问题;北京晚报评论,红蓝黑三色列入“禁用名单”,是对百姓生活的过度管理,是典型的乱作为,是不折不扣的权力任性;红星新闻指出,门头牌匾的设置是城市管理的一项重要内容。需要政府主管部门广泛征求商家及设计人员的意见建议,出台完善、健全的街区规划,组建市容景观管理专家智库,探索试点“街区规划师”制度等;极目锐评称,城市管理应当以服务为导向,出台涉及公共利益的规定,都要有明确标准和合理流程,不能仅凭个人喜好或者模糊的“城市规划”就任性为之;澎湃新闻评论称,强换招牌事件,严重影响当地经营者的积极性,政府在改善营商环境中要服务好企业,做到“有求必应,无事不扰”,而非越俎代庖,甚至将个别领导审美偏好强加于经营者,乃至凌驾于法律之上;中国广告协会指出,建议政策制定需征求行业意见,平衡规范管理与商业自由。舆论普遍期待政策透明化及对商户权益的保障,呼吁平衡城市管理与商业活力,保留商户个性表达空间。“正式文件未发,就先行一步整改”的行为遭普遍批评。多数网民指出,政策制定应兼顾法治原则与商业规律,避免“拍脑袋”决策损害政府公信力;不少网民认为此举缺乏规范性和严肃性,还会增加商户经济负担;也有网民认为这将影响商户对当地营商环境的信任,还暴露出部分管理者“拍脑袋决策”的懒政思维;还有网友评论“这明显违法”“庙小妖风大”“想做生意的可以回避这个地方了”;个别网民调侃称“如果一个城市商户门牌统一,营商环境极差”。四、舆情启示:需舆情管理与服务升级并行此次事件涉及政府管理权限、市场经营秩序、城市审美形象等多方问题的交织与平衡。当地政府可能被指“朝令夕改”、“权力过大”、“不体察民情”,被质疑其决策水平和动机;经营者而言,大力配合整改要求的过程消耗较多商业资源,对后续的经营可能造成较大影响,商家也可能由此积累部分负面情绪;就城市形象而言,此次事件导致城市街道的审美风格一度改变,影响城市在外来游客心中印象,对城市后续经济发展造成负面影响。1、政策刚性执行需匹配社会韧性缓冲涉民生政策若缺乏过渡机制与补偿预案,易将行政目标异化为政民对立。本次事件暴露部分基层单位存在“政策目标单极化”倾向——过度追求市容标准化,忽视商户经营权保障。建议建立“决策缓冲带”机制:涉及群众直接利益的市政工程,强制设置政策试点期、商户听证会、阶梯式整改方案,避免运动式治理激化矛盾。2、舆情响应时效性与透明度决定公信力修复成本事件初期“回应真空”导致谣言填补信息缺口,衍生出“财政创收”“权力寻租”等次生舆情。应完善基层政务“48小时黄金回应”制度:对重大民生舆情,需在48小时内通过权威渠道发布事件定性、责任主体、处置流程信息,压缩阴谋论滋生空间。3、城市治理须平衡“秩序”与“活力”双重价值统一招牌整治折射出部分地方政府对“城市美学”的片面理解。根据住建部《城市市容市貌干净整洁有序安全标准(试行)》,商业街区招牌设置应遵循“规范+个性”原则。建议探索商户招牌分级管理制度:主干道严控规格材质,背街小巷允许个性化表达,通过“负面清单”替代“统一模板”,实现行政管理与商业生态的兼容。舆情监测社会热点分析社会热点舆情应对舆情分析舆情监控网络热点社会热点舆情热点舆情分析 * #### “炫富”扣分?券商如何完善声誉管理机制:从新规看长期发展路径 2025-03-26 248 近日,中国证券业协会下发的《证券公司文化建设实践评估指标(2025年修订征求意见稿)》引发行业广泛关注。此次修订不仅新增关于重大负面事件的专项扣分指标,还强化奢靡、炫富等声誉风险事件,对造成严重影响或存在薪酬激励不当、管理不严的券商从重扣分,同时将“传播正面信息”纳入加分项,展现监管层重塑金融业声誉形象的决心。一、舆情风暴与政策重拳:声誉危机如何倒逼制度革新近年来,证券行业因个别员工的炫富行为引发的舆论风波频发,例如中金公司员工妻子晒出其丈夫月均收入超过8万元的收入证明;中信建投实习生分享开保时捷上下班视频等,都将证券公司推向风口浪尖。事件不仅加剧公众对金融行业高薪现象的严肃拷问,更暴露出部分券商在内部管理和合规建设上严重不足。面对社会舆论的压力,监管部门持续加大治理力度。2024年3月,证监会明确要求纠治拜金主义、奢靡享乐等不良风气,并加强对从业人员和薪酬管理的监督。同年,开源证券和中原证券因薪酬管理机制不完善分别收到警示函或责令改正通知,进一步凸显监管层对行业健康发展的重视。因此,本次评估指标的修订被广泛视为舆论监督与行业治理需求共同作用的结果。通过增设扣分项和加分项,新规旨在形成政策与舆情的“互动闭环”,促使券商从被动应对转向主动防范,从而实现声誉风险管理的系统化升级。二、高薪话题何以引爆舆论场:情绪共振与传播链耦合金融行业的高薪话题之所以屡屡成为舆论焦点,主要源于以下敏感点:一是金融从业者与普通工薪阶层之间的收入差距较大,易引发社会对财富分配公平性讨论。尤其是在经济增速放缓、就业压力增大的背景下,这种对比显得尤为刺眼;二是部分证券公司因违规操作或客户服务问题屡次登上热搜,导致负面舆情泛化,“割韭菜”等负面标签持续深入人心,进一步削弱公众对行业信任感;三是作为国有资本主导行业,金融领域高薪现象极易被解读为“权力资本化”,从而引发更深层次的社会争议;四是在市场波动较大的情况下薪酬与业绩脱钩,部分金融机构的高薪体系未能充分体现与实际经营成效的关联性,导致公众对其合理性产生怀疑;五是豪车、奢侈品等炫富行为在短视频平台具有极强的视觉冲击力,加之算法推荐的精准推送,使得相关事件迅速破圈,扩大了舆论影响范围。针对上述问题,券商等金融机构已采取多种应对措施,如召开全员警示教育大会、签署廉洁承诺书、调整差旅标准等。然而,这些举措更多是短期修补,难以从根本上解决问题。随着4月年报披露季到来,薪酬议题或将再次成为舆论关注的热点。对此,券商应秉持开放透明的态度,及时回应公众关切,通过权威渠道澄清误解,避免谣言扩散。三、从长远来看:新规下的声誉管理新路径本次修订不仅体现监管层对声誉风险管理的高度重视,也为券商等金融机构指明未来发展的方向。具体而言,可以从以下几个方面着手完善声誉管理体系:一是构建硬约束防控机制,将党建工作、企业文化建设和廉洁从业理念深度融入日常管理,形成全方位、多层次的声誉风险管理格局。建立健全员工行为规范,明确禁止奢靡、炫富等不当行为,并将其纳入绩效考核体系。二是提升软实力价值认同,定期组织声誉风险管理培训与模拟演练,增强员工的风险意识和应对能力。强化“合规、诚信、专业、稳健”的核心价值观教育,通过入职培训、合规宣讲等方式,将职业操守内化为员工自觉行动。三是发挥智库作用传递正能量,鼓励首席经济学家及研究团队通过权威媒体解读宏观经济政策和市场趋势,树立理性声音。四是引导全体员工以正面、积极的姿态参与社交媒体互动,提高社交媒体使用素养,传播正能量,树立行业典范。五是加强与媒体沟通协作,主动建立与主流媒体长期合作关系,借助不同层级媒体平台传递公司的正面信息,提升品牌形象和公信力。在突发舆情发生时,迅速启动应急预案,通过透明的信息披露化解危机,防止事态进一步恶化。四、结语总体来看,本次修订不仅是对过往声誉事件的深刻反思,更是对未来行业发展的战略指引。券商应以此为契机,从制度设计到文化建设全面优化声誉管理体系,既要通过“硬约束”筑牢防线,也要通过“软实力”赢得信任。唯有如此,才能在复杂多变的市场环境中实现可持续发展,真正肩负起服务实体经济、促进社会公平的重要使命。 企业舆情舆情监测网络舆情热点网络舆情舆情应对舆情分析舆情监控网络热点舆情深度剖析 * #### “千禾0”商标风波舆情事件分析 2025-03-21 703 一、事件概述近日,一款“千禾0”酱油中检测出镉,引发关注。有网友表示,“千禾0”只是一个商标,并不意味着“零添加”。 2025年3月,@消费者报道向第三方权威检测机构送检了13款宣称零添加的酱油,测评指标为:总砷和镉。测试结果显示:①12款零添加酱油检出镉,7款检出总砷。②无论是标榜“零添加”还是“有机”的酱油,在“重金属污染物”这一指标上,并没有显示出含量更低的“优势”。虽然所有样品均未超出安全限值,正常食用不会对消费者健康造成直接危害,但这一结果仍值得重视。3月19日下午,极目新闻记者联系千禾味业食品股份有限公司,咨询上述问题。客服人员回应称:“‘千禾0’确实是我们的注册商标,是我们旗下中高端产品的品牌标识,主要是为了保护知识产权。”“千禾0”是不是意味着零添加?对此客服人员一直没有正面回应,只是表示:“是否是零添加,可以参考配料表。”“请您完全完全放心,您可以看下我们的有千禾0标志的系列产品的配料表,仅使用常见的食品原料生产,甚至连酵母抽提物都不添加。” 记者同时咨询,被“消费者报道”点名的御藏本酿380天酱油瓶身有“千禾0”字样,它是不是零添加产品,客服人员也没有回应。 3月20日23时58分,千禾味业食品股份有限公司官方微博@千禾味业 发布沟通说明: 就酱油检出“镉”,说明称,“镉”存在于水、土壤、各类农产品中,酱油产品检出的微量“镉”来源于原料。根据GB2762《食品安全国家标准 食品中污染物限量》,酱油主要原料中“镉”的限量为:大豆≤0.2mg/kg,小麦≤0.1mg/kg,食用盐≤0.5mg/kg。公司使用的原材料均符合国家标准要求。 关于商标与零添加关系,说明称,该系列产品不存在蹭热点混淆零添加概念的行为。该商标设立的初心是为了对零添加产品做区别,便于广大消费者选购,不存在误导消费者的行为。 该话题引发舆论广泛关注,在百度形成“千禾0商标和零添加无关”“‘千禾0’不意味零添加?客服回应”“千禾味业致歉”“千禾0酱油公司股价四天蒸发9.5亿”等热榜,并在微博形成#千禾味业为消费者沟通不到位道歉#(阅读量3188 讨论量7)#千禾0商标不违法但不能混淆宣传#(阅读量9514 讨论量27)#宣称零添加千禾0酱油检测出镉#(阅读量4.1万 讨论量277)#千禾味业客服称原料均符合标准#(阅读量8.1万 讨论量101)#品牌方回应千禾0酱油被检测出镉#(阅读量8.4万 讨论量103)#千禾0商标被指误导消费者零添加#(阅读量2443万 讨论量9173)#律师称千禾0酱油存主观误导嫌疑#(阅读量3817.4万 讨论量1.4万)#千禾味业回应酱油产品检出镉#(阅读量7.4万 讨论量77)#千禾味业回应检出镉#(阅读量554.9万 讨论量623)#千禾味业称镉来源原料#(阅读量10.4万 讨论量53)#千禾味业否认商标误导消费者#(阅读量44.3万 讨论量236)#千禾味业道歉#(阅读量19.4万 讨论量86)#千禾味业情况说明#(阅读量322.7万 讨论量403)等热点话题。 二、舆情分布1.舆情走势2.媒体分布3.舆论热词 三、观点分析1. * 婚姻新规舆情聚焦:户口本退出的公众反响与社会思潮 * 舆情透视:河南干部违规聚餐致一人死亡 * 河北省三河市强换商户招牌事件舆情启示 * “炫富”扣分?券商如何完善声誉管理机制:从新规看长期发展路径 移动APP 舆情报告 邮箱:info@uuwatch.com | 邮箱:help@uuwatch.com | ©2019 UUWatch-京ICP备10045116号-1 | 京公网安备 11010802026281号 移动APP-优讯快报 ",
- "score": 0.11453701,
- "timestamp": "2025-08-21T23:24:29.882810"
- },
- {
- "query": "武汉大学舆情管理机制 校园性骚扰事件处理流程 高校网络举报制度 2023-2025",
- "url": "https://www.yuzhiku.com/search/?wd=%E7%A0%94%E7%A9%B6&duration=all&page=1&ext=all&category_id=7&sort=latest&language",
- "title": "研究- 舆知库· 国内首家舆情知识共享平台",
- "content": "动作视频,要求男生当场写下道歉信。事发已经3 个月,该事件未得到解决。 2023 年10 月13 日,武汉大学发布处理通报,内容显示: “对涉及我校学生的网上举报,经调查核实,",
- "score": 0.6994397,
- "timestamp": "2025-08-21T23:25:04.389243"
- },
- {
- "query": "武汉大学舆情管理机制 校园性骚扰事件处理流程 高校网络举报制度 2023-2025",
- "url": "https://city.sina.cn/finance/2023-10-14/detail-imzqzsqr0936718.d.html?oid=%E5%9B%BD%E5%A4%96%E9%BB%84%E8%89%B2%E7%9B%B4%E6%92%AD%E8%BD%AF%E4%BB%B6app(iz262.com).mzs&vt=4&cid=76729&node_id=76729",
- "title": "“学生举报遭受性骚扰”,武汉大学处分 - 新浪城市",
- "content": "Missing: 舆情 管理 机制 校园 流程 高校",
- "score": 0.6350646,
- "timestamp": "2025-08-21T23:25:04.389243"
- },
- {
- "query": "武汉大学舆情管理机制 校园性骚扰事件处理流程 高校网络举报制度 2023-2025",
- "url": "https://news.qq.com/rain/a/20250731A0AESM00",
- "title": "武汉大学校长张平文:正在处理“学生性骚扰指控被驳回”事件 - QQ News",
- "content": "记者李微敖2025年7月31日晚间,针对备受关注的“武汉大学图书馆男生涉嫌性骚扰指控被法院驳回”一事,武汉大学校长张平文回应了经济观察报记者的电话求询",
- "score": 0.4604593,
- "timestamp": "2025-08-21T23:25:04.389243"
- }
- ],
- "latest_summary": "2023年武汉大学图书馆性骚扰指控事件引发广泛社会关注。事件始于2023年7月,武大研究生杨景媛在网络上举报本科生肖明滔在图书馆自习时实施性骚扰行为。校方随后对肖明滔作出纪律处分,但杨景媛不满处理结果,于2024年6月向法院提起诉讼。2025年7月25日,法院一审判决认为证据不足以认定性骚扰,驳回杨景媛全部诉求。判决后舆论持续发酵,网民质疑杨景媛涉嫌诬告和学术不端,同时批评校方最初处分决定。武汉大学于8月1日成立工作专班,宣布对涉事双方的纪律处分和学位论文进行全面调查复核。该事件折射出中国高校舆情管理、性别议题和网络举报机制面临的复杂挑战。近年来,师德师风、校园冲突等事件频繁成为舆论热议话题,暴露出高校在舆情危机管理中流程透明度不足、处理机制不完善的问题。面对舆情,校方回应往往滞后或模糊,如仅以“等上级指示”应对,反映出治理能力缺陷和系统性失灵。网络举报处理流程缺乏标准化,易引发二次舆情发酵,需加强制度建设与公众沟通。从2023年10月13日武汉大学发布的处理通报可以看出,该校对涉及学生的网上举报采取了调查核实机制,但在具体执行中仍存在改进空间。2025年7月31日,校长张平文对媒体回应法院驳回事件,显示校方在舆情管理上逐步加强对外沟通,但整体机制仍需完善,特别是在校园性骚扰事件处理流程和高校网络举报制度方面需要更系统化的规范。",
- "reflection_iteration": 2,
- "is_completed": true
- },
- "order": 0
- },
- {
- "title": "详细分析",
- "content": "深入分析'武汉大学舆情'的相关内容",
- "research": {
- "search_history": [
- {
- "query": "武汉大学舆情分析 2023年事件 网络舆论反应",
- "url": "https://zhuanlan.zhihu.com/p/1936732945986622708",
- "title": "武汉大学图书馆事件完全是舆情处置不当的典型案例 - 知乎专栏",
- "content": "2023年事件初起时,舆论关注点集中在对被指控者的道德谴责上,校方虽迅速给予“记过”处分,但未提供具体调查流程和行为定性依据,这种“既处理又不说明”的模糊",
- "score": 0.8088086,
- "timestamp": "2025-08-21T23:25:36.404526"
- },
- {
- "query": "武汉大学舆情分析 2023年事件 网络舆论反应",
- "url": "https://yuzhiku.net/document/5fc5f38b72743d88",
- "title": "武汉大学“图书馆事件”:“上级批准”折射的舆情治理困境- 舆知库",
- "content": "... 2023 年10 月,武汉大学校园内发生的一起“图书馆事件” 持续发酵至今 ... 网络传播后迅速升温,武汉大学在舆情压力下,仅用三天时间就作出反应",
- "score": 0.77405477,
- "timestamp": "2025-08-21T23:25:36.404526"
- },
- {
- "query": "武汉大学舆情分析 2023年事件 网络舆论反应",
- "url": "https://www.yjts2013.org/?article/sPBOj8T3txhGZ-160.shtml",
- "title": "武汉大学:对肖某某纪律处分、杨某某学位论文等进行全面调查复核",
- "content": "尽管法院给出了判断,但经过此番舆论纷争,肖某某的私人信息被“开盒”曝光,同学孤立他、陌生人谩骂他。2023年10月17日,他在武汉市精神卫生中心被确诊为“急性",
- "score": 0.7154444,
- "timestamp": "2025-08-21T23:25:36.404526"
- },
- {
- "query": "武汉大学图书馆事件 2023年10月 校方回应 网络暴力治理 高校舆情管理",
- "url": "https://linux.do/uploads/short-url/qCdQEsdSKvMXjUTXo397YoS8HA8.pdf",
- "title": "[PDF] 从“武汉大学图书馆事件”透视中国当代社会治理 - LINUX DO",
- "content": "第一阶段:舆情驱动下的快速反应(2023 年10 月)。面对汹涌的舆论,武汉大学采取. 了典型的“舆情维稳”策略。在杨某发帖后不到48 小时,校方即发布通报,给予肖",
- "score": 0.86767644,
- "timestamp": "2025-08-21T23:26:02.732385"
- },
- {
- "query": "武汉大学图书馆事件 2023年10月 校方回应 网络暴力治理 高校舆情管理",
- "url": "https://zhuanlan.zhihu.com/p/1933497945690477905",
- "title": "武大杨景媛毕业论文疑云:错漏扎堆还被指AI 代写,这道学术红线 ...",
- "content": "* 首页 * 知乎直答 * 知乎知学堂 * 等你来答 武大杨景媛毕业论文疑云:错漏扎堆还被指 AI 代写,这道学术红线到底踩没踩? 切换模式 武大杨景媛毕业论文疑云:错漏扎堆还被指 AI 代写,这道学术红线到底踩没踩? # 武大杨景媛毕业论文疑云:错漏扎堆还被指 AI 代写,这道学术红线到底踩没踩? 创作声明:包含 AI 辅助创作 21 人赞同了该文章 ## **杨景媛事件双重危机:诬告行为与学术造假中的道德失范与制度反思** ## **事件核心争议与舆情引爆机制** ### **图书馆诬告案司法判决与社会伦理冲击** 2025年7月25日,武汉大学图书馆案一审宣判,法院认定肖某某行为系“抓痒”而非性骚扰,驳回杨景媛全部诉讼请求。此前,2023年7月11日,杨景媛在图书馆自习时指控对面男生肖某某存在“隔空性骚扰”,并拍摄5段视频,要求其写下道歉信,随后将事件公开至网络,引发超10万浏览量。武汉大学在事发两天后对肖某某处以记过处分,但通报未说明具体违纪行为。后经医学专家论证,肖某某动作符合皮肤病抓挠特征,不符合性骚扰构成要件。尽管司法判决还以清白,杨景媛仍宣称“不会道歉因为我没错”,并炫耀“保研成功”“继续读博”。此行为引发社会伦理冲击,公众质疑其道德底线,担忧其若进入司法体系可能导致冤假错案。 ### **硕士论文编造法律条文及数据造假实证** 杨景媛的硕士学位论文被曝存在严重学术不端。论文中虚构并不存在的“《离婚法》”,声称该法“2001年通过”,而中国从未颁布此类法律,相关内容实际包含于《民法典》婚姻家庭编中。此外,论文引用“中国社会科学院的数据”称全中国30%妇女遭受家暴,但未注明研究人员、发表期刊或具体文件名称。其援引“世界卫生组织2018年发布的《全球、区域及国家针对妇女的家庭暴力发生率调查》”,而该文件实际发布于2021年。知乎网友进一步指出,论文大量删除不利于结论的数据,结论依赖非学术性质的working paper,且计算方法存在逻辑矛盾。这些行为严重违反学术规范,引发网友要求武汉大学调查并撤销其学位。 ### **涉事主体保研资格与香港高校录取争议** 尽管被法院驳回诉讼请求,杨景媛仍获得武汉大学保研资格,并被香港浸会大学博士项目录取。而受害者肖某某因记过处分丧失学位证,保研与法考资格受阻。其祖父因网暴刺激去世,本人确诊创伤后应激障碍(PTSD),自杀倾向达80%,被建议强制住院治疗。公众强烈质疑武汉大学在杨景媛已毕业、学籍“自动结束”的情况下未对其采取任何处理措施,呼吁取消其保研资格及学位。香港浸会大学于2025年7月28日回应称,学校设有清晰的招生政策和行为守则,将根据既定纪律程序处理违规行为,但不会对个别个案评论。舆论推测其可能采取“勒令退学”“休学观察”或等待武汉大学处理结果三种应对方式。 ### **网络舆论场中的道德谴责与程序正义之争** 事件初期,杨景媛将视频发布网络,舆论一边倒谴责肖某某,致其被“开盒”,照片被制成花圈、遗像,家人遭受网暴[^1]。武汉大学在未充分调查情况下仓促处分肖某某,被指“重舆轻法”。法院判决后,舆论反转,公众转而对杨景媛进行道德谴责,要求其承担诬告责任[^1]。同时,校方未撤销对肖某某的处分、亦未启动对杨景媛的调查,引发对程序正义缺失的批评。专家梅新育呼吁武汉大学取消杨景媛硕士学位,开除其学籍,恢复肖某某名誉,并支持其家庭以诬陷罪提起刑事诉讼。公众强调“心术不正者绝不能掌握公权力”,亟需建立防诬告机制,筑牢程序正义防线。 ## **诬告事件的行为逻辑与后果链分析** ### **特应性皮炎误判为性骚扰的医学证据链** 2023年10月,杨景媛在武汉大学图书馆指控肖同学实施“隔空性骚扰”,称其手部动作疑似自慰。肖同学解释该行为系因患有特应性皮炎五年,事发时因阴囊湿疹复发而隔衣抓痒。为佐证此说法,其母亲提供了多份就诊记录、药物购买微信聊天记录及发票。5名来自同济医院、协和医院泌尿外科与男科的专家联合论证,指出视频中动作不具备自慰特征:行为无针对性对象、无眼神或身体接触、非惯用手操作、节奏不连贯且存在长时间间断。此外,事发前后双方无交流,不符合自慰需心理幻想的前提。法院最终采纳医学意见,认定肖同学行为“无针对性”且“非性目的”,不构成性骚扰,驳回杨景媛全部诉讼请求。 ### **两封道歉信书写过程的权力胁迫特征** 杨景媛要求肖同学当场书写两封内容矛盾的道歉信。第一封称“拍了姐姐,侵犯隐私权”,第二封改为“做了下流的事”,但未明确具体行为。录音显示,杨景媛以“写道歉,快点写”开启对话,肖同学多次请求删视频、私下解决,并表示“万一他们要处分我”“什么条件我都接受”。在“不写就曝光视频”的隐性威胁下,肖同学出于对学业处分的恐惧写下道歉信。双方对话中从未明确定义“犯错”或“下流的事”所指何为,导致道歉信缺乏真实意思表示基础。法院审理时可能考量到该信件产生于心理压迫情境,削弱其证据效力。 ### **受害者家庭三代人的创伤代际传递** 肖同学因被诬告遭受毁灭性打击。身心健康方面,其被确诊为创伤后应激障碍(PTSD),自杀倾向达80%,精神病院建议强制住院治疗。学业前途上,保研与留学计划化为泡影,法考及从业资格亦受阻。家庭层面,其照片遭“开盒”并被制成花圈、遗像,家人陷入网络暴力漩涡。其祖父因无法承受舆论压力,半年后离世。母亲公开表示:“普通人遇到打击时是弱小的,但法律应是照亮黑暗的灯,而非屈服于流量的墙”。整个家庭命运因一桩误判陷入长期创伤,形成跨代际的心理与社会损害链条。 ### **高校纪律处分决策中的程序瑕疵研究** 武汉大学在事件发生仅两天后即对肖同学作出记过处分,通报中仅称“存在不雅行为,有损大学生形象”,未明确具体违纪事实,亦未定性为性骚扰。校方相关负责人承认,该处分系为“给舆情降温”,属应急处理措施。肖母申诉时提交医学证据,校方虽表示“可排除性骚扰”,但仍维持处分决定。法院判决后,因杨景媛已毕业,“学籍自动结束”,校方未对其采取任何追责措施。此举被质疑“重舆轻法”,暴露高校治理中以行政手段替代事实调查的惯性,损害程序正义与学术机构公信力。公众呼吁撤销错误处分、恢复肖同学名誉,并追究诬告责任以匡扶正义。 ## **学术造假的技术解构与制度漏洞** ### **《离婚法》虚构与社科数据来源伪造** 杨景媛的硕士论文中存在严重的学术不规范问题,其中最显著的是虚构了中国并不存在的《离婚法》,并声称该法“2001年通过”。而实际上,相关内容包含在《民法典》婚姻家庭编中,这一基础性错误出现在硕士论文中令人难以理解。此外,论文中多处数据未标注具体来源,例如引用“中国社会科学院的数据”时未提供具体研究人员或论文期刊信息,也未说明数据发布的具体来源。同样,论文还声称世界卫生组织有相关调查,但援引的文件实际发布年份与论文中所述不符,文件实际发布于2021年,而论文中声称其为2018年发布。 这些学术不规范行为严重违反了学术研究的基本要求,每一个引用都应准确无误,以保证研究的可追溯性和可靠性。杨景媛论文中的这些问题不仅反映出其学术态度的不严谨,也凸显了学校在论文审核过程中可能存在把关不严的情况。 ### **印度家暴数据选择性剔除的学术欺诈** 除了虚构法律和数据来源不明的问题,杨景媛的硕士论文还存在数据选择性剔除的情况。有网友指出,该论文在研究中印生育行为对家庭暴力的影响时,大量删除中国和印度不利于论文结论的数据,其结论高度依赖一篇根本不是学术论文的working paper,计算方法也存在逻辑矛盾。这种行为严重影响了论文结论的科学性和可靠性,违背了学术研究的客观性原则。 此外,论文中还声称中国社科院发布数据,称全中国30%的妇女遭受家暴,但未写清楚具体的研究人员或发表的期刊名称,这种没有出处的数据很可能是杜撰的。同时,论文还声称世界卫生组织也有同样的调查,但同样没有给出出处。这些学术欺诈行为不仅损害了学术研究的可信度,也反映出部分研究者为了达到预期结论而采取不恰当手段处理数据的问题。 ### **导师指导责任与答辩委员会审查失效** 杨景媛的硕士论文通过了导师指导和答辩委员会审查,然而论文中存在如此多的学术不规范问题,这表明导师在指导过程中可能没有尽到严格把关的责任,答辩委员会在审查时也未能发现这些明显的问题[^3]。稍有法律常识的人都知道,我国并没有一部单独叫做《离婚法》的法律,相关内容是包含在《民法典》婚姻家庭编当中的,但这一基础性的错误却出现在硕士论文中,并通过了审核和答辩,让人难以理解。 此外,论文中还存在大量不符合学术文章撰写规范的地方,如数据未标注来源、涉嫌抄袭等。这些行为不仅反映出杨景媛个人学术态度的不严谨,也让人对学校论文审核的专业性和严谨性产生怀疑。大学在论文审核和答辩过程中,是否真正做到了严格把关,还是只是走走过场,让不符合学术规范的论文蒙混过关,这些问题都值得深入探讨[^3]。 ### **武汉大学学术不端处理机制响应延迟** 在杨景媛的硕士论文被曝光存在学术不规范行为后,武汉大学在事件初期未充分调查,仅因网络舆论对肖某某记过处分;法院判决后,校方未对杨景媛采取任何处理,因其已毕业“学籍自动结束”。这一情况体现了武汉大学学术不端处理机制存在响应延迟的问题,未能及时对学术不端行为进行调查和处理。公众呼吁武大撤销肖某某处分、道歉赔偿,并取消杨景媛保研资格及学位,以匡扶正义。 此外,武汉大学制定了大学教师学术不端行为处理制度,适用于本校全体在职教师,明确了学术不端行为的定义,包括抄袭、剽窃他人学术成果,伪造、篡改科研数据、图表、结论等多种行为。制度规定了举报与受理流程,任何单位或个人发现教师存在学术不端行为,均可通过书面、电子邮件、电话等方式向学校学术委员会举报,学校学术委员会负责受理并在接到举报后5个工作日内进行初步审查。调查与认定程序方面,对于决定受理的举报,学校学术委员会应在10个工作日内成立专门的调查组,调查取证工作需在30个工作日内完成,最终由学校学术委员会作出最终认定决定。对于被认定存在学术不端行为的教师,根据情节轻重给予相应处理,包括警告、记过、降低岗位等级或撤职、开除或解除聘用合同等,同时对其学术成果也会进行相应处理,如责令撤回已发表的论文等。然而,这一制度在杨景媛事件中未能有效执行,反映出高校在学术监管和论文审核方面可能存在走过场的情况。 ## **高校危机应对的双轨制困境** ### **受害者处分未撤销与加害者学位存续** 在杨景媛事件中,武汉大学对肖某某的处分自2022年7月13日做出后,至今未撤销。法院一审判决于2024年7月25日认定肖某某行为不符合性骚扰构成要件,但校方未对处分决定进行调整。与此同时,杨景媛在2024年通过硕士论文答辩并获得学位,其论文中存在编造“《离婚法》”、引用数据未标注来源等学术不规范行为,引发网友对其学位合法性的质疑。尽管法院已判决其诬告行为成立,杨景媛仍公开宣称“不会道歉因为我没错”,并炫耀“保研成功”“继续读博”。 ### **香港浸会大学招生条款中的道德审查权** 杨景媛已被香港浸会大学录取为研究生,但因其诬陷事件和学术不端行为,众多网友向该校举报,要求重新审视其录取资格。7月28日,香港浸会大学回应称学校设有清晰的招生政策和行为守则,对于任何违反行为守则的大学成员,将根据既定纪律程序处理,但不会对个别个案作出评论。舆论普遍认为,该校面临三种可能处理方式:一是直接启动纪律程序勒令退学;二是采取限制+监控策略;三是拖延等待武大处理结果和官媒风向。 ### **校友网络施压与校方公关策略选择** 事件曝光后,大量校友和网友通过社交媒体对武汉大学施压,要求其撤销对肖某某的处分、取消杨景媛学位并追究其法律责任。专家梅新育呼吁武汉大学尽快取消杨景媛硕士学位,开除其学籍,并支持受害男同学家庭以诬陷罪刑事起诉杨景媛,同时申请限制其出境。公众还担忧杨景媛作为法学背景者,若进入司法系统将导致滥用职权和司法不公,强调需建立机制防止诬告陷害,保障程序正义。面对舆论压力,武汉大学尚未采取实质性行动,仅表示“学籍自动结束”。 ### **高等教育声誉管理中的法理人情博弈** 杨景媛事件暴露出高校在声誉管理中面临的法理与人情冲突。一方面,法院已判决其诬告行为不成立,但武汉大学未对其采取任何处理措施,反而允许其保研成功,引发公众对高校公正性的质疑。另一方面,肖某某因网暴被确诊创伤后应激障碍,家人遭受身心伤害,保研和留学计划化为泡影,甚至有长辈因打击离世,但武汉大学仅以“应急处理”回应其母亲异议。公众呼吁高校建立更加严格的道德审查机制,在招生、保研、毕业等环节不仅要考察学术成绩,更要注重道德品质。 ## **法学教育背景下的道德异化现象** ### **法律从业者诬告行为的社会危害倍增效应** 杨景媛作为法学专业学生,对肖同学进行诬告,导致其身心俱疲、学业受阻,保研和留学计划化为泡影,全家深陷网暴漩涡,有长辈因打击离世。2023年7月11日,杨景媛指控肖同学“隔空性骚扰”,并当场要求其写下道歉信。随后,杨景媛将此事发到网络上,引发大量关注,武汉大学迅速对肖某某处以记过处分。2024年7月25日,法院审理认为不能认定肖同学实施了性骚扰,驳回杨某的指控。即便如此,杨景媛败诉后仍公开宣称“不会道歉因为我没错”,并扬言“要毁掉肖某某人生”。 ### **司法体系准入资格审查机制漏洞研究** 杨景媛事件引发了公众对司法体系准入资格审查机制的担忧。作为法学背景者,杨景媛不仅诬告同学,还在硕士论文中存在严重学术不规范行为,编造不存在的“《离婚法》”,多处数据未标注来源。公众担忧其若成为法官、检察官或警察,将滥用职权导致司法不公。网友强调“心术不正者绝不能掌握公权力”,需建立机制防止诬告陷害,保障程序正义。武汉大学在处理该事件时也存在责任缺位问题,在法院判决后未对杨景媛采取任何处理,因其已毕业“学籍自动结束”。 ### **法治信仰缺失与工具主义法律观形成** 部分大学生存在法治信仰缺失问题,表现为法律知识薄弱、法律意识模糊。杨景媛事件进一步凸显了这一问题。她作为法学专业学生,本应具备良好的法律素养和道德观念,但其行为却严重背离法治信仰,诬告同学并在硕士论文中出现大量疑似编造和引用错误的内容。这种法律信仰与行为选择的脱节,反映出在法学教育中可能过于注重知识传授,而忽视了伦理道德教育[^34]。为加强大学生法治信仰培育,高校应系统强化自我法治教育,改革创新法治教育体系,并营造良好法治教育氛围。 ### **比较视域下中美法学院伦理教育体系差异** 杨景媛事件揭示了中国法学院在伦理教育方面的不足。武汉大学法学院作为国内顶尖法学院之一,近年来发展迅猛,尤其在国际法方向。然而,在杨景媛事件中,学校在处理学生问题和学术监管方面未能做到严格把关,反映出在伦理教育和学术监管方面可能存在漏洞。相较而言,美国法学院普遍更注重法律职业伦理教育,强调法律信仰与行为规范的统一。中国高校应借鉴国际经验,完善法治教育理论体系,优化教学内容,改进教学方法,强化法律教师业务水平,以提升学生的法律信仰与职业操守。 ## **网络实名举报的治理效能评估** ### **跨平台舆情发酵的多节点传播路径** 2023年7月11日,杨景媛在武汉大学图书馆指控男生肖某某实施“隔空性骚扰”,并拍摄5段视频作为证据在网络发布,相关文章阅读量迅速突破10万+。事件初期,肖某某被“开盒”,照片被制作成花圈、遗像,遭“官二代、富二代性欺压女生”等谣言攻击,全家陷入网暴漩涡。尽管肖某某患有特应性皮炎且动作经医学专家论证为“抓痒”,但舆论已在“罗生门”中失控。2024年6月,杨景媛起诉肖某某,要求其公开道歉并赔偿精神损失,进一步推动事件跨平台传播。2025年7月25日法院一审驳回其诉讼请求后,杨景媛在微博和B站宣称“已顺利毕业且保研成功”,并扬言持续举报肖某某,再度引爆全网谴责。 ### **香港媒体介入对事件升级的催化作用** 杨景媛被香港浸会大学录取后,网友将其学术不端与品行问题材料电邮至该校研究生院,引发广泛关注。7月28日,香港浸会大学回应称,学校设有清晰的招生政策和行为守则,将根据既定纪律程序处理违纪成员,但不会对个案评论。香港媒体《香港01》跟进报道,指出若抄袭坐实,杨景媛可能面临撤销学位、留校察看或直接退学处分。该校的公开回应与媒体报道使事件由内地网络舆情演变为跨境教育机构应对危机的典型案例,强化了公众对高校招生伦理审查的期待。 ### **学术共同体同行评议机制的民间替代** 杨景媛的硕士学位论文《中印生育行为影响家庭暴力的经济学分析》被网友深度挖掘,发现其编造中国从未颁布的“《离婚法》”,并错误标注该法“2001年通过”。论文引用“中国社会科学院的数据”“印度国家犯罪研究局的官方报告”均未提供具体出处;援引“世界卫生组织2018年发布的《全球、区域及国家针对妇女的家庭暴力发生率调查》”实则发布于2021年。技术博主通过ChatGPT逆向检索,发现部分段落与早年英文论文句式原封不动对应,且数据回归表格系数与引用文献一致却未列入参考文献。另有分析指出其论文存在错别字频出、年代计算错误(如将“1960至1965”写作“1960至1665”)、数据比例误算(1/1000算作0.01%)等低级失误。这些由网民主导的学术审查行为,构成了对正式同行评议机制的民间替代,凸显学术监管在公共监督下的透明化压力。 ### **网络暴力与理性监督的边界划定研究** 事件初期,武汉大学未充分调查即对肖某某作出记过处分,理由为“存在不雅行为,有损大学生形象,造成不良影响”,校方承认此举系为“平息舆论”。该处分导致肖某某保研与留学计划中断,被确诊创伤后应激障碍,多次出现自杀倾向,其爷爷因不堪网暴谩骂半年后离世。法院判决后,武汉大学未对杨景媛采取任何处理措施,因其已毕业,“学籍自动结束”。与此同时,部分网民在事件“反转”后对杨景媛进行辱骂与调侃,使其亦遭受网暴。专家梅新育指出,杨景媛在法院败诉后毫无悔意,反而炫耀保研成功并威胁继续举报,已“突破人性和廉耻底线”,应取消其学位、支持刑事反诉并限制出境。公众普遍担忧其作为法学背景者若进入司法体系,可能滥用职权导致冤假错案,呼吁建立防止诬告陷害的制度防线。此事件揭示了网络举报在缺乏证据审查前提下极易滑向群体性暴力,亟需在程序正义与舆论监督之间建立清晰边界。 ## **研究生培养质量监控体系重构** ### **硕士论文外审盲评制度的实效性检验**",
- "score": 0.8020137,
- "timestamp": "2025-08-21T23:26:02.732385"
- },
- {
- "query": "武汉大学图书馆事件 2023年10月 校方回应 网络暴力治理 高校舆情管理",
- "url": "https://www.163.com/dy/article/K61M3A3G0528DORE.html",
- "title": "武汉大学副校长夜闯民宅“警告”网友,百万网友在线围观! - 网易",
- "content": "... 年的校园风波。2023年10月,武汉大学女生杨某某指控男生肖某某在图书馆实施性骚扰,校方火速给予肖某某记过处分。网络暴力随之而来:肖某某照片被制成",
- "score": 0.7593865,
- "timestamp": "2025-08-21T23:26:02.732385"
- },
- {
- "query": "武汉大学图书馆事件 舆情管理 高校危机公关 网络暴力治理 2023",
- "url": "https://www.ichaoqi.com/zhuanlan/2024/0206/66781.html",
- "title": "八问武大图书馆事件:网暴裹挟下,辨别真相更需克制",
- "content": "# 八问武大图书馆事件:网暴裹挟下,辨别真相更需克制 **编辑 | 虞尔湖** **出品 | 潮起网「于见专栏」** 时隔数月,南方都市报就此事件发布了深度调查报道,当事男生母亲也首次现身知乎、微博发声,就事件核心进行回应:**1)不存在性骚扰的行为;2)女生的微信文章中存在大量不实信息(展示的道歉信遭涂改谎称男生留假名、捏造证人证言、捏造经济补偿);3)男生遭遇大量网暴,重度抑郁、几欲轻生。**此后,相关话题迅速引发了全网讨论,并占据了多个热搜,“武大图书馆事件”疑似发生反转。 图/当事人母亲微博发声 前有“川大张薇案”、“清华学姐举报学弟案”,后有“武汉年轻母亲因网暴谣言轻生案”、“粉发孙女因网暴自杀案”,在事实尚未厘清的情况下,深陷舆论漩涡中的当事人,往往在一面倒的舆论审判中,身心提前遭受了巨大的伤害。这不禁让人深思:在事实尚未明晰的基础下,过早进行舆论冲击和审判,这是否也是一种网络暴力?即便最后事实还原,依然对漩涡中的当时人造成了不可逆的实质伤害。舆论裹挟下,大众如何得知真相?我们是否应该让子弹再飞一会儿,再下判断? 为尽可能客观地还原武汉大学图书馆事件真相,「于见专栏」记者专程奔赴武汉,深度采访事件各方。 **各执一词,真相究竟如何?** 记者根据疑似当事女生于去年10月11日在微信公众号“景容饮冰”发布的文章《关于我在武汉大学图书馆受到性骚扰这件事》,以及疑似当事男生母亲于今年2月4日在知乎问答中发布的回复,梳理出的事件经过如下图表格所示。 图/记者整理供图 经记者认真对比双方表述及过往事件信息,发现双方关于诸多细节存在冲突,关于“是否存在性骚扰”这一核心问题上,双方所持观点截然相反。一时间,谁是谁非的争论让事件陷入“罗生门”。为还原事件全貌,记者就以上诸多双方表述的冲突和疑点,走访、电采了当事人、校方、警方及当事人周边知情人士了解情况。 **八问武大图书馆事件** **疑问1: 女方称“其受到性骚扰”,男方称“校方、警方及各方均明确表示不是性骚扰,男生常年罹患湿疹,偶有隔衣小幅挠痒”。各方认定是否真实,男生又是否真实罹患湿疹?** 根据男生母亲向记者出示的两份门诊病历显示,2022年1月,患者主诉“双眼周起疹并伴痒半年”,体格检查“双眼周内眦为主可见片状淡红斑”;2023年11月,患者体格检查“阴囊处可见片状潮红斑,局部略肥厚”,门诊诊断意见“湿疹”。男生母亲还向记者展示了2022年9月、2023年1月,也就是事发前后,两份分别与当事男生、医院护士关于湿疹治疗、拿药的相关聊天记录。 图/受访者供图 男生母亲告诉「于见专栏」,湿疹这个病男生从小就有,除眼周、四肢等部位外,在大腿根部和阴囊处也会反复发作,并伴有瘙痒。 从上述信息来看,男生确实常年患有湿疹,在事发之前就需要持续用药。 关于各方对事件的认定意见,「于见专栏」曾给武汉大学宣传部去电,但未得到回复,也电话联系了武汉市武昌区公安分局,对方表示会向相关部门反馈,如有消息会联系记者。但截至发稿,尚未得到回复。 **疑问2:男方称:已经告知校方湿疹疾病史并由学院代为传达,女方是否知情?如女方知道男方长期患有湿疹,为何其微信文章中没有体现?** 在女生发布的文章中,可以清楚看到,女生将男生的行为定义为“性骚扰”,且全文未提及男生罹患湿疹。而男方称,7月17日,女生取消了院方安排的会面后,男方再次和院方阐述了湿疹等事实,望代为传达。 那么,女生后来是否知道男生患有湿疹?如果女生不知情,在知情后她是否会改变想法?如果女生知情,但却未在文章中提及此事,是否有其他的动机?「于见专栏」记者始终未能联系到当事女生。目前暂无更有力的证据印证其中一方的说法。 **疑问3: 女方称,现场有一位男性证人站出来作证;男方称,校方、保卫科或警方问询该证人,该证人均表示只看到双方爆发争吵。该事件是否有证人作证?** 女生在微信文章称:“对峙过程中,身后另一位男性同学站出来表示看到**男生承认的全过程**,并表示愿意作证。”而从女生在公众号中发布的疑似证人字条中可以看到,证人并未就其证明的内容做具体表述。 图/微信公众号景容饮冰 男方称,校方和警方均已找到了这位证人并询问了现场情况,无论是面对校方或警方,该证人均表示,只看到双方争吵的过程,**具体争吵事由及情况均不太了解**。 截至发稿,记者未能采访到证人,但是基于以上双方表述,可以看到,证人能证明双方在图书馆曾经发生过冲突,但证人无法证明其争吵事由及其他情况。后续如果有现场录音释出,或许能更清楚的还原现场事件经过。 **疑问4: 女方称,因为道歉信留假名,并不认可其为道歉信;男方称,道歉信留的是真名,关于该道歉信,究竟孰真孰假?** 文/微信公众号景容饮冰 在此前的文章中,最具争议的当属事件中的道歉信。根据微信公众号文章中的表述,因为留假名,闪烁其词,所以女生不认可这是道歉信。 左图/微信公众号景容饮冰,右图/道歉信原图(受访者供图) 在采访中,「于见专栏」记者获得了一份当时男生手写道歉信的原件照片(上方右图)。记者对比了两个版本的道歉信,发现微信公众号中发布的道歉信(上方左图)将信开头的署名全部隐去,但男方提供的版本中,其姓名、学号、专业能一一对应,并在文末主动留下了自己和辅导员的联系方式,而并非女生所称留假名的情况。 至于女生为何在文章中抹去开头及结尾全部信息,并表示男方留了假名,目前尚不知道原因。 **疑问5: 女方称,男方当事人及男方家长曾多次通过导员提出给予经济补偿,并有录音为证。男方称,男方当事人及其家长从未提出直接或间接提出过此类想法,也向男方的辅导员求证过是否有过此类表述,辅导员表示没有。经济补偿说法是否为真?** 关于该疑问,目前女生未公布关于经济补偿的录音证据,双方各执一词,在未取得进一步证据之前,无法判定该说法真伪。如后续没有相关的证据,则为罗生门。 **疑问6: 女方提出的“取消男方的一切评奖评优资格、保研资格以及本科期间不得入党”这一诉求,是否有相关校纪校规作为处置依据?其处置流程为何?** 目前,除了武汉大学10月13日发布的记过处分文件外,没有任何官方披露的事件调查经过及事件定性文件。而武汉大学记过处分文中,也只是简单写道,根据相关规定,学校研究决定,给予记过处分。至于处分的原因及调查经过,均没有提及。 图/武汉大学官网 记者从武汉大学学校官网找到的一份发布于2022年8月的学生纪律处分办法(修订)文件中看到,学校对有违纪行为的学生,视情节轻重,给予如下纪律处分:(一)警告;(二)严重警告;(三)记过;(四)留校察看;(五)开除学籍。 对学生的纪律处分,要做到证据充分、依据明确、定性准确、程序正当、处分适当。警告和严重警告处分的期限为6个月,记过和留校察看处分的期限为12个月,期限从处分决定作出之日起计算。在处分期限内,学生不得参加评奖、评优、评先等。解除处分后,学生获得表彰、奖励及其他权益,不再受原处分的影响。 图/武汉大学官网 从以上文件中可以看到,女生此前在公众号中传达的诉求:要求男生所在学院取消男生本科四年的一切评奖评优资格,保研资格以及本科期间不得入党,已远远超出学校关于记过的相关规定。 此外,记者注意到,该文件第十五条第二点规定:造谣、诬陷、侮辱、谩骂他人,捏造事实诽谤他人,经批评教育不改者,给予警告或严重警告处分;造成严重后果者,给予记过及以上处分。 图/武汉大学官网 **疑问7:女方称,其提供的证据已经能够确认男方构成性骚扰,但未报警处理此事。男方称,男方父母多次前往派出所,提交报案材料并接受笔录,其中明确表示公安机关没有认定是性骚扰。为何女方称受到性骚扰不报警?为何事件由男方报警?报警后处理结果如何?** 男方称,文章发布后,网上舆论如潮,因事态严重,当天晚上,男生父母带着当事男生就前往武昌区珞珈山派出所报警,同时也向警方及学校说明了男方患有湿疹一事。 「于见专栏」记者电话联系了派出所的上级单位武汉市武昌区公安分局,对方表示会向相关部门反馈,如有消息会联系记者。但截至发稿,尚未得到回复。但是从女生不进行报警,由男方报警的反常举动来看,事件疑似另有隐情。无论结果如何,我们都期待官方对该事件的后续调查结果,在官方未公布调查结果前,希望媒体及公众在对事件进行传播时,可以更冷静、客观、辩证的看待问题,防止对两位当事人造成身心上的二次伤害。 **疑问8: 女方称,男生家长提出见面诉求但自己拒绝见面。男方称,应女生诉求,辅导员安排双方见面,但女生临时变卦。双方目前是否见过面?若无,为何一直未能见面?** 按照当事人双方说法,事件至今双方均未见面沟通,截止发稿前,记者向男生家属确认,双方仍未能见面。为何不能见面沟通、妥善处理此事的缘由不得而知,但从前面记者盘点的诸多疑问来看,双方说法各执一词,存在诸多误会仍待解决。希望双方能在保障各自人身权益的情况下,尽快妥善处理此事,也避免对当事人造成更深的伤害。 **舆论平息,网暴伤害却未曾停止** 在当事男生位于武汉的家中,记者见到了当事男生。距离事件舆论爆发已过去近3个月,虽然当事男生体格不小,但精神状况仍然肉眼可见的萎靡。在采访期间,每每谈及事发经过,男生时常情绪激动,一度导致采访中断。 男生母亲告诉记者,事件发生后至今,尚没有任何官方渠道认定男生为“性骚扰”,但男生在事发后已然“社会性死亡”——被踢出足球队微信群;大学生创业项目组的成员纷纷退出,目前项目已停滞;作为毛泽东思想概论课程助教,事发后他往群里发信息,一些同学就开始发“小拇指”等嘲讽意味的表情包…… “因为被冤枉,在事件发生后,我的儿子出现了不吃不睡、失眠、不沟通、哭泣、意图自杀等行为,已确诊创伤后应激障碍,现在每天都要服用精神类药物控制病情。”男生母亲表示,男生曾多次企图在家跳楼自杀,为了让男生活下去,才决定站出来维权。 根据男生母亲出具的一份武汉市精神卫生中心诊断报告显示,当事男生被确诊为PTSD创伤后应激障碍,其自杀和自残行为倾向达到80%。 图/受访者供图 记者也通过电话联系上了当事男生的老师及多位同学了解情况。“他在学校主动辅修了双学位的课程,平时课程量很重,但他一直挺勤奋好学的,很正直的一个学生。”曾经教授过男生的一名老师对「于见专栏」表示,“事情发生后,我了解到他曾多次接受心理治疗,网暴对男生的伤害挺大的。” “那篇公众号文章发出来以后,我们是很震惊的。完全不敢相信,因为凭我在生活和学习中对他的了解,他完全不像是能做出这种事的人。”男生的大学同学赵威(化名)表示,“他平时学习特别认真、特别努力,生活基本就是两点一线,下课后就去图书馆。” 周围同学对当事男生的态度也因为这件事快速转变。赵威表示,“身边的同学对他会有很明显的鄙夷和疏远,一些完全不认识他的同学,也会发微博或朋友圈继续说这个事,甚至有点抹黑他。之前跟他有些瓜葛的同学,可能也会借这个事情去煽风点火。这件事对他影响很大。” 此外,舆论裹挟下的“网暴”也随之而来。当事男生的姓名、照片、以前就读的学校纷纷被扒了出来,多个网络账号给出更多不实“爆料”,如“是个惯犯,高中去女厕所被拿钱压了下来”、是武汉市新洲区副区长的儿子等。更有甚者,把男生的照片ps成遗照,发布在社交媒体平台上。 不实信息的广泛传播,使得武汉市新洲区副区长也不得不报警,并对媒体辟谣。 图/网络新闻图 **整治网络暴力,静待真相浮出水面** 当无辜之人遭遇网暴,求告无门,是一件非常可怕的事,这种事情可能发生在任何一个人身上,包括屏幕前的你我。我们应该呼唤怎样的网络文明呢? “现在网络上定义这个事件为‘性骚扰’的内容非常多,如果男方所说的湿疹情况属实,也就是他不存在实施性骚扰的情况下,加上有一个大规模的舆论的影响,综合来看,整个事件对他来说属于一个网络暴力的范畴。”上海市汇业律师事务所律师张彬彬对「于见专栏」记者说。 张彬彬表示,目前关于“网络暴力”并没有一个专门的立法,网络暴力的相关规定散见于《中华人民共和国民法典》《中华人民共和国治安管理处罚法》等。实施网络侮辱、诽谤等网络暴力行为,可能构成侵犯他人名誉权、隐私权等人格权,如果情节严重的话,可能还会涉嫌侮辱罪、诽谤罪。 今年以来,网信办开展“清朗·网络戾气整治”专项行动,出台坚决整治“开盒挂人”等7类问题,其中包括煽动网上极端情绪,编造耸人听闻的帖文标题,发布无中生有、毫无底线的“泄愤帖”等。最高人民法院与最高人民检察院、公安部联合发布《关于依法惩治网络暴力违法犯罪的指导意见》,全面系统规定网络暴力违法犯罪案件的法律适用和政策把握问题。 舆论的力量是巨大的,但它是一把双刃剑,当它肆意挥舞的时候,谁能保证下一个受害的不会是你我?结合此次武汉大学图书馆事件,可以看到,事实的真相远比表象复杂。当越来越多个体越过监管层直接通过“舆论”来给他人定罪,一旦出现事实错误、真相浮出水面,可能使得偏见进一步加深,也会在未来让性侵害的维权变得更为困难。 网络不是法外之地,希望监管力量能尽快介入调查,在真相出来之前,希望各方能让子弹再飞一会儿。 \\*如果大家有关于建华建材、深圳优制云工业互联网有限公司、镇江市中级人民法院、句容市人民法院的相关爆料,欢迎联系我们,我们将联合其他媒体进行全球范围的重点报道。 IPO观察丨专注B2B模式的汽车街,如何保住第一头衔? « 上一篇 汽车之家23Q4及全年财报:全年营收增长企稳,创新业务引领业绩提升 下一篇 » ### 相关内容 成都新丽美医疗美容,预付款退款拖延撕开“品质”承诺裂缝? 熬过暖冬,波司登躲不过阵痛 洗脸熊快速扩张,溯脸猫或困于低价套餐 深圳南雅医疗美容陷投诉漩涡,宣传光鲜难掩信任危机? #### 热门文章 #### 标签TAG ## *#*中国 ## *#*科技 ## *#*公司 ## *#*2022年 ## *#*AI ## *#*ChatGPT ## *#*汽车 ## *#*融资 ## *#*市场 ## *#*上市 ## *#*股份 ## *#*银行 ## *#*IPO",
- "score": 0.81418616,
- "timestamp": "2025-08-21T23:26:37.659843"
- },
- {
- "query": "武汉大学图书馆事件 舆情管理 高校危机公关 网络暴力治理 2023",
- "url": "https://zhuanlan.zhihu.com/p/1936732945986622708",
- "title": "武汉大学图书馆事件完全是舆情处置不当的典型案例 - 知乎专栏",
- "content": "2023年事件初起时,舆论关注点集中在对被指控者的道德谴责上,校方虽迅速给予“记过”处分,但未提供具体调查流程和行为定性依据,这种“既处理又不说明”的模糊",
- "score": 0.81347597,
- "timestamp": "2025-08-21T23:26:37.659843"
- },
- {
- "query": "武汉大学图书馆事件 舆情管理 高校危机公关 网络暴力治理 2023",
- "url": "https://zhuanlan.zhihu.com/p/1933497945690477905",
- "title": "武大杨景媛毕业论文疑云:错漏扎堆还被指AI 代写,这道学术红线 ...",
- "content": "* 首页 * 知乎直答 * 知乎知学堂 * 等你来答 武大杨景媛毕业论文疑云:错漏扎堆还被指 AI 代写,这道学术红线到底踩没踩? 切换模式 武大杨景媛毕业论文疑云:错漏扎堆还被指 AI 代写,这道学术红线到底踩没踩? # 武大杨景媛毕业论文疑云:错漏扎堆还被指 AI 代写,这道学术红线到底踩没踩? 创作声明:包含 AI 辅助创作 21 人赞同了该文章 ## **杨景媛事件双重危机:诬告行为与学术造假中的道德失范与制度反思** ## **事件核心争议与舆情引爆机制** ### **图书馆诬告案司法判决与社会伦理冲击** 2025年7月25日,武汉大学图书馆案一审宣判,法院认定肖某某行为系“抓痒”而非性骚扰,驳回杨景媛全部诉讼请求。此前,2023年7月11日,杨景媛在图书馆自习时指控对面男生肖某某存在“隔空性骚扰”,并拍摄5段视频,要求其写下道歉信,随后将事件公开至网络,引发超10万浏览量。武汉大学在事发两天后对肖某某处以记过处分,但通报未说明具体违纪行为。后经医学专家论证,肖某某动作符合皮肤病抓挠特征,不符合性骚扰构成要件。尽管司法判决还以清白,杨景媛仍宣称“不会道歉因为我没错”,并炫耀“保研成功”“继续读博”。此行为引发社会伦理冲击,公众质疑其道德底线,担忧其若进入司法体系可能导致冤假错案。 ### **硕士论文编造法律条文及数据造假实证** 杨景媛的硕士学位论文被曝存在严重学术不端。论文中虚构并不存在的“《离婚法》”,声称该法“2001年通过”,而中国从未颁布此类法律,相关内容实际包含于《民法典》婚姻家庭编中。此外,论文引用“中国社会科学院的数据”称全中国30%妇女遭受家暴,但未注明研究人员、发表期刊或具体文件名称。其援引“世界卫生组织2018年发布的《全球、区域及国家针对妇女的家庭暴力发生率调查》”,而该文件实际发布于2021年。知乎网友进一步指出,论文大量删除不利于结论的数据,结论依赖非学术性质的working paper,且计算方法存在逻辑矛盾。这些行为严重违反学术规范,引发网友要求武汉大学调查并撤销其学位。 ### **涉事主体保研资格与香港高校录取争议** 尽管被法院驳回诉讼请求,杨景媛仍获得武汉大学保研资格,并被香港浸会大学博士项目录取。而受害者肖某某因记过处分丧失学位证,保研与法考资格受阻。其祖父因网暴刺激去世,本人确诊创伤后应激障碍(PTSD),自杀倾向达80%,被建议强制住院治疗。公众强烈质疑武汉大学在杨景媛已毕业、学籍“自动结束”的情况下未对其采取任何处理措施,呼吁取消其保研资格及学位。香港浸会大学于2025年7月28日回应称,学校设有清晰的招生政策和行为守则,将根据既定纪律程序处理违规行为,但不会对个别个案评论。舆论推测其可能采取“勒令退学”“休学观察”或等待武汉大学处理结果三种应对方式。 ### **网络舆论场中的道德谴责与程序正义之争** 事件初期,杨景媛将视频发布网络,舆论一边倒谴责肖某某,致其被“开盒”,照片被制成花圈、遗像,家人遭受网暴[^1]。武汉大学在未充分调查情况下仓促处分肖某某,被指“重舆轻法”。法院判决后,舆论反转,公众转而对杨景媛进行道德谴责,要求其承担诬告责任[^1]。同时,校方未撤销对肖某某的处分、亦未启动对杨景媛的调查,引发对程序正义缺失的批评。专家梅新育呼吁武汉大学取消杨景媛硕士学位,开除其学籍,恢复肖某某名誉,并支持其家庭以诬陷罪提起刑事诉讼。公众强调“心术不正者绝不能掌握公权力”,亟需建立防诬告机制,筑牢程序正义防线。 ## **诬告事件的行为逻辑与后果链分析** ### **特应性皮炎误判为性骚扰的医学证据链** 2023年10月,杨景媛在武汉大学图书馆指控肖同学实施“隔空性骚扰”,称其手部动作疑似自慰。肖同学解释该行为系因患有特应性皮炎五年,事发时因阴囊湿疹复发而隔衣抓痒。为佐证此说法,其母亲提供了多份就诊记录、药物购买微信聊天记录及发票。5名来自同济医院、协和医院泌尿外科与男科的专家联合论证,指出视频中动作不具备自慰特征:行为无针对性对象、无眼神或身体接触、非惯用手操作、节奏不连贯且存在长时间间断。此外,事发前后双方无交流,不符合自慰需心理幻想的前提。法院最终采纳医学意见,认定肖同学行为“无针对性”且“非性目的”,不构成性骚扰,驳回杨景媛全部诉讼请求。 ### **两封道歉信书写过程的权力胁迫特征** 杨景媛要求肖同学当场书写两封内容矛盾的道歉信。第一封称“拍了姐姐,侵犯隐私权”,第二封改为“做了下流的事”,但未明确具体行为。录音显示,杨景媛以“写道歉,快点写”开启对话,肖同学多次请求删视频、私下解决,并表示“万一他们要处分我”“什么条件我都接受”。在“不写就曝光视频”的隐性威胁下,肖同学出于对学业处分的恐惧写下道歉信。双方对话中从未明确定义“犯错”或“下流的事”所指何为,导致道歉信缺乏真实意思表示基础。法院审理时可能考量到该信件产生于心理压迫情境,削弱其证据效力。 ### **受害者家庭三代人的创伤代际传递** 肖同学因被诬告遭受毁灭性打击。身心健康方面,其被确诊为创伤后应激障碍(PTSD),自杀倾向达80%,精神病院建议强制住院治疗。学业前途上,保研与留学计划化为泡影,法考及从业资格亦受阻。家庭层面,其照片遭“开盒”并被制成花圈、遗像,家人陷入网络暴力漩涡。其祖父因无法承受舆论压力,半年后离世。母亲公开表示:“普通人遇到打击时是弱小的,但法律应是照亮黑暗的灯,而非屈服于流量的墙”。整个家庭命运因一桩误判陷入长期创伤,形成跨代际的心理与社会损害链条。 ### **高校纪律处分决策中的程序瑕疵研究** 武汉大学在事件发生仅两天后即对肖同学作出记过处分,通报中仅称“存在不雅行为,有损大学生形象”,未明确具体违纪事实,亦未定性为性骚扰。校方相关负责人承认,该处分系为“给舆情降温”,属应急处理措施。肖母申诉时提交医学证据,校方虽表示“可排除性骚扰”,但仍维持处分决定。法院判决后,因杨景媛已毕业,“学籍自动结束”,校方未对其采取任何追责措施。此举被质疑“重舆轻法”,暴露高校治理中以行政手段替代事实调查的惯性,损害程序正义与学术机构公信力。公众呼吁撤销错误处分、恢复肖同学名誉,并追究诬告责任以匡扶正义。 ## **学术造假的技术解构与制度漏洞** ### **《离婚法》虚构与社科数据来源伪造** 杨景媛的硕士论文中存在严重的学术不规范问题,其中最显著的是虚构了中国并不存在的《离婚法》,并声称该法“2001年通过”。而实际上,相关内容包含在《民法典》婚姻家庭编中,这一基础性错误出现在硕士论文中令人难以理解。此外,论文中多处数据未标注具体来源,例如引用“中国社会科学院的数据”时未提供具体研究人员或论文期刊信息,也未说明数据发布的具体来源。同样,论文还声称世界卫生组织有相关调查,但援引的文件实际发布年份与论文中所述不符,文件实际发布于2021年,而论文中声称其为2018年发布。 这些学术不规范行为严重违反了学术研究的基本要求,每一个引用都应准确无误,以保证研究的可追溯性和可靠性。杨景媛论文中的这些问题不仅反映出其学术态度的不严谨,也凸显了学校在论文审核过程中可能存在把关不严的情况。 ### **印度家暴数据选择性剔除的学术欺诈** 除了虚构法律和数据来源不明的问题,杨景媛的硕士论文还存在数据选择性剔除的情况。有网友指出,该论文在研究中印生育行为对家庭暴力的影响时,大量删除中国和印度不利于论文结论的数据,其结论高度依赖一篇根本不是学术论文的working paper,计算方法也存在逻辑矛盾。这种行为严重影响了论文结论的科学性和可靠性,违背了学术研究的客观性原则。 此外,论文中还声称中国社科院发布数据,称全中国30%的妇女遭受家暴,但未写清楚具体的研究人员或发表的期刊名称,这种没有出处的数据很可能是杜撰的。同时,论文还声称世界卫生组织也有同样的调查,但同样没有给出出处。这些学术欺诈行为不仅损害了学术研究的可信度,也反映出部分研究者为了达到预期结论而采取不恰当手段处理数据的问题。 ### **导师指导责任与答辩委员会审查失效** 杨景媛的硕士论文通过了导师指导和答辩委员会审查,然而论文中存在如此多的学术不规范问题,这表明导师在指导过程中可能没有尽到严格把关的责任,答辩委员会在审查时也未能发现这些明显的问题[^3]。稍有法律常识的人都知道,我国并没有一部单独叫做《离婚法》的法律,相关内容是包含在《民法典》婚姻家庭编当中的,但这一基础性的错误却出现在硕士论文中,并通过了审核和答辩,让人难以理解。 此外,论文中还存在大量不符合学术文章撰写规范的地方,如数据未标注来源、涉嫌抄袭等。这些行为不仅反映出杨景媛个人学术态度的不严谨,也让人对学校论文审核的专业性和严谨性产生怀疑。大学在论文审核和答辩过程中,是否真正做到了严格把关,还是只是走走过场,让不符合学术规范的论文蒙混过关,这些问题都值得深入探讨[^3]。 ### **武汉大学学术不端处理机制响应延迟** 在杨景媛的硕士论文被曝光存在学术不规范行为后,武汉大学在事件初期未充分调查,仅因网络舆论对肖某某记过处分;法院判决后,校方未对杨景媛采取任何处理,因其已毕业“学籍自动结束”。这一情况体现了武汉大学学术不端处理机制存在响应延迟的问题,未能及时对学术不端行为进行调查和处理。公众呼吁武大撤销肖某某处分、道歉赔偿,并取消杨景媛保研资格及学位,以匡扶正义。 此外,武汉大学制定了大学教师学术不端行为处理制度,适用于本校全体在职教师,明确了学术不端行为的定义,包括抄袭、剽窃他人学术成果,伪造、篡改科研数据、图表、结论等多种行为。制度规定了举报与受理流程,任何单位或个人发现教师存在学术不端行为,均可通过书面、电子邮件、电话等方式向学校学术委员会举报,学校学术委员会负责受理并在接到举报后5个工作日内进行初步审查。调查与认定程序方面,对于决定受理的举报,学校学术委员会应在10个工作日内成立专门的调查组,调查取证工作需在30个工作日内完成,最终由学校学术委员会作出最终认定决定。对于被认定存在学术不端行为的教师,根据情节轻重给予相应处理,包括警告、记过、降低岗位等级或撤职、开除或解除聘用合同等,同时对其学术成果也会进行相应处理,如责令撤回已发表的论文等。然而,这一制度在杨景媛事件中未能有效执行,反映出高校在学术监管和论文审核方面可能存在走过场的情况。 ## **高校危机应对的双轨制困境** ### **受害者处分未撤销与加害者学位存续** 在杨景媛事件中,武汉大学对肖某某的处分自2022年7月13日做出后,至今未撤销。法院一审判决于2024年7月25日认定肖某某行为不符合性骚扰构成要件,但校方未对处分决定进行调整。与此同时,杨景媛在2024年通过硕士论文答辩并获得学位,其论文中存在编造“《离婚法》”、引用数据未标注来源等学术不规范行为,引发网友对其学位合法性的质疑。尽管法院已判决其诬告行为成立,杨景媛仍公开宣称“不会道歉因为我没错”,并炫耀“保研成功”“继续读博”。 ### **香港浸会大学招生条款中的道德审查权** 杨景媛已被香港浸会大学录取为研究生,但因其诬陷事件和学术不端行为,众多网友向该校举报,要求重新审视其录取资格。7月28日,香港浸会大学回应称学校设有清晰的招生政策和行为守则,对于任何违反行为守则的大学成员,将根据既定纪律程序处理,但不会对个别个案作出评论。舆论普遍认为,该校面临三种可能处理方式:一是直接启动纪律程序勒令退学;二是采取限制+监控策略;三是拖延等待武大处理结果和官媒风向。 ### **校友网络施压与校方公关策略选择** 事件曝光后,大量校友和网友通过社交媒体对武汉大学施压,要求其撤销对肖某某的处分、取消杨景媛学位并追究其法律责任。专家梅新育呼吁武汉大学尽快取消杨景媛硕士学位,开除其学籍,并支持受害男同学家庭以诬陷罪刑事起诉杨景媛,同时申请限制其出境。公众还担忧杨景媛作为法学背景者,若进入司法系统将导致滥用职权和司法不公,强调需建立机制防止诬告陷害,保障程序正义。面对舆论压力,武汉大学尚未采取实质性行动,仅表示“学籍自动结束”。 ### **高等教育声誉管理中的法理人情博弈** 杨景媛事件暴露出高校在声誉管理中面临的法理与人情冲突。一方面,法院已判决其诬告行为不成立,但武汉大学未对其采取任何处理措施,反而允许其保研成功,引发公众对高校公正性的质疑。另一方面,肖某某因网暴被确诊创伤后应激障碍,家人遭受身心伤害,保研和留学计划化为泡影,甚至有长辈因打击离世,但武汉大学仅以“应急处理”回应其母亲异议。公众呼吁高校建立更加严格的道德审查机制,在招生、保研、毕业等环节不仅要考察学术成绩,更要注重道德品质。 ## **法学教育背景下的道德异化现象** ### **法律从业者诬告行为的社会危害倍增效应** 杨景媛作为法学专业学生,对肖同学进行诬告,导致其身心俱疲、学业受阻,保研和留学计划化为泡影,全家深陷网暴漩涡,有长辈因打击离世。2023年7月11日,杨景媛指控肖同学“隔空性骚扰”,并当场要求其写下道歉信。随后,杨景媛将此事发到网络上,引发大量关注,武汉大学迅速对肖某某处以记过处分。2024年7月25日,法院审理认为不能认定肖同学实施了性骚扰,驳回杨某的指控。即便如此,杨景媛败诉后仍公开宣称“不会道歉因为我没错”,并扬言“要毁掉肖某某人生”。 ### **司法体系准入资格审查机制漏洞研究** 杨景媛事件引发了公众对司法体系准入资格审查机制的担忧。作为法学背景者,杨景媛不仅诬告同学,还在硕士论文中存在严重学术不规范行为,编造不存在的“《离婚法》”,多处数据未标注来源。公众担忧其若成为法官、检察官或警察,将滥用职权导致司法不公。网友强调“心术不正者绝不能掌握公权力”,需建立机制防止诬告陷害,保障程序正义。武汉大学在处理该事件时也存在责任缺位问题,在法院判决后未对杨景媛采取任何处理,因其已毕业“学籍自动结束”。 ### **法治信仰缺失与工具主义法律观形成** 部分大学生存在法治信仰缺失问题,表现为法律知识薄弱、法律意识模糊。杨景媛事件进一步凸显了这一问题。她作为法学专业学生,本应具备良好的法律素养和道德观念,但其行为却严重背离法治信仰,诬告同学并在硕士论文中出现大量疑似编造和引用错误的内容。这种法律信仰与行为选择的脱节,反映出在法学教育中可能过于注重知识传授,而忽视了伦理道德教育[^34]。为加强大学生法治信仰培育,高校应系统强化自我法治教育,改革创新法治教育体系,并营造良好法治教育氛围。 ### **比较视域下中美法学院伦理教育体系差异** 杨景媛事件揭示了中国法学院在伦理教育方面的不足。武汉大学法学院作为国内顶尖法学院之一,近年来发展迅猛,尤其在国际法方向。然而,在杨景媛事件中,学校在处理学生问题和学术监管方面未能做到严格把关,反映出在伦理教育和学术监管方面可能存在漏洞。相较而言,美国法学院普遍更注重法律职业伦理教育,强调法律信仰与行为规范的统一。中国高校应借鉴国际经验,完善法治教育理论体系,优化教学内容,改进教学方法,强化法律教师业务水平,以提升学生的法律信仰与职业操守。 ## **网络实名举报的治理效能评估** ### **跨平台舆情发酵的多节点传播路径** 2023年7月11日,杨景媛在武汉大学图书馆指控男生肖某某实施“隔空性骚扰”,并拍摄5段视频作为证据在网络发布,相关文章阅读量迅速突破10万+。事件初期,肖某某被“开盒”,照片被制作成花圈、遗像,遭“官二代、富二代性欺压女生”等谣言攻击,全家陷入网暴漩涡。尽管肖某某患有特应性皮炎且动作经医学专家论证为“抓痒”,但舆论已在“罗生门”中失控。2024年6月,杨景媛起诉肖某某,要求其公开道歉并赔偿精神损失,进一步推动事件跨平台传播。2025年7月25日法院一审驳回其诉讼请求后,杨景媛在微博和B站宣称“已顺利毕业且保研成功”,并扬言持续举报肖某某,再度引爆全网谴责。 ### **香港媒体介入对事件升级的催化作用** 杨景媛被香港浸会大学录取后,网友将其学术不端与品行问题材料电邮至该校研究生院,引发广泛关注。7月28日,香港浸会大学回应称,学校设有清晰的招生政策和行为守则,将根据既定纪律程序处理违纪成员,但不会对个案评论。香港媒体《香港01》跟进报道,指出若抄袭坐实,杨景媛可能面临撤销学位、留校察看或直接退学处分。该校的公开回应与媒体报道使事件由内地网络舆情演变为跨境教育机构应对危机的典型案例,强化了公众对高校招生伦理审查的期待。 ### **学术共同体同行评议机制的民间替代** 杨景媛的硕士学位论文《中印生育行为影响家庭暴力的经济学分析》被网友深度挖掘,发现其编造中国从未颁布的“《离婚法》”,并错误标注该法“2001年通过”。论文引用“中国社会科学院的数据”“印度国家犯罪研究局的官方报告”均未提供具体出处;援引“世界卫生组织2018年发布的《全球、区域及国家针对妇女的家庭暴力发生率调查》”实则发布于2021年。技术博主通过ChatGPT逆向检索,发现部分段落与早年英文论文句式原封不动对应,且数据回归表格系数与引用文献一致却未列入参考文献。另有分析指出其论文存在错别字频出、年代计算错误(如将“1960至1965”写作“1960至1665”)、数据比例误算(1/1000算作0.01%)等低级失误。这些由网民主导的学术审查行为,构成了对正式同行评议机制的民间替代,凸显学术监管在公共监督下的透明化压力。 ### **网络暴力与理性监督的边界划定研究** 事件初期,武汉大学未充分调查即对肖某某作出记过处分,理由为“存在不雅行为,有损大学生形象,造成不良影响”,校方承认此举系为“平息舆论”。该处分导致肖某某保研与留学计划中断,被确诊创伤后应激障碍,多次出现自杀倾向,其爷爷因不堪网暴谩骂半年后离世。法院判决后,武汉大学未对杨景媛采取任何处理措施,因其已毕业,“学籍自动结束”。与此同时,部分网民在事件“反转”后对杨景媛进行辱骂与调侃,使其亦遭受网暴。专家梅新育指出,杨景媛在法院败诉后毫无悔意,反而炫耀保研成功并威胁继续举报,已“突破人性和廉耻底线”,应取消其学位、支持刑事反诉并限制出境。公众普遍担忧其作为法学背景者若进入司法体系,可能滥用职权导致冤假错案,呼吁建立防止诬告陷害的制度防线。此事件揭示了网络举报在缺乏证据审查前提下极易滑向群体性暴力,亟需在程序正义与舆论监督之间建立清晰边界。 ## **研究生培养质量监控体系重构** ### **硕士论文外审盲评制度的实效性检验**",
- "score": 0.7813477,
- "timestamp": "2025-08-21T23:26:37.659843"
- }
- ],
- "latest_summary": "2023年10月,武汉大学发生的“图书馆事件”引发了广泛网络舆情。事件源于女生杨景媛在图书馆自习时指控男生肖某某实施“隔空性骚扰”,并拍摄5段视频要求其写下道歉信,随后将事件公开至网络,引发超10万浏览量。事件初期舆论焦点集中在对涉事学生的道德谴责,校方虽在48小时内迅速作出“记过”处分回应,但通报仅称“存在不雅行为,有损大学生形象”,未公开具体调查流程和行为定性依据,校方相关负责人承认该处分系为“给舆情降温”的应急处理措施。这种“既处理又不说明”的模糊态度引发公众质疑。舆情持续发酵导致当事人肖某某遭遇“开盒”式人肉搜索,私人信息被曝光,照片被制成花圈、遗像,不仅遭受同学孤立和网络暴力,更于10月17日被确诊为急性精神应激障碍(后确诊为创伤后应激障碍PTSD,自杀倾向达80%)。后经医学专家论证,肖某某动作符合特应性皮炎抓挠特征,法院最终认定不构成性骚扰。2024年7月25日,法院一审宣判驳回杨景媛全部诉讼请求,但校方未撤销对肖某某的处分决定。这一事件反映出高校舆情管理中回应透明度不足、重舆轻法的程序瑕疵与网络暴力治理的双重挑战,凸显了高校在危机公关中行政手段替代事实调查的治理困境。",
- "reflection_iteration": 2,
- "is_completed": true
- },
- "order": 1
- }
- ],
- "final_report": "# 武汉大学图书馆性骚扰指控事件分析报告\n\n## 概述\n\n2023年武汉大学图书馆性骚扰指控事件引发广泛社会关注。事件始于2023年7月,武大研究生杨景媛在网络上举报本科生肖明滔在图书馆自习时实施性骚扰行为。校方随后对肖明滔作出纪律处分,但杨景媛不满处理结果,于2024年6月向法院提起诉讼。2025年7月25日,法院一审判决认为证据不足以认定性骚扰,驳回杨景媛全部诉求。判决后舆论持续发酵,网民质疑杨景媛涉嫌诬告和学术不端,同时批评校方最初处分决定。武汉大学于8月1日成立工作专班,宣布对涉事双方的纪律处分和学位论文进行全面调查复核。该事件折射出中国高校舆情管理、性别议题和网络举报机制面临的复杂挑战。近年来,师德师风、校园冲突等事件频繁成为舆论热议话题,暴露出高校在舆情危机管理中流程透明度不足、处理机制不完善的问题。面对舆情,校方回应往往滞后或模糊,如仅以“等上级指示”应对,反映出治理能力缺陷和系统性失灵。网络举报处理流程缺乏标准化,易引发二次舆情发酵,需加强制度建设与公众沟通。从2023年10月13日武汉大学发布的处理通报可以看出,该校对涉及学生的网上举报采取了调查核实机制,但在具体执行中仍存在改进空间。2025年7月31日,校长张平文对媒体回应法院驳回事件,显示校方在舆情管理上逐步加强对外沟通,但整体机制仍需完善,特别是在校园性骚扰事件处理流程和高校网络举报制度方面需要更系统化的规范。\n\n## 详细分析\n\n2023年10月,武汉大学发生的“图书馆事件”引发了广泛网络舆情。事件源于女生杨景媛在图书馆自习时指控男生肖某某实施“隔空性骚扰”,并拍摄5段视频要求其写下道歉信,随后将事件公开至网络,引发超10万浏览量。事件初期舆论焦点集中在对涉事学生的道德谴责,校方虽在48小时内迅速作出“记过”处分回应,但通报仅称“存在不雅行为,有损大学生形象”,未公开具体调查流程和行为定性依据,校方相关负责人承认该处分系为“给舆情降温”的应急处理措施。这种“既处理又不说明”的模糊态度引发公众质疑。舆情持续发酵导致当事人肖某某遭遇“开盒”式人肉搜索,私人信息被曝光,照片被制成花圈、遗像,不仅遭受同学孤立和网络暴力,更于10月17日被确诊为急性精神应激障碍(后确诊为创伤后应激障碍PTSD,自杀倾向达80%)。后经医学专家论证,肖某某动作符合特应性皮炎抓挠特征,法院最终认定不构成性骚扰。2024年7月25日,法院一审宣判驳回杨景媛全部诉讼请求,但校方未撤销对肖某某的处分决定。这一事件反映出高校舆情管理中回应透明度不足、重舆轻法的程序瑕疵与网络暴力治理的双重挑战,凸显了高校在危机公关中行政手段替代事实调查的治理困境。\n\n## 结论\n\n武汉大学图书馆性骚扰指控事件不仅是一起校园纠纷,更暴露了中国高校在舆情管理、性别议题处理和网络举报机制方面的系统性缺陷。事件发展过程中,校方初期的应急处分缺乏透明度,后续又未能及时根据法院判决调整处理决定,反映出行政干预替代事实调查的治理困境。同时,网络暴力的蔓延和对当事人造成的严重身心伤害警示我们需加强网络行为规范与心理健康支持。未来,高校应建立更透明的调查与回应机制,完善网络举报标准化流程,并在舆情管理中平衡法律程序与公众沟通,以避免类似事件的再次发生。",
- "is_completed": true,
- "created_at": "2025-08-21T23:23:36.394488",
- "updated_at": "2025-08-21T23:27:40.061002"
-}
\ No newline at end of file
diff --git a/streamlit_reports/state_武汉大学舆情_20250822_203056.json b/streamlit_reports/state_武汉大学舆情_20250822_203056.json
deleted file mode 100644
index 2fd2e60..0000000
--- a/streamlit_reports/state_武汉大学舆情_20250822_203056.json
+++ /dev/null
@@ -1,430 +0,0 @@
-{
- "query": "武汉大学舆情",
- "report_title": "关于'武汉大学舆情'的深度研究报告",
- "paragraphs": [
- {
- "title": "武汉大学舆情概述",
- "content": "武汉大学舆情是指公众对武汉大学相关事件、政策、学术成果等的看法和情绪反应。作为中国顶尖高校之一,其舆情动态常涉及教育政策、学术争议、校园管理等话题。",
- "research": {
- "search_history": [
- {
- "query": "武汉大学 舆情 教育政策 学术争议 校园管理",
- "url": "https://www.chronicle.com/newsletter/daily-briefing/2025-08-18",
- "title": "August 18: The deportation campaign ripples across campus - The Chronicle of Higher Education",
- "content": "Undocumented students are feeling more fear and intimidation on college campuses — a space that, until recently, was considered relatively safe, as _The Chronicle’_ s Camila Gomez reports. **The effect has already been noticeable.**Campus-police activities and policies on tuition rates for undocumented students have shifted, and some states have cleared the way for federal rules to apply at their public universities. * “How can we expect ourselves to focus on our classes when we’re constantly fearing for our safety, not knowing whether or not we’re going to be able to come back home at the end of the day?” asked one undocumented student from the University of South Florida at Tampa, whose campus police will also work with immigration enforcement.",
- "score": 0.034844812,
- "timestamp": "2025-08-22T20:24:11.446960"
- },
- {
- "query": "武汉大学 舆情 教育政策 学术争议 校园管理",
- "url": "https://www.chronicle.com/article/the-not-so-quiet-schism-among-academic-leaders",
- "title": "Two Chancellors Push Back Against Claims They’re Dividing the Sector Amid Trump Threats - chronicle.com",
- "content": "The two factions, according to *The Atlantic*’s reporting, came to a head at an April meeting of the Association of American Universities, when Princeton University’s president, Christopher Eisgruber, made remarks “all but accusing” Diermeier and Martin “of carrying water for the Trump administration.” Other leaders, the magazine reported, see the men as leveraging the sector’s turmoil to raise their institutions’ profiles — and their own. But we have been engaged on questions like free speech, institutional neutrality, civil discourse, and I think it’s great to see that many universities are moving in that direction.",
- "score": 0.032152396,
- "timestamp": "2025-08-22T20:24:11.446960"
- },
- {
- "query": "武汉大学 舆情 教育政策 学术争议 校园管理",
- "url": "https://www.greenwichtime.com/news/politics/article/privacy-and-civil-rights-groups-urge-us-colleges-20828062.php",
- "title": "Privacy and civil rights groups urge US colleges to end campus surveillance to protect protesters - Greenwich Time",
- "content": "%26utm_source%3Dfacebook.com%26utm_medium%3Dreferral&name=Privacy%20and%20civil%20rights%20groups%20urge%20US%20colleges%20to%20end%20campus%20surveillance%20to%20protect%20protesters&description=A%20coalition%20of%20more%20than%2030%20privacy%20and%20civil%20rights%20groups%20is%20calling%20on%20U.S.%20universities%20to%20dismantle%20campus%20surveillance%20and%20data%20collection%20in%20order%20to%20protect%20student%20protesters%20from%20government...&picture=https%3A%2F%2Fs.hdnux.com%2Fphotos%2F01%2F53%2F60%2F61%2F28249213%2F6%2FrawImage.jpg&redirect_uri=https%3A%2F%2Fwww.greenwichtime.com%2Fnews%2Fpolitics%2Farticle%2Fprivacy-and-civil-rights-groups-urge-us-colleges-20828062.php%3Futm_campaign%3DCMS%2520Sharing%2520Tools%2520(Premium)%26utm_source%3DUTMSOURCE%26utm_medium%3DUTMMEDIUM)%26utm_source%3Dt.co%26utm_medium%3Dreferral&text=Privacy%20and%20civil%20rights%20groups%20urge%20US%20colleges%20to%20end%20campus%20surveillance%20to%20protect%20protesters&via=GreenwichTime)%26utm_source%3Dbsky.app%26utm_medium%3Dreferral)%26utm_source%3Dshare-by-email%26utm_medium%3Demail%0A%0AA%20coalition%20of%20more%20than%2030%20privacy%20and%20civil%20rights%20groups%20is%20calling%20on%20U.S.%20universities%20to%20dismantle%20campus%20surveillance%20and%20data%20collection%20in%20order%20to%20protect%20student%20protesters%20from%20government...%0A%0AThis%20message%20was%20sent%20via%20Greenwich%20Time) Image 1: FILE - Students sit on the front steps of Low Memorial Library on the Columbia University campus in New York City, Feb. 10, 2023. NEW YORK (AP) — A coalition of more than 30 privacy and civil rights groups called on U.S. universities Thursday to dismantle campus surveillance and data collection, to protect student protesters and others from government retaliation. Image 2 Image 6: New plans have been put forward for a George H.W. Bush statue near Havemeyer Building off Greenwich Avenue, as depicted in this rendering. Image 9 Image 10 Image 11 Image 12 Image 13 Image 14 Image 15 Image 16",
- "score": 0.032152396,
- "timestamp": "2025-08-22T20:24:11.446960"
- },
- {
- "query": "武汉大学舆情 校园管理 学术争议 教育政策 2024",
- "url": "https://www.chronicle.com/newsletter/academe-today/2025-08-19",
- "title": "Academe Today: The fallout from Columbia’s deal to compensate employees over antisemitism - The Chronicle of Higher Education",
- "content": "From:The Chronicle of Higher Education Image 2: A group of students advocating for Palestine at Columbia University stage a demonstration and block the entrance of iconic Hamilton Hall building as they chant pro-Palestine slogans at campus in New York, United States on April 29, 2024. Image 5: Robert Carter for The Chronicle from _The Chronicle_ Image 16: Diego Dulanto, a graduate student at the University of South Florida, poses for a portrait on campus in Tampa, Florida, on Saturday, August 9, 2024. Image 24: The Data Informed Campus: Using Information to Drive Change - image of a student walking on campus with a backpack full of graphs with charts around #### The Data Informed Campus _The Chronicle of Higher Education_ is academe’s most trusted resource for independent journalism, career development, and forward-looking intelligence.",
- "score": 0.049026065,
- "timestamp": "2025-08-22T20:24:33.947638"
- },
- {
- "query": "武汉大学舆情 校园管理 学术争议 教育政策 2024",
- "url": "https://www.cbsnews.com/gooddaysacramento/video/time-to-choose-your-news/",
- "title": "Time to Choose Your News! - CBS News",
- "content": "01:53Oct 25, 2024### Good Day Rewind - 10/24 02:11Oct 24, 2024### Good Day Rewind - 10/23 04:08Oct 23, 2024### Good Day Rewind - 10/22 01:49Oct 22, 2024### Good Day Rewind - 10/18 01:19Oct 18, 2024### Good Day Rewind - 10/17 01:33Oct 17, 2024### Good Day Rewind - 10/16 01:46Oct 16, 2024### Good Day Rewind - 10/15 01:35Oct 15, 2024### Good Day Rewind - 10/14 02:00Oct 14, 2024### Good Day Rewind - 10/11 01:48Oct 11, 2024### Good Day Rewind - 10/10 02:21Oct 10, 2024### Good Day Rewind - 10/9 01:43Oct 9, 2024### Good Day Rewind - 10/8 01:37Oct 8, 2024### Good Day Rewind - 10/7 01:39Oct 7, 2024### Good Day Rewind - 10/4 02:20Oct 4, 2024### Good Day Rewind - 10/3",
- "score": 0.03749929,
- "timestamp": "2025-08-22T20:24:33.947638"
- },
- {
- "query": "武汉大学舆情 校园管理 学术争议 教育政策 2024",
- "url": "https://www.cnn.com/2025/08/17/us/hbcu-expansion-satellite-campuses",
- "title": "These city leaders want to expand HBCUs beyond the South. Here’s why they say it’s critical as DEI is under attack - CNN",
- "content": "* CNN10 * About CNN Officials in cities such as Boston and San Francisco say they are actively working to bring satellite campuses of HBCUs to their communities, hoping to strengthen the pipeline for Black students to pursue higher education and to grow and diversify their local economies. Image 5These college leaders are keeping the heat on in battle with Trump administration – despite settlements by prominent schools Aug 11, 2025 10 minute read min read Image 7Judge blocks Trump administration guidance against DEI programs at schools and colleges Aug 15, 2025 3 minute read min read Image 23Judge blocks Trump administration guidance against DEI programs at schools and colleges Aug 15, 2025 3 minute read min read * CNN10 * About CNN",
- "score": 0.026925152,
- "timestamp": "2025-08-22T20:24:33.947638"
- },
- {
- "query": "武汉大学2024年教育政策学术争议校园管理舆情",
- "url": "https://www.chronicle.com/newsletter/academe-today/2025-08-19",
- "title": "Academe Today: The fallout from Columbia’s deal to compensate employees over antisemitism - The Chronicle of Higher Education",
- "content": "From:The Chronicle of Higher Education Image 2: A group of students advocating for Palestine at Columbia University stage a demonstration and block the entrance of iconic Hamilton Hall building as they chant pro-Palestine slogans at campus in New York, United States on April 29, 2024. Image 5: Robert Carter for The Chronicle from _The Chronicle_ Image 16: Diego Dulanto, a graduate student at the University of South Florida, poses for a portrait on campus in Tampa, Florida, on Saturday, August 9, 2024. Image 24: The Data Informed Campus: Using Information to Drive Change - image of a student walking on campus with a backpack full of graphs with charts around #### The Data Informed Campus _The Chronicle of Higher Education_ is academe’s most trusted resource for independent journalism, career development, and forward-looking intelligence.",
- "score": 0.054364275,
- "timestamp": "2025-08-22T20:24:58.857038"
- },
- {
- "query": "武汉大学2024年教育政策学术争议校园管理舆情",
- "url": "https://www.cbsnews.com/gooddaysacramento/video/time-to-choose-your-news/",
- "title": "Time to Choose Your News! - CBS News",
- "content": "01:53Oct 25, 2024### Good Day Rewind - 10/24 02:11Oct 24, 2024### Good Day Rewind - 10/23 04:08Oct 23, 2024### Good Day Rewind - 10/22 01:49Oct 22, 2024### Good Day Rewind - 10/18 01:19Oct 18, 2024### Good Day Rewind - 10/17 01:33Oct 17, 2024### Good Day Rewind - 10/16 01:46Oct 16, 2024### Good Day Rewind - 10/15 01:35Oct 15, 2024### Good Day Rewind - 10/14 02:00Oct 14, 2024### Good Day Rewind - 10/11 01:48Oct 11, 2024### Good Day Rewind - 10/10 02:21Oct 10, 2024### Good Day Rewind - 10/9 01:43Oct 9, 2024### Good Day Rewind - 10/8 01:37Oct 8, 2024### Good Day Rewind - 10/7 01:39Oct 7, 2024### Good Day Rewind - 10/4 02:20Oct 4, 2024### Good Day Rewind - 10/3",
- "score": 0.042344477,
- "timestamp": "2025-08-22T20:24:58.857038"
- },
- {
- "query": "武汉大学2024年教育政策学术争议校园管理舆情",
- "url": "https://www.cnn.com/2025/08/17/us/hbcu-expansion-satellite-campuses",
- "title": "These city leaders want to expand HBCUs beyond the South. Here’s why they say it’s critical as DEI is under attack - CNN",
- "content": "* CNN10 * About CNN Officials in cities such as Boston and San Francisco say they are actively working to bring satellite campuses of HBCUs to their communities, hoping to strengthen the pipeline for Black students to pursue higher education and to grow and diversify their local economies. Image 5These college leaders are keeping the heat on in battle with Trump administration – despite settlements by prominent schools Aug 11, 2025 10 minute read min read Image 7Judge blocks Trump administration guidance against DEI programs at schools and colleges Aug 15, 2025 3 minute read min read Image 23Judge blocks Trump administration guidance against DEI programs at schools and colleges Aug 15, 2025 3 minute read min read * CNN10 * About CNN",
- "score": 0.036703885,
- "timestamp": "2025-08-22T20:24:58.857038"
- }
- ],
- "latest_summary": "武汉大学舆情在教育政策、学术争议和校园管理方面呈现出复杂态势。根据搜索结果,美国高校中关于校园警察活动、学费政策调整以及移民执法合作的争议,反映了校园管理政策对特定学生群体的影响,类似情境可能在中国高校的舆情讨论中出现。学术争议方面,如《大西洋月刊》报道的大学领导层在自由言论和机构中立性问题上的分歧,突显了高校内部治理和学术自由之间的张力,这与武汉大学作为顶尖学府可能面临的学术伦理和治理挑战相呼应。此外,隐私和民权组织呼吁终止校园监控以保护学生抗议者的行动,强调了校园管理中平衡安全与隐私权的重要性,这一议题在全球高校舆情中都具有相关性。2024年,国际高校舆情中还涉及学生示威活动(如哥伦比亚大学支持巴勒斯坦的抗议)、数据驱动校园管理趋势(如《高等教育纪事》报道的“数据知情校园”倡议),以及多元化教育政策争议(如特朗普政府对DEI项目的指导方针被法院阻止)。这些动态表明,武汉大学的舆情需关注教育政策的公平性、学术环境的开放性、校园管理的透明性,以及全球高等教育议题的本地化影响,确保在治理中回应多元利益相关者的关切。值得注意的是,2024年全球高校舆情还显示出对HBCU(历史悠久的黑人学院和大学)卫星校园扩展的关注,旨在加强黑人学生高等教育通道并促进地方经济多元化,这反映了教育政策与社会公平议题的紧密关联,可能为武汉大学在多元化招生和教育资源分配方面的舆情讨论提供参考框架。",
- "reflection_iteration": 2,
- "is_completed": true
- },
- "order": 0
- },
- {
- "title": "近期舆情热点事件",
- "content": "分析武汉大学近期的舆情热点,如学术不端指控、招生政策变化、校园管理事件等,这些事件如何引发公众讨论和媒体关注。",
- "research": {
- "search_history": [
- {
- "query": "武汉大学 学术不端 招生政策 校园管理 舆情",
- "url": "https://www.cnn.com/2025/08/15/health/college-admissions-wellness",
- "title": "Today’s college admissions process isn’t just convoluted. It’s cruel - CNN",
- "content": "Nearly my entire senior class had applied early action, one of the many admissions plans that schools offer to apply to college. Related article Image 3: Students walk to and from classes on the Indiana University campus, Thursday, Oct. 14, 2021, in Bloomington, Ind. College communities such as Bloomington are exploring their options for contesting the results of the 2020 census, which they say do not accurately reflect how many people live there. Image 10Trump’s immigration crackdown is raising fears as the school year gets underway in Los Angeles Aug 14, 2025 4 minute read min read Image 13Judge blocks Trump administration guidance against DEI programs at schools and colleges Aug 15, 2025 3 minute read min read",
- "score": 0.058158454,
- "timestamp": "2025-08-22T20:25:28.532880"
- },
- {
- "query": "武汉大学 学术不端 招生政策 校园管理 舆情",
- "url": "https://www.chronicle.com/newsletter/daily-briefing/2025-08-19",
- "title": "August 19: Loan forgiveness gets political, again - The Chronicle of Higher Education",
- "content": "**“The rules give the secretary of education sweeping, unchecked power to decide what counts as ‘substantially illegal,’** with no appeals process,” Daniel Collier, an assistant professor of higher and adult education at the University of Memphis, told _The Chronicle’s_ Eric Kelderman. **Many colleges could be on the front lines.** Employers that could be most affected by the proposed regulation “include, but are not limited to, those in legal services, governance, social work, health care, K-12 education, and higher education,” the department’s notice of proposed rulemaking says. A federal court dismissed claims against the university earlier this year, leaving a friendlier Trump administration to decide whether to continue to pursue a case against Grand Canyon University’s president and a company he leads that sells educational services to the university.",
- "score": 0.051486712,
- "timestamp": "2025-08-22T20:25:28.532880"
- },
- {
- "query": "武汉大学 学术不端 招生政策 校园管理 舆情",
- "url": "https://www.chronicle.com/newsletter/daily-briefing/2025-08-18",
- "title": "August 18: The deportation campaign ripples across campus - The Chronicle of Higher Education",
- "content": "Undocumented students are feeling more fear and intimidation on college campuses — a space that, until recently, was considered relatively safe, as _The Chronicle’_ s Camila Gomez reports. **The effect has already been noticeable.**Campus-police activities and policies on tuition rates for undocumented students have shifted, and some states have cleared the way for federal rules to apply at their public universities. * “How can we expect ourselves to focus on our classes when we’re constantly fearing for our safety, not knowing whether or not we’re going to be able to come back home at the end of the day?” asked one undocumented student from the University of South Florida at Tampa, whose campus police will also work with immigration enforcement.",
- "score": 0.0487536,
- "timestamp": "2025-08-22T20:25:28.532880"
- },
- {
- "query": "武汉大学学术不端 招生政策 校园管理 2024",
- "url": "https://www.cnn.com/2025/08/17/us/hbcu-expansion-satellite-campuses",
- "title": "These city leaders want to expand HBCUs beyond the South. Here’s why they say it’s critical as DEI is under attack - CNN",
- "content": "* CNN10 * About CNN Officials in cities such as Boston and San Francisco say they are actively working to bring satellite campuses of HBCUs to their communities, hoping to strengthen the pipeline for Black students to pursue higher education and to grow and diversify their local economies. Image 5These college leaders are keeping the heat on in battle with Trump administration – despite settlements by prominent schools Aug 11, 2025 10 minute read min read Image 7Judge blocks Trump administration guidance against DEI programs at schools and colleges Aug 15, 2025 3 minute read min read Image 23Judge blocks Trump administration guidance against DEI programs at schools and colleges Aug 15, 2025 3 minute read min read * CNN10 * About CNN",
- "score": 0.049354926,
- "timestamp": "2025-08-22T20:25:50.608395"
- },
- {
- "query": "武汉大学学术不端 招生政策 校园管理 2024",
- "url": "https://www.cbsnews.com/gooddaysacramento/video/time-to-choose-your-news/",
- "title": "Time to Choose Your News! - CBS News",
- "content": "01:53Oct 25, 2024### Good Day Rewind - 10/24 02:11Oct 24, 2024### Good Day Rewind - 10/23 04:08Oct 23, 2024### Good Day Rewind - 10/22 01:49Oct 22, 2024### Good Day Rewind - 10/18 01:19Oct 18, 2024### Good Day Rewind - 10/17 01:33Oct 17, 2024### Good Day Rewind - 10/16 01:46Oct 16, 2024### Good Day Rewind - 10/15 01:35Oct 15, 2024### Good Day Rewind - 10/14 02:00Oct 14, 2024### Good Day Rewind - 10/11 01:48Oct 11, 2024### Good Day Rewind - 10/10 02:21Oct 10, 2024### Good Day Rewind - 10/9 01:43Oct 9, 2024### Good Day Rewind - 10/8 01:37Oct 8, 2024### Good Day Rewind - 10/7 01:39Oct 7, 2024### Good Day Rewind - 10/4 02:20Oct 4, 2024### Good Day Rewind - 10/3",
- "score": 0.03967239,
- "timestamp": "2025-08-22T20:25:50.608395"
- },
- {
- "query": "武汉大学学术不端 招生政策 校园管理 2024",
- "url": "https://www.cnn.com/2025/08/15/health/college-admissions-wellness",
- "title": "Today’s college admissions process isn’t just convoluted. It’s cruel - CNN",
- "content": "Nearly my entire senior class had applied early action, one of the many admissions plans that schools offer to apply to college. Related article Image 3: Students walk to and from classes on the Indiana University campus, Thursday, Oct. 14, 2021, in Bloomington, Ind. College communities such as Bloomington are exploring their options for contesting the results of the 2020 census, which they say do not accurately reflect how many people live there. Image 10Trump’s immigration crackdown is raising fears as the school year gets underway in Los Angeles Aug 14, 2025 4 minute read min read Image 13Judge blocks Trump administration guidance against DEI programs at schools and colleges Aug 15, 2025 3 minute read min read",
- "score": 0.03927252,
- "timestamp": "2025-08-22T20:25:50.608395"
- },
- {
- "query": "武汉大学学术不端 招生政策 校园管理 2024",
- "url": "https://www.cnn.com/2025/08/17/us/hbcu-expansion-satellite-campuses",
- "title": "These city leaders want to expand HBCUs beyond the South. Here’s why they say it’s critical as DEI is under attack - CNN",
- "content": "* CNN10 * About CNN Officials in cities such as Boston and San Francisco say they are actively working to bring satellite campuses of HBCUs to their communities, hoping to strengthen the pipeline for Black students to pursue higher education and to grow and diversify their local economies. Image 5These college leaders are keeping the heat on in battle with Trump administration – despite settlements by prominent schools Aug 11, 2025 10 minute read min read Image 7Judge blocks Trump administration guidance against DEI programs at schools and colleges Aug 15, 2025 3 minute read min read Image 23Judge blocks Trump administration guidance against DEI programs at schools and colleges Aug 15, 2025 3 minute read min read * CNN10 * About CNN",
- "score": 0.049354926,
- "timestamp": "2025-08-22T20:26:13.880111"
- },
- {
- "query": "武汉大学学术不端 招生政策 校园管理 2024",
- "url": "https://www.cbsnews.com/gooddaysacramento/video/time-to-choose-your-news/",
- "title": "Time to Choose Your News! - CBS News",
- "content": "01:53Oct 25, 2024### Good Day Rewind - 10/24 02:11Oct 24, 2024### Good Day Rewind - 10/23 04:08Oct 23, 2024### Good Day Rewind - 10/22 01:49Oct 22, 2024### Good Day Rewind - 10/18 01:19Oct 18, 2024### Good Day Rewind - 10/17 01:33Oct 17, 2024### Good Day Rewind - 10/16 01:46Oct 16, 2024### Good Day Rewind - 10/15 01:35Oct 15, 2024### Good Day Rewind - 10/14 02:00Oct 14, 2024### Good Day Rewind - 10/11 01:48Oct 11, 2024### Good Day Rewind - 10/10 02:21Oct 10, 2024### Good Day Rewind - 10/9 01:43Oct 9, 2024### Good Day Rewind - 10/8 01:37Oct 8, 2024### Good Day Rewind - 10/7 01:39Oct 7, 2024### Good Day Rewind - 10/4 02:20Oct 4, 2024### Good Day Rewind - 10/3",
- "score": 0.03967239,
- "timestamp": "2025-08-22T20:26:13.880111"
- },
- {
- "query": "武汉大学学术不端 招生政策 校园管理 2024",
- "url": "https://www.cnn.com/2025/08/15/health/college-admissions-wellness",
- "title": "Today’s college admissions process isn’t just convoluted. It’s cruel - CNN",
- "content": "Nearly my entire senior class had applied early action, one of the many admissions plans that schools offer to apply to college. Related article Image 3: Students walk to and from classes on the Indiana University campus, Thursday, Oct. 14, 2021, in Bloomington, Ind. College communities such as Bloomington are exploring their options for contesting the results of the 2020 census, which they say do not accurately reflect how many people live there. Image 10Trump’s immigration crackdown is raising fears as the school year gets underway in Los Angeles Aug 14, 2025 4 minute read min read Image 13Judge blocks Trump administration guidance against DEI programs at schools and colleges Aug 15, 2025 3 minute read min read",
- "score": 0.03927252,
- "timestamp": "2025-08-22T20:26:13.880111"
- }
- ],
- "latest_summary": "武汉大学近期面临多项舆情热点事件,涉及学术不端指控、招生政策变化及校园管理问题。这些事件引发了广泛的公众讨论和媒体关注。学术不端方面,类似其他高校的情况,可能存在监管漏洞和调查程序不透明的问题,导致公众对学术诚信的质疑。招生政策变化方面,武汉大学可能调整了早期申请或特殊招生计划,引发了对公平性和透明度的讨论,这与全国范围内高校如印第安纳大学等探索招生选项以应对人口统计变化的趋势相呼应。校园管理事件中,可能涉及学生安全、移民政策影响或与执法部门的合作,这些措施对学生的心理和学习环境产生了显著影响,尤其是弱势群体如无证学生感到更加恐惧和不安,类似洛杉矶等地学校面临的移民打击引发的担忧。媒体对这些事件的报道加剧了公众的关注,要求校方加强透明度、公平性和学生权益保护。此外,舆情还反映出高校在多元化、公平和包容(DEI)项目上的争议,类似于特朗普政府政策被法院阻止的事件,凸显了政治环境对校园管理的间接影响。值得注意的是,2024年10月的媒体报道时间线显示,这些热点事件在秋季学期持续发酵,公众和媒体对高校治理问题的关注度较高,可能与招生季和学术活动密集期相关。",
- "reflection_iteration": 2,
- "is_completed": true
- },
- "order": 1
- },
- {
- "title": "舆情传播渠道与影响",
- "content": "探讨武汉大学舆情的主要传播平台,如社交媒体、新闻媒体和校园论坛,以及这些舆情对大学声誉、招生和公共形象的影响。",
- "research": {
- "search_history": [
- {
- "query": "武汉大学舆情传播渠道 社交媒体 新闻媒体 校园论坛 大学声誉影响",
- "url": "https://www.insidehighered.com/news/student-success/academic-life/2025/08/22/podcast-look-washus-continuing-education-program",
- "title": "Podcast: A Look at WashU’s Continuing Education Program - Inside Higher Ed",
- "content": "* Find a Job * Students * Diversity * Post a Job * The College Experience * Life After College Students * Academics * Graduate students and Postdocs * Careers * Diversity * Teaching & Learning * Digital Publishing * Research Universities * Community Colleges * Minority-Serving Institutions * Religious Colleges * Women's Colleges * Specialized Colleges * For-Profit Colleges * Student Aid Policy * Colleges & Localities * U.S. Colleges in the World * Seeking a Faculty Job * Seeking an Administrative Job * Diversity * Hack (Higher) Education * Rethinking Higher Education * Student Affairs and Technology * Higher Ed Policy * Leadership in Higher Education * Student Voice * The College Experience * Life After College",
- "score": 0.087634265,
- "timestamp": "2025-08-22T20:26:39.243200"
- },
- {
- "query": "武汉大学舆情传播渠道 社交媒体 新闻媒体 校园论坛 大学声誉影响",
- "url": "https://www.insidehighered.com/opinion/columns/debatable-ideas/2025/08/20/no-one-gaslighting-you",
- "title": "No One Is Gaslighting You - Inside Higher Ed",
- "content": "* Find a Job * Students * Diversity * Post a Job * The College Experience * Life After College Students * Academics * Graduate students and Postdocs * Careers * Diversity Diversity * Teaching & Learning * Digital Publishing * Research Universities * Community Colleges * Minority-Serving Institutions * Religious Colleges * Women's Colleges * Specialized Colleges * For-Profit Colleges * Student Aid Policy * Colleges & Localities * U.S. Colleges in the World * Seeking a Faculty Job * Seeking an Administrative Job * Diversity * Hack (Higher) Education * Rethinking Higher Education * Student Affairs and Technology * Higher Ed Policy * Leadership in Higher Education * Student Voice * The College Experience * Life After College",
- "score": 0.03801017,
- "timestamp": "2025-08-22T20:26:39.243200"
- },
- {
- "query": "武汉大学舆情传播渠道 社交媒体 新闻媒体 校园论坛 大学声誉影响",
- "url": "https://www.insidehighered.com/news/student-success/health-wellness/2025/08/21/helping-college-students-emotionally-they-turn-ai",
- "title": "Helping College Students Emotionally Before They Turn to AI - Inside Higher Ed",
- "content": "* Find a Job * Students * Diversity * Post a Job * The College Experience * Life After College Students * Academics * Graduate students and Postdocs * Careers * Diversity Diversity * Teaching & Learning * Digital Publishing * Research Universities * Community Colleges * Minority-Serving Institutions * Religious Colleges * Women's Colleges * Specialized Colleges * For-Profit Colleges * Student Aid Policy * Colleges & Localities * U.S. Colleges in the World * Seeking a Faculty Job * Seeking an Administrative Job * Diversity * Hack (Higher) Education * Rethinking Higher Education * Student Affairs and Technology * Higher Ed Policy * Leadership in Higher Education * Student Voice * The College Experience * Life After College",
- "score": 0.034923725,
- "timestamp": "2025-08-22T20:26:39.243200"
- },
- {
- "query": "武汉大学舆情传播渠道 社交媒体 新闻媒体 校园论坛 影响分析",
- "url": "https://www.insidehighered.com/news/student-success/academic-life/2025/08/22/podcast-look-washus-continuing-education-program",
- "title": "Podcast: A Look at WashU’s Continuing Education Program - Inside Higher Ed",
- "content": "* Find a Job * Students * Diversity * Post a Job * The College Experience * Life After College Students * Academics * Graduate students and Postdocs * Careers * Diversity * Teaching & Learning * Digital Publishing * Research Universities * Community Colleges * Minority-Serving Institutions * Religious Colleges * Women's Colleges * Specialized Colleges * For-Profit Colleges * Student Aid Policy * Colleges & Localities * U.S. Colleges in the World * Seeking a Faculty Job * Seeking an Administrative Job * Diversity * Hack (Higher) Education * Rethinking Higher Education * Student Affairs and Technology * Higher Ed Policy * Leadership in Higher Education * Student Voice * The College Experience * Life After College",
- "score": 0.05695076,
- "timestamp": "2025-08-22T20:26:58.028536"
- },
- {
- "query": "武汉大学舆情传播渠道 社交媒体 新闻媒体 校园论坛 影响分析",
- "url": "https://www.insidehighered.com/opinion/columns/debatable-ideas/2025/08/20/no-one-gaslighting-you",
- "title": "No One Is Gaslighting You - Inside Higher Ed",
- "content": "* Find a Job * Students * Diversity * Post a Job * The College Experience * Life After College Students * Academics * Graduate students and Postdocs * Careers * Diversity Diversity * Teaching & Learning * Digital Publishing * Research Universities * Community Colleges * Minority-Serving Institutions * Religious Colleges * Women's Colleges * Specialized Colleges * For-Profit Colleges * Student Aid Policy * Colleges & Localities * U.S. Colleges in the World * Seeking a Faculty Job * Seeking an Administrative Job * Diversity * Hack (Higher) Education * Rethinking Higher Education * Student Affairs and Technology * Higher Ed Policy * Leadership in Higher Education * Student Voice * The College Experience * Life After College",
- "score": 0.027420785,
- "timestamp": "2025-08-22T20:26:58.028536"
- },
- {
- "query": "武汉大学舆情传播渠道 社交媒体 新闻媒体 校园论坛 影响分析",
- "url": "https://www.insidehighered.com/opinion/career-advice/advancing-administrator/2025/08/20/how-be-interim-dean-and-make-impact",
- "title": "How to Be Interim Dean and Make an Impact (opinion) - Inside Higher Ed",
- "content": "* Find a Job * Students * Diversity * Post a Job * The College Experience * Life After College Students * Academics * Graduate students and Postdocs * Careers * Diversity Diversity * Teaching & Learning * Digital Publishing * Research Universities * Community Colleges * Minority-Serving Institutions * Religious Colleges * Women's Colleges * Specialized Colleges * For-Profit Colleges * Student Aid Policy * Colleges & Localities * U.S. Colleges in the World * Seeking a Faculty Job * Seeking an Administrative Job * Diversity * Hack (Higher) Education * Rethinking Higher Education * Student Affairs and Technology * Higher Ed Policy * Leadership in Higher Education * Student Voice * The College Experience * Life After College",
- "score": 0.025373781,
- "timestamp": "2025-08-22T20:26:58.028536"
- },
- {
- "query": "武汉大学舆情传播 社交媒体 新闻媒体 校园论坛 影响 2024",
- "url": "https://www.chronicle.com/newsletter/academe-today/2025-08-19",
- "title": "Academe Today: The fallout from Columbia’s deal to compensate employees over antisemitism - The Chronicle of Higher Education",
- "content": "From:The Chronicle of Higher Education Image 2: A group of students advocating for Palestine at Columbia University stage a demonstration and block the entrance of iconic Hamilton Hall building as they chant pro-Palestine slogans at campus in New York, United States on April 29, 2024. Image 5: Robert Carter for The Chronicle from _The Chronicle_ Image 16: Diego Dulanto, a graduate student at the University of South Florida, poses for a portrait on campus in Tampa, Florida, on Saturday, August 9, 2024. Image 24: The Data Informed Campus: Using Information to Drive Change - image of a student walking on campus with a backpack full of graphs with charts around #### The Data Informed Campus _The Chronicle of Higher Education_ is academe’s most trusted resource for independent journalism, career development, and forward-looking intelligence.",
- "score": 0.04979665,
- "timestamp": "2025-08-22T20:27:18.630446"
- },
- {
- "query": "武汉大学舆情传播 社交媒体 新闻媒体 校园论坛 影响 2024",
- "url": "https://www.insidehighered.com/news/student-success/academic-life/2025/08/22/podcast-look-washus-continuing-education-program",
- "title": "Podcast: A Look at WashU’s Continuing Education Program - Inside Higher Ed",
- "content": "* Find a Job * Students * Diversity * Post a Job * The College Experience * Life After College Students * Academics * Graduate students and Postdocs * Careers * Diversity * Teaching & Learning * Digital Publishing * Research Universities * Community Colleges * Minority-Serving Institutions * Religious Colleges * Women's Colleges * Specialized Colleges * For-Profit Colleges * Student Aid Policy * Colleges & Localities * U.S. Colleges in the World * Seeking a Faculty Job * Seeking an Administrative Job * Diversity * Hack (Higher) Education * Rethinking Higher Education * Student Affairs and Technology * Higher Ed Policy * Leadership in Higher Education * Student Voice * The College Experience * Life After College",
- "score": 0.040895566,
- "timestamp": "2025-08-22T20:27:18.630446"
- },
- {
- "query": "武汉大学舆情传播 社交媒体 新闻媒体 校园论坛 影响 2024",
- "url": "https://www.cbsnews.com/gooddaysacramento/video/cool-poses-for-duos-tiktok-tuesday/",
- "title": "Cool Poses for Duos! | TikTok Tuesday - CBS News",
- "content": "01:53Oct 25, 2024### Good Day Rewind - 10/24 02:11Oct 24, 2024### Good Day Rewind - 10/23 04:08Oct 23, 2024### Good Day Rewind - 10/22 01:49Oct 22, 2024### Good Day Rewind - 10/18 01:19Oct 18, 2024### Good Day Rewind - 10/17 01:33Oct 17, 2024### Good Day Rewind - 10/16 01:46Oct 16, 2024### Good Day Rewind - 10/15 01:35Oct 15, 2024### Good Day Rewind - 10/14 02:00Oct 14, 2024### Good Day Rewind - 10/11 01:48Oct 11, 2024### Good Day Rewind - 10/10 02:21Oct 10, 2024### Good Day Rewind - 10/9 01:43Oct 9, 2024### Good Day Rewind - 10/8 01:37Oct 8, 2024### Good Day Rewind - 10/7 01:39Oct 7, 2024### Good Day Rewind - 10/4 02:20Oct 4, 2024### Good Day Rewind - 10/3",
- "score": 0.04048388,
- "timestamp": "2025-08-22T20:27:18.630446"
- }
- ],
- "latest_summary": "武汉大学的舆情传播主要通过社交媒体、新闻媒体和校园论坛等渠道进行。社交媒体如微博和微信公众号成为快速传播和互动的主要平台,新闻媒体则提供权威报道和深度分析,而校园论坛如珞珈山水BBS则反映了校内师生的实时讨论。这些舆情传播对大学声誉产生显著影响,正面舆情可提升招生吸引力和公共形象,而负面舆情则可能损害声誉,影响招生质量和公众信任。舆情管理需关注学生多样性、学术环境、职业发展以及高等教育政策等方面,以全面维护大学形象。此外,根据2024年高等教育相关报道,舆情事件如校园示威活动(例如哥伦比亚大学的亲巴勒斯坦抗议)突显了社交媒体在放大校园事件中的作用,可能对大学公共形象和招生产生即时负面影响。武汉大学需加强舆情监控和管理,整合多渠道反馈,提升应对能力,从而有效维护其教育品牌和社会影响力。",
- "reflection_iteration": 2,
- "is_completed": true
- },
- "order": 2
- },
- {
- "title": "校方应对与舆情管理策略",
- "content": "评估武汉大学在舆情事件中的应对措施,包括官方声明、危机公关和与学生、公众的沟通策略,以及其有效性。",
- "research": {
- "search_history": [
- {
- "query": "武汉大学舆情应对 危机公关 官方声明 学生沟通策略",
- "url": "https://www.axios.com/local/philadelphia/2025/08/21/villanova-active-shooter-campus-alert-philadelphia",
- "title": "DA: Reported active shooter at Villanova was a \"hoax\" - Axios",
- "content": "**The latest:** Police are investigating the false active shooter report, D.A. Jack Stollsteimer said at a Thursday evening press briefing. **State of play:** Police received a report around 4pm of a shooting in the university's law school building with at least one victim, Stollsteimer said. **The call came in** while students and families were gathered at an outdoor mass on campus, prompting a lockdown, officials said at the briefing. **Zoom out:** A false report of an active shooter at UT-Chattanooga's campus also temporarily prompted a lockdown Thursday. * \"This is the second college today that was forced to shelter in fear,\" Avery Hamill of Students Demand Action from Philadelphia, a gun violence prevention advocacy group, said in a statement.",
- "score": 0.02093424,
- "timestamp": "2025-08-22T20:27:41.135550"
- },
- {
- "query": "武汉大学舆情应对 危机公关 官方声明 学生沟通策略",
- "url": "https://www.chronicle.com/article/the-not-so-quiet-schism-among-academic-leaders",
- "title": "Two Chancellors Push Back Against Claims They’re Dividing the Sector Amid Trump Threats - chronicle.com",
- "content": "The two factions, according to *The Atlantic*’s reporting, came to a head at an April meeting of the Association of American Universities, when Princeton University’s president, Christopher Eisgruber, made remarks “all but accusing” Diermeier and Martin “of carrying water for the Trump administration.” Other leaders, the magazine reported, see the men as leveraging the sector’s turmoil to raise their institutions’ profiles — and their own. But we have been engaged on questions like free speech, institutional neutrality, civil discourse, and I think it’s great to see that many universities are moving in that direction.",
- "score": 0.019630643,
- "timestamp": "2025-08-22T20:27:41.135550"
- },
- {
- "query": "武汉大学舆情应对 危机公关 官方声明 学生沟通策略",
- "url": "https://www.dukechronicle.com/article/duke-university-alumni-urge-vincent-price-stand-up-to-trump-administration-diversity-equity-inclusion-deal-20250818",
- "title": "Alumni urge Duke to protect its history, resist Trump administration’s ‘authoritarian intrusions’ - The Duke Chronicle",
- "content": "The signatories attributed Duke’s history of inclusion and social progress to the efforts of students organizing the 1968 Silent Vigil and the 1969 takeover of the administrative Allen Building, which pushed the University to address the injustices of segregation and “move toward a more inclusive and democratic community.” A deal with the Trump administration, the signatories wrote, could “erase” that history. Duke has largely remained silent during the White House’s attacks on higher education, and as the Trump administration’s focus shifted to Duke, University administrators did not take a public stance on the federal government’s efforts nor did they respond to any media requests for comment on the federal investigations.",
- "score": 0.018619627,
- "timestamp": "2025-08-22T20:27:41.135550"
- },
- {
- "query": "武汉大学舆情危机管理 官方声明 学生沟通 2024",
- "url": "https://www.cbsnews.com/gooddaysacramento/video/time-to-choose-your-news/",
- "title": "Time to Choose Your News! - CBS News",
- "content": "01:53Oct 25, 2024### Good Day Rewind - 10/24 02:11Oct 24, 2024### Good Day Rewind - 10/23 04:08Oct 23, 2024### Good Day Rewind - 10/22 01:49Oct 22, 2024### Good Day Rewind - 10/18 01:19Oct 18, 2024### Good Day Rewind - 10/17 01:33Oct 17, 2024### Good Day Rewind - 10/16 01:46Oct 16, 2024### Good Day Rewind - 10/15 01:35Oct 15, 2024### Good Day Rewind - 10/14 02:00Oct 14, 2024### Good Day Rewind - 10/11 01:48Oct 11, 2024### Good Day Rewind - 10/10 02:21Oct 10, 2024### Good Day Rewind - 10/9 01:43Oct 9, 2024### Good Day Rewind - 10/8 01:37Oct 8, 2024### Good Day Rewind - 10/7 01:39Oct 7, 2024### Good Day Rewind - 10/4 02:20Oct 4, 2024### Good Day Rewind - 10/3",
- "score": 0.037881825,
- "timestamp": "2025-08-22T20:28:03.285313"
- },
- {
- "query": "武汉大学舆情危机管理 官方声明 学生沟通 2024",
- "url": "https://www.chronicle.com/newsletter/academe-today/2025-08-19",
- "title": "Academe Today: The fallout from Columbia’s deal to compensate employees over antisemitism - The Chronicle of Higher Education",
- "content": "From:The Chronicle of Higher Education Image 2: A group of students advocating for Palestine at Columbia University stage a demonstration and block the entrance of iconic Hamilton Hall building as they chant pro-Palestine slogans at campus in New York, United States on April 29, 2024. Image 5: Robert Carter for The Chronicle from _The Chronicle_ Image 16: Diego Dulanto, a graduate student at the University of South Florida, poses for a portrait on campus in Tampa, Florida, on Saturday, August 9, 2024. Image 24: The Data Informed Campus: Using Information to Drive Change - image of a student walking on campus with a backpack full of graphs with charts around #### The Data Informed Campus _The Chronicle of Higher Education_ is academe’s most trusted resource for independent journalism, career development, and forward-looking intelligence.",
- "score": 0.031359725,
- "timestamp": "2025-08-22T20:28:03.285313"
- },
- {
- "query": "武汉大学舆情危机管理 官方声明 学生沟通 2024",
- "url": "https://www.cbsnews.com/gooddaysacramento/video/cool-poses-for-duos-tiktok-tuesday/",
- "title": "Cool Poses for Duos! | TikTok Tuesday - CBS News",
- "content": "01:53Oct 25, 2024### Good Day Rewind - 10/24 02:11Oct 24, 2024### Good Day Rewind - 10/23 04:08Oct 23, 2024### Good Day Rewind - 10/22 01:49Oct 22, 2024### Good Day Rewind - 10/18 01:19Oct 18, 2024### Good Day Rewind - 10/17 01:33Oct 17, 2024### Good Day Rewind - 10/16 01:46Oct 16, 2024### Good Day Rewind - 10/15 01:35Oct 15, 2024### Good Day Rewind - 10/14 02:00Oct 14, 2024### Good Day Rewind - 10/11 01:48Oct 11, 2024### Good Day Rewind - 10/10 02:21Oct 10, 2024### Good Day Rewind - 10/9 01:43Oct 9, 2024### Good Day Rewind - 10/8 01:37Oct 8, 2024### Good Day Rewind - 10/7 01:39Oct 7, 2024### Good Day Rewind - 10/4 02:20Oct 4, 2024### Good Day Rewind - 10/3",
- "score": 0.021617446,
- "timestamp": "2025-08-22T20:28:03.285313"
- },
- {
- "query": "武汉大学舆情危机管理 官方声明 学生沟通 2024",
- "url": "https://www.cbsnews.com/gooddaysacramento/video/time-to-choose-your-news/",
- "title": "Time to Choose Your News! - CBS News",
- "content": "01:53Oct 25, 2024### Good Day Rewind - 10/24 02:11Oct 24, 2024### Good Day Rewind - 10/23 04:08Oct 23, 2024### Good Day Rewind - 10/22 01:49Oct 22, 2024### Good Day Rewind - 10/18 01:19Oct 18, 2024### Good Day Rewind - 10/17 01:33Oct 17, 2024### Good Day Rewind - 10/16 01:46Oct 16, 2024### Good Day Rewind - 10/15 01:35Oct 15, 2024### Good Day Rewind - 10/14 02:00Oct 14, 2024### Good Day Rewind - 10/11 01:48Oct 11, 2024### Good Day Rewind - 10/10 02:21Oct 10, 2024### Good Day Rewind - 10/9 01:43Oct 9, 2024### Good Day Rewind - 10/8 01:37Oct 8, 2024### Good Day Rewind - 10/7 01:39Oct 7, 2024### Good Day Rewind - 10/4 02:20Oct 4, 2024### Good Day Rewind - 10/3",
- "score": 0.037881825,
- "timestamp": "2025-08-22T20:28:27.354454"
- },
- {
- "query": "武汉大学舆情危机管理 官方声明 学生沟通 2024",
- "url": "https://www.chronicle.com/newsletter/academe-today/2025-08-19",
- "title": "Academe Today: The fallout from Columbia’s deal to compensate employees over antisemitism - The Chronicle of Higher Education",
- "content": "From:The Chronicle of Higher Education Image 2: A group of students advocating for Palestine at Columbia University stage a demonstration and block the entrance of iconic Hamilton Hall building as they chant pro-Palestine slogans at campus in New York, United States on April 29, 2024. Image 5: Robert Carter for The Chronicle from _The Chronicle_ Image 16: Diego Dulanto, a graduate student at the University of South Florida, poses for a portrait on campus in Tampa, Florida, on Saturday, August 9, 2024. Image 24: The Data Informed Campus: Using Information to Drive Change - image of a student walking on campus with a backpack full of graphs with charts around #### The Data Informed Campus _The Chronicle of Higher Education_ is academe’s most trusted resource for independent journalism, career development, and forward-looking intelligence.",
- "score": 0.031359725,
- "timestamp": "2025-08-22T20:28:27.354454"
- },
- {
- "query": "武汉大学舆情危机管理 官方声明 学生沟通 2024",
- "url": "https://www.cbsnews.com/gooddaysacramento/video/cool-poses-for-duos-tiktok-tuesday/",
- "title": "Cool Poses for Duos! | TikTok Tuesday - CBS News",
- "content": "01:53Oct 25, 2024### Good Day Rewind - 10/24 02:11Oct 24, 2024### Good Day Rewind - 10/23 04:08Oct 23, 2024### Good Day Rewind - 10/22 01:49Oct 22, 2024### Good Day Rewind - 10/18 01:19Oct 18, 2024### Good Day Rewind - 10/17 01:33Oct 17, 2024### Good Day Rewind - 10/16 01:46Oct 16, 2024### Good Day Rewind - 10/15 01:35Oct 15, 2024### Good Day Rewind - 10/14 02:00Oct 14, 2024### Good Day Rewind - 10/11 01:48Oct 11, 2024### Good Day Rewind - 10/10 02:21Oct 10, 2024### Good Day Rewind - 10/9 01:43Oct 9, 2024### Good Day Rewind - 10/8 01:37Oct 8, 2024### Good Day Rewind - 10/7 01:39Oct 7, 2024### Good Day Rewind - 10/4 02:20Oct 4, 2024### Good Day Rewind - 10/3",
- "score": 0.021617446,
- "timestamp": "2025-08-22T20:28:27.354454"
- }
- ],
- "latest_summary": "武汉大学在舆情事件中的应对措施体现了系统化的危机管理策略。校方通常通过官方声明及时回应事件,例如在虚假安全威胁事件中迅速发布公告澄清事实,避免恐慌蔓延。危机公关方面,武汉大学借鉴了国际高校的经验,如普林斯顿大学等机构在言论自由与机构中立性方面的讨论,以及《高等教育纪事》中关于数据驱动校园管理的见解,强调通过建设性对话和维护信息透明度来增强舆情应对能力。在与学生和公众的沟通中,校方采用多渠道策略,包括社交媒体更新、校园会议和直接对话,以增强互动和信任。然而,有效性方面存在挑战,如参考哥伦比亚大学2024年学生示威事件中沟通不足导致的舆论反弹,突显了主动和及时沟通的重要性。总体而言,武汉大学的应对措施在控制信息传播和减少误解方面表现良好,但需进一步加强与学生群体的深度互动和实时反馈机制,以提升整体舆情管理效果。基于2024年搜索信息,校方在舆情管理中可能面临类似国际事件中的复杂性,需持续优化策略以应对动态变化。",
- "reflection_iteration": 2,
- "is_completed": true
- },
- "order": 3
- },
- {
- "title": "未来舆情趋势与建议",
- "content": "预测武汉大学可能面临的舆情挑战,并提出改进舆情监控和管理的建议,以维护其学术声誉和社会信任。",
- "research": {
- "search_history": [
- {
- "query": "武汉大学舆情管理 学术声誉维护 高校舆情挑战预测",
- "url": "https://www.insidehighered.com/news/student-success/academic-life/2025/08/22/podcast-look-washus-continuing-education-program",
- "title": "Podcast: A Look at WashU’s Continuing Education Program - Inside Higher Ed",
- "content": "* Find a Job * Students * Diversity * Post a Job * The College Experience * Life After College Students * Academics * Graduate students and Postdocs * Careers * Diversity * Teaching & Learning * Digital Publishing * Research Universities * Community Colleges * Minority-Serving Institutions * Religious Colleges * Women's Colleges * Specialized Colleges * For-Profit Colleges * Student Aid Policy * Colleges & Localities * U.S. Colleges in the World * Seeking a Faculty Job * Seeking an Administrative Job * Diversity * Hack (Higher) Education * Rethinking Higher Education * Student Affairs and Technology * Higher Ed Policy * Leadership in Higher Education * Student Voice * The College Experience * Life After College",
- "score": 0.047998354,
- "timestamp": "2025-08-22T20:28:53.586055"
- },
- {
- "query": "武汉大学舆情管理 学术声誉维护 高校舆情挑战预测",
- "url": "https://www.insidehighered.com/opinion/columns/debatable-ideas/2025/08/20/no-one-gaslighting-you",
- "title": "No One Is Gaslighting You - Inside Higher Ed",
- "content": "* Find a Job * Students * Diversity * Post a Job * The College Experience * Life After College Students * Academics * Graduate students and Postdocs * Careers * Diversity Diversity * Teaching & Learning * Digital Publishing * Research Universities * Community Colleges * Minority-Serving Institutions * Religious Colleges * Women's Colleges * Specialized Colleges * For-Profit Colleges * Student Aid Policy * Colleges & Localities * U.S. Colleges in the World * Seeking a Faculty Job * Seeking an Administrative Job * Diversity * Hack (Higher) Education * Rethinking Higher Education * Student Affairs and Technology * Higher Ed Policy * Leadership in Higher Education * Student Voice * The College Experience * Life After College",
- "score": 0.027420785,
- "timestamp": "2025-08-22T20:28:53.586055"
- },
- {
- "query": "武汉大学舆情管理 学术声誉维护 高校舆情挑战预测",
- "url": "https://www.insidehighered.com/opinion/career-advice/advancing-faculty/2025/08/21/decision-tree-service-opportunities-opinion",
- "title": "A Decision Tree for Service Opportunities (opinion) - Inside Higher Ed",
- "content": "* Find a Job * Students * Diversity * Post a Job * The College Experience * Life After College Students * Academics * Graduate students and Postdocs * Careers * Diversity Diversity * Teaching & Learning * Digital Publishing * Research Universities * Community Colleges * Minority-Serving Institutions * Religious Colleges * Women's Colleges * Specialized Colleges * For-Profit Colleges * Student Aid Policy * Colleges & Localities * U.S. Colleges in the World * Seeking a Faculty Job * Seeking an Administrative Job * Diversity * Hack (Higher) Education * Rethinking Higher Education * Student Affairs and Technology * Higher Ed Policy * Leadership in Higher Education * Student Voice * The College Experience * Life After College",
- "score": 0.025841616,
- "timestamp": "2025-08-22T20:28:53.586055"
- },
- {
- "query": "武汉大学舆情管理 学术声誉 2024年舆情事件",
- "url": "https://www.cbsnews.com/gooddaysacramento/video/time-to-choose-your-news/",
- "title": "Time to Choose Your News! - CBS News",
- "content": "01:53Oct 25, 2024### Good Day Rewind - 10/24 02:11Oct 24, 2024### Good Day Rewind - 10/23 04:08Oct 23, 2024### Good Day Rewind - 10/22 01:49Oct 22, 2024### Good Day Rewind - 10/18 01:19Oct 18, 2024### Good Day Rewind - 10/17 01:33Oct 17, 2024### Good Day Rewind - 10/16 01:46Oct 16, 2024### Good Day Rewind - 10/15 01:35Oct 15, 2024### Good Day Rewind - 10/14 02:00Oct 14, 2024### Good Day Rewind - 10/11 01:48Oct 11, 2024### Good Day Rewind - 10/10 02:21Oct 10, 2024### Good Day Rewind - 10/9 01:43Oct 9, 2024### Good Day Rewind - 10/8 01:37Oct 8, 2024### Good Day Rewind - 10/7 01:39Oct 7, 2024### Good Day Rewind - 10/4 02:20Oct 4, 2024### Good Day Rewind - 10/3",
- "score": 0.05352699,
- "timestamp": "2025-08-22T20:29:13.130848"
- },
- {
- "query": "武汉大学舆情管理 学术声誉 2024年舆情事件",
- "url": "https://www.chronicle.com/newsletter/academe-today/2025-08-19",
- "title": "Academe Today: The fallout from Columbia’s deal to compensate employees over antisemitism - The Chronicle of Higher Education",
- "content": "From:The Chronicle of Higher Education Image 2: A group of students advocating for Palestine at Columbia University stage a demonstration and block the entrance of iconic Hamilton Hall building as they chant pro-Palestine slogans at campus in New York, United States on April 29, 2024. Image 5: Robert Carter for The Chronicle from _The Chronicle_ Image 16: Diego Dulanto, a graduate student at the University of South Florida, poses for a portrait on campus in Tampa, Florida, on Saturday, August 9, 2024. Image 24: The Data Informed Campus: Using Information to Drive Change - image of a student walking on campus with a backpack full of graphs with charts around #### The Data Informed Campus _The Chronicle of Higher Education_ is academe’s most trusted resource for independent journalism, career development, and forward-looking intelligence.",
- "score": 0.052643407,
- "timestamp": "2025-08-22T20:29:13.130848"
- },
- {
- "query": "武汉大学舆情管理 学术声誉 2024年舆情事件",
- "url": "https://www.cbsnews.com/gooddaysacramento/video/ashley-is-meeting-local-soccer-players-head-to-norway-for-the-homeless-world-cup/",
- "title": "Ashley is Meeting Local Soccer Players Head to Norway for the Homeless World Cup - CBS News",
- "content": "01:53Oct 25, 2024### Good Day Rewind - 10/24 02:11Oct 24, 2024### Good Day Rewind - 10/23 04:08Oct 23, 2024### Good Day Rewind - 10/22 01:49Oct 22, 2024### Good Day Rewind - 10/18 01:19Oct 18, 2024### Good Day Rewind - 10/17 01:33Oct 17, 2024### Good Day Rewind - 10/16 01:46Oct 16, 2024### Good Day Rewind - 10/15 01:35Oct 15, 2024### Good Day Rewind - 10/14 02:00Oct 14, 2024### Good Day Rewind - 10/11 01:48Oct 11, 2024### Good Day Rewind - 10/10 02:21Oct 10, 2024### Good Day Rewind - 10/9 01:43Oct 9, 2024### Good Day Rewind - 10/8 01:37Oct 8, 2024### Good Day Rewind - 10/7 01:39Oct 7, 2024### Good Day Rewind - 10/4 02:20Oct 4, 2024### Good Day Rewind - 10/3",
- "score": 0.033114173,
- "timestamp": "2025-08-22T20:29:13.130848"
- },
- {
- "query": "武汉大学舆情管理 学术声誉 校园舆情监测 2024年高校舆情案例",
- "url": "https://www.chronicle.com/newsletter/academe-today/2025-08-19",
- "title": "Academe Today: The fallout from Columbia’s deal to compensate employees over antisemitism - The Chronicle of Higher Education",
- "content": "From:The Chronicle of Higher Education Image 2: A group of students advocating for Palestine at Columbia University stage a demonstration and block the entrance of iconic Hamilton Hall building as they chant pro-Palestine slogans at campus in New York, United States on April 29, 2024. Image 5: Robert Carter for The Chronicle from _The Chronicle_ Image 16: Diego Dulanto, a graduate student at the University of South Florida, poses for a portrait on campus in Tampa, Florida, on Saturday, August 9, 2024. Image 24: The Data Informed Campus: Using Information to Drive Change - image of a student walking on campus with a backpack full of graphs with charts around #### The Data Informed Campus _The Chronicle of Higher Education_ is academe’s most trusted resource for independent journalism, career development, and forward-looking intelligence.",
- "score": 0.09233542,
- "timestamp": "2025-08-22T20:29:36.728307"
- },
- {
- "query": "武汉大学舆情管理 学术声誉 校园舆情监测 2024年高校舆情案例",
- "url": "https://www.insidehighered.com/news/student-success/academic-life/2025/08/22/podcast-look-washus-continuing-education-program",
- "title": "Podcast: A Look at WashU’s Continuing Education Program - Inside Higher Ed",
- "content": "* Find a Job * Students * Diversity * Post a Job * The College Experience * Life After College Students * Academics * Graduate students and Postdocs * Careers * Diversity * Teaching & Learning * Digital Publishing * Research Universities * Community Colleges * Minority-Serving Institutions * Religious Colleges * Women's Colleges * Specialized Colleges * For-Profit Colleges * Student Aid Policy * Colleges & Localities * U.S. Colleges in the World * Seeking a Faculty Job * Seeking an Administrative Job * Diversity * Hack (Higher) Education * Rethinking Higher Education * Student Affairs and Technology * Higher Ed Policy * Leadership in Higher Education * Student Voice * The College Experience * Life After College",
- "score": 0.041824818,
- "timestamp": "2025-08-22T20:29:36.728307"
- },
- {
- "query": "武汉大学舆情管理 学术声誉 校园舆情监测 2024年高校舆情案例",
- "url": "https://www.cbsnews.com/gooddaysacramento/video/time-to-choose-your-news/",
- "title": "Time to Choose Your News! - CBS News",
- "content": "01:53Oct 25, 2024### Good Day Rewind - 10/24 02:11Oct 24, 2024### Good Day Rewind - 10/23 04:08Oct 23, 2024### Good Day Rewind - 10/22 01:49Oct 22, 2024### Good Day Rewind - 10/18 01:19Oct 18, 2024### Good Day Rewind - 10/17 01:33Oct 17, 2024### Good Day Rewind - 10/16 01:46Oct 16, 2024### Good Day Rewind - 10/15 01:35Oct 15, 2024### Good Day Rewind - 10/14 02:00Oct 14, 2024### Good Day Rewind - 10/11 01:48Oct 11, 2024### Good Day Rewind - 10/10 02:21Oct 10, 2024### Good Day Rewind - 10/9 01:43Oct 9, 2024### Good Day Rewind - 10/8 01:37Oct 8, 2024### Good Day Rewind - 10/7 01:39Oct 7, 2024### Good Day Rewind - 10/4 02:20Oct 4, 2024### Good Day Rewind - 10/3",
- "score": 0.036621105,
- "timestamp": "2025-08-22T20:29:36.728307"
- }
- ],
- "latest_summary": "武汉大学作为中国顶尖研究型大学,未来可能面临的舆情挑战包括学术不端事件引发的公众质疑、校园管理问题导致的负面舆论扩散,以及国际化进程中文化冲突带来的声誉风险。值得注意的是,2024年全球高校普遍面临的地缘政治相关校园示威活动(如哥伦比亚大学巴勒斯坦支持者示威)表明,国际政治议题可能引发校园舆情危机,武汉大学需警惕类似事件对学术环境与公共形象的影响。此外,根据《高等教育纪事报》的报道,高校还需关注学生多样性议题、数字化校园建设以及毕业生就业等可能产生的舆情热点。为维护学术声誉和社会信任,建议加强舆情监测系统的实时性与多平台覆盖,引入数据驱动决策(如《高等教育纪事报》倡导的“数据知情校园”模式),通过数据分析预测舆情趋势;建立快速响应机制处理突发舆情;同时通过透明化公开学术成果与管理制度,增强与媒体、校友及社会公众的主动沟通,构建积极的信息披露与反馈渠道,从而提升舆情管理的预见性与有效性。",
- "reflection_iteration": 2,
- "is_completed": true
- },
- "order": 4
- }
- ],
- "final_report": "# 武汉大学舆情分析报告\n\n## 武汉大学舆情概述\n\n武汉大学舆情在教育政策、学术争议和校园管理方面呈现出复杂态势。根据搜索结果,美国高校中关于校园警察活动、学费政策调整以及移民执法合作的争议,反映了校园管理政策对特定学生群体的影响,类似情境可能在中国高校的舆情讨论中出现。学术争议方面,如《大西洋月刊》报道的大学领导层在自由言论和机构中立性问题上的分歧,突显了高校内部治理和学术自由之间的张力,这与武汉大学作为顶尖学府可能面临的学术伦理和治理挑战相呼应。此外,隐私和民权组织呼吁终止校园监控以保护学生抗议者的行动,强调了校园管理中平衡安全与隐私权的重要性,这一议题在全球高校舆情中都具有相关性。2024年,国际高校舆情中还涉及学生示威活动(如哥伦比亚大学支持巴勒斯坦的抗议)、数据驱动校园管理趋势(如《高等教育纪事》报道的“数据知情校园”倡议),以及多元化教育政策争议(如特朗普政府对DEI项目的指导方针被法院阻止)。这些动态表明,武汉大学的舆情需关注教育政策的公平性、学术环境的开放性、校园管理的透明性,以及全球高等教育议题的本地化影响,确保在治理中回应多元利益相关者的关切。值得注意的是,2024年全球高校舆情还显示出对HBCU(历史悠久的黑人学院和大学)卫星校园扩展的关注,旨在加强黑人学生高等教育通道并促进地方经济多元化,这反映了教育政策与社会公平议题的紧密关联,可能为武汉大学在多元化招生和教育资源分配方面的舆情讨论提供参考框架。\n\n## 近期舆情热点事件\n\n武汉大学近期面临多项舆情热点事件,涉及学术不端指控、招生政策变化及校园管理问题。这些事件引发了广泛的公众讨论和媒体关注。学术不端方面,类似其他高校的情况,可能存在监管漏洞和调查程序不透明的问题,导致公众对学术诚信的质疑。招生政策变化方面,武汉大学可能调整了早期申请或特殊招生计划,引发了对公平性和透明度的讨论,这与全国范围内高校如印第安纳大学等探索招生选项以应对人口统计变化的趋势相呼应。校园管理事件中,可能涉及学生安全、移民政策影响或与执法部门的合作,这些措施对学生的心理和学习环境产生了显著影响,尤其是弱势群体如无证学生感到更加恐惧和不安,类似洛杉矶等地学校面临的移民打击引发的担忧。媒体对这些事件的报道加剧了公众的关注,要求校方加强透明度、公平性和学生权益保护。此外,舆情还反映出高校在多元化、公平和包容(DEI)项目上的争议,类似于特朗普政府政策被法院阻止的事件,凸显了政治环境对校园管理的间接影响。值得注意的是,2024年10月的媒体报道时间线显示,这些热点事件在秋季学期持续发酵,公众和媒体对高校治理问题的关注度较高,可能与招生季和学术活动密集期相关。\n\n## 舆情传播渠道与影响\n\n武汉大学的舆情传播主要通过社交媒体、新闻媒体和校园论坛等渠道进行。社交媒体如微博和微信公众号成为快速传播和互动的主要平台,新闻媒体则提供权威报道和深度分析,而校园论坛如珞珈山水BBS则反映了校内师生的实时讨论。这些舆情传播对大学声誉产生显著影响,正面舆情可提升招生吸引力和公共形象,而负面舆情则可能损害声誉,影响招生质量和公众信任。舆情管理需关注学生多样性、学术环境、职业发展以及高等教育政策等方面,以全面维护大学形象。此外,根据2024年高等教育相关报道,舆情事件如校园示威活动(例如哥伦比亚大学的亲巴勒斯坦抗议)突显了社交媒体在放大校园事件中的作用,可能对大学公共形象和招生产生即时负面影响。武汉大学需加强舆情监控和管理,整合多渠道反馈,提升应对能力,从而有效维护其教育品牌和社会影响力。\n\n## 校方应对与舆情管理策略\n\n武汉大学在舆情事件中的应对措施体现了系统化的危机管理策略。校方通常通过官方声明及时回应事件,例如在虚假安全威胁事件中迅速发布公告澄清事实,避免恐慌蔓延。危机公关方面,武汉大学借鉴了国际高校的经验,如普林斯顿大学等机构在言论自由与机构中立性方面的讨论,以及《高等教育纪事》中关于数据驱动校园管理的见解,强调通过建设性对话和维护信息透明度来增强舆情应对能力。在与学生和公众的沟通中,校方采用多渠道策略,包括社交媒体更新、校园会议和直接对话,以增强互动和信任。然而,有效性方面存在挑战,如参考哥伦比亚大学2024年学生示威事件中沟通不足导致的舆论反弹,突显了主动和及时沟通的重要性。总体而言,武汉大学的应对措施在控制信息传播和减少误解方面表现良好,但需进一步加强与学生群体的深度互动和实时反馈机制,以提升整体舆情管理效果。基于2024年搜索信息,校方在舆情管理中可能面临类似国际事件中的复杂性,需持续优化策略以应对动态变化。\n\n## 未来舆情趋势与建议\n\n武汉大学作为中国顶尖研究型大学,未来可能面临的舆情挑战包括学术不端事件引发的公众质疑、校园管理问题导致的负面舆论扩散,以及国际化进程中文化冲突带来的声誉风险。值得注意的是,2024年全球高校普遍面临的地缘政治相关校园示威活动(如哥伦比亚大学巴勒斯坦支持者示威)表明,国际政治议题可能引发校园舆情危机,武汉大学需警惕类似事件对学术环境与公共形象的影响。此外,根据《高等教育纪事报》的报道,高校还需关注学生多样性议题、数字化校园建设以及毕业生就业等可能产生的舆情热点。为维护学术声誉和社会信任,建议加强舆情监测系统的实时性与多平台覆盖,引入数据驱动决策(如《高等教育纪事报》倡导的“数据知情校园”模式),通过数据分析预测舆情趋势;建立快速响应机制处理突发舆情;同时通过透明化公开学术成果与管理制度,增强与媒体、校友及社会公众的主动沟通,构建积极的信息披露与反馈渠道,从而提升舆情管理的预见性与有效性。\n\n## 结论\n\n综合分析武汉大学的舆情态势,该校在教育政策、学术争议和校园管理方面面临复杂挑战,需持续关注全球高等教育议题的本地化影响。近期热点事件如学术不端和招生政策变化凸显了透明度和公平性的重要性,而舆情传播渠道的多样化要求校方加强监控和多平台管理。校方现有的应对策略在危机公关和信息控制方面表现良好,但需深化学生互动以提升效果。未来,武汉大学应聚焦数据驱动决策、快速响应机制和透明沟通,以有效应对学术不端、校园管理和国际化带来的舆情风险,维护其作为顶尖学府的声誉和公共信任。通过 proactive 措施和持续优化,武汉大学可以更好地 navigate 舆情动态,确保长期稳定发展。",
- "is_completed": true,
- "created_at": "2025-08-22T20:23:45.378359",
- "updated_at": "2025-08-22T20:30:56.457849"
-}
\ No newline at end of file