package search import ( "context" "encoding/json" "fmt" "net/http" ) // zhipuProfile 是智谱 BigModel 联网搜索(design/27 §2.1 国内首选;分册 11)。 // // 认证:`Authorization: Bearer `;纯 REST(POST + JSON)⇒ 零新依赖。 // 官方**定价已确证**(分册 11 的官方定价页逐字):Search-Std 0.01 元/次、 // Search-Pro 0.03、Search-Pro-Sogou 0.05、Search-Pro-Quark 0.05。 // // ⚠️ 诚实边界(分册 11「未确证」):**完整请求/响应 JSON schema 未取到**,只确证了 // 字段名(请求侧 search_engine/search_query/…,响应侧 title/link/content)。 // 因此这里:① 请求只发确证过的两个字段;② 响应用共享提取器按**多组候选键**容错, // 不把任何猜测的字段当事实。拿到真实 key 后应按官方 API Reference 复核并收窄候选键。 type zhipu struct{ cfg Config } // zhipuPath 是联网搜索端点路径(分册 11 官方原文:POST https://open.bigmodel.cn/api/paas/v4/web_search)。 const zhipuPath = "/api/paas/v4/web_search" // zhipuEngineStd 是默认搜索型号。取 `search_std`(自研基础搜索,官方定价页确证 // 0.01 元/次,四档里最便宜)。⚠️ `search_engine` 是否可选官方未明说(分册 11), // 显式写死一档以固定成本;要换档请改这里或后续开放为配置项。 const zhipuEngineStd = "search_std" // newZhipu 造一家智谱 provider。 func newZhipu(cfg Config) Provider { return &zhipu{cfg: cfg} } // Name 实现 Provider。 func (p *zhipu) Name() string { return NameZhipu } // Search 实现 Provider。 func (p *zhipu) Search(ctx context.Context, q Query) ([]Result, error) { // TODO(未确证):响应可能很大(自带网页正文,分册 11 提示注意本仓 1 MiB 上限)。 // 官方另有 `content_size` 可限正文长度,但其取值域未确证 ⇒ 不写死,留待拿到 // 真实 key 后按官方文档补上(当前超限会如实返回 ErrResponseTooLarge)。 payload := map[string]any{ "search_engine": zhipuEngineStd, "search_query": q.Text, } body, err := json.Marshal(payload) if err != nil { return nil, fmt.Errorf("search: zhipu: encode request: %w", err) } data, err := do(ctx, p.cfg, request{ Method: http.MethodPost, URL: baseURLJoin(p.cfg.BaseURL, zhipuPath), Headers: map[string]string{"Authorization": "Bearer " + p.cfg.APIKey}, Body: body, }) if err != nil { return nil, err } return extractResults(data, zhipuSpec()) } // zhipuSpec 是智谱响应的候选形状。官方示例以「工具块」给出 WebBrowserOutput{title,link,content} // (分册 11),故 title/link/content 是确证键;外层数组键名未确证 ⇒ 给多组候选。 func zhipuSpec() spec { return spec{ Arrays: []string{"search_result", "results", "data", "data.results", "web_search_result"}, Title: []string{"title", "name"}, Link: []string{"link", "url"}, Snippet: []string{"snippet", "summary", "abstract"}, Content: []string{"content", "text"}, // 官方示例未见发布时间/评分字段 ⇒ 只给通用候选,取不到就是空。 Published: []string{"publish_date", "published", "date"}, Score: []string{"score", "rank"}, } }