重排
POST /v1/rerank —— 给一个检索式和一批候选文档打相关性分并排序。
什么时候需要重排#
典型场景是检索增强生成(RAG)。向量检索能从十万篇文档里快速捞出五十篇"大概相关"的,但它比的是向量距离,精度有限;重排模型会把检索式和每篇文档成对地读一遍再打分,准得多,代价是慢得多、贵得多。
所以标准做法是两段式:先用向量化粗筛出几十篇,再用重排从中选出最相关的三五篇喂给模型。直接拿重排扫全库是跑不动的。
与向量化的关键区别:向量化把每段文本单独编码,结果可以缓存复用;重排的输入是"检索式加文档"这一对,换个检索式就得重算,没有可缓存的中间产物。
一次完整调用#
curl:三篇候选,只要最相关的两篇
bash
curl https://ai.oceango.hk/v1/rerank \ -H "Authorization: Bearer $AIROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "cohere/rerank-v3.5", "query": "如何申请退款", "documents": [ "我们的办公地址位于香港中环。", "订单在支付后 7 天内可以申请全额退款,请在订单详情页点击退款。", "退款到账时间取决于发卡行,通常为 3 到 5 个工作日。" ], "top_n": 2 }'响应:按分数降序,index 指回你传进来的下标
json
{ "model": "cohere/rerank-v3.5", "results": [ { "index": 1, "relevance_score": 0.981 }, { "index": 2, "relevance_score": 0.774 } ], "usage": { "prompt_tokens": 96, "total_tokens": 96 }}index 是原始下标,不是名次
results[0].index 等于 1,意思是"你传进来的第 2 篇(下标 1)最相关"。名次是数组顺序本身。这样设计是为了让你直接用 documents[r.index] 映射回自己的数据,不必回传文档原文。请求字段#
modelstring必填- 重排模型名。规则与对话端点一致,见模型与路由。
querystring必填- 检索式,至少一个字符。
documentsarray必填- 候选文档,1 到 1000 篇。每项可以是字符串,也可以是带
text字段的对象——对象形态的原始 JSON 会被完整保留,return_documents为真时原样回带。只有text会发给上游:各家对文档里的额外字段处理不一致,有的忽略,有的报错,还有的会把它们也纳入排序。 top_ninteger- 只返回前 N 条。不传表示全部返回;超过文档数时自动钳到文档数。负数报
invalid_request而不是当成 0——静默返回空结果会让你去查自己的检索质量,而不是这一行参数。 return_documentsboolean- 默认
false。为真时在每条结果里带回文档原文。我们回带的是你传进来的那一份,不是上游回显的——见下一节。
两件与直觉不同的事#
这两点如果不知道,很容易写出看起来对、实际错的代码。
排序与 top_n 截断都在我方完成。上游返回的顺序不保证,拿未排序的结果直接截断会丢掉最相关的文档。
接进检索流程#
Python:粗筛加精排的两段式
python
import os, requests
API = "https://ai.oceango.hk/v1"HEADERS = {"Authorization": f"Bearer {os.environ['AIROUTER_API_KEY']}"}
def rerank(query, docs, top_n=3): body = { "model": "cohere/rerank-v3.5", "query": query, "documents": docs, "top_n": top_n, } r = requests.post(API + "/rerank", json=body, headers=HEADERS, timeout=60) r.raise_for_status() # index 指回 docs 的下标,直接拿它取回原文 return [docs[item["index"]] for item in r.json()["results"]]
candidates = vector_search(question, limit=50) # 你自己的向量检索context = rerank(question, candidates, top_n=3) # 精排后只留 3 篇Node.js:对象形态的文档,原样拿回自己的字段
javascript
const res = await fetch('https://ai.oceango.hk/v1/rerank', { method: 'POST', headers: { Authorization: `Bearer ${process.env.AIROUTER_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ model: 'cohere/rerank-v3.5', query: '如何申请退款', // 带上你自己的字段:只有 text 会发给上游,其余原样保留 documents: [ { id: 'kb-17', text: '订单在支付后 7 天内可以申请全额退款。', url: '/help/refund' }, { id: 'kb-42', text: '我们的办公地址位于香港中环。', url: '/help/contact' }, ], return_documents: true, top_n: 1, }),});
const { results } = await res.json();console.log(results[0].document.id); // kb-17,你传进去的字段还在计费口径#
只按输入计费,没有输出那一半——重排的产物是分数,不是文本。计费的 token 数是检索式加上全部文档的文本量。
检索式只算一次
模型内部会把检索式与每篇文档配对,但我们按一次计。1 个检索式配 50 篇文档,算的是"检索式加 50 篇文档"的总量,不是 50 遍检索式。
这意味着成本几乎完全由文档总长度决定。粗筛阶段少捞一些、或者把长文档切短,比换模型更能省钱。完整口径见计费口径。