Files
Cyrene_v2/plugins/tools/io/search/provider.go
T
AskaEth 69a3b73777 feat(search): 原生 provider 层(searxng/智谱/阿里IQS/博查/火山/百度)+ 降级链(契约无改动)
主人定调「先把原生的做进插件,MCP 路线等后端 MCP 完善后再接」。
- 新包 plugins/tools/io/search/:一 provider 一文件 + 共享 http/提取器 + 归一化 Result;域名过滤/去重/排序**归一化后统一做一次**
- 配置 15 键:WEB_SEARCH_PROVIDER(默认 searxng)/_FALLBACK/_MAX_RESULTS + 五家 ×(API_KEY/BASE_URL);WEB_SEARCH_TIMEOUT 语义改为「**每次上游尝试**」(降级链才有意义)
- 硬语义:未注册/未配置的主 provider ⇒ **启动期报错**(不许静默回落);off ⇒ 不注册工具;缺 key 的家不进可用清单;降级链**如实标实际应答的 provider**(变异:拿主力冒充 ⇒ 测试红「provider = zhipu, want bocha」)
- 密钥绝不进日志/响应(有断言);错误分类(鉴权/限流/额度/网络)+ **空结果 ≠ 成功**(变异暴露过一个死分支⇒下移到 searchWith 修掉)
- searxng 真机 e2e 通过(provider=searxng 5 条);**其余五家无 key ⇒ 如实标未实测**;契约无改动(无新端点、工具 schema 不变)
- 注册表留好 B 档接入位(tavily/exa/firecrawl/serper/serpapi),并有「未登记⇒启动报错」的防回归断言
2026-10-05 22:38:20 +08:00

78 lines
3.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package search
import (
"context"
"net/http"
)
// Provider 是一种搜索后端的**窄接口**(design/27 §4)。实现必须并发安全:
// 工具循环可能从多个 goroutine 调它。
type Provider interface {
// Name 返回该 provider 的配置名(即 WEB_SEARCH_PROVIDER / WEB_SEARCH_FALLBACK 里的取值)。
Name() string
// Search 执行一次检索,返回**已归一化**的结果(可能为空)。
//
// 返回空切片(len==0)**不是错误**:它表示「上游正常应答但什么都没查到」,
// 由调用方按「空结果 ≠ 成功」处置(见 ErrNoResults 的注释)。
Search(ctx context.Context, q Query) ([]Result, error)
}
// Query 是一次检索的输入。工具 schema 仍是 `query`/`count`(design/27 §4:不破坏契约)。
//
// 域名过滤**不在这里**:按 design/27 §4,域名过滤/去重/排序统一在归一化之后做一次
// (见 Finalize 的 Options),各家 provider 一律不许自己过滤。
type Query struct {
// Text 是检索词。
Text string
// Count 是调用方期望的结果条数(>0 才有意义)。provider 支持就透传,
// 不支持(如阿里云 IQS 默认 10 条不可调)就忽略 —— 本地截断仍然生效。
Count int
}
// Result 是所有 provider **统一归一化**后的结果(design/27 §4)。
type Result struct {
Link string
Title string
Snippet string
Content string
Published string
Score float64
}
// 本期内置的 provider 名(对外配置值 WEB_SEARCH_PROVIDER 的合法取值之一)。
const (
// NameSearXNG 是自建 SearXNG(A 档、零 key、本地兜底)。
NameSearXNG = "searxng"
// NameZhipu 是智谱 BigModel 联网搜索(design/27 §2.1 国内首选)。
NameZhipu = "zhipu"
// NameAliyunIQS 是阿里云信息查询服务 IQS(HTTP 免签名免 SDK)。
NameAliyunIQS = "aliyun_iqs"
// NameBocha 是博查 Bocha(中文优先)。
NameBocha = "bocha"
// NameVolcengine 是火山引擎·豆包搜索 SearchInfinity。
NameVolcengine = "volcengine"
// NameBaiduQianfan 是百度千帆 AI 搜索。
NameBaiduQianfan = "baidu_qianfan"
)
// Config 是构造一家 provider 所需的一切接线(密钥/地址/客户端/响应上限/URL 守卫)。
//
// 它由组合根(plugins/tools/io 的 searchWires)填好 —— provider 自己不读配置、
// 不读环境变量、不开新的 HTTP 客户端(单二进制、零新依赖:design/27 §1.5)。
type Config struct {
// APIKey 是上游凭据。**绝不进日志/响应/提示词**。
APIKey string
// BaseURL 是上游基地址(不含各家自己的路径),可被各自的环境变量覆盖。
BaseURL string
// Client 是这一套工具共用的 HTTP 客户端(已带 SSRF 重定向守卫与超时)。
Client *http.Client
// MaxBytes 是单次响应体读取上限(字节);≤0 用包内默认。
MaxBytes int64
// Guard 是**组合根注入的 SSRF 守卫**:发请求前对最终地址做一次判定
// (scheme / host / 解析结果)。它复用 plugins/tools/io 的既有守卫与本工具自己
// 那份白名单,**不改** http / web-fetch 共用的白名单。
//
// 为 nil 时不做判定(search 包的夹具测试直接用 httptest 客户端)。
Guard func(ctx context.Context, rawURL string) error
}