
Wenn Sie unseren Implementierungsleitfaden für DeepSearch/DeepResearch bereits gelesen haben, lassen Sie uns tiefer in einige Details eintauchen, die die Qualität deutlich verbessern können. In diesem Beitrag konzentrieren wir uns auf zwei zentrale Herausforderungen: die Nutzung von Embeddings für die Snippet-Auswahl aus langen Webseiten und die Verwendung von Rerankers zur Priorisierung von URLs beim Crawling.
Einige erinnern sich vielleicht an unsere frühere Schlussfolgerung, dass "Embeddings nur für Query-Deduplizierung wie STS-Aufgaben (Semantic Textual Similarity) nützlich waren, während Rerankers nicht einmal Teil unserer ursprünglichen DeepSearch-Implementierung waren." Nun, es stellt sich heraus, dass beide immer noch sehr wertvoll sind - nur nicht auf die konventionelle Art und Weise, wie man es erwarten würde. Wir haben immer den schlankesten möglichen Weg verfolgt. Wir fügen keine Komponenten hinzu, nur um ihre Existenz oder unseren Wert als Embedding- & Reranker-Anbieter zu rechtfertigen. Wir konzentrieren uns darauf, was Suche wirklich im Kern benötigt.
Nach Wochen von Experimenten und Iterationen haben wir ungewöhnliche, aber effektive Verwendungen für beide in DeepSearch/DeepResearch-Systemen entdeckt. Durch ihre Anwendung haben wir die Qualität von Jina DeepSearch deutlich verbessert (Sie können es gerne ausprobieren). Wir möchten diese Erkenntnisse mit anderen Praktikern in diesem Bereich teilen.
tagSnippet-Auswahl aus langen Inhalten
Das Problem ist folgendes: Nachdem wir Jina Reader zum Lesen von Webseiteninhalten verwendet haben, müssen wir diesen als Wissensobjekt dem Kontext des Agenten für die Schlussfolgerung hinzufügen. Während das Einfügen des vollständigen Inhalts in das Kontextfenster des LLM der einfachste Weg ist, ist es unter Berücksichtigung der Token-Kosten und Generierungsgeschwindigkeit nicht optimal. In der Praxis müssen wir identifizieren, welche Teile des Inhalts für die Frage am relevantesten sind und nur diese Teile selektiv als Wissen zum Kontext des Agenten hinzufügen.
LLM-basierte Filterung hat die gleichen Kosten- und Latenzprobleme, also suchen wir nach Lösungen mit kleineren Modellen: Wir brauchen kleinere und kostengünstigere, aber dennoch mehrsprachige Modelle – ein entscheidender Faktor, da wir nicht garantieren können, dass entweder die Anfrage oder die Dokumente immer auf Englisch sein werden.
Wir haben auf der einen Seite eine Frage (entweder die ursprüngliche Anfrage oder eine Lückenfrage) und auf der anderen Seite einen großen Markdown-Inhalt, bei dem der größte Teil irrelevant ist. Wir müssen die relevantesten Snippets für die Anfrage auswählen. Dies ähnelt dem Chunking-Problem, mit dem sich die RAG-Community seit 2023 beschäftigt - nur relevante Chunks mittels Retriever-Modellen abzurufen, um sie im Kontextfenster für die Zusammenfassung zu platzieren. In unserem Fall gibt es jedoch zwei wichtige Unterschiede:
- Begrenzte Chunks aus einer begrenzten Anzahl von Dokumenten. Wenn jeder Chunk ungefähr 500 Token enthält, dann hat ein typisches langes Webdokument etwa 200.000 Token (p50) bis 1.000.000 Token (p99), und wir verwenden Jina Reader, um 4-5 URLs pro Schritt abzurufen, was ungefähr Hunderte von Chunks ergibt - also Hunderte von Embedding-Vektoren und Hunderte von Kosinus-Ähnlichkeiten. Dies ist mit JavaScript im Arbeitsspeicher ohne Vektordatenbank leicht zu bewältigen.
- Wir brauchen aufeinanderfolgende Chunks, um effektive Wissens-Snippets zu bilden. Wir können keine Snippets akzeptieren, die verstreute Sätze wie
[1-2, 6-7, 9, 14, 17, ...]
kombinieren. Ein nützlicheres Wissens-Snippet würde Mustern wie[3-15, 17-24, ...]
folgen - immer mit aufeinanderfolgendem Text. Dies macht es für das LLM einfacher, aus der Wissensquelle zu kopieren und zu zitieren und reduziert Halluzinationen.
Der Rest sind all die Vorbehalte, über die Praktiker klagen: Jeder Chunk kann nicht zu lang sein, da Embedding-Modelle langen Kontext nicht gut verarbeiten können; Chunking führt zu Kontextverlust und macht Chunk-Embeddings i.i.d; und wie findet man überhaupt die besten Grenzmarkierungen, die sowohl Lesbarkeit als auch Semantik erhalten? Wenn Sie wissen, wovon wir sprechen, dann wurden Sie wahrscheinlich von diesen Problemen in Ihren RAG-Implementierungen heimgesucht.
Aber kurz gesagt - Late-Chunking mit jina-embeddings-v3 löst alle drei Probleme auf elegante Weise. Late-Chunking behält die Kontextinformationen für jeden Chunk bei, ist unempfindlich gegenüber Grenzmarkierungen, und jina-embeddings-v3 selbst ist SOTA in asymmetrischen mehrsprachigen Retrieval-Aufgaben. Interessierte Leser können unseren Blogbeiträgen oder Papers für Details folgen, aber hier ist die Gesamtimplementierung.
Conv1D
funktioniert. Der Prozess beginnt damit, ein langes Dokument in Chunks fester Länge zu teilen, die dann mit jina-embeddings-v3 mit aktiviertem Late-Chunking eingebettet werden. Nach der Berechnung der Ähnlichkeitswerte zwischen jedem Chunk und der Anfrage bewegt sich ein gleitendes Fenster über die Ähnlichkeitswerte, um das Fenster mit dem höchsten Durchschnittswert zu finden.


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");
}
Verwendung von Late Chunking und Conv1D-artigem Mean Pooling zur Auswahl des besten Snippets in Bezug auf die Frage.
Stellen Sie sicher, dass Sie die Jina Embeddings API mit den folgenden Einstellungen für retrieval task
, late_chunking
und truncate
aufrufen:
await axios.post(
'https://api.jina.ai/v1/embeddings',
{
model: "jina-embeddings-v3",
task: "retrieval.passage",
late_chunking: true,
input: chunks,
truncate: true
},
{ headers });
Für das Embedding der Frage müssen Sie task
zu retrieval.query
ändern und late_chunking
deaktivieren
Die vollständige Implementierung finden Sie auf Github:
tagURL-Ranking für das nächste Lesen
Das Problem ist Folgendes: Während einer DeepSearch-Sitzung sammeln Sie wahrscheinlich viele URLs von Suchmaschinenergebnisseiten (SERP) und entdecken noch mehr, wenn Sie einzelne Webseiten lesen (die On-Page-Links). Die Gesamtzahl der eindeutigen URLs kann leicht in die Hunderte gehen. Auch hier ist es ineffizient, einfach alle URLs direkt in den Kontext des LLM zu übertragen - es verschwendet wertvollen Kontextfensterplatz und, was noch problematischer ist, wir haben festgestellt, dass LLMs URLs im Wesentlichen zufällig auswählen. Es ist wichtig, das LLM zu URLs zu führen, die mit höchster Wahrscheinlichkeit die benötigte Antwort enthalten.
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"
Beste Option für die Verwendung von Jina Reader zum Crawlen einer Seite in DeepSearch. Dies sammelt alle On-Page-Links in einem separaten links
Feld und entfernt sie aus dem content
Feld.
Betrachten Sie dieses Problem als einen kontextbezogenen PageRank, bei dem wir Hunderte von URLs während einer Sitzung gewichten müssen. Wir ranken URLs basierend auf mehreren Faktoren, die letzte Aktualisierungszeit, Domänenhäufigkeit, Pfadstruktur und vor allem semantische Relevanz zur Anfrage kombinieren, um eine Gesamtbewertung zu erstellen. Denken Sie daran, dass wir nur die Informationen verwenden können, die vor dem tatsächlichen Besuch der URL verfügbar sind:
Häufigkeitssignale: URLs, die mehrfach über verschiedene Quellen erscheinen, erhalten zusätzliches Gewicht. URLs von Domains, die häufig in Suchergebnissen auftauchen, erhalten einen Boost, da beliebte Domains oft maßgebliche Inhalte enthalten.
Pfadstruktur: Wir analysieren URL-Pfade, um Inhaltscluster zu identifizieren. URLs innerhalb gemeinsamer Pfadhierarchien erhalten höhere Bewertungen, wobei ein Abklingfaktor auf tiefere Pfade angewendet wird.
Semantische Relevanz: Wir verwenden jina-reranker-v2-base-multilingual, um die semantische Relevanz zwischen der Frage und den Textinformationen jeder URL zu bewerten, was ein klassisches Reranking-Problem ist. Die Textinformationen jeder URL stammen aus:
- Titel & Snippets aus SERP-API-Ergebnissen (
https://s.jina.ai/
mit'X-Respond-With': 'no-content'
) - Ankertext von On-Page-URLs (
https://r.jina.ai
mit'X-With-Links-Summary': 'all'
)
Letzte Aktualisierungszeit: Einige DeepSearch-Anfragen sind zeitkritisch, daher sind kürzlich aktualisierte URLs wertvoller als ältere. Ohne eine große Suchmaschine wie Google zu sein, ist es eine Herausforderung, die letzte Aktualisierungszeit zuverlässig zu bestimmen. Wir haben einen mehrschichtigen Ansatz implementiert, der folgende Signale kombiniert und einen vertrauenswürdigen Zeitstempel liefert, der bei Bedarf aktuellere Inhalte priorisiert.
- SERP-API-Filter (wie der
tbs
Parameter von s.jina.ai zur Filterung nach Aktualität) - HTTP-Header-Analyse (Last-Modified, ETag)
- Metadaten-Extraktion (Meta-Tags, Schema.org Zeitstempel)
- Inhaltsmuster-Erkennung (sichtbare Daten im HTML)
- CMS-spezifische Indikatoren für Plattformen wie WordPress, Drupal und Ghost
Geschützter Inhalt: Einige Inhalte auf Social-Media-Plattformen sind geschützt oder einfach hinter Bezahlschranken, und ohne Anmeldung oder Verletzung ihrer Nutzungsbedingungen gibt es keine legitime Möglichkeit, auf diese Inhalte zuzugreifen. Wir sollten aktiv eine Liste problematischer URLs und Hostnamen pflegen, um deren Rankings zu senken und keine Zeit mit unzugänglichen Inhalten zu verschwenden.
Domain-Vielfalt: In manchen Fällen stammen die am höchsten gewichteten URLs alle von denselben Hostnamen, was DeepSearch in einem lokalen Optimum gefangen halten und die endgültige Qualität der Ergebnisse reduzieren kann. Sehen Sie sich die obigen Beispiele an, wo alle Top-URLs von StackOverflow stammen. Zur Verbesserung der Vielfalt können wir einen Explore-Exploit-Ansatz implementieren, indem wir die Top-k am höchsten bewerteten URLs von jedem Hostname auswählen.
Die vollständige Implementierung des URL-Rankings finden Sie auf unserem 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>
Denken Sie daran, URL-Gewichtungen in den Kontext des Agenten einzufügen und LLMs anzuweisen, die Gewichtungen zu respektieren.
tagFazit
Seit der Veröffentlichung unseres DeepSearch-Systems am 2. Februar 2025 haben wir zwei Implementierungsdetails entdeckt, die die Qualität erheblich verbessert haben. Bemerkenswerterweise nutzen beide multilinguale Embeddings und Reranker auf eine „In-Context"-Weise – sie operieren in einem viel kleineren Maßstab als die vorberechneten Indizes, die diese Modelle typischerweise benötigen. Dies erklärt unser anfängliches Übersehen.
Dies deutet auf eine faszinierende Polarisierung in der Zukunft der Suchtechnologie hin. Betrachten wir ein Framework analog zu Kahnemans Zwei-System-Theorie:
- Fast-Think (grep, BM25, SQL): Schnelles, regelbasiertes Pattern Matching mit minimalem Rechenaufwand.
- Slow-Think (LLM): Umfassendes Reasoning mit tiefem kontextuellem Verständnis, das erhebliche Rechenleistung erfordert.
- Mid-Think (Embeddings, Reranker): Im Zwischenbereich gefangen? Zu „fortgeschritten"/semantisch für einfaches Pattern Matching, aber ohne echte Reasoning-Fähigkeiten.
Wir beobachten möglicherweise die zunehmende Beliebtheit einer zweigeteilten Architektur, bei der leichtgewichtiges, effizientes SQL/BM25 die erste Inhaltsgewinnung übernimmt und direkt in leistungsstarke LLMs für die Tiefenverarbeitung einfließt. Diese LLMs integrieren zunehmend die semantischen Funktionen, die zuvor spezialisierte Mid-Level-Modelle erforderten. Die verbleibende Rolle für Mid-Think-Modelle verlagert sich auf spezialisierte In-Context-Aufgaben: Filterung, Deduplizierung und Operationen mit begrenztem Umfang, bei denen vollständiges Reasoning ineffizient wäre.
Dennoch bleiben die Auswahl kritischer Snippets und das Ranking von URLs fundamentale Komponenten mit direkter Auswirkung auf die Qualität der DeepSearch/DeepResearch-Systeme. Wir hoffen, dass unsere Erkenntnisse Verbesserungen in Ihren eigenen Implementierungen anstoßen.
Query-Expansion bleibt ein weiterer entscheidender Qualitätsfaktor. Wir evaluieren aktiv mehrere Ansätze – von einfachen Prompt-basierten Umschreibungen bis hin zu kleinen Sprachmodellen und Reasoning-basierten Methoden. Achten Sie auf unsere kommenden Erkenntnisse zu diesem Thema. Bleiben Sie dran.