Merge pull request #3376 from googio/feat/serply-search
feat(web_search): 新增Serply搜索源
This commit is contained in:
5 files changed
+71
-7
No files matched your search
@@ -2,13 +2,14 @@
|
||||
|
||||
## 功能简介
|
||||
|
||||
联网搜索插件 `web_search` 支持在对话过程中实时联网搜索信息并返回结果。插件支持两个搜索源:秘塔(Metaso)和Tavily,用户可根据需要选择其中一个。
|
||||
联网搜索插件 `web_search` 支持在对话过程中实时联网搜索信息并返回结果。插件支持三个搜索源:秘塔(Metaso)、Tavily和Serply,用户可根据需要选择其中一个。
|
||||
|
||||
## API Key申请指南
|
||||
|
||||
目前我们适配了`秘塔搜索`和`Tavily搜索`。
|
||||
目前我们适配了`秘塔搜索`、`Tavily搜索`和`Serply搜索`。
|
||||
- Tavily搜索:每个月1000次免费额度。
|
||||
- 秘塔搜索:拥有较为优质的国内数据源。
|
||||
- Serply搜索:返回谷歌搜索结果,注册赠送2500次免费额度,额度用完后需要付费。
|
||||
|
||||
## API Key申请指南
|
||||
|
||||
@@ -24,6 +25,13 @@
|
||||
- 在控制台中创建API Key
|
||||
- 复制生成的API Key(以 `tvly-` 为前缀),这是配置所需的关键信息
|
||||
|
||||
### 方式三:使用Serply搜索
|
||||
|
||||
- 访问 [Serply](https://serply.io),注册并登录账号
|
||||
- 在控制台中创建API Key
|
||||
- 复制生成的API Key,这是配置所需的关键信息
|
||||
- 接口说明见 [Serply文档](https://serply.io/docs)
|
||||
|
||||
## 配置方式
|
||||
|
||||
### 方式1. 使用智控台部署(推荐)
|
||||
@@ -32,14 +40,14 @@
|
||||
- 进入"配置角色"页面,选择要配置的智能体
|
||||
- 点击"编辑功能"按钮,在右侧参数配置区域找到"联网搜索"插件
|
||||
- 勾选"联网搜索"
|
||||
- 填入搜索源(`metaso`或`tavily`),并将对应的`API Key`填入配置项
|
||||
- 填入搜索源(`metaso`、`tavily`或`serply`),并将对应的`API Key`填入配置项
|
||||
- 保存配置,再保存智能体配置
|
||||
|
||||
### 方式2. 单模块xiaozhi-server部署
|
||||
|
||||
在 `data/.config.yaml` 中配置:
|
||||
|
||||
- 将搜索源填入 `provider`,可选值为 `metaso` 或 `tavily`
|
||||
- 将搜索源填入 `provider`,可选值为 `metaso`、`tavily` 或 `serply`
|
||||
- 将申请到的API Key填入 `api_key`
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
-- 联网搜索插件新增Serply搜索源
|
||||
|
||||
UPDATE `ai_model_provider`
|
||||
SET `fields` = JSON_SET(
|
||||
`fields`,
|
||||
'$[0].label',
|
||||
'搜索源:metaso / tavily / serply'
|
||||
)
|
||||
WHERE `id` = 'SYSTEM_PLUGIN_WEB_SEARCH';
|
||||
@@ -746,3 +746,10 @@ databaseChangeLog:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202609021000.sql
|
||||
- changeSet:
|
||||
id: 202609172100
|
||||
author: googio
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202609172100.sql
|
||||
@@ -172,17 +172,18 @@ plugins:
|
||||
# ragflow知识库id
|
||||
dataset_ids: ["123456789"]
|
||||
# 联网搜索插件配置
|
||||
# 支持秘塔(metaso)和Tavily两个搜索源
|
||||
# 支持秘塔(metaso)、Tavily和Serply三个搜索源
|
||||
# 秘塔API Key申请:https://metaso.cn/search-api/api-keys
|
||||
# Tavily API Key申请:https://app.tavily.com/home
|
||||
# Serply API Key申请:https://serply.io
|
||||
web_search:
|
||||
# 搜索源:metaso / tavily
|
||||
# 搜索源:metaso / tavily / serply
|
||||
provider: "metaso"
|
||||
# 工具描述,用于告知LLM何时调用此工具,可根据需要自定义
|
||||
description: "联网搜索工具。当用户明确需要联网搜索问题时使用此工具。"
|
||||
# 返回结果数量(默认5)
|
||||
max_results: 5
|
||||
# 秘塔API Key(mk-前缀) / Tavily API Key(tvly-前缀)
|
||||
# 秘塔API Key(mk-前缀) / Tavily API Key(tvly-前缀) / Serply API Key
|
||||
api_key: "mk-xxx"
|
||||
# 声纹识别配置
|
||||
voiceprint:
|
||||
|
||||
@@ -112,6 +112,43 @@ async def _search_tavily(api_key: str, query: str, max_results: int) -> str:
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
async def _search_serply(api_key: str, query: str, max_results: int) -> str:
|
||||
"""调用Serply搜索API"""
|
||||
url = "https://api.serply.io/v1/search"
|
||||
headers = {
|
||||
"X-Api-Key": api_key,
|
||||
"User-Agent": "xiaozhi-esp32-server",
|
||||
}
|
||||
# Serply单页最多返回10条,且返回条数可能略多于num,需要本地再截断一次
|
||||
params = {"q": query, "num": min(max_results, 10)}
|
||||
logger.bind(tag=TAG).debug(f"Serply搜索请求 | URL: {url} | params: {params}")
|
||||
async with httpx.AsyncClient(timeout=httpx.Timeout(15.0, connect=3.0)) as client:
|
||||
response = await client.get(url, params=params, headers=headers)
|
||||
data = response.json()
|
||||
logger.bind(tag=TAG).debug(f"Serply搜索响应 | status: {response.status_code}")
|
||||
|
||||
if response.status_code != 200:
|
||||
detail = data.get("detail", "") if isinstance(data, dict) else ""
|
||||
logger.bind(tag=TAG).error(
|
||||
f"Serply搜索失败 | status: {response.status_code} | detail: {detail}"
|
||||
)
|
||||
return "联网搜索请求失败,请检查API Key是否正确。"
|
||||
|
||||
results = data.get("results", [])
|
||||
if not results:
|
||||
return "未找到相关搜索结果。"
|
||||
|
||||
lines = ["【联网搜索结果】"]
|
||||
for i, item in enumerate(results[:max_results], 1):
|
||||
title = item.get("title", "无标题")
|
||||
snippet = item.get("description", "")
|
||||
lines.append(f"{i}. 标题:{title}")
|
||||
if snippet:
|
||||
lines.append(f" 摘要:{snippet}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
@register_function("web_search", WEB_SEARCH_FUNCTION_DESC, ToolType.SYSTEM_CTL)
|
||||
async def web_search(conn: "ConnectionHandler", query: str = None):
|
||||
logger.bind(tag=TAG).info(f"web_search 被调用 | query={query}")
|
||||
@@ -136,6 +173,8 @@ async def web_search(conn: "ConnectionHandler", query: str = None):
|
||||
result_text = await _search_metaso(api_key, query, max_results)
|
||||
elif provider == "tavily":
|
||||
result_text = await _search_tavily(api_key, query, max_results)
|
||||
elif provider == "serply":
|
||||
result_text = await _search_serply(api_key, query, max_results)
|
||||
else:
|
||||
return ActionResponse(
|
||||
Action.REQLLM,
|
||||
|
||||
Reference in new issue
Block a user