Import source: axon/services/reranker
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,75 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_model = None
|
||||||
|
_predict_lock = asyncio.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
def load_reranker() -> None:
|
||||||
|
global _model
|
||||||
|
from axon.config import settings
|
||||||
|
from sentence_transformers import CrossEncoder
|
||||||
|
|
||||||
|
logger.info("Loading reranker model: %s", settings.reranker_model)
|
||||||
|
_model = CrossEncoder(settings.reranker_model)
|
||||||
|
logger.info("Reranker model loaded")
|
||||||
|
|
||||||
|
|
||||||
|
def get_reranker() -> "CrossEncoder | None":
|
||||||
|
return _model
|
||||||
|
|
||||||
|
|
||||||
|
def chunk_text(text: str, max_tokens: int = 512, max_chunks: int = 4) -> list[str]:
|
||||||
|
if not text:
|
||||||
|
return [""]
|
||||||
|
words = text.split()
|
||||||
|
words_per_chunk = max(1, int(max_tokens * 0.75))
|
||||||
|
chunks = [
|
||||||
|
" ".join(words[i : i + words_per_chunk])
|
||||||
|
for i in range(0, len(words), words_per_chunk)
|
||||||
|
]
|
||||||
|
return (chunks or [""])[:max_chunks]
|
||||||
|
|
||||||
|
|
||||||
|
def _sigmoid(x: np.ndarray) -> np.ndarray:
|
||||||
|
return 1.0 / (1.0 + np.exp(-x))
|
||||||
|
|
||||||
|
|
||||||
|
async def rerank_results(
|
||||||
|
query: str,
|
||||||
|
contents: list[tuple[str, str]],
|
||||||
|
) -> list[tuple[str, str, float]]:
|
||||||
|
if not contents:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Build (url, chunks) pairs and flat passage list
|
||||||
|
url_chunks: list[tuple[str, list[str]]] = []
|
||||||
|
flat_passages: list[str] = []
|
||||||
|
for url, text in contents:
|
||||||
|
chunks = chunk_text(text)
|
||||||
|
url_chunks.append((url, chunks))
|
||||||
|
flat_passages.extend(chunks)
|
||||||
|
|
||||||
|
pairs = [[query, p] for p in flat_passages]
|
||||||
|
|
||||||
|
def _predict():
|
||||||
|
return _model.predict(pairs)
|
||||||
|
|
||||||
|
async with _predict_lock:
|
||||||
|
raw_scores = await asyncio.to_thread(_predict)
|
||||||
|
scores: list[float] = _sigmoid(np.array(raw_scores, dtype=float)).tolist()
|
||||||
|
|
||||||
|
# Pick best chunk per URL
|
||||||
|
results: list[tuple[str, str, float]] = []
|
||||||
|
idx = 0
|
||||||
|
for url, chunks in url_chunks:
|
||||||
|
chunk_scores = scores[idx : idx + len(chunks)]
|
||||||
|
best_i = int(np.argmax(chunk_scores))
|
||||||
|
results.append((url, chunks[best_i], round(chunk_scores[best_i], 6)))
|
||||||
|
idx += len(chunks)
|
||||||
|
|
||||||
|
return sorted(results, key=lambda t: t[2], reverse=True)
|
||||||
Reference in New Issue
Block a user