如果您已經閱讀過我們的 DeepSearch/DeepResearch 實作指南,讓我們深入探討一些能大幅提升質量的細節。在這篇文章中,我們將聚焦於兩個關鍵挑戰:利用 embeddings 從長網頁中選擇片段以及使用 rerankers 來為爬取 URL 進行優先級排序。
有些人可能還記得我們之前的結論,即「embeddings 只對類似 STS 任務(語義文本相似度)的查詢去重有用,而 rerankers 甚至不是我們原始 DeepSearch 實作的一部分。」事實證明,這兩者仍然相當有價值 - 只是不是以人們常見的方式。我們一直遵循最精簡的路徑。我們不會為了證明它們的存在價值或我們作為 embedding 和 reranker 提供者的價值而添加組件。我們關注的是搜尋真正需要的基本要素。
經過數週的實驗和迭代,我們發現了這兩者在 DeepSearch/DeepResearch 系統中不尋常但有效的使用方式。透過應用它們,我們顯著改善了 Jina DeepSearch 的質量(歡迎試用)。我們想與在這個領域工作的同行分享這些見解。
tag從長內容中選擇片段
問題是這樣的:在使用 Jina Reader 讀取網頁內容後,我們需要將其作為知識項目添加到 agent 的上下文中以進行推理。雖然將完整內容直接放入 LLM 的上下文視窗是最簡單的方式,但考慮到 token 成本和生成速度,這並不是最佳選擇。在實踐中,我們需要識別內容中與問題最相關的部分,並有選擇地只將這些部分作為知識添加到 agent 的上下文中。
基於 LLM 的過濾存在相同的成本和延遲問題,讓我們尋找更小型模型的解決方案:我們需要更小更便宜,但仍然支援多語言的模型 – 這是一個關鍵因素,因為我們無法保證查詢或文件始終是英文的。
一邊是問題(原始查詢或差距問題),另一邊是大量的 markdown 內容,其中大部分內容都是不相關的。我們需要為查詢選擇最相關的片段。這類似於 RAG 社群自 2023 年以來一直在處理的分塊問題 - 使用檢索模型只檢索相關的塊放入上下文視窗進行摘要。然而,在我們的情況下有兩個關鍵差異:
- 來自有限文件的有限塊。如果每個塊包含大約 500 個 tokens,那麼一個典型的長網頁文件大約有 200,000 個 tokens(p50)到 1,000,000 個 tokens(p99),我們使用 Jina Reader 每步擷取 4-5 個 URL,這將產生大約數百個塊 - 意味著數百個 embedding 向量和數百個餘弦相似度。這可以輕鬆地用 JavaScript 在記憶體中管理,無需向量資料庫。
- 我們需要連續的塊來形成有效的知識片段。我們不能接受結合分散句子的片段,如
[1-2, 6-7, 9, 14, 17, ...]。更有用的知識片段應該遵循像[3-15, 17-24, ...]這樣的模式 - 始終保持文本連續性。這讓 LLM 更容易從知識源複製和引用,並減少產生幻覺。
其餘的都是實踐者抱怨的問題:每個塊不能太長,因為 embedding 模型無法很好地處理長上下文;分塊會導致上下文丟失,使塊嵌入變得獨立同分布;而且如何找到最佳的邊界提示,既要保持可讀性又要保持語義?如果您理解我們在說什麼,那麼您在 RAG 實作中可能也被這些問題困擾過。
但長話短說 - 使用 jina-embeddings-v3 的延遲分塊完美解決了這三個問題。延遲分塊保留了每個塊的上下文資訊,對邊界提示不敏感,而 jina-embeddings-v3 本身在非對稱多語言檢索任務中達到了 SOTA。感興趣的讀者可以查看我們的部落格文章或論文了解詳情,但這裡是整體實作。
Conv1D。該過程首先將長文檔分割成固定長度的塊,然後使用開啟延遲分塊的 jina-embeddings-v3 進行嵌入。在計算每個塊與查詢之間的相似度分數後,滑動視窗會在相似度分數上移動,以找到平均值最高的視窗。


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");
}使用後期分塊和 Conv1D 類似的平均池化來選擇與問題最相關的片段。
確保在呼叫 Jina Embeddings API 時設定以下的 retrieval task、late_chunking 和 truncate 參數:
await axios.post(
'https://api.jina.ai/v1/embeddings',
{
model: "jina-embeddings-v3",
task: "retrieval.passage",
late_chunking: true,
input: chunks,
truncate: true
},
{ headers }); 在嵌入問題時,請確保將 task 更改為 retrieval.query 並關閉 late_chunking
完整實作可以在 Github 上找到:
tag為下一次閱讀排序 URL
問題是這樣的:在 DeepSearch 會話期間,你可能會從搜尋引擎結果頁面(SERP)收集大量 URL,並在閱讀個別網頁時發現更多(那些頁面內的連結)。唯一 URL 的總數很容易達到數百個。再次強調,直接將所有 URL 丟進 LLM 的上下文是低效的——這會浪費寶貴的上下文視窗空間,而更麻煩的是,我們發現 LLM 基本上是隨機挑選 URL。引導 LLM 朝向最有可能包含你所需答案的 URL 是至關重要的。
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"在 DeepSearch 中使用 Jina Reader 爬取頁面的最佳選項。這會在單獨的 links 欄位中收集所有頁面內連結,並從 content 欄位中移除它們。
將這個問題想像成一個上下文內的 PageRank,我們需要在一個會話期間權衡數百個 URL。我們根據多個因素對 URL 進行排名,這些因素結合了最後更新時間、域名頻率、路徑結構,最重要的是與查詢的語義相關性,來創建一個綜合分數。記住,我們只能使用在實際訪問 URL 之前 可用的資訊:
頻率信號:出現在不同來源多次的 URL 會獲得額外的權重。來自在搜索結果中頻繁出現的域名的 URL 會得到提升,因為熱門域名通常包含權威內容。
路徑結構:我們分析 URL 路徑以識別內容集群。在常見路徑層次結構中的 URL 獲得更高的分數,對更深層路徑應用衰減因子。
語義相關性:我們使用 jina-reranker-v2-base-multilingual 來評估問題與每個 URL 文本資訊之間的語義相關性,這是一個經典的重排序問題。每個 URL 的文本資訊來自:
- 來自 SERP API 結果的標題和摘要(
https://s.jina.ai/與'X-Respond-With': 'no-content') - 頁面內 URL 的錨文本(
https://r.jina.ai與'X-With-Links-Summary': 'all')
最後更新時間:某些 DeepSearch 查詢是時間敏感的,所以最近更新的 URL 比舊的更有價值。在不是像 Google 那樣的主要搜尋引擎的情況下,可靠地確定最後更新時間是具有挑戰性的。我們實現了一個多層次方法,結合以下信號並提供一個具有信心分數的時間戳,在需要時優先考慮更新的內容。
- SERP API 過濾器(例如 s.jina.ai 的
tbs參數用於按最近程度過濾) - HTTP 標頭分析(Last-Modified、ETag)
- 元數據提取(meta 標籤、Schema.org 時間戳)
- 內容模式識別(HTML 中可見的日期)
- 針對 WordPress、Drupal 和 Ghost 等平台的 CMS 特定指標
受限內容:社交媒體平台上的某些內容是受限的或者只是在付費牆後面,在不登入或違反其服務條款的情況下,沒有合法的方式獲取這些內容。我們應該主動維護一個問題 URL 和主機名列表來降低它們的排名,避免在無法訪問的內容上浪費時間。
域名多樣性:在某些情況下,權重最高的 URL 都來自相同的主機名,這可能使 DeepSearch 陷入局部最優,並降低最終結果的質量。檢查上面的例子,其中所有頂級 URL 都來自 StackOverflow。為了提高多樣性,我們可以實施探索-利用方法,從每個主機名選擇排名最高的前 k 個 URL。
URL 排名的完整實作可以在我們的 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>請記得將 URL 權重放入代理的上下文中,並指示 LLM 遵守這些權重。
tag結論
自從我們的 DeepSearch 系統於 2025 年 2 月 2 日發布以來,我們發現了兩個能夠大幅提升品質的實作細節。值得注意的是,這兩者都以「上下文」的方式使用多語言嵌入和重排序器——與這些模型通常需要的預計算索引相比,運作規模要小得多。這解釋了我們最初的疏忽。
這點出了搜尋技術未來的一個有趣兩極化現象。考慮一個類似於 Kahneman 雙系統理論的框架:
- 快思考(grep、BM25、SQL):快速、有規則的模式匹配,計算需求最小。
- 慢思考(LLM):具有深度上下文理解的全面推理,需要大量計算。
- 中思考(嵌入、重排序器):處於模糊地帶?對於簡單的模式匹配來說太「進階」/語義化,但又缺乏真正的推理能力。
我們可能正在見證一種雙分架構的興起,其中輕量級、高效的 SQL/BM25 處理初始內容檢索,直接輸入到強大的 LLM 進行深度處理。這些 LLM 越來越多地整合了之前需要專門中層模型的語義功能。中思考模型的剩餘角色轉向特定的上下文任務:過濾、去重複,以及完整推理效率不高的有限範圍操作。
然而,選擇關鍵片段和排序 URL 仍然是直接影響 DeepSearch/DeepResearch 系統品質的基本組件。我們希望我們的見解能激發您自己實作的改進。
查詢擴展仍然是另一個關鍵的品質決定因素。我們正在積極評估多種方法——從基本的提示詞重寫到小型語言模型和基於推理的方法。請期待我們即將發布的相關發現。敬請關注。











