如果你已经读过 Jina 上一篇经典长文《DeepSearch/DeepResearch 的设计与实现》,那么不妨再深挖一些能大幅提升回答质量的细节。这次,我们将重点关注两个细节:
-
从长网页提取最优文本段:如何利用迟分(late-chunking)算法,从长网页内容中选取最相关的信息小片段。 -
对收集到的URL进行重排:如何利用重排器(Reranker) 让 LLM Agent 在几百个URL中聪明地选择爬取哪一个 URL?
可能有人还记得我们上一篇里的结论:“在 DeepSearch 中,Embeddings 模型仅适用于诸如 STS(语义文本相似度)任务之类的查询去重,而 Reranker 甚至不在我们最初的 DeepSearch 编程实现中。”
现在看来,这两类召回模型还是有其价值的,只是用法和我们常规的认知不太一样。我们做搜索一直遵循“80-20”原则,不会为了照顾情绪价值,或为证明自己作为 Embeddings 和 Reranker 提供商的市场存在感而硬上什么模型。我们非常 80-20,非常务实,务实到我们只关心搜索系统的最最本质的需求。
所以,经过数周的反复试验和迭代,我们发现了 Embeddings 和 Reranker 在 DeepSearch/DeepResearch 系统中的一些非常规但非常有效的应用。用了这些方法后,我们显著提高了 Jina DeepSearch 的质量(欢迎大家来体验)。我们也想把这些经验分享给在这个领域一起摸爬滚打的同行们。
从长文本中选取最优文本段
问题是这样的:用 ジーナ・リーダー 读取网页内容后,我们需要把它作为一条知识,放到 Agent 的上下文里,供它推理。虽然把全部内容一股脑塞进 LLM 的上下文是最省事的办法,但考虑到 トークン 成本和生成速度,这肯定不是最好的选择。在实际应用里,我们需要找出内容中与问题最相关的部分,只把这些部分作为知识添加到 Agent 的上下文里。
💡 这里说的是,即使用 Jina Reader 清理成干净 Markdown 后,内容仍然太长的情况。比如 GitHub Issues、Reddit 帖子、论坛讨论和博客文章等长页面中。
基于 LLM 的筛选方法也有同样的成本和延迟问题,所以我们得找找有没有小模型的解决方案:我们需要更小、更便宜,但仍然支持多种语言的模型。这是个关键因素,因为我们没法保证问题或文档永远都是中文的。
我们一边是问题(原始查询或“信息差”问题),另一边是大量的 Markdown 内容,其中大部分内容都是无关紧要的。我们需要选出与问题最相关的片段。这就很像 ラグ 社区自 2023 年以来一直在努力解决的分块问题——使用检索器模型只检索相关块,并放到上下文窗口中进行总结。
不过,我们的情况有两个关键区别:
-
有限数量文档中的有限数量的文本块。
假设每个块大约有 500 个 Token,那么一个典型的长网页文档大约有 20 万 Token(中位数)到 100 万 Token(99 分位)。我们每一步用 Jina Reader 抓取 4-5 个 URL,这样大概会产生几百个文本块。也就是说,几百个向量和几百个余弦相似度。用 JavaScript 在内存里就能轻松处理,根本不需要向量数据库。
-
我们需要连续的文本块来形成有效的知识摘要。
我们不能接受像 [1-2, 6-7, 9, 14, 17, ...] 这样由分散的句子组成的摘要。更有用的知识摘要应该是像 [3-15, 17-24, ...] 这样的,更能保持文本的连贯性。这样 LLM 更容易从知识源中复制和引用,也能减少“幻觉”。
剩下的都是开发者们抱怨的那些注意事项:每个文本块不能太长,因为向量模型处理不了太长的上下文;分块会导致上下文丢失,并使得每个文本块的向量都是独立同分布的;以及,到底怎么找到既保持可读性又保持语义的最佳边界?如果你知道我们在说什么,那你很可能在你的 RAG 实现中也被这些问题困扰过。
不过长话短说——使用 ジーナ・エンベディングス-V3
な 迟分(Late Chunking)完美地解决了这三个问题。“迟分”保留了每个块的上下文信息,对边界不敏感,而且 ジーナ・エンベディングス-V3
本身在非对称多语言检索任务中也是最先进的。感兴趣的读者可以关注 Jina 的博客文章或论文了解详情,总体的实现是这样的。
🔗 https://arxiv.org/pdf/2409.04701
使用迟分的片段选择的流程图
这张图展示了摘要选择算法,它的工作原理类似于一维卷积(Conv1D)。这个过程首先把一个长文档分割成固定长度的块,然后用开启了迟分的 ジーナ・エンベディングス-V3
向量化这些文本块。计算完每个块和问题之间的相似度分数后,一个滑动窗口会在这些相似度分数上移动,以找到平均值最高的窗口。
以下是示意代码:用迟分和类似“一维卷积”的平均池化,挑出跟问题最相关的段落。
関数cherryPick(question, longContext, options) { 以下のようになります。
if (longContext.length < options.snippetLength * options.numSnippets)
return longContext; }.
const chunks = splitIntoChunks(longContext, options.chunkSize);
const chunkEmbeddings = getEmbeddings(chunks, "回収.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 = [.
const similaritiesCopy = [..similarities];
for (let i = 0; i < options.numSnippets; i++) { .
let bestStartIndex = 0; let bestScore = -Infinity; let bestScore = -Infinity
let bestScore = -Infinity;
for (let j = 0; j bestScore) { (windowScore = windowScore)
bestScore = windowScore; const windowScore = average(windowScores); if (windowScore > bestScore) { }.
bestStartIndex = j; }.
}
}
const startIndex = bestStartIndex * options.chunkSize;
const endIndex = Math.min(startIndex + options.snippetLength, longContext.length); snippets.push(longContext.substring(startIndex, longContext.length)); } } } スニペット。
snippets.push(longContext.substring(startIndex, endIndex));
for (let k = bestStartIndex; k < bestStartIndex + chunksPerSnippet; k++)
similaritiesCopy[k] = -無限大;
}
return snippets.join("\n");
}
调用 Jina Embeddings API 的时候,记得把 タスク
设置成 retrieval,打开 レイト・チャンキング
属切り捨てる
也要像下面这样设置:
await axios.post(
'https://api.jina.ai/v1/embeddings',
{
model: "jina-embeddings-v3",
task: "retrieval.passage",
late_chunking: true,
input: chunks,
truncate: true
},
{ headers });
如果是要向量化问题,记得把 タスク
换成 検索クエリー
,然后关掉 レイト・チャンキング
.
完整的实现代码可以在 GitHub 上找到:https://github.com/jina-ai/node-DeepResearch/blob/main/src/tools/jina-latechunk.ts
为“下一步阅读”进行 URL 排序
问题如下:在每一次 DeepSearch 漫长过程中,你可能会从搜索引擎结果页(SERP)里收集一堆 URL,每打开一个网页,又能顺藤摸瓜找出不少新链接,就算是去重后,也是轻轻松松几百个网址。同样的,一股脑儿全塞给 LLM 肯定不行,浪费宝贵的上下文长度不说,更要命的是,我们发现 LLM 基本上就是瞎选。所以,得想办法引导 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をクリックする前の情報だけです。
1.周波数信号URLが異なるソースに何度も表示される場合、そのURLはより重視される。また、あるドメイン名が検索結果に頻繁に表示される場合、そのドメインからのURLはさらに加点される。これは、一般的に、人気のあるドメインは、より権威のあるコンテンツを含む傾向があるためです。
2.路線構成URLのパス構造を分析し、どのようなコンテンツが集まっているかを判断します。複数のURLがすべて同じパス階層に属していれば、スコアは高くなりますが、パスが深くなればなるほど、スコアボーナスは徐々に減っていきます。
3.意味的関連性を使用する。 jina-reranker-v2-base-多言語
これは、質問の意味的関連性と各URLのテキスト情報(タイトルや抄録など)を評価するためのもので、典型的な並べ替え問題である。各URLのテキスト情報はいくつかの場所から得られる:
-
Search Engine Results Page (SERP) API タイトルと要約を返す (https://s.jina.ai/ このインターフェイスは、'X-Respond-With': 'no-content'で、タイトルと要約だけを返し、特定のコンテンツは返しません)。 -
ページ上のURLのアンカーテキスト(https://r.jina.ai のインターフェイスを使い、'X-With-Links-Summary': 'all'を設定すると、ページ内のすべてのリンクの要約情報、つまりアンカーテキストが返されます)。
4.最終更新日DeepSearchのクエリの中には、高い適時性が要求されるものがあるため、一般的には、新しいURLほど高い値が付けられます。しかし、Googleの大規模なインデックス作成能力がなければ、ウェブページの最終更新時刻を正確に判断することは困難です。そこで、以下のシグナルを組み合わせてタイムスタンプと信頼スコアを与え、必要なときに最新のコンテンツを優先的に表示できるようにしています:
-
SERP APIが提供するフィルタリング機能(例えば、s.jina.aiのtbsパラメータは時間によるフィルタリングを可能にする)。 -
HTTPヘッダー情報の分析(Last-ModifiedやETagフィールドなど)。 -
メタデータの抽出(メタタグやSchema.orgのタイムスタンプなど)。 -
コンテンツパターン認識(HTMLに表示される日付を認識)。 -
特定のCMSプラットフォーム(WordPress、Drupal、Ghostなど)の指標
5.制限されたコンテンツソーシャルメディアプラットフォームの中には、アクセスが制限されていたり、有料であったりするコンテンツがあります。ログインせずにこのコンテンツに合法的にアクセスする方法はありません。したがって、私たちは積極的にこれらの問題のあるURLやドメインのブラックリストを維持し、ランキングを下げ、このアクセスできないコンテンツにコンピューティングリソースを浪費しないようにしています。
6.ドメイン名の多様性上位のURLがすべて同じドメインからのものであることがあり、DeepSearchが「局所最適」に陥り、最終結果の品質に影響を与えることがあります。前述したように、上位のURLはすべてStackOverflowからのものであるため、結果の多様性を高めるために、「exploit-and-exploit」戦略を使用することができます: 各ドメインから上位K個のURLを選択します。
URLソートの完全な実装コードはGithubにあります: https://github.com/jina-ai/node-DeepResearch/blob/main/src/utils/url-tools.ts#L192
<アクション訪問
- クロールしてURLの全コンテンツを読み取ることで、URLの全文、最終更新日時などを取得することができます。
- もしあれば、に記載されているURLをチェックすること。
- より多くの知識を得るために、以下の関連するURLを選択し、訪問してください。
<url-list
+ weight: 0.20 "https://huggingface.co/docs/datasets/en/loading": "Load - Hugging Faceデータセットを待つ代わりに時間を節約できます。Dataset builderのダウンロードがタイムアウトするのを待つ代わりに、Datasetが直接キャッシュを検索するので、時間を節約できます。 環境を設定する.....データセットによっては、Gitタグやブランチ、コミットに基づいて複数のバージョンを持っている場合があります。 リビジョンパラメータを使って、読み込みたいデータセットのバージョンを指定します。ロードする..."
+ weight: 0.20 "https://huggingface.co/docs/datasets/en/index": "Datasets - Hugging Face🤗 Datasetsは、オーディオのデータセットに簡単にアクセスして共有するためのライブラリです。データセットを共有するためのライブラリです。 コンピューター 視覚、自然言語処理(NLP)タスク。..." のデータセットをロードする。
+ weight: 0.17 "https://github.com/huggingface/datasets/issues/7175": "[FSTimeoutError] load_dataset - Issue #7175 - huggingface/datasetsHuggingFaceM4/VQAv2をロードするためにload_datasetを使用すると、FSTimeoutErrorが発生します。 Error TimeoutError: 上記の例外は、HuggingFaceM4/VQAv2をロードするためにload_datasetを使用すると、FSTimeoutErrorが発生します。上記の例外は、以下の直接の原因です..."
+ weight: 0.15 "https://github.com/huggingface/datasets/issues/6465":"`load_dataset`は、....を再ダウンロードする代わりに、古いキャッシュを使用します。.データセットがハブ上で更新された場合、load_datasetを使用すると、更新されたデータセットを再ダウンロードする代わりに、ローカルにキャッシュされたデータセットをロードします。" + weight: 0.15 "":"`load_dataset` uses out-of-date cache instead of re-downloading a ....
+ 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 ..私は彼らのデータビューアからparquetオプションを使ってデータを取得する必要がありました。 しかし、私はそれを実行しようとすると、HTTPエラーのようなものがあります。 私はダウンロードを試しました... ..."
</url-list
</action-visit
概要
2025年2月2日にジーナのDeepSearchが利用可能になって以来、品質を劇的に向上させることができる2つのエンジニアリングの詳細が発見された。興味深いことに、どちらの詳細も、多言語EmbeddingとRerankerモデルを「in-context window」方式で利用している。これらのモデルが通常必要とする、事前に計算された膨大なインデックスに比べれば大したことはない。
このことは、検索技術の未来が二極化した方向に進むことを予感させるかもしれない。この傾向を理解するために、カーネマンの二重過程論を借りることができる:
-
早く考えろ。 高速思考 (grep、BM25、SQL): 最小限の計算で高速なルールベースのパターンマッチング。 -
ゆっくり考える スローシンク (LLM):深い文脈理解を伴う包括的な推論を行うが、計算量が多い。 -
ミディアムバンド ミッドシンク (エンベッディング、リランカー他、リコールモデル):ある程度の意味理解を持ち、単純なパターンマッチングよりは良いが、LLMよりははるかに推論が少ない。
ひとつの可能性がある:二層構造の検索アーキテクチャがますます普及している軽量で効率的なSQL/BM25が検索の入力を担当し、その結果を直接LLMに送り、出力を検索する。中間層モデルの残存価値は、特定のコンテキスト・ウィンドウ内のタスク(例えば、フィルタリング、重複排除、ソートなど)にシフトされる。このようなシナリオでは、LLMで完全な推論を行うのは非効率的である。
しかし、とにかくだ。キークリップの選択 歌で応える URLソート それでもなお、DeepSearch/DeepResearch システムの品質に直接影響する基本的な側面は変わりません。私たちの調査結果が、皆様のシステムの改善に役立つことを願っています。
クエリーの拡大も、クオリティを決める重要な要素だ。野菜だ。単純なプロンプトベースの書き換えから、小さなモデル、推論ベースのアプローチまで、様々なアプローチを積極的に評価しています。今後の研究成果にご期待ください!