Files
Cyrene_v2/plugins/tools/io/search/volcengine.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

96 lines
3.6 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"
"encoding/json"
"fmt"
"net/http"
)
// volcengineProfile 是火山引擎·豆包搜索(SearchInfinity,design/27 §2.1;分册 13 官方原文确证)。
//
// 接口形态:**POST `/search_api/web_search` + `Authorization: Bearer <API_KEY>`**
// ⇒ 标准库即可,无需签名/SDK(不必走 IAM AK/SK 的 TOP 网关那条路)。
// 请求字段是**大写驼峰**(Query/SearchType/Count/Filter),与其它家不同 —— 这是上游口径。
//
// 分册 13 确证的口径:`Query` 1~100 字符且**不支持多词搜索**;账号维度默认 10 QPS;
// 每账号每月 500 次免费(自 2026-03-17 起对个人账号生效);按量 0.020 元/次。
//
// ⚠️ 诚实边界(分册 13「未确证」):**响应字段未逐字取全**(含「如意」结构化卡)
// ⇒ 用共享提取器按多组候选键容错,不把猜测的字段当事实;拿到真实 key 后按官方文档收窄。
type volcengine struct{ cfg Config }
// volcenginePath 是 API Key 接入的端点路径
// (分册 13 官方原文:POST https://open.feedcoopapi.com/search_api/web_search)。
const volcenginePath = "/search_api/web_search"
// volcengineSearchTypeWeb 是搜索类型枚举(分册 13 官方原文:目前支持 `web`,需开通 web搜索)。
const volcengineSearchTypeWeb = "web"
// volcengineDefaultCount / volcengineMaxCount 是条数口径
// (分册 13 官方原文:Count 最多 50 条,默认 10 条)。
const (
volcengineDefaultCount = 10
volcengineMaxCount = 50
)
// newVolcengine 造一家火山·豆包搜索 provider。
func newVolcengine(cfg Config) Provider { return &volcengine{cfg: cfg} }
// Name 实现 Provider。
func (p *volcengine) Name() string { return NameVolcengine }
// Search 实现 Provider。
func (p *volcengine) Search(ctx context.Context, q Query) ([]Result, error) {
payload := map[string]any{
"Query": q.Text,
"SearchType": volcengineSearchTypeWeb,
"Count": volcengineCount(q.Count),
}
body, err := json.Marshal(payload)
if err != nil {
return nil, fmt.Errorf("search: volcengine: encode request: %w", err)
}
data, err := do(ctx, p.cfg, request{
Method: http.MethodPost,
URL: baseURLJoin(p.cfg.BaseURL, volcenginePath),
Headers: map[string]string{"Authorization": "Bearer " + p.cfg.APIKey},
Body: body,
})
if err != nil {
return nil, err
}
return extractResults(data, volcengineSpec())
}
// volcengineCount 把调用方要的条数收进官方允许区间(分册 13:最多 50,默认 10)。
func volcengineCount(n int) int {
if n <= 0 {
return volcengineDefaultCount
}
if n > volcengineMaxCount {
return volcengineMaxCount
}
return n
}
// volcengineSpec 是火山·豆包搜索响应的**候选**形状。
//
// TODO(未确证):分册 13 只逐字取到了请求字段;响应结构(外层 Result.WebResults 与
// 每条结果的 Title/Url/Snippet/Summary/PublishTime/RankScore 等)**未确证**。
// 这里按可信度从高到低列候选键做容错解析 —— 它是「尽量认得出来」,不是「已确证」;
// 拿到真实 key 后必须按官方文档复核并删掉不成立的候选。
func volcengineSpec() spec {
return spec{
Arrays: []string{"Result.WebResults", "WebResults", "results", "Result.Results"},
Title: []string{"Title", "title"},
Link: []string{"Url", "url", "Link"},
Snippet: []string{
"Snippet", "snippet", "Summary", "summary",
},
Content: []string{"Content", "content", "Summary", "summary"},
Published: []string{"PublishTime", "publishTime", "PublishTimeMs"},
Score: []string{"RankScore", "rankScore", "Score", "score"},
}
}