feat(web_search): 新增 Metaso 搜索引擎支持
为联网搜索工具集成了新的搜索引擎 Metaso,为用户提供更多搜索选择。 - 在搜索引擎基类 `BaseSearchEngine` 中添加了可选的 `read_url` 方法,为未来支持直接读取网页内容奠定基础。 - 更新了插件加载、工具执行逻辑和配置文件模板,以完整支持 Metaso 引擎。 - 将默认回复生成器的任务超时时间从 15 秒延长至 45 秒,以适应联网搜索等耗时较长的操作,提高稳定性。
This commit is contained in:
@@ -1303,7 +1303,7 @@ class DefaultReplyer:
|
||||
}
|
||||
|
||||
# 设置超时
|
||||
timeout = 15.0 # 秒
|
||||
timeout = 45.0 # 秒
|
||||
|
||||
async def get_task_result(task_name, task):
|
||||
try:
|
||||
|
||||
@@ -3,7 +3,7 @@ Base search engine interface
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
class BaseSearchEngine(ABC):
|
||||
@@ -24,6 +24,12 @@ class BaseSearchEngine(ABC):
|
||||
"""
|
||||
pass
|
||||
|
||||
async def read_url(self, url: str) -> Optional[str]:
|
||||
"""
|
||||
读取URL内容,如果引擎不支持则返回None
|
||||
"""
|
||||
return None
|
||||
|
||||
@abstractmethod
|
||||
def is_available(self) -> bool:
|
||||
"""
|
||||
|
||||
107
src/plugins/built_in/web_search_tool/engines/metaso_engine.py
Normal file
107
src/plugins/built_in/web_search_tool/engines/metaso_engine.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""
|
||||
Metaso Search Engine (Chat Completions Mode)
|
||||
"""
|
||||
import json
|
||||
from typing import Any, List
|
||||
|
||||
import httpx
|
||||
|
||||
from src.common.logger import get_logger
|
||||
from src.plugin_system.apis import config_api
|
||||
|
||||
from ..utils.api_key_manager import create_api_key_manager_from_config
|
||||
from .base import BaseSearchEngine
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class MetasoClient:
|
||||
"""A client to interact with the Metaso API."""
|
||||
|
||||
def __init__(self, api_key: str):
|
||||
self.api_key = api_key
|
||||
self.base_url = "https://metaso.cn/api/v1"
|
||||
self.headers = {
|
||||
"Authorization": f"Bearer {self.api_key}",
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
async def search(self, query: str, **kwargs) -> List[dict[str, Any]]:
|
||||
"""Perform a search using the Metaso Chat Completions API."""
|
||||
payload = {"model": "fast", "stream": True, "messages": [{"role": "user", "content": query}]}
|
||||
search_url = f"{self.base_url}/chat/completions"
|
||||
full_response_content = ""
|
||||
|
||||
async with httpx.AsyncClient(timeout=90.0) as client:
|
||||
try:
|
||||
async with client.stream("POST", search_url, headers=self.headers, json=payload) as response:
|
||||
response.raise_for_status()
|
||||
async for line in response.aiter_lines():
|
||||
if line.startswith("data:"):
|
||||
data_str = line[len("data:") :].strip()
|
||||
if data_str == "[DONE]":
|
||||
break
|
||||
try:
|
||||
data = json.loads(data_str)
|
||||
delta = data.get("choices", [{}])[0].get("delta", {})
|
||||
content_chunk = delta.get("content")
|
||||
if content_chunk:
|
||||
full_response_content += content_chunk
|
||||
except json.JSONDecodeError:
|
||||
logger.warning(f"Metaso stream: could not decode JSON line: {data_str}")
|
||||
continue
|
||||
|
||||
if not full_response_content:
|
||||
logger.warning("Metaso search returned an empty stream.")
|
||||
return []
|
||||
|
||||
return [
|
||||
{
|
||||
"title": query,
|
||||
"url": "https://metaso.cn/",
|
||||
"snippet": full_response_content,
|
||||
"provider": "Metaso (Chat)",
|
||||
}
|
||||
]
|
||||
except httpx.HTTPStatusError as e:
|
||||
logger.error(f"HTTP error occurred while searching with Metaso Chat: {e.response.text}")
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred while searching with Metaso Chat: {e}", exc_info=True)
|
||||
return []
|
||||
|
||||
|
||||
class MetasoSearchEngine(BaseSearchEngine):
|
||||
"""Metaso Search Engine implementation."""
|
||||
|
||||
def __init__(self):
|
||||
self._initialize_clients()
|
||||
|
||||
def _initialize_clients(self):
|
||||
"""Initialize Metaso clients."""
|
||||
metaso_api_keys = config_api.get_global_config("web_search.metaso_api_keys", None)
|
||||
self.api_manager = create_api_key_manager_from_config(
|
||||
metaso_api_keys, lambda key: MetasoClient(api_key=key), "Metaso"
|
||||
)
|
||||
|
||||
def is_available(self) -> bool:
|
||||
"""Check if the Metaso search engine is available."""
|
||||
return self.api_manager.is_available()
|
||||
|
||||
async def search(self, args: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
"""Execute a Metaso search."""
|
||||
if not self.is_available():
|
||||
return []
|
||||
|
||||
query = args["query"]
|
||||
try:
|
||||
metaso_client = self.api_manager.get_next_client()
|
||||
if not metaso_client:
|
||||
logger.error("Could not get Metaso client.")
|
||||
return []
|
||||
|
||||
return await metaso_client.search(query)
|
||||
except Exception as e:
|
||||
logger.error(f"Metaso search failed: {e}", exc_info=True)
|
||||
return []
|
||||
@@ -22,6 +22,7 @@ class WEBSEARCHPLUGIN(BasePlugin):
|
||||
提供网络搜索和URL解析功能,支持多种搜索引擎:
|
||||
- Exa (需要API密钥)
|
||||
- Tavily (需要API密钥)
|
||||
- Metaso (需要API密钥)
|
||||
- DuckDuckGo (免费)
|
||||
- Bing (免费)
|
||||
"""
|
||||
@@ -43,6 +44,7 @@ class WEBSEARCHPLUGIN(BasePlugin):
|
||||
from .engines.exa_engine import ExaSearchEngine
|
||||
from .engines.searxng_engine import SearXNGSearchEngine
|
||||
from .engines.tavily_engine import TavilySearchEngine
|
||||
from .engines.metaso_engine import MetasoSearchEngine
|
||||
|
||||
# 实例化所有搜索引擎,这会触发API密钥管理器的初始化
|
||||
exa_engine = ExaSearchEngine()
|
||||
@@ -50,6 +52,7 @@ class WEBSEARCHPLUGIN(BasePlugin):
|
||||
ddg_engine = DDGSearchEngine()
|
||||
bing_engine = BingSearchEngine()
|
||||
searxng_engine = SearXNGSearchEngine()
|
||||
metaso_engine = MetasoSearchEngine()
|
||||
|
||||
# 报告每个引擎的状态
|
||||
engines_status = {
|
||||
@@ -58,6 +61,7 @@ class WEBSEARCHPLUGIN(BasePlugin):
|
||||
"DuckDuckGo": ddg_engine.is_available(),
|
||||
"Bing": bing_engine.is_available(),
|
||||
"SearXNG": searxng_engine.is_available(),
|
||||
"Metaso": metaso_engine.is_available(),
|
||||
}
|
||||
|
||||
available_engines = [name for name, available in engines_status.items() if available]
|
||||
|
||||
@@ -15,6 +15,7 @@ from ..engines.ddg_engine import DDGSearchEngine
|
||||
from ..engines.exa_engine import ExaSearchEngine
|
||||
from ..engines.searxng_engine import SearXNGSearchEngine
|
||||
from ..engines.tavily_engine import TavilySearchEngine
|
||||
from ..engines.metaso_engine import MetasoSearchEngine
|
||||
from ..utils.formatters import deduplicate_results, format_search_results
|
||||
|
||||
logger = get_logger("web_search_tool")
|
||||
@@ -51,6 +52,7 @@ class WebSurfingTool(BaseTool):
|
||||
"ddg": DDGSearchEngine(),
|
||||
"bing": BingSearchEngine(),
|
||||
"searxng": SearXNGSearchEngine(),
|
||||
"metaso": MetasoSearchEngine(),
|
||||
}
|
||||
|
||||
async def execute(self, function_args: dict[str, Any]) -> dict[str, Any]:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[inner]
|
||||
version = "7.3.1"
|
||||
version = "7.3.2"
|
||||
|
||||
#----以下是给开发人员阅读的,如果你只是部署了MoFox-Bot,不需要阅读----
|
||||
#如果你想要修改配置文件,请递增version的值
|
||||
@@ -489,11 +489,12 @@ enable_web_search_tool = true # 是否启用联网搜索tool
|
||||
enable_url_tool = true # 是否启用URL解析tool
|
||||
tavily_api_keys = ["None"]# Tavily API密钥列表,支持轮询机制
|
||||
exa_api_keys = ["None"]# EXA API密钥列表,支持轮询机制
|
||||
metaso_api_keys = ["None"]# Metaso API密钥列表,支持轮询机制
|
||||
searxng_instances = [] # SearXNG 实例 URL 列表
|
||||
searxng_api_keys = []# SearXNG 实例 API 密钥列表
|
||||
|
||||
# 搜索引擎配置
|
||||
enabled_engines = ["ddg"] # 启用的搜索引擎列表,可选: "exa", "tavily", "ddg","bing"
|
||||
enabled_engines = ["ddg"] # 启用的搜索引擎列表,可选: "exa", "tavily", "ddg","bing", "metaso"
|
||||
search_strategy = "single" # 搜索策略: "single"(使用第一个可用引擎), "parallel"(并行使用所有启用的引擎), "fallback"(按顺序尝试,失败则尝试下一个)
|
||||
|
||||
[sleep_system]
|
||||
|
||||
Reference in New Issue
Block a user