package search import ( "context" "encoding/json" "fmt" "net/http" ) // baiduQianfanProfile 是百度·千帆 AI 搜索(design/27 §2.1;分册 16 官方原文确证)。 // // 接口形态:**POST `/v2/ai_search/web_search` + `Authorization: Bearer `** // ⇒ 标准库即可。 // // 分册 16 确证的口径: // // - 请求必须走 `messages` 数组,且**百度搜索仅支持单轮输入**(多轮则以最后一条 // content 为查询)⇒ 本 provider 只放一条 user 消息。 // - `search_source` 固定值 `baidu_search_v2`。 // - `resource_type_filter[].top_k` 上限:**网页 50**(默认 20)。 // - 计费:标准版 0.036 元/次、增强版 0.072 元/次;每月免费 1500 次(约每天 50 次)。 // // ⚠️ 诚实边界(分册 16「未确证」):**响应结构未逐字取全** ⇒ 用共享提取器按多组候选键 // 容错,不把猜测的字段当事实;拿到真实 key 后按官方文档收窄。 type baiduQianfan struct{ cfg Config } // baiduQianfanPath 是百度搜索端点路径 // (分册 16 官方原文:POST /v2/ai_search/web_search,HOST qianfan.baidubce.com)。 const baiduQianfanPath = "/v2/ai_search/web_search" // baiduSearchSourceV2 是搜索引擎版本固定值(分册 16 官方原文:固定值 `baidu_search_v2`)。 const baiduSearchSourceV2 = "baidu_search_v2" // baiduDefaultTopK / baiduMaxTopK 是网页模态的 top_k 口径 // (分册 16 官方原文:默认 `[{"type":"web","top_k":20}]`,网页 top_k 上限 50)。 const ( baiduDefaultTopK = 20 baiduMaxTopK = 50 ) // newBaiduQianfan 造一家百度千帆 provider。 func newBaiduQianfan(cfg Config) Provider { return &baiduQianfan{cfg: cfg} } // Name 实现 Provider。 func (p *baiduQianfan) Name() string { return NameBaiduQianfan } // Search 实现 Provider。 func (p *baiduQianfan) Search(ctx context.Context, q Query) ([]Result, error) { payload := map[string]any{ "messages": []map[string]string{{"role": "user", "content": q.Text}}, // 单轮输入:百度搜索只认最后一条 content,这里就只放一条。 "search_source": baiduSearchSourceV2, "resource_type_filter": []map[string]any{ {"type": "web", "top_k": baiduTopK(q.Count)}, }, } body, err := json.Marshal(payload) if err != nil { return nil, fmt.Errorf("search: baidu_qianfan: encode request: %w", err) } data, err := do(ctx, p.cfg, request{ Method: http.MethodPost, URL: baseURLJoin(p.cfg.BaseURL, baiduQianfanPath), Headers: map[string]string{"Authorization": "Bearer " + p.cfg.APIKey}, Body: body, }) if err != nil { return nil, err } return extractResults(data, baiduQianfanSpec()) } // baiduTopK 把调用方要的条数收进官方允许区间(分册 16:网页 top_k 上限 50,默认 20)。 func baiduTopK(n int) int { if n <= 0 { return baiduDefaultTopK } if n > baiduMaxTopK { return baiduMaxTopK } return n } // baiduQianfanSpec 是百度千帆响应的**候选**形状。 // // TODO(未确证):分册 16 只逐字取到了请求参数;响应结构**未确证**。 // 这里的候选键是容错解析(尽量认得出来),拿到真实 key 后必须按官方文档复核并收窄。 func baiduQianfanSpec() spec { return spec{ Arrays: []string{"references", "search_results", "data.references", "results"}, Title: []string{"title", "Title"}, Link: []string{"url", "Url", "link"}, Snippet: []string{"snippet", "summary", "abstract"}, Content: []string{"content", "Content"}, Published: []string{"date", "publish_time", "publishTime"}, Score: []string{"score", "rank"}, } }