🆕 程式碼
1) src/day27_reranker.js
// src/day27_reranker.js
import { openai } from "./aiClient.js";
import { answerWithRAG } from "./day16_rag_store.js";
/**
* 使用 LLM 模擬 Cross-Encoder Reranker
* @param {string} query
* @param {Array} chunks - [{id, text, docId}]
*/
export async function rerank(query, chunks) {
// 對每個 chunk 做「查詢 + chunk」評分
const prompt = chunks.map((c, i) =>
`片段${i+1}:${c.text}`
).join("\n\n");
const res = await openai.chat.completions.create({
model: "gpt-4o-mini", // 模擬 cross-encoder
temperature: 0,
messages: [
{ role: "system", content: "你是 Reranker,請根據查詢與片段相關性,給每個片段 0~1 分數。" },
{ role: "user", content: `查詢:${query}\n\n${prompt}\n\n請輸出 JSON 陣列,例如 [{"id":"片段1","score":0.87},...]` }
]
});
let scores = [];
try {
scores = JSON.parse(res.choices[0].message.content);
} catch {
// fallback: 預設順序
scores = chunks.map((c,i)=>({id:c.id,score:1-(i*0.05)}));
}
const byId = new Map(scores.map(s=>[s.id,s.score]));
return chunks.map((c,i)=>({
...c,
rerankScore: byId.get(`片段${i+1}`) || 0
})).sort((a,b)=>b.rerankScore-a.rerankScore);
}
/**
* 檢索 + Rerank
*/
export async function retrieveWithRerank({ tenant, ns, query, preK=20, finalK=6 }) {
const { sources } = await answerWithRAG({ tenant, ns, query, topK: preK });
const reranked = await rerank(query, sources);
return reranked.slice(0, finalK);
}
新增 Rerank 策略:
import { retrieveWithRerank } from "../../../../../src/day27_reranker.js";
...
if (strategy === "rerank") {
const chunks = await retrieveWithRerank({ tenant, ns, query: q, preK: 20, finalK: 6 });
const ctxText = chunks.map((h,i)=>`# 片段${i+1}(${h.docId})\n${h.text}`).join("\n\n");
const res = await openai.chat.completions.create({
model:"gpt-4o-mini", temperature:0.2,
messages:[
{ role:"system", content:"你是知識庫助理,根據最佳片段回答。" },
{ role:"user", content:`問題:${q}\n\n片段:\n${ctxText}` }
]
});
const answer = res.choices?.[0]?.message?.content?.trim() || "沒有足夠資訊。";
return NextResponse.json({ ok:true, strategy:"rerank", answer, sources: chunks });
}
加一個策略選項:
<select ... value={strategy} onChange={e=>setStrategy(e.target.value as any)}>
<option value="default">Default(Top-K)</option>
<option value="section">Section-first</option>
<option value="qrewrite">Query Rewrite</option>
<option value="hybrid">Hybrid</option>
+ <option value="rerank">Rerank(Cross-Encoder)</option>
</select>
▶️ 驗收流程
/studio 選擇 Rerank。
問:「退貨要多久?」
預檢索抓 20 個候選。
Reranker 重新排序,把最相關的 6 個送進回答。
回傳 JSON:strategy: "rerank",sources 包含 rerankScore。