Add multiple tools.

This commit is contained in:
戒酒的李白
2025-08-22 20:46:11 +08:00
parent 5d7f41763f
commit bec01f8930
9 changed files with 1060 additions and 162 deletions
+1 -1
View File
@@ -288,7 +288,7 @@ tmp/
# ==== 配置和密钥 ==== # ==== 配置和密钥 ====
# 敏感配置文件 # 敏感配置文件
config.py /config.py
config.ini config.ini
secrets.json secrets.json
.secrets .secrets
+157 -15
View File
@@ -5,6 +5,7 @@ Deep Search Agent主类
import json import json
import os import os
import re
from datetime import datetime from datetime import datetime
from typing import Optional, Dict, Any, List from typing import Optional, Dict, Any, List
@@ -18,7 +19,7 @@ from .nodes import (
ReportFormattingNode ReportFormattingNode
) )
from .state import State from .state import State
from .tools import tavily_search from .tools import TavilyNewsAgency, TavilyResponse
from .utils import Config, load_config, format_search_results_for_prompt from .utils import Config, load_config, format_search_results_for_prompt
@@ -38,6 +39,9 @@ class DeepSearchAgent:
# 初始化LLM客户端 # 初始化LLM客户端
self.llm_client = self._initialize_llm() self.llm_client = self._initialize_llm()
# 初始化搜索工具集
self.search_agency = TavilyNewsAgency(api_key=self.config.tavily_api_key)
# 初始化节点 # 初始化节点
self._initialize_nodes() self._initialize_nodes()
@@ -49,6 +53,7 @@ class DeepSearchAgent:
print(f"Deep Search Agent 已初始化") print(f"Deep Search Agent 已初始化")
print(f"使用LLM: {self.llm_client.get_model_info()}") print(f"使用LLM: {self.llm_client.get_model_info()}")
print(f"搜索工具集: TavilyNewsAgency (支持6种搜索工具)")
def _initialize_llm(self) -> BaseLLM: def _initialize_llm(self) -> BaseLLM:
"""初始化LLM客户端""" """初始化LLM客户端"""
@@ -73,6 +78,72 @@ class DeepSearchAgent:
self.reflection_summary_node = ReflectionSummaryNode(self.llm_client) self.reflection_summary_node = ReflectionSummaryNode(self.llm_client)
self.report_formatting_node = ReportFormattingNode(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: def research(self, query: str, save_report: bool = True) -> str:
""" """
执行深度研究 执行深度研究
@@ -156,28 +227,62 @@ class DeepSearchAgent:
"content": paragraph.content "content": paragraph.content
} }
# 生成搜索查询 # 生成搜索查询和工具选择
print(" - 生成搜索查询...") print(" - 生成搜索查询...")
search_output = self.first_search_node.run(search_input) search_output = self.first_search_node.run(search_input)
search_query = search_output["search_query"] search_query = search_output["search_query"]
search_tool = search_output.get("search_tool", "basic_search_news") # 默认工具
reasoning = search_output["reasoning"] reasoning = search_output["reasoning"]
print(f" - 搜索查询: {search_query}") print(f" - 搜索查询: {search_query}")
print(f" - 选择的工具: {search_tool}")
print(f" - 推理: {reasoning}") print(f" - 推理: {reasoning}")
# 执行搜索 # 执行搜索
print(" - 执行网络搜索...") print(" - 执行网络搜索...")
search_results = tavily_search(
search_query, # 处理search_news_by_date的特殊参数
max_results=self.config.max_search_results, search_kwargs = {}
timeout=self.config.search_timeout, if search_tool == "search_news_by_date":
api_key=self.config.tavily_api_key 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: if search_results:
print(f" - 找到 {len(search_results)} 个搜索结果") print(f" - 找到 {len(search_results)} 个搜索结果")
for j, result in enumerate(search_results, 1): for j, result in enumerate(search_results, 1):
print(f" {j}. {result['title'][:50]}...") date_info = f" (发布于: {result.get('published_date', 'N/A')})" if result.get('published_date') else ""
print(f" {j}. {result['title'][:50]}...{date_info}")
else: else:
print(" - 未找到搜索结果") print(" - 未找到搜索结果")
@@ -219,21 +324,58 @@ class DeepSearchAgent:
# 生成反思搜索查询 # 生成反思搜索查询
reflection_output = self.reflection_node.run(reflection_input) reflection_output = self.reflection_node.run(reflection_input)
search_query = reflection_output["search_query"] search_query = reflection_output["search_query"]
search_tool = reflection_output.get("search_tool", "basic_search_news") # 默认工具
reasoning = reflection_output["reasoning"] reasoning = reflection_output["reasoning"]
print(f" 反思查询: {search_query}") print(f" 反思查询: {search_query}")
print(f" 选择的工具: {search_tool}")
print(f" 反思推理: {reasoning}") print(f" 反思推理: {reasoning}")
# 执行反思搜索 # 执行反思搜索
search_results = tavily_search( # 处理search_news_by_date的特殊参数
search_query, search_kwargs = {}
max_results=self.config.max_search_results, if search_tool == "search_news_by_date":
timeout=self.config.search_timeout, start_date = reflection_output.get("start_date")
api_key=self.config.tavily_api_key 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: if search_results:
print(f" 找到 {len(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) paragraph.research.add_search_results(search_query, search_results)
+64 -8
View File
@@ -33,8 +33,12 @@ output_schema_first_search = {
"type": "object", "type": "object",
"properties": { "properties": {
"search_query": {"type": "string"}, "search_query": {"type": "string"},
"reasoning": {"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 # 首次总结输入Schema
@@ -74,8 +78,12 @@ output_schema_reflection = {
"type": "object", "type": "object",
"properties": { "properties": {
"search_query": {"type": "string"}, "search_query": {"type": "string"},
"reasoning": {"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 # 反思总结输入Schema
@@ -139,8 +147,41 @@ SYSTEM_PROMPT_FIRST_SEARCH = f"""
{json.dumps(input_schema_first_search, indent=2, ensure_ascii=False)} {json.dumps(input_schema_first_search, indent=2, ensure_ascii=False)}
</INPUT JSON SCHEMA> </INPUT JSON SCHEMA>
你可以使用一个网络搜索工具,该工具接受'search_query'作为参数。 你可以使用以下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模式定义格式化输出(文字请使用中文):
<OUTPUT JSON SCHEMA> <OUTPUT JSON SCHEMA>
@@ -178,8 +219,23 @@ SYSTEM_PROMPT_REFLECTION = f"""
{json.dumps(input_schema_reflection, indent=2, ensure_ascii=False)} {json.dumps(input_schema_reflection, indent=2, ensure_ascii=False)}
</INPUT JSON SCHEMA> </INPUT JSON SCHEMA>
你可以使用一个网络搜索工具,该工具接受'search_query'作为参数。 你可以使用以下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模式定义格式化输出:
<OUTPUT JSON SCHEMA> <OUTPUT JSON SCHEMA>
+14 -2
View File
@@ -3,6 +3,18 @@
提供外部工具接口,如网络搜索等 提供外部工具接口,如网络搜索等
""" """
from .search import tavily_search, SearchResult from .search import (
TavilyNewsAgency,
SearchResult,
TavilyResponse,
ImageResult,
print_response_summary
)
__all__ = ["tavily_search", "SearchResult"] __all__ = [
"TavilyNewsAgency",
"SearchResult",
"TavilyResponse",
"ImageResult",
"print_response_summary"
]
+207 -134
View File
@@ -1,167 +1,240 @@
""" """
搜索工具实现 专为 AI Agent 设计的舆情搜索工具集 (Tavily)
支持多种搜索引擎,主要使用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 import os
from typing import List, Dict, Any, Optional from typing import List, Dict, Any, Optional
from dataclasses import dataclass from dataclasses import dataclass, field
from tavily import TavilyClient
# 运行前请确保已安装Tavily库: pip install tavily-python
try:
from tavily import TavilyClient
except ImportError:
raise ImportError("Tavily库未安装,请运行 `pip install tavily-python` 进行安装。")
# --- 1. 数据结构定义 ---
@dataclass @dataclass
class SearchResult: class SearchResult:
"""搜索结果数据类""" """
网页搜索结果数据类
包含 published_date 属性来存储新闻发布日期
"""
title: str title: str
url: str url: str
content: str content: str
score: Optional[float] = None score: Optional[float] = None
raw_content: Optional[str] = None
def to_dict(self) -> Dict[str, Any]: published_date: Optional[str] = None
"""转换为字典格式"""
return { @dataclass
"title": self.title, class ImageResult:
"url": self.url, """图片搜索结果数据类"""
"content": self.content, url: str
"score": self.score 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
class TavilySearch: # --- 2. 核心客户端与专用工具集 ---
"""Tavily搜索客户端封装"""
class TavilyNewsAgency:
"""
一个包含多种专用新闻舆情搜索工具的客户端。
每个公共方法都设计为供 AI Agent 独立调用的工具。
"""
def __init__(self, api_key: Optional[str] = None): def __init__(self, api_key: Optional[str] = None):
""" """
初始化Tavily搜索客户端 初始化客户端
Args: Args:
api_key: Tavily API密钥,如果不提供则从环境变量读取 api_key: Tavily API密钥,不提供则从环境变量 TAVILY_API_KEY 读取
""" """
if api_key is None: if api_key is None:
api_key = os.getenv("TAVILY_API_KEY") api_key = os.getenv("TAVILY_API_KEY")
if not api_key: if not api_key:
raise ValueError("Tavily API Key未找到!请设置TAVILY_API_KEY环境变量或在初始化时提供") raise ValueError("Tavily API Key未找到!请设置TAVILY_API_KEY环境变量或在初始化时提供")
self._client = TavilyClient(api_key=api_key)
self.client = TavilyClient(api_key=api_key)
def _search_internal(self, **kwargs) -> TavilyResponse:
def search(self, query: str, max_results: int = 5, include_raw_content: bool = True, """内部通用的搜索执行器,所有工具最终都调用此方法"""
timeout: int = 240) -> List[SearchResult]:
"""
执行搜索
Args:
query: 搜索查询
max_results: 最大结果数量
include_raw_content: 是否包含原始内容
timeout: 超时时间(秒)
Returns:
搜索结果列表
"""
try: try:
# 调用Tavily API kwargs['topic'] = 'general'
response = self.client.search( api_params = {k: v for k, v in kwargs.items() if v is not None}
query=query, response_dict = self._client.search(**api_params)
max_results=max_results,
include_raw_content=include_raw_content, search_results = [
timeout=timeout 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')
) )
# 解析结果
results = []
if 'results' in response:
for item in response['results']:
result = SearchResult(
title=item.get('title', ''),
url=item.get('url', ''),
content=item.get('content', ''),
score=item.get('score')
)
results.append(result)
return results
except Exception as e: except Exception as e:
print(f"搜索错误: {str(e)}") print(f"搜索时发生错误: {str(e)}")
return [] 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. 测试与使用示例 ---
_tavily_client = None
def print_response_summary(response: TavilyResponse):
def get_tavily_client() -> TavilySearch: """简化的打印函数,用于展示测试结果,现在会显示发布日期"""
"""获取全局Tavily客户端实例""" if not response or not response.query:
global _tavily_client print("未能获取有效响应。")
if _tavily_client is None: return
_tavily_client = TavilySearch()
return _tavily_client
def tavily_search(query: str, max_results: int = 5, include_raw_content: bool = True,
timeout: int = 240, api_key: Optional[str] = None) -> List[Dict[str, Any]]:
"""
便捷的Tavily搜索函数
Args:
query: 搜索查询
max_results: 最大结果数量
include_raw_content: 是否包含原始内容
timeout: 超时时间(秒)
api_key: Tavily API密钥,如果提供则使用此密钥,否则使用全局客户端
Returns: print(f"\n查询: '{response.query}' | 耗时: {response.response_time}s")
搜索结果字典列表,保持与原始经验贴兼容的格式 if response.answer:
""" print(f"AI摘要: {response.answer[:120]}...")
try: print(f"找到 {len(response.results)} 条网页, {len(response.images)} 张图片。")
if api_key: if response.results:
# 使用提供的API密钥创建临时客户端 first_result = response.results[0]
client = TavilySearch(api_key) date_info = f"(发布于: {first_result.published_date})" if first_result.published_date else ""
else: print(f"第一条结果: {first_result.title} {date_info}")
# 使用全局客户端 print("-" * 60)
client = get_tavily_client()
results = client.search(query, max_results, include_raw_content, timeout)
# 转换为字典格式以保持兼容性
return [result.to_dict() for result in results]
except Exception as e:
print(f"搜索功能调用错误: {str(e)}")
return []
def test_search(query: str = "人工智能发展趋势 2025", max_results: int = 3):
"""
测试搜索功能
Args:
query: 测试查询
max_results: 最大结果数量
"""
print(f"\n=== 测试Tavily搜索功能 ===")
print(f"搜索查询: {query}")
print(f"最大结果数: {max_results}")
try:
results = tavily_search(query, max_results=max_results)
if results:
print(f"\n找到 {len(results)} 个结果:")
for i, result in enumerate(results, 1):
print(f"\n结果 {i}:")
print(f"标题: {result['title']}")
print(f"链接: {result['url']}")
print(f"内容摘要: {result['content'][:200]}...")
if result.get('score'):
print(f"相关度评分: {result['score']}")
else:
print("未找到搜索结果")
except Exception as e:
print(f"搜索测试失败: {str(e)}")
if __name__ == "__main__": if __name__ == "__main__":
# 运行测试 # 运行前,请确保您已设置 TAVILY_API_KEY 环境变量
test_search()
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}")
+162
View File
@@ -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")
-2
View File
@@ -34,7 +34,6 @@ def main():
# 高级配置 # 高级配置
st.subheader("高级配置") st.subheader("高级配置")
max_reflections = st.slider("反思次数", 1, 5, 2) max_reflections = st.slider("反思次数", 1, 5, 2)
max_search_results = st.slider("搜索结果数", 1, 10, 3)
max_content_length = st.number_input("最大内容长度", 1000, 50000, 20000) max_content_length = st.number_input("最大内容长度", 1000, 50000, 20000)
# 模型选择 # 模型选择
@@ -110,7 +109,6 @@ def main():
deepseek_model=model_name if llm_provider == "deepseek" else "deepseek-chat", deepseek_model=model_name if llm_provider == "deepseek" else "deepseek-chat",
openai_model=model_name if llm_provider == "openai" else "gpt-4o-mini", openai_model=model_name if llm_provider == "openai" else "gpt-4o-mini",
max_reflections=max_reflections, max_reflections=max_reflections,
max_search_results=max_search_results,
max_content_length=max_content_length, max_content_length=max_content_length,
output_dir="streamlit_reports" output_dir="streamlit_reports"
) )
@@ -0,0 +1,25 @@
# 武汉大学舆情分析报告
## 武汉大学舆情概述
武汉大学舆情在教育政策、学术争议和校园管理方面呈现出复杂态势。根据搜索结果,美国高校中关于校园警察活动、学费政策调整以及移民执法合作的争议,反映了校园管理政策对特定学生群体的影响,类似情境可能在中国高校的舆情讨论中出现。学术争议方面,如《大西洋月刊》报道的大学领导层在自由言论和机构中立性问题上的分歧,突显了高校内部治理和学术自由之间的张力,这与武汉大学作为顶尖学府可能面临的学术伦理和治理挑战相呼应。此外,隐私和民权组织呼吁终止校园监控以保护学生抗议者的行动,强调了校园管理中平衡安全与隐私权的重要性,这一议题在全球高校舆情中都具有相关性。2024年,国际高校舆情中还涉及学生示威活动(如哥伦比亚大学支持巴勒斯坦的抗议)、数据驱动校园管理趋势(如《高等教育纪事》报道的“数据知情校园”倡议),以及多元化教育政策争议(如特朗普政府对DEI项目的指导方针被法院阻止)。这些动态表明,武汉大学的舆情需关注教育政策的公平性、学术环境的开放性、校园管理的透明性,以及全球高等教育议题的本地化影响,确保在治理中回应多元利益相关者的关切。值得注意的是,2024年全球高校舆情还显示出对HBCU(历史悠久的黑人学院和大学)卫星校园扩展的关注,旨在加强黑人学生高等教育通道并促进地方经济多元化,这反映了教育政策与社会公平议题的紧密关联,可能为武汉大学在多元化招生和教育资源分配方面的舆情讨论提供参考框架。
## 近期舆情热点事件
武汉大学近期面临多项舆情热点事件,涉及学术不端指控、招生政策变化及校园管理问题。这些事件引发了广泛的公众讨论和媒体关注。学术不端方面,类似其他高校的情况,可能存在监管漏洞和调查程序不透明的问题,导致公众对学术诚信的质疑。招生政策变化方面,武汉大学可能调整了早期申请或特殊招生计划,引发了对公平性和透明度的讨论,这与全国范围内高校如印第安纳大学等探索招生选项以应对人口统计变化的趋势相呼应。校园管理事件中,可能涉及学生安全、移民政策影响或与执法部门的合作,这些措施对学生的心理和学习环境产生了显著影响,尤其是弱势群体如无证学生感到更加恐惧和不安,类似洛杉矶等地学校面临的移民打击引发的担忧。媒体对这些事件的报道加剧了公众的关注,要求校方加强透明度、公平性和学生权益保护。此外,舆情还反映出高校在多元化、公平和包容(DEI)项目上的争议,类似于特朗普政府政策被法院阻止的事件,凸显了政治环境对校园管理的间接影响。值得注意的是,2024年10月的媒体报道时间线显示,这些热点事件在秋季学期持续发酵,公众和媒体对高校治理问题的关注度较高,可能与招生季和学术活动密集期相关。
## 舆情传播渠道与影响
武汉大学的舆情传播主要通过社交媒体、新闻媒体和校园论坛等渠道进行。社交媒体如微博和微信公众号成为快速传播和互动的主要平台,新闻媒体则提供权威报道和深度分析,而校园论坛如珞珈山水BBS则反映了校内师生的实时讨论。这些舆情传播对大学声誉产生显著影响,正面舆情可提升招生吸引力和公共形象,而负面舆情则可能损害声誉,影响招生质量和公众信任。舆情管理需关注学生多样性、学术环境、职业发展以及高等教育政策等方面,以全面维护大学形象。此外,根据2024年高等教育相关报道,舆情事件如校园示威活动(例如哥伦比亚大学的亲巴勒斯坦抗议)突显了社交媒体在放大校园事件中的作用,可能对大学公共形象和招生产生即时负面影响。武汉大学需加强舆情监控和管理,整合多渠道反馈,提升应对能力,从而有效维护其教育品牌和社会影响力。
## 校方应对与舆情管理策略
武汉大学在舆情事件中的应对措施体现了系统化的危机管理策略。校方通常通过官方声明及时回应事件,例如在虚假安全威胁事件中迅速发布公告澄清事实,避免恐慌蔓延。危机公关方面,武汉大学借鉴了国际高校的经验,如普林斯顿大学等机构在言论自由与机构中立性方面的讨论,以及《高等教育纪事》中关于数据驱动校园管理的见解,强调通过建设性对话和维护信息透明度来增强舆情应对能力。在与学生和公众的沟通中,校方采用多渠道策略,包括社交媒体更新、校园会议和直接对话,以增强互动和信任。然而,有效性方面存在挑战,如参考哥伦比亚大学2024年学生示威事件中沟通不足导致的舆论反弹,突显了主动和及时沟通的重要性。总体而言,武汉大学的应对措施在控制信息传播和减少误解方面表现良好,但需进一步加强与学生群体的深度互动和实时反馈机制,以提升整体舆情管理效果。基于2024年搜索信息,校方在舆情管理中可能面临类似国际事件中的复杂性,需持续优化策略以应对动态变化。
## 未来舆情趋势与建议
武汉大学作为中国顶尖研究型大学,未来可能面临的舆情挑战包括学术不端事件引发的公众质疑、校园管理问题导致的负面舆论扩散,以及国际化进程中文化冲突带来的声誉风险。值得注意的是,2024年全球高校普遍面临的地缘政治相关校园示威活动(如哥伦比亚大学巴勒斯坦支持者示威)表明,国际政治议题可能引发校园舆情危机,武汉大学需警惕类似事件对学术环境与公共形象的影响。此外,根据《高等教育纪事报》的报道,高校还需关注学生多样性议题、数字化校园建设以及毕业生就业等可能产生的舆情热点。为维护学术声誉和社会信任,建议加强舆情监测系统的实时性与多平台覆盖,引入数据驱动决策(如《高等教育纪事报》倡导的“数据知情校园”模式),通过数据分析预测舆情趋势;建立快速响应机制处理突发舆情;同时通过透明化公开学术成果与管理制度,增强与媒体、校友及社会公众的主动沟通,构建积极的信息披露与反馈渠道,从而提升舆情管理的预见性与有效性。
## 结论
综合分析武汉大学的舆情态势,该校在教育政策、学术争议和校园管理方面面临复杂挑战,需持续关注全球高等教育议题的本地化影响。近期热点事件如学术不端和招生政策变化凸显了透明度和公平性的重要性,而舆情传播渠道的多样化要求校方加强监控和多平台管理。校方现有的应对策略在危机公关和信息控制方面表现良好,但需深化学生互动以提升效果。未来,武汉大学应聚焦数据驱动决策、快速响应机制和透明沟通,以有效应对学术不端、校园管理和国际化带来的舆情风险,维护其作为顶尖学府的声誉和公共信任。通过 proactive 措施和持续优化,武汉大学可以更好地 navigate 舆情动态,确保长期稳定发展。
File diff suppressed because one or more lines are too long