package rag import ( "bytes" "context" "encoding/json" "fmt" "net/http" "sort" "time" ) // rerankClient 调用 DashScope 文本重排(gte-rerank)。可选阶段:未配则跳过。 // DashScope 原生格式(非 OpenAI 兼容): // // POST {baseURL} {model, input:{query, documents}, parameters:{top_n, return_documents:false}} // resp {output:{results:[{index, relevance_score}]}} type rerankClient struct { baseURL string apiKey string model string hc *http.Client } func newRerankClient(baseURL, apiKey, model string) *rerankClient { if baseURL == "" || model == "" { return nil } return &rerankClient{baseURL: baseURL, apiKey: apiKey, model: model, hc: &http.Client{Timeout: 20 * time.Second}} } func (r *rerankClient) ready() bool { return r != nil && r.baseURL != "" } // rerank 用重排模型对候选重新打分排序,返回前 topN。出错时返回原序(降级)。 func (r *rerankClient) rerank(ctx context.Context, query string, hits []Hit, topN int) ([]Hit, error) { docs := make([]string, len(hits)) for i, h := range hits { docs[i] = h.Text } body, _ := json.Marshal(map[string]any{ "model": r.model, "input": map[string]any{"query": query, "documents": docs}, "parameters": map[string]any{"top_n": topN, "return_documents": false}, }) req, err := http.NewRequestWithContext(ctx, http.MethodPost, r.baseURL, bytes.NewReader(body)) if err != nil { return hits, err } req.Header.Set("Content-Type", "application/json") if r.apiKey != "" { req.Header.Set("Authorization", "Bearer "+r.apiKey) } resp, err := r.hc.Do(req) if err != nil { return hits, err } defer resp.Body.Close() if resp.StatusCode >= 400 { buf := new(bytes.Buffer) _, _ = buf.ReadFrom(resp.Body) return hits, fmt.Errorf("rerank http %d: %s", resp.StatusCode, buf.String()) } var out struct { Output struct { Results []struct { Index int `json:"index"` RelevanceScore float32 `json:"relevance_score"` } `json:"results"` } `json:"output"` } if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { return hits, err } res := make([]Hit, 0, len(out.Output.Results)) for _, rr := range out.Output.Results { if rr.Index >= 0 && rr.Index < len(hits) { res = append(res, Hit{Text: hits[rr.Index].Text, Score: rr.RelevanceScore}) } } if len(res) == 0 { return hits, nil } sort.Slice(res, func(i, j int) bool { return res[i].Score > res[j].Score }) return res, nil }