From 91069525a53eb5c2545d68353e1065aa078229cd Mon Sep 17 00:00:00 2001 From: "IETM_FIXE\\ietm6" Date: Wed, 10 Jun 2026 15:10:39 +0200 Subject: [PATCH] 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 --- brain/app/application/embeddings.py | 10 ++++++-- brain/app/application/notebook_rag.py | 2 +- .../mistral_embedding_adapter.py | 3 ++- .../ollama_embedding_adapter.py | 23 +++++++++++++++++-- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/brain/app/application/embeddings.py b/brain/app/application/embeddings.py index 5252dbd..958d964 100644 --- a/brain/app/application/embeddings.py +++ b/brain/app/application/embeddings.py @@ -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]]: ... diff --git a/brain/app/application/notebook_rag.py b/brain/app/application/notebook_rag.py index 0bfa721..0b848a2 100644 --- a/brain/app/application/notebook_rag.py +++ b/brain/app/application/notebook_rag.py @@ -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( diff --git a/brain/app/infrastructure/mistral_embedding_adapter.py b/brain/app/infrastructure/mistral_embedding_adapter.py index 7dbf9e1..1b1f2fa 100644 --- a/brain/app/infrastructure/mistral_embedding_adapter.py +++ b/brain/app/infrastructure/mistral_embedding_adapter.py @@ -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]] = [] diff --git a/brain/app/infrastructure/ollama_embedding_adapter.py b/brain/app/infrastructure/ollama_embedding_adapter.py index e584e24..5a498f3 100644 --- a/brain/app/infrastructure/ollama_embedding_adapter.py +++ b/brain/app/infrastructure/ollama_embedding_adapter.py @@ -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)