Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions astrbot/core/astr_main_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
BraveWebSearchTool,
FirecrawlExtractWebPageTool,
FirecrawlWebSearchTool,
MetasoWebSearchTool,
TavilyExtractWebPageTool,
TavilyWebSearchTool,
normalize_legacy_web_search_config,
Expand Down Expand Up @@ -1116,6 +1117,8 @@ async def _apply_web_search_tools(
req.func_tool.add_tool(tool_mgr.get_builtin_tool(FirecrawlExtractWebPageTool))
elif provider == "baidu_ai_search":
req.func_tool.add_tool(tool_mgr.get_builtin_tool(BaiduWebSearchTool))
elif provider == "metaso":
req.func_tool.add_tool(tool_mgr.get_builtin_tool(MetasoWebSearchTool))


def _get_compress_provider(
Expand Down
2 changes: 1 addition & 1 deletion astrbot/core/computer/booters/shipyard_search_file_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def _build_grep_command(


def _quote_command(command: list[str]) -> str:
return " ".join(shlex.quote(part) for part in command)
return shlex.join(command)


def build_search_command(
Expand Down
12 changes: 12 additions & 0 deletions astrbot/core/config/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"websearch_brave_key": [],
"websearch_baidu_app_builder_key": "",
"websearch_firecrawl_key": [],
"websearch_metaso_key": [],
"web_search_link": False,
"display_reasoning_text": False,
"identifier": False,
Expand Down Expand Up @@ -3223,6 +3224,7 @@
"bocha",
"brave",
"firecrawl",
"metaso",
],
"condition": {
"provider_settings.web_search": True,
Expand Down Expand Up @@ -3268,6 +3270,16 @@
"provider_settings.web_search": True,
},
},
"provider_settings.websearch_metaso_key": {
"description": "Metaso API Key",
"type": "list",
"items": {"type": "string"},
"hint": "可添加多个 Key 进行轮询。内置 Key 每天有 100 次免费查询额度,配置自己的 Key 可获得更高配额。",
"condition": {
"provider_settings.websearch_provider": "metaso",
"provider_settings.web_search": True,
},
},
"provider_settings.websearch_baidu_app_builder_key": {
"description": "百度千帆智能云 APP Builder API Key",
"type": "string",
Expand Down
121 changes: 121 additions & 0 deletions astrbot/core/tools/web_search_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
from astrbot.core.astr_agent_context import AstrAgentContext
from astrbot.core.tools.registry import builtin_tool


class WebSearchError(RuntimeError):
"""Raised when a web search provider request fails."""


WEB_SEARCH_TOOL_NAMES = [
"web_search_baidu",
"web_search_tavily",
Expand All @@ -21,6 +26,7 @@
"web_search_brave",
"web_search_firecrawl",
"firecrawl_extract_web_page",
"web_search_metaso",
]
_TAVILY_WEB_SEARCH_TOOL_CONFIG = {
"provider_settings.web_search": True,
Expand All @@ -42,6 +48,10 @@
"provider_settings.web_search": True,
"provider_settings.websearch_provider": "baidu_ai_search",
}
_METASO_WEB_SEARCH_TOOL_CONFIG = {
"provider_settings.web_search": True,
"provider_settings.websearch_provider": "metaso",
}


@std_dataclass
Expand Down Expand Up @@ -76,6 +86,11 @@ async def get(self, provider_settings: dict) -> str:
_BOCHA_KEY_ROTATOR = _KeyRotator("websearch_bocha_key", "BoCha")
_BRAVE_KEY_ROTATOR = _KeyRotator("websearch_brave_key", "Brave")
_FIRECRAWL_KEY_ROTATOR = _KeyRotator("websearch_firecrawl_key", "Firecrawl")
_METASO_KEY_ROTATOR = _KeyRotator("websearch_metaso_key", "Metaso")
_METASO_DEFAULT_API_KEY = "mk-E384C1DD5E8501BB7EFE27C949AFDE5B"
# The above default API key is intentionally public. It is the official Metaso
# free-tier key provided by Metaso for evaluation and low-volume use (100 queries/day).
# Configure your own key via websearch_metaso_key for higher quotas.


def normalize_legacy_web_search_config(cfg) -> None:
Expand All @@ -99,6 +114,7 @@ def normalize_legacy_web_search_config(cfg) -> None:
"websearch_bocha_key",
"websearch_brave_key",
"websearch_firecrawl_key",
"websearch_metaso_key",
):
value = provider_settings.get(setting_name)
if isinstance(value, str):
Expand Down Expand Up @@ -370,6 +386,65 @@ async def _baidu_search(
]


async def _metaso_search(
provider_settings: dict,
payload: dict,
) -> list[SearchResult]:
keys = provider_settings.get("websearch_metaso_key", [])
metaso_key = (
await _METASO_KEY_ROTATOR.get(provider_settings)
if keys
else _METASO_DEFAULT_API_KEY
)
headers = {
"Authorization": f"Bearer {metaso_key}",
"Content-Type": "application/json",
}
async with aiohttp.ClientSession(trust_env=True) as session:
async with session.post(
"https://metaso.cn/api/v1/search",
json=payload,
headers=headers,
) as response:
if response.status in (401, 403):
raise WebSearchError(
"Metaso search failed: unauthorized. Check your Metaso API key."
)
if response.status == 429:
raise WebSearchError(
"Metaso search failed: rate-limited. Try again later."
)
if response.status != 200:
reason = await response.text()
raise WebSearchError(
f"Metaso search failed: {reason}, status: {response.status}",
)
data = await response.json()
code = data.get("code", 0)
if code == 3003:
raise WebSearchError(
"Metaso search failed: daily search limit reached. "
"See: https://metaso.cn/search-api/playground"
)
if code == 2005:
raise WebSearchError(
"Metaso search failed: API key rejected. Check your Metaso API key."
)
if code != 0:
raise WebSearchError(
f"Metaso search failed: code={code}, message={data.get('message', '')}",
)
webpages = data.get("webpages", [])
return [
SearchResult(
title=item.get("title", ""),
url=item.get("link", ""),
snippet=item.get("snippet") or item.get("summary") or "",
)
for item in webpages
]


@builtin_tool(config=_TAVILY_WEB_SEARCH_TOOL_CONFIG)
@pydantic_dataclass
class TavilyWebSearchTool(FunctionTool[AstrAgentContext]):
Expand Down Expand Up @@ -803,10 +878,56 @@ async def call(self, context, **kwargs) -> ToolExecResult:
return _search_result_payload(results)


@builtin_tool(config=_METASO_WEB_SEARCH_TOOL_CONFIG)
@pydantic_dataclass
class MetasoWebSearchTool(FunctionTool[AstrAgentContext]):
name: str = "web_search_metaso"
description: str = (
"A web search tool based on Metaso Search API, used to retrieve web pages "
"related to the user's query. Metaso provides 100 free queries per day by "
"default. Configure your own API key (websearch_metaso_key) for higher quotas."
)
parameters: dict = Field(
default_factory=lambda: {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Required. Search query."},
"size": {
"type": "integer",
"description": "Optional. Number of search results to return. Range: 1-100. Default is 10.",
},
},
"required": ["query"],
}
)

async def call(self, context, **kwargs) -> ToolExecResult:
_, provider_settings, _ = _get_runtime(context)
size = int(kwargs.get("size", 10))
if size < 1:
size = 1
if size > 100:
size = 100

payload = {
"q": kwargs["query"],
"scope": "webpage",
"size": size,
}

results = await _metaso_search(provider_settings, payload)
if not results:
return "Error: Metaso searcher did not return any results."
return _search_result_payload(results)


__all__ = [
"BaiduWebSearchTool",
"BochaWebSearchTool",
"BraveWebSearchTool",
"FirecrawlExtractWebPageTool",
"FirecrawlWebSearchTool",
"MetasoWebSearchTool",
"TavilyExtractWebPageTool",
"TavilyWebSearchTool",
"WEB_SEARCH_TOOL_NAMES",
Expand Down
1 change: 0 additions & 1 deletion astrbot/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@

Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@
"description": "Firecrawl API Key",
"hint": "Multiple keys can be added for rotation."
},
"websearch_metaso_key": {
"description": "Metaso API Key",
"hint": "Multiple keys can be added for rotation. Built-in key has 100 free queries/day; configure your own for higher quotas."
},
"websearch_baidu_app_builder_key": {
"description": "Baidu Qianfan Smart Cloud APP Builder API Key",
"hint": "Reference: [https://console.bce.baidu.com/iam/#/iam/apikey/list](https://console.bce.baidu.com/iam/#/iam/apikey/list)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@
"description": "API-ключ Firecrawl",
"hint": "Можно добавить несколько ключей для ротации."
},
"websearch_metaso_key": {
"description": "API-ключ Metaso",
"hint": "Можно добавить несколько ключей для ротации. Встроенный ключ даёт 100 бесплатных запросов/день; укажите свой для более высоких квот."
},
"websearch_baidu_app_builder_key": {
"description": "API-ключ Baidu Qianfan APP Builder",
"hint": "Ссылка: [https://console.bce.baidu.com/iam/#/iam/apikey/list](https://console.bce.baidu.com/iam/#/iam/apikey/list)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@
"description": "Firecrawl API Key",
"hint": "可添加多个 Key 进行轮询。"
},
"websearch_metaso_key": {
"description": "Metaso API Key",
"hint": "可添加多个 Key 进行轮询。内置 Key 每天 100 次免费查询,配置自己的 Key 可提升配额。"
},
"websearch_baidu_app_builder_key": {
"description": "百度千帆智能云 APP Builder API Key",
"hint": "参考:[https://console.bce.baidu.com/iam/#/iam/apikey/list](https://console.bce.baidu.com/iam/#/iam/apikey/list)"
Expand Down
Loading