
Si vous avez déjà lu notre guide d'implémentation DeepSearch/DeepResearch, plongeons plus profondément dans certains détails qui peuvent grandement améliorer la qualité. Dans cet article, nous nous concentrerons sur deux défis clés : l'utilisation des embeddings pour la sélection d'extraits de pages web longues et l'utilisation de rerankers pour prioriser les URLs à crawler.
Certains se souviendront de notre conclusion précédente indiquant que "les embeddings n'étaient utiles que pour la déduplication de requêtes comme les tâches STS (similarité textuelle sémantique), tandis que les rerankers ne faisaient même pas partie de notre implémentation originale de DeepSearch." Il s'avère que les deux sont toujours très utiles - juste pas de la manière conventionnelle à laquelle on pourrait s'attendre. Nous avons toujours suivi la voie la plus simple possible. Nous n'ajoutons pas de composants juste pour justifier leur existence ou notre valeur en tant que fournisseur d'embeddings et de rerankers. Nous sommes pragmatiques - sur ce dont la recherche a réellement besoin comme fondation.
Ainsi, après des semaines d'expériences et d'itérations, nous avons découvert des utilisations peu communes mais efficaces pour les deux dans les systèmes DeepSearch/DeepResearch. En les appliquant, nous avons significativement amélioré la qualité de Jina DeepSearch (n'hésitez pas à l'essayer). Nous aimerions partager ces insights avec nos collègues praticiens travaillant dans ce domaine.
tagSélectionner des extraits de contenus longs
Le problème est le suivant : après avoir utilisé Jina Reader pour lire le contenu d'une page web, nous devons l'ajouter comme élément de connaissance au contexte de l'agent pour le raisonnement. Bien que déverser tout le contenu dans la fenêtre de contexte du LLM soit la façon la plus simple, ce n'est pas optimal en considérant les coûts en tokens et la vitesse de génération. En pratique, nous devons identifier quelles parties du contenu sont les plus pertinentes pour la question et ajouter sélectivement uniquement ces parties comme connaissance au contexte de l'agent.
Le filtrage basé sur LLM a les mêmes problèmes de coût et de latence, alors cherchons des solutions avec des modèles plus petits : nous avons besoin de modèles plus petits et moins coûteux, mais toujours multilingues – un facteur crucial puisque nous ne pouvons pas garantir que la requête ou les documents seront toujours en anglais.
Nous avons une question d'un côté (soit la requête originale soit une question de gap) et un grand contenu markdown de l'autre côté, où la plupart du contenu est non pertinent. Nous devons sélectionner les extraits les plus pertinents pour la requête. Cela ressemble au problème de chunking auquel la communauté RAG est confrontée depuis 2023 - récupérer uniquement les chunks pertinents en utilisant des modèles de retrieval à placer dans la fenêtre de contexte pour la synthèse. Cependant, il y a deux différences clés dans notre cas :
- Chunks limités d'un nombre limité de documents. Si chaque chunk contient environ 500 tokens, alors un document web long typique a environ 200 000 tokens (p50) à 1 000 000 tokens (p99), et nous utilisons Jina Reader pour récupérer 4-5 URLs à chaque étape, cela donnerait environ des centaines de chunks - signifiant des centaines de vecteurs d'embedding et des centaines de similarités cosinus. C'est facilement gérable avec JavaScript en mémoire sans base de données vectorielle.
- Nous avons besoin de chunks consécutifs pour former des extraits de connaissance efficaces. Nous ne pouvons pas accepter des extraits combinant des phrases dispersées comme
[1-2, 6-7, 9, 14, 17, ...].Un extrait de connaissance plus utile suivrait des motifs comme[3-15, 17-24, ...]- maintenant toujours un texte consécutif. Cela facilite la copie et la citation par le LLM depuis la source de connaissance et réduit l'hallucination.
Le reste concerne tous les problèmes dont les praticiens se plaignent : chaque chunk ne peut pas être trop long puisque les modèles d'embedding ne peuvent pas bien gérer les longs contextes ; le chunking introduit une perte de contexte et rend les embeddings de chunks i.i.d ; et comment même trouver les meilleurs indices de frontière qui maintiennent à la fois la lisibilité et la sémantique ? Si vous savez de quoi nous parlons, alors vous avez probablement été hanté par ces problèmes dans vos implémentations RAG.
Mais pour faire court - le late-chunking avec jina-embeddings-v3 résout magnifiquement les trois problèmes. Le late chunking maintient l'info de contexte pour chaque chunk, est insensible aux indices de frontière, et jina-embeddings-v3 lui-même est SOTA dans les tâches de recherche multilingue asymétrique. Les lecteurs intéressés peuvent suivre nos articles de blog ou papers pour les détails, mais voici l'implémentation globale.
Conv1D. Le processus commence par diviser un long document en chunks de longueur fixe, qui sont ensuite intégrés avec jina-embeddings-v3 avec l'option late-chunking activée. Après avoir calculé les scores de similarité entre chaque chunk et la requête, une fenêtre glissante se déplace à travers les scores de similarité pour trouver la fenêtre avec la valeur moyenne la plus élevée.


