1、新增联网搜索工具类(web_search)
2、修复新闻描述配置获取源问题,改为从连接配置获取
3、新增web_search所需配置项数据集
This commit is contained in:
DaGou12138
2026-05-12 15:16:08 +08:00
parent 1b5c00231a
commit 46e7f8508d
6 changed files with 203 additions and 34 deletions
@@ -55,15 +55,7 @@ CHANNEL_MAP = {
DEFAULT_NEWS_SOURCES = "澎湃新闻;百度热搜;财联社"
def _get_newsnow_config(conn):
"""从连接配置获取newsnow插件配置,优先用conn.common_config,兜底用conn.config"""
# 优先从公共配置获取(保留本地config.yaml的配置)
common_plugins = getattr(conn, "common_config", {}).get("plugins", {})
common_newsnow = common_plugins.get("get_news_from_newsnow", {})
common_sources = common_newsnow.get("news_sources", "")
if isinstance(common_sources, str) and common_sources.strip():
return common_sources
# 兜底从连接配置获取
# 从连接配置获取
plugins = conn.config.get("plugins", {})
newsnow = plugins.get("get_news_from_newsnow", {})
sources = newsnow.get("news_sources", "")
@@ -91,24 +83,6 @@ def get_news_sources_from_config(conn):
# 从默认配置获取可用的新闻源名称(运行时由get_news_sources_from_config动态获取)
example_sources_str = DEFAULT_NEWS_SOURCES.replace(";","")
def init_news_description(config: dict):
"""项目启动时调用一次,根据配置更新工具描述中的新闻源示例"""
from types import SimpleNamespace
from plugins_func.register import all_function_registry
# 复用get_news_sources_from_config,用SimpleNamespace模拟conn
conn_wrapper = SimpleNamespace(config=config)
news_sources = get_news_sources_from_config(conn_wrapper)
sources_str = news_sources.replace(";","")
func_item = all_function_registry.get("get_news_from_newsnow")
if func_item:
func_item.description["function"]["parameters"]["properties"]["source"][
"description"
] = f"新闻源的标准中文名称,例如{sources_str}等。可选参数,如果不提供则使用默认新闻源"
logger.bind(tag=TAG).info(f"新闻工具描述已初始化,可用新闻源: {sources_str}")
GET_NEWS_FROM_NEWSNOW_FUNCTION_DESC = {
"type": "function",
"function": {
@@ -0,0 +1,158 @@
import requests
from config.logger import setup_logging
from plugins_func.register import (
register_function,
ToolType,
ActionResponse,
Action,
)
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from core.connection import ConnectionHandler
TAG = __name__
logger = setup_logging()
_DEFAULT_DESCRIPTION = (
"联网搜索工具。当用户明确需要联网搜索问题时使用此工具。"
)
WEB_SEARCH_FUNCTION_DESC = {
"type": "function",
"function": {
"name": "web_search",
"description": _DEFAULT_DESCRIPTION,
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索关键词或问题",
}
},
"required": ["query"],
},
},
}
def _search_metaso(api_key: str, query: str, max_results: int) -> str:
"""调用秘塔搜索API"""
url = "https://metaso.cn/api/v1/search"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
payload = {
"q": query,
"size": max_results,
"stream": False,
"scope": "webpage",
"includeSummary": True,
"includeRawContent": False,
"conciseSnippet": False,
}
logger.bind(tag=TAG).debug(f"秘塔搜索请求 | URL: {url} | payload: {payload}")
response = requests.post(url, json=payload, headers=headers, timeout=15)
response.raise_for_status()
data = response.json()
logger.bind(tag=TAG).debug(f"秘塔搜索响应 | status: {response.status_code}")
webpages = data.get("webpages", [])
if not webpages:
return "未找到相关搜索结果。"
lines = ["【联网搜索结果】"]
for i, item in enumerate(webpages, 1):
title = item.get("title", "无标题")
snippet = item.get("summary", "")
date = item.get("date", "")
lines.append(f"{i}. 标题:{title}")
if date:
lines.append(f" 日期:{date}")
if snippet:
lines.append(f" 摘要:{snippet}")
return "\n".join(lines)
def _search_tavily(api_key: str, query: str, max_results: int) -> str:
"""调用Tavily搜索API"""
url = "https://api.tavily.com/search"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
payload = {
"query": query,
"max_results": max_results,
"search_depth": "advanced",
"include_answer": "advanced",
}
logger.bind(tag=TAG).debug(f"Tavily搜索请求 | URL: {url} | payload: {payload}")
response = requests.post(url, json=payload, headers=headers, timeout=15)
response.raise_for_status()
data = response.json()
logger.bind(tag=TAG).debug(f"Tavily搜索响应 | status: {response.status_code} | data: {data}")
results = data.get("results", [])
if not results:
return "未找到相关搜索结果。"
answer = data.get("answer", "")
lines = [f"【联网搜索结果】\n总结:{answer}"]
# for i, item in enumerate(results, 1):
# title = item.get("title", "无标题")
# summary = item.get("content", "")
# lines.append(f"{i}. 标题:{title}")
# if summary:
# lines.append(f" 摘要:{summary}")
return "\n".join(lines)
@register_function("web_search", WEB_SEARCH_FUNCTION_DESC, ToolType.SYSTEM_CTL)
def web_search(conn: "ConnectionHandler", query: str = None):
logger.bind(tag=TAG).info(f"web_search 被调用 | query={query}")
if not query:
return ActionResponse(Action.REQLLM, "请提供搜索关键词。", None)
web_search_config = conn.config.get("plugins", {}).get("web_search", {})
provider = web_search_config.get("provider", "").lower()
max_results = int(web_search_config.get("max_results", 3))
logger.bind(tag=TAG).info(f"web_search 配置 | provider={provider} | max_results={max_results} | config_keys={list(web_search_config.keys())}")
api_key = web_search_config.get("api_key", "")
if not api_key:
return ActionResponse(
Action.REQLLM,
"联网搜索功能未配置API Key,请在配置文件中填写。",
None,
)
if provider == "metaso":
search_fn = lambda: _search_metaso(api_key, query, max_results)
elif provider == "tavily":
search_fn = lambda: _search_tavily(api_key, query, max_results)
else:
return ActionResponse(
Action.REQLLM,
f"联网搜索功能未配置或配置的搜索源无效(当前:{provider}),请检查配置。",
None,
)
try:
result_text = search_fn()
logger.bind(tag=TAG).info(f"搜索结果组装完成:\n{result_text}")
except requests.exceptions.Timeout:
logger.bind(tag=TAG).error("联网搜索请求超时")
result_text = "联网搜索请求超时,请稍后重试。"
except requests.exceptions.RequestException as e:
logger.bind(tag=TAG).error(f"联网搜索请求失败: {e}")
result_text = "联网搜索请求失败,请稍后重试。"
except Exception as e:
logger.bind(tag=TAG).error(f"联网搜索异常: {e}")
result_text = "联网搜索出现异常,请稍后重试。"
return ActionResponse(Action.REQLLM, result_text, None)