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

59 lines
2.1 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"
"net/url"
)
// aliyunIQSProfile 是阿里云信息查询服务(AI 联网搜索,IQS;design/27 §2.1,分册 15)。
//
// 接入形态对本仓最友好:**GET `/search/genericSearch` + `X-API-Key`(免签名、免 SDK)**
// ⇒ 标准库即可,恒不破「零新依赖」。
//
// 分册 15 确证的两个硬约束:
//
// - 官方原文:**默认返回网页 10 条,暂不支持调整** ⇒ 本 provider 忽略 Query.Count
// (本地截断仍生效,见 io 层的 Finalize)。
// - `mainText` 默认只给**前 500 字符**(要 3000 字需联系客户经理开通)⇒ 正文偏短是
// 上游口径,不是本仓截断。
type aliyunIQS struct{ cfg Config }
// aliyunIQSPath 是联网搜索端点路径(分册 15 官方原文:GET /search/genericSearch?query=<q>)。
const aliyunIQSPath = "/search/genericSearch"
// newAliyunIQS 造一家阿里云 IQS provider。
func newAliyunIQS(cfg Config) Provider { return &aliyunIQS{cfg: cfg} }
// Name 实现 Provider。
func (p *aliyunIQS) Name() string { return NameAliyunIQS }
// Search 实现 Provider。
func (p *aliyunIQS) Search(ctx context.Context, q Query) ([]Result, error) {
target := baseURLJoin(p.cfg.BaseURL, aliyunIQSPath) + "?query=" + url.QueryEscape(q.Text)
data, err := do(ctx, p.cfg, request{
Method: http.MethodGet,
URL: target,
Headers: map[string]string{"X-API-Key": p.cfg.APIKey},
})
if err != nil {
return nil, err
}
return extractResults(data, aliyunIQSSpec())
}
// aliyunIQSSpec 是 IQS 的响应形状(分册 15「响应字段」官方原文确证):
// `pageItems[].{title,link,snippet,mainText,markdownText,publishTime,score}`。
// 正文优先取 `mainText`(纯文本,更适合进提示词),其次 `markdownText`。
func aliyunIQSSpec() spec {
return spec{
Arrays: []string{"pageItems"},
Title: []string{"title"},
Link: []string{"link"},
Snippet: []string{"snippet"},
Content: []string{"mainText", "markdownText"},
Published: []string{"publishTime"},
Score: []string{"score"},
}
}