RAG : prefixes de tache nomic-embed (search_document / search_query)

nomic-embed-text est entraine avec des prefixes de tache distincts pour le
corpus et la question ; sans eux la pertinence du retrieval est degradee.
Applique uniquement aux modeles nomic (les autres restent neutres).
NB : les sources indexees avant ce changement doivent etre re-uploadees.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 15:10:39 +02:00
parent 23878f1c63
commit 91069525a5
4 changed files with 32 additions and 6 deletions

View File

@@ -14,7 +14,13 @@ class EmbeddingError(Exception):
class EmbeddingProvider(Protocol):
"""Calcule les vecteurs d'une liste de textes (ordre préservé)."""
"""Calcule les vecteurs d'une liste de textes (ordre préservé).
async def embed(self, texts: list[str]) -> list[list[float]]:
`kind` distingue les DOCUMENTS indexés ("document") de la QUESTION posée
("query") : certains modèles (nomic-embed-text) sont entraînés avec des
préfixes de tâche distincts et perdent en pertinence sans eux. Les adapters
qui n'en ont pas besoin (mistral-embed) ignorent simplement le paramètre.
"""
async def embed(self, texts: list[str], kind: str = "document") -> list[list[float]]:
...

View File

@@ -86,7 +86,7 @@ class NotebookRagUseCase:
ids = [s for s in source_ids if vector_store.exists(s)]
if not ids or not query.strip():
return []
query_vectors = await self._embedder.embed([query])
query_vectors = await self._embedder.embed([query], kind="query")
if not query_vectors:
return []
return vector_store.search(

View File

@@ -28,7 +28,8 @@ class MistralEmbeddingProvider:
self._model = settings.mistral_embedding_model
self._timeout = settings.llm_timeout_seconds
async def embed(self, texts: list[str]) -> list[list[float]]:
async def embed(self, texts: list[str], kind: str = "document") -> list[list[float]]:
# `kind` ignoré : mistral-embed n'utilise pas de préfixe de tâche.
if not texts:
return []
out: list[list[float]] = []

View File

@@ -11,6 +11,14 @@ from app.application.embeddings import EmbeddingError
from app.core.config import Settings
# Préfixes de tâche des modèles nomic-embed : le modèle est ENTRAÎNÉ avec
# (search_document pour le corpus, search_query pour la question). Sans eux,
# la pertinence du retrieval est mesurablement dégradée. Ne s'applique qu'aux
# modèles nomic — les autres (mxbai, bge…) ont leurs propres conventions ou
# aucune ; on reste neutre pour eux.
_NOMIC_PREFIXES = {"document": "search_document: ", "query": "search_query: "}
class OllamaEmbeddingProvider:
"""Implémente EmbeddingProvider via Ollama /api/embed (batch)."""
@@ -19,11 +27,22 @@ class OllamaEmbeddingProvider:
self._model = settings.ollama_embedding_model
self._timeout = settings.llm_timeout_seconds
async def embed(self, texts: list[str]) -> list[list[float]]:
def _prepare(self, texts: list[str], kind: str) -> list[str]:
"""Applique le préfixe de tâche si le modèle est de la famille nomic-embed.
NB : les sources indexées AVANT l'introduction des préfixes doivent être
ré-uploadées pour que documents et questions vivent dans le même espace.
"""
if "nomic-embed" not in self._model:
return texts
prefix = _NOMIC_PREFIXES.get(kind, _NOMIC_PREFIXES["document"])
return [prefix + t for t in texts]
async def embed(self, texts: list[str], kind: str = "document") -> list[list[float]]:
if not texts:
return []
url = f"{self._base_url}/api/embed"
payload = {"model": self._model, "input": texts}
payload = {"model": self._model, "input": self._prepare(texts, kind)}
async with httpx.AsyncClient(timeout=self._timeout) as client:
try:
response = await client.post(url, json=payload)