
Если вы уже прочитали наше руководство по внедрению DeepSearch/DeepResearch, давайте глубже рассмотрим некоторые детали, которые могут значительно улучшить качество. В этой статье мы сосредоточимся на двух ключевых задачах: использование эмбеддингов для выбора фрагментов из длинных веб-страниц и использование ранжировщиков для приоритизации URL-адресов при сканировании.
Некоторые могут вспомнить наш предыдущий вывод о том, что "эмбеддинги были полезны только для дедупликации запросов, как в задачах STS (semantic textual similarity), в то время как ранжировщики даже не входили в нашу первоначальную реализацию DeepSearch". Оказывается, оба компонента все еще довольно ценны — просто не так, как можно было бы ожидать. Мы всегда следовали самому простому возможному пути. Мы не добавляем компоненты только для того, чтобы оправдать их существование или нашу ценность как поставщика эмбеддингов и ранжировщиков. Мы основываемся на том, что действительно необходимо для поиска в его основе.
После нескольких недель экспериментов и итераций мы обнаружили необычные, но эффективные способы использования обоих компонентов в системах DeepSearch/DeepResearch. Применяя их, мы значительно улучшили качество Jina DeepSearch (можете попробовать сами). Мы хотели бы поделиться этими выводами с коллегами, работающими в этой области.
tagВыбор фрагмента из длинного контента
Проблема заключается в следующем: после использования Jina Reader для чтения содержимого веб-страницы, нам нужно добавить его как элемент знаний в контекст агента для рассуждений. Хотя загрузка всего содержимого в контекстное окно LLM — самый простой способ, это не оптимально с точки зрения стоимости токенов и скорости генерации. На практике нам нужно определить, какие части контента наиболее релевантны вопросу, и выборочно добавлять только эти части в контекст агента.
Фильтрация на основе LLM имеет те же проблемы с затратами и задержкой, поэтому давайте найдем решения с использованием меньших моделей: нам нужны меньшие и более дешевые, но все же многоязычные модели — это критически важный фактор, поскольку мы не можем гарантировать, что запрос или документы всегда будут на английском языке.
С одной стороны у нас есть вопрос (либо исходный запрос, либо уточняющий вопрос), а с другой — большой markdown-контент, где большая часть содержимого нерелевантна. Нам нужно выбрать наиболее релевантные фрагменты для запроса. Это напоминает проблему разбиения на чанки, с которой сообщество RAG боролось с 2023 года — извлечение только релевантных чанков с помощью моделей-ретриверов для размещения в контекстном окне для суммаризации. Однако в нашем случае есть два ключевых отличия:
- Ограниченное количество чанков из ограниченного числа документов. Если каждый чанк содержит примерно 500 токенов, то типичный длинный веб-документ имеет около 200 000 токенов (p50) до 1 000 000 токенов (p99), и мы используем Jina Reader для получения 4-5 URL на каждом шаге, это даст примерно сотни чанков — то есть сотни векторов эмбеддингов и сотни косинусных сходств. С этим легко справиться в JavaScript с памятью без векторной базы данных.
- Нам нужны последовательные чанки для формирования эффективных фрагментов знаний. Мы не можем принимать фрагменты, комбинирующие разрозненные предложения вроде
[1-2, 6-7, 9, 14, 17, ...].
Более полезный фрагмент знаний будет следовать шаблонам вроде[3-15, 17-24, ...]
— всегда сохраняя последовательный текст. Это облегчает LLM копирование и цитирование из источника знаний и уменьшает галлюцинации.
Остальное — это все те предостережения, на которые жаловались практики: каждый чанк не может быть слишком длинным, поскольку модели эмбеддингов плохо справляются с длинным контекстом; разбиение на чанки приводит к потере контекста и делает эмбеддинги чанков независимыми и одинаково распределенными; и как вообще найти лучшие граничные маркеры, которые сохраняют как читаемость, так и семантику? Если вы понимаете, о чем мы говорим, то, вероятно, вас тоже преследовали эти проблемы в ваших 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 с параметрами 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 вы, вероятно, соберете много URL-адресов со страниц результатов поиска (SERP) и обнаружите еще больше при чтении отдельных веб-страниц (ссылки на странице). Общее количество уникальных 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"
Лучший вариант использования Jina Reader для сканирования страницы в DeepSearch. Это соберет все ссылки на странице в отдельное поле 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 (например, параметр
tbs
s.jina.ai для фильтрации по новизне) - Анализ HTTP-заголовков (Last-Modified, ETag)
- Извлечение метаданных (мета-теги, временные метки Schema.org)
- Распознавание шаблонов контента (видимые даты в HTML)
- Специфические индикаторы CMS для платформ, таких как WordPress, Drupal и Ghost
Закрытый контент: Некоторый контент в социальных сетях закрыт или просто находится за платным доступом, и без входа в систему или нарушения их Условий использования нет законного способа получить этот контент. Мы должны активно поддерживать список проблемных 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 2 февраля 2025 года мы обнаружили две детали реализации, которые существенно улучшили качество. Примечательно, что обе используют многоязычные embeddings и rerankers "в контексте" — работая в гораздо меньшем масштабе, чем традиционные предварительно вычисленные индексы, которые обычно требуются этим моделям. Это объясняет, почему мы изначально упустили их из виду.
Это указывает на интересную поляризацию в будущем технологий поиска. Рассмотрим framework, аналогичный теории двойного процесса Канемана:
- Быстрое мышление (grep, BM25, SQL): Быстрое, управляемое правилами сопоставление шаблонов с минимальными вычислительными требованиями.
- Медленное мышление (LLM): Комплексное рассуждение с глубоким пониманием контекста, требующее значительных вычислений.
- Среднее мышление (embeddings, rerankers): Застряло в лимбо? Слишком "продвинутое"/семантическое для простого сопоставления шаблонов, но не обладающее истинными способностями к рассуждению.
Возможно, мы наблюдаем популярность бифуркационной архитектуры, где легковесный, эффективный SQL/BM25 обрабатывает начальное извлечение контента, передавая его напрямую в мощные LLM для глубокой обработки. Эти LLM все чаще включают семантические функции, которые ранее требовали специализированных моделей среднего уровня. Оставшаяся роль для моделей среднего мышления смещается к специализированным контекстным задачам: фильтрации, дедупликации и операциям ограниченного охвата, где полное рассуждение было бы неэффективным.
Тем не менее, выбор критически важных фрагментов и ранжирование URL остаются фундаментальными компонентами, напрямую влияющими на качество систем DeepSearch/DeepResearch. Мы надеемся, что наши выводы помогут улучшить ваши собственные реализации.
Расширение запросов по-прежнему остается еще одним важным фактором качества. Мы активно оцениваем множество подходов — от базовых переписываний на основе промптов до малых языковых моделей и методов, основанных на рассуждениях. Ждите наших предстоящих результатов по этому направлению. Следите за обновлениями.