function cherryPick(question, longContext, options) {
if (longContext.length < options.snippetLength * options.numSnippets)
return longContext;
const chunks = splitIntoChunks(longContext, options.chunkSize);
const chunkEmbeddings = getEmbeddings(chunks, "retrieval.passage");
const questionEmbedding = getEmbeddings([question], "retrieval.query")[0];
const similarities = chunkEmbeddings.map(embed =>
cosineSimilarity(questionEmbedding, embed));
const chunksPerSnippet = Math.ceil(options.snippetLength / options.chunkSize);
const snippets = [];
const similaritiesCopy = [...similarities];
for (let i = 0; i < options.numSnippets; i++) {
let bestStartIndex = 0;
let bestScore = -Infinity;
for (let j = 0; j <= similarities.length - chunksPerSnippet; j++) {
const windowScores = similaritiesCopy.slice(j, j + chunksPerSnippet);
const windowScore = average(windowScores);
if (windowScore > bestScore) {
bestScore = windowScore;
bestStartIndex = j;
}
}
const startIndex = bestStartIndex * options.chunkSize;
const endIndex = Math.min(startIndex + options.snippetLength, longContext.length);
snippets.push(longContext.substring(startIndex, endIndex));
for (let k = bestStartIndex; k < bestStartIndex + chunksPerSnippet; k++)
similaritiesCopy[k] = -Infinity;
}
return snippets.join("\n\n");
}Utilisation du découpage tardif et du pooling moyen de type Conv1D pour sélectionner le meilleur extrait par rapport à la question.
Assurez-vous d'appeler l'API Jina Embeddings avec le paramètre task de retrieval, late_chunking et truncate définis comme ci-dessous :
await axios.post(
'https://api.jina.ai/v1/embeddings',
{
model: "jina-embeddings-v3",
task: "retrieval.passage",
late_chunking: true,
input: chunks,
truncate: true
},
{ headers }); Pour l'embedding de la question, assurez-vous de changer task en retrieval.query et de désactiver late_chunking
L'implémentation complète est disponible sur Github :
tagClassement des URLs pour la prochaine lecture
Le problème est le suivant : pendant une session DeepSearch, vous allez probablement collecter beaucoup d'URLs à partir des pages de résultats des moteurs de recherche (SERP) et en découvrir encore plus à chaque fois que vous lisez des pages web individuelles (ces liens sur la page). Le nombre total d'URLs uniques peut facilement atteindre les centaines. Encore une fois, simplement déverser toutes les URLs directement dans le contexte du LLM est inefficace - cela gaspille un espace précieux dans la fenêtre de contexte et, plus problématique encore, nous avons constaté que les LLMs choisissent essentiellement les URLs au hasard. Il est crucial de guider le LLM vers les URLs qui ont la plus grande probabilité de contenir la réponse dont vous avez besoin.
curl https://r.jina.ai/https://example.com \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "X-Retain-Images: none" \
-H "X-Md-Link-Style: discarded" \
-H "X-Timeout: 20" \
-H "X-With-Links-Summary: all"Meilleure option pour utiliser Jina Reader pour explorer une page dans DeepSearch. Cela collectera tous les liens de la page dans un champ links séparé, et les supprimera du champ content.
Considérez ce problème comme un PageRank en contexte où nous devons pondérer des centaines d'URLs pendant une session. Nous classons les URLs en fonction de multiples facteurs qui combinent la dernière mise à jour, la fréquence du domaine, la structure du chemin et, plus important encore, la pertinence sémantique par rapport à la requête pour créer un score composite. Rappelez-vous que nous ne pouvons utiliser que les informations disponibles avant de visiter réellement l'URL :
Signaux de fréquence : Les URLs qui apparaissent plusieurs fois à travers différentes sources reçoivent un poids supplémentaire. Les URLs provenant de domaines qui apparaissent fréquemment dans les résultats de recherche reçoivent un boost, car les domaines populaires contiennent souvent du contenu faisant autorité.
Structure du chemin : Nous analysons les chemins des URLs pour identifier les clusters de contenu. Les URLs au sein de hiérarchies de chemins communes reçoivent des scores plus élevés, avec un facteur de décroissance appliqué aux chemins plus profonds.
Pertinence sémantique : Nous utilisons jina-reranker-v2-base-multilingual pour évaluer la pertinence sémantique entre la question et les informations textuelles de chaque URL, ce qui est un problème classique de reclassement. Les informations textuelles de chaque URL proviennent de :
- Titre et extraits des résultats de l'API SERP (
https://s.jina.ai/avec'X-Respond-With': 'no-content') - Texte d'ancrage des URLs de la page (
https://r.jina.aiavec'X-With-Links-Summary': 'all')
Dernière mise à jour : Certaines requêtes DeepSearch sont sensibles au temps, donc les URLs récemment mises à jour sont plus précieuses que les anciennes. Sans être un moteur de recherche majeur comme Google, déterminer de manière fiable la dernière date de mise à jour est un défi. Nous avons implémenté une approche multicouche qui combine les signaux suivants et fournit un horodatage avec score de confiance qui priorise le contenu plus récent lorsque nécessaire.
- Filtres API SERP (comme le paramètre
tbsde s.jina.ai pour filtrer par récence) - Analyse des en-têtes HTTP (Last-Modified, ETag)
- Extraction de métadonnées (meta tags, horodatages Schema.org)
- Reconnaissance de motifs de contenu (dates visibles en HTML)
- Indicateurs spécifiques aux CMS pour les plateformes comme WordPress, Drupal et Ghost
Contenu restreint : Certains contenus sur les plateformes de médias sociaux sont restreints ou simplement derrière des paywalls, et sans connexion ou violation de leurs CGU, il n'y a pas de moyen légitime d'accéder à ce contenu. Nous devrions activement maintenir une liste d'URLs et de noms d'hôtes problématiques pour réduire leurs classements, évitant ainsi de perdre du temps sur du contenu inaccessible.
Diversité des domaines : Dans certains cas, les URLs les mieux pondérées proviennent toutes des mêmes noms d'hôtes, ce qui peut piéger DeepSearch dans un optimum local et réduire la qualité finale des résultats. Regardez les exemples ci-dessus où toutes les URLs principales proviennent de StackOverflow. Pour améliorer la diversité, nous pouvons implémenter une approche d'exploration-exploitation en sélectionnant les k URLs les mieux classées de chaque nom d'hôte.
L'implémentation complète du classement des URLs est disponible sur notre Github.
<action-visit>
- Crawl and read full content from URLs, you can get the fulltext, last updated datetime etc of any URL.
- Must check URLs mentioned in <question> if any
- Choose and visit relevant URLs below for more knowledge. higher weight suggests more relevant:
<url-list>
+ weight: 0.20 "https://huggingface.co/docs/datasets/en/loading": "Load - Hugging FaceThis saves time because instead of waiting for the Dataset builder download to time out, Datasets will look directly in the cache. Set the environment ...Some datasets may have more than one version based on Git tags, branches, or commits. Use the revision parameter to specify the dataset version you want to load ..."
+ weight: 0.20 "https://huggingface.co/docs/datasets/en/index": "Datasets - Hugging Face🤗 Datasets is a library for easily accessing and sharing datasets for Audio, Computer Vision, and Natural Language Processing (NLP) tasks. Load a dataset in a ..."
+ weight: 0.17 "https://github.com/huggingface/datasets/issues/7175": "[FSTimeoutError] load_dataset · Issue #7175 · huggingface/datasetsWhen using load_dataset to load HuggingFaceM4/VQAv2, I am getting FSTimeoutError. Error TimeoutError: The above exception was the direct cause of the following ..."
+ weight: 0.15 "https://github.com/huggingface/datasets/issues/6465": "`load_dataset` uses out-of-date cache instead of re-downloading a ...When a dataset is updated on the hub, using load_dataset will load the locally cached dataset instead of re-downloading the updated dataset."
+ weight: 0.12 "https://stackoverflow.com/questions/76923802/hugging-face-http-request-on-data-from-parquet-format-when-the-only-way-to-get-i": "Hugging face HTTP request on data from parquet format when the ...I've had to get the data from their data viewer using the parquet option. But when I try to run it, there is some sort of HTTP error. I've tried downloading ..."
</url-list>
</action-visit>N'oubliez pas d'inclure les poids des URL dans le contexte de l'agent et d'instruire les LLM à respecter ces poids.
tagConclusion
Depuis la sortie de notre système DeepSearch le 2 février 2025, nous avons découvert deux détails d'implémentation qui ont considérablement amélioré la qualité. Fait remarquable, les deux utilisent des embeddings et des rerankers multilingues de manière « in-context » - opérant à une échelle beaucoup plus petite que les index pré-calculés que ces modèles nécessitent habituellement. Cela explique notre oubli initial.
Cela indique une fascinante polarisation dans l'avenir de la technologie de recherche. Considérons un cadre analogue à la théorie du double processus de Kahneman :
- Pensée rapide (grep, BM25, SQL) : Mise en correspondance de motifs rapide et régie par des règles avec des exigences de calcul minimales.
- Pensée lente (LLM) : Raisonnement complet avec une compréhension contextuelle approfondie, nécessitant des calculs importants.
- Pensée intermédiaire (embeddings, rerankers) : Pris dans les limbes ? Trop « avancé »/sémantique pour une simple correspondance de motifs mais manquant de véritables capacités de raisonnement.
Nous assistons peut-être à la popularité d'une architecture bifurquée où SQL/BM25 léger et efficace gère la récupération initiale du contenu, alimentant directement des LLM puissants pour un traitement approfondi. Ces LLM intègrent de plus en plus les fonctions sémantiques qui nécessitaient auparavant des modèles spécialisés de niveau intermédiaire. Le rôle restant pour les modèles de pensée intermédiaire se déplace vers des tâches in-context spécialisées : filtrage, déduplication et opérations à portée limitée où un raisonnement complet serait inefficace.
Néanmoins, la sélection d'extraits critiques et le classement des URL restent des composants fondamentaux ayant un impact direct sur la qualité du système DeepSearch/DeepResearch. Nous espérons que nos observations susciteront des améliorations dans vos propres implémentations.
L'expansion des requêtes continue d'être un autre déterminant crucial de la qualité. Nous évaluons activement plusieurs approches, allant des réécritures basiques basées sur les prompts aux petits modèles de langage et aux méthodes basées sur le raisonnement. Attendez-vous à nos prochaines découvertes sur ce front bientôt. Restez à l'écoute.











