- 检索调优参数(rrf_k/rerank/rerank_candidates/score_threshold)透传到引擎实际执行 - Recall 去重,避免同一 Note 多 Block 重复导致 Recall 超 1 - RAG 运行改为后台异步执行:创建即 queued + 202,支持取消与 SSE 实时事件 - 数据集元数据校验,坏文件隔离跳过;citation_required 语义修正 - modes 空/重复校验;配置快照记录模型版本与索引元信息 Co-Authored-By: Claude Code <noreply@anthropic.com>
63 lines
2.0 KiB
Python
63 lines
2.0 KiB
Python
"""Reranker 统一接口与轻量实现。
|
|
|
|
真实默认是 BGE reranker 类 Cross-Encoder,第一阶段先用词面重叠 + 原始分数加权的
|
|
确定性精排跑通链路;后续替换实现即可。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Protocol, runtime_checkable
|
|
|
|
from app.textutils import tokens
|
|
|
|
|
|
@dataclass
|
|
class RankedCandidate:
|
|
block_id: str
|
|
score: float
|
|
text: str = "" # 块正文,供轻量精排计算词面重叠
|
|
|
|
|
|
@runtime_checkable
|
|
class RerankerProvider(Protocol):
|
|
"""统一 Reranker 接口:输入候选块,输出按相关性重排后的候选块。"""
|
|
|
|
model_id: str
|
|
version: str
|
|
|
|
async def rerank(self, query: str, candidates: list[RankedCandidate]) -> list[RankedCandidate]: ...
|
|
|
|
|
|
class LexicalReranker:
|
|
"""轻量精排:query 与块正文的词面重叠度,与归一化后的原始分数加权求和。"""
|
|
|
|
model_id = "lexical-v1"
|
|
version = "1"
|
|
|
|
def __init__(self, lexical_weight: float = 0.5) -> None:
|
|
self.lexical_weight = lexical_weight
|
|
|
|
async def rerank(self, query: str, candidates: list[RankedCandidate]) -> list[RankedCandidate]:
|
|
if not candidates:
|
|
return []
|
|
|
|
# 把原始分数(RRF 等)归一化到 [0,1],便于与重叠度同量纲加权
|
|
scores = [c.score for c in candidates]
|
|
lo, hi = min(scores), max(scores)
|
|
span = (hi - lo) or 1.0
|
|
|
|
query_tokens = set(tokens(query))
|
|
ranked: list[RankedCandidate] = []
|
|
for c in candidates:
|
|
norm = (c.score - lo) / span
|
|
if query_tokens:
|
|
overlap = len(query_tokens & set(tokens(c.text))) / len(query_tokens)
|
|
else:
|
|
overlap = 0.0
|
|
final = self.lexical_weight * overlap + (1 - self.lexical_weight) * norm
|
|
ranked.append(RankedCandidate(block_id=c.block_id, score=final, text=c.text))
|
|
|
|
ranked.sort(key=lambda c: c.score, reverse=True)
|
|
return ranked
|