99 lines
2.8 KiB
Python
99 lines
2.8 KiB
Python
import asyncio
|
|
import os
|
|
import re
|
|
from functools import lru_cache
|
|
|
|
import httpx
|
|
from fastembed import SparseTextEmbedding
|
|
|
|
from .config import (
|
|
EMBEDDINGS_DENSE_MODEL,
|
|
EMBEDDINGS_DENSE_URL,
|
|
SPARSE_MODEL_NAME,
|
|
get_upstream_kwargs,
|
|
logger,
|
|
)
|
|
from .schemas import DenseEmbeddingResponse, Question, SparseVector
|
|
|
|
|
|
@lru_cache(maxsize=1)
|
|
def get_sparse_model() -> SparseTextEmbedding:
|
|
logger.info("Loading local sparse model %s", SPARSE_MODEL_NAME)
|
|
return SparseTextEmbedding(model_name=SPARSE_MODEL_NAME)
|
|
|
|
|
|
async def embed_dense(client: httpx.AsyncClient, text: str) -> list[float]:
|
|
response = await client.post(
|
|
str(EMBEDDINGS_DENSE_URL),
|
|
**get_upstream_kwargs(),
|
|
json={
|
|
"model": os.getenv("EMBEDDINGS_DENSE_MODEL", EMBEDDINGS_DENSE_MODEL),
|
|
"input": [text],
|
|
},
|
|
)
|
|
response.raise_for_status()
|
|
payload = DenseEmbeddingResponse.model_validate(response.json())
|
|
if not payload.data:
|
|
raise ValueError("Dense embedding response is empty")
|
|
return payload.data[0].embedding
|
|
|
|
|
|
async def embed_dense_multi(client: httpx.AsyncClient, texts: list[str]) -> list[list[float]]:
|
|
tasks = [embed_dense(client, t) for t in texts]
|
|
return list(await asyncio.gather(*tasks))
|
|
|
|
|
|
def embed_sparse(text: str) -> SparseVector:
|
|
vectors = list(get_sparse_model().embed([text]))
|
|
if not vectors:
|
|
raise ValueError("Sparse embedding response is empty")
|
|
item = vectors[0]
|
|
return SparseVector(
|
|
indices=[int(i) for i in item.indices.tolist()],
|
|
values=[float(v) for v in item.values.tolist()],
|
|
)
|
|
|
|
|
|
def _normalize_query(text: str) -> str:
|
|
return re.sub(r"\s+", " ", text).strip()
|
|
|
|
|
|
def build_primary_query(question: Question) -> str:
|
|
q = question.search_text.strip() if question.search_text else ""
|
|
if not q:
|
|
q = question.text.strip()
|
|
return _normalize_query(q)
|
|
|
|
|
|
def build_extra_dense_queries(question: Question) -> list[str]:
|
|
extras: list[str] = []
|
|
for v in question.variants or []:
|
|
q = _normalize_query(v)
|
|
if q:
|
|
extras.append(q)
|
|
for h in question.hyde or []:
|
|
q = _normalize_query(h)
|
|
if q:
|
|
extras.append(q)
|
|
return extras
|
|
|
|
|
|
def build_sparse_query(question: Question) -> str:
|
|
kws = question.keywords or []
|
|
if kws:
|
|
return " ".join(kws)
|
|
return build_primary_query(question)
|
|
|
|
|
|
def build_entity_tokens(question: Question) -> list[str]:
|
|
tokens: list[str] = []
|
|
if question.entities:
|
|
for field in (
|
|
question.entities.people,
|
|
question.entities.emails,
|
|
question.entities.documents,
|
|
question.entities.names,
|
|
question.entities.links,
|
|
):
|
|
tokens.extend(field or [])
|
|
return [t.strip() for t in tokens if t.strip()]
|