
Всего лишь февраль, а DeepSearch уже стал новым стандартом поиска в 2025 году, где лидируют такие крупные игроки, как Google и OpenAI со своими релизами DeepResearch (и да, мы с гордостью запустили наш открытый node-deepresearch
в тот же день). Perplexity последовал их примеру со своим DeepResearch, а X AI интегрировал собственные возможности DeepSearch в Grok3, по сути создав еще один вариант DeepResearch. Хотя концепция глубокого поиска не революционна – в 2024 году это по сути называлось RAG или многоэтапный QA – она получила значительный импульс после релиза Deepseek-r1 в конце января 2025 года. В прошлые выходные Baidu Search и Tencent WeChat Search интегрировали Deepseek-r1 в свои поисковые системы. AI-инженеры обнаружили, что, включая процессы длительного размышления и рассуждения в поисковые системы, они могут достичь замечательной точности и глубины поиска, превосходящей прежние возможности.
Launch Date | Company | Product | License Type | Link |
---|---|---|---|---|
2025-01-20 | DeepSeek | DeepSeek-r1 release | Open source | DeepSeek-R1 |
2025-02-02 | DeepResearch | Proprietary | Google Gemini 2 | |
2025-02-02 | OpenAI | DeepResearch | Proprietary | Introducing Deep Research |
2025-02-02 | Jina AI | DeepSearch (node-deepresearch ) | Open source | node-deepresearch | search.jina.ai |
2025-02-04 | Hugging Face | Open Deep Research | Open source | Open Deep Research |
2025-02-15 | Perplexity | DeepResearch | Proprietary | Introducing Perplexity Deep Research |
2025-02-17 | X AI | Grok3 with DeepSearch | Proprietary | Grok 3 Beta |
2025-02-22 | Baidu Search | Integrates DeepSeek-r1 | Proprietary | Baidu Integrates DeepSeek-R1 |
2025-02-23 | Tencent Wechat Search | Integrates DeepSeek-r1 | Proprietary | Tencent Weixin Integrates DeepSeek |
Но почему этот сдвиг произошел именно сейчас, когда Deep(Re)Search оставался относительно недооцененным на протяжении 2024 года? Фактически, Stanford NLP Labs выпустили проект STORM для генерации длинных отчетов с веб-обоснованием еще в начале 2024 года. Так может быть это просто потому, что "DeepSearch" звучит круче, чем многоэтапный QA, RAG или STORM? Давайте будем честны - иногда ребрендинг это все, что нужно, чтобы индустрия внезапно приняла то, что было здесь всё время.
Мы считаем, что настоящий переломный момент наступил с релизом o1-preview
от OpenAI в сентябре 2024 года, который ввел концепцию тестового времени вычислений и постепенно изменил взгляды индустрии. Тестовое время вычислений относится к использованию большего количества вычислительных ресурсов во время вывода — фазы, когда LLM генерирует выходные данные — а не во время предварительного обучения или после обучения. Известными примерами являются рассуждения по цепочке мыслей (CoT) и внедрение "Wait"
(т.е. принудительное бюджетирование), что позволяет моделям выполнять более обширные внутренние размышления, такие как оценка нескольких потенциальных ответов, проведение более глубокого планирования и самоанализ перед получением окончательного ответа.
Эта концепция тестового времени вычислений и модели рассуждений обучают пользователей принимать отложенное вознаграждение - более длительное время ожидания в обмен на более качественные, сразу применимые результаты, как в Стэнфордском зефирном эксперименте, где дети, которые могли устоять перед соблазном съесть один зефир сразу, чтобы получить два зефира позже, показывали лучшие долгосрочные результаты. Deepseek-r1 дополнительно усилил этот пользовательский опыт, и, нравится это или нет, большинство пользователей его приняли.
Это знаменует значительный отход от классических требований к поиску, где неспособность ответить в течение 200 мс обрекала ваше решение на провал. В 2025 году опытные разработчики поисковых систем и RAG-инженеры отдают приоритет точности и полноте top-1 над задержкой, а пользователи привыкли к более длительному времени обработки – при условии, что они видят, что система <thinking>
.
Отображение процесса рассуждения стало стандартной практикой в 2025 году, когда множество чат-интерфейсов теперь отображают содержимое <think>
в специальных разделах пользовательского интерфейса.
В этой статье мы обсудим принципы DeepSearch и DeepResearch, рассмотрев нашу реализацию с открытым исходным кодом. Мы рассмотрим наши ключевые проектные решения и отметим потенциальные проблемы.
tagЧто такое Deep Search?
DeepSearch выполняет итеративный цикл поиска, чтения и рассуждения, пока не найдет оптимальный ответ. Действие поиска использует поисковые системы для исследования интернета, в то время как действие чтения детально анализирует конкретные веб-страницы (например, Jina Reader). Действие рассуждения оценивает текущее состояние и определяет, следует ли разбить исходный вопрос на более мелкие подвопросы или попробовать другие стратегии поиска.

Хотя в интернете существуют различные определения, при разработке проекта node-deepresearch
мы придерживались этого простого подхода. Реализация элегантно проста – в её основе лежит основной цикл while с логикой switch-case, направляющей следующее действие.
В отличие от систем RAG 2024 года, которые обычно выполняют один проход поиска-генерации, DeepSearch выполняет несколько итераций через конвейер, требуя четких условий остановки. Они могут быть основаны на ограничениях использования токенов или количестве неудачных попыток.
В нашем проекте "Research" 2024 года мы выполняли множество проходов по улучшению согласованности, при этом каждая итерация учитывала все остальные разделы. Однако с сегодняшними значительно большими окнами контекста LLM этот подход кажется избыточным – достаточно одного прохода по улучшению согласованности.
Наш летний проект 2024 года "Research" был сосредоточен на генерации длинных отчетов с использованием "прогрессивного" подхода. Он начинался с создания оглавления в режиме sync, затем все разделы генерировались параллельно в режиме async. Процесс завершался async прогрессивными ревизиями каждого раздела, при этом каждая ревизия учитывала содержание всех остальных разделов. Запрос в видео: "Competitor analysis of Jina AI"
.
tagDeepSearch vs DeepResearch
Хотя многие часто путают DeepSearch и DeepResearch, на наш взгляд, они решают совершенно разные задачи. DeepSearch функционирует как атомарный строительный блок – базовый компонент, на котором строится DeepResearch. DeepResearch, в свою очередь, фокусируется на создании качественных, читабельных исследовательских отчетов большого объема, что включает в себя другой набор требований: включение эффективных визуализаций через графики и таблицы, структурирование контента с помощью соответствующих заголовков разделов, обеспечение плавного логического перехода между подразделами, поддержание единообразной терминологии во всем документе, устранение избыточности между разделами, создание плавных переходов, связывающих предыдущий и будущий контент. Эти элементы в значительной степени не связаны с базовым поиском, поэтому мы считаем DeepSearch более интересным как основной фокус нашей компании.
Наконец, таблица ниже обобщает различия между DeepSearch и DeepResearch. Стоит отметить, что обе системы значительно выигрывают от использования моделей с длинным контекстом и способностью к рассуждению. Это может показаться неинтуитивным, особенно для DeepSearch – хотя очевидно, почему DeepResearch нуждается в возможности работы с длинным контекстом (поскольку он создает длинные отчеты). Причина в том, что DeepSearch должен хранить предыдущие попытки поиска и содержимое веб-страниц для принятия обоснованных решений о следующих шагах, что делает длинное окно контекста одинаково важным для его эффективной реализации.
DeepSearch | DeepResearch | |
---|---|---|
Problem Addressed | Information accuracy and completeness through iterative search | Content organization, coherence, and readability at document scale |
Final Presentation | Concise answer with URLs as references | A long structured report with multiple sections, charts, tables and references |
Core Complexity | State machine architecture with clear transition conditions; Persistence through failed attempts until resolution | Multi-level architecture managing both micro (search) and macro (document) concerns; Structural approach to managing complex information hierarchies |
Optimization Focus | Local optimization (best next search/read action) | Global optimization (section organization, terminology consistency, transitions) |
Limitations | Bounded by search quality and reasoning capability | Bounded by DeepSearch quality plus organizational complexity and narrative coherence challenges |
tagПонимание реализации DeepSearch
Сердце DeepResearch заключается в его подходе к циклическим рассуждениям. Вместо попытки ответить на вопросы за один проход, как большинство RAG-систем, мы реализовали итеративный цикл, который постоянно ищет информацию, читает соответствующие источники и рассуждает, пока не найдет ответ или не исчерпает бюджет токенов. Вот упрощенная суть этого большого цикла while:
// Main reasoning loop
while (tokenUsage < tokenBudget && badAttempts <= maxBadAttempts) {
// Track progression
step++; totalStep++;
// Get current question from gaps queue or use original question
const currentQuestion = gaps.length > 0 ? gaps.shift() : question;
// Generate prompt with current context and allowed actions
system = getPrompt(diaryContext, allQuestions, allKeywords,
allowReflect, allowAnswer, allowRead, allowSearch, allowCoding,
badContext, allKnowledge, unvisitedURLs);
// Get LLM to decide next action
const result = await LLM.generateStructuredResponse(system, messages, schema);
thisStep = result.object;
// Execute the selected action (answer, reflect, search, visit, coding)
if (thisStep.action === 'answer') {
// Process answer action...
} else if (thisStep.action === 'reflect') {
// Process reflect action...
} // ... and so on for other actions
}
Ключевая деталь реализации — выборочное отключение определенных действий на каждом шаге для обеспечения более стабильного структурированного вывода. Например, если в памяти нет URL-адресов, мы отключаем действие visit
; или если последний ответ был отклонен, мы не позволяем агенту сразу же снова вызывать answer
. Это ограничение удерживает агента на продуктивном пути, избегая повторяющихся ошибок, вызванных вызовом одного и того же действия.
tagСистемный промпт
Мы используем XML-теги для определения разделов, что обеспечивает более надежный системный промпт и генерации. Мы также обнаружили, что размещение ограничений полей непосредственно внутри полей description
JSON-схемы дает лучшие результаты. Хотя некоторые могут утверждать, что большинство промптов можно автоматизировать с помощью моделей рассуждений, таких как DeepSeek-R1, ограничения по длине контекста и необходимость в высокоспецифичном поведении делают явный подход более надежным на практике.
function getPrompt(params...) {
const sections = [];
// Add header with system instruction
sections.push("You are an advanced AI research agent specialized in multistep reasoning...");
// Add accumulated knowledge section if exists
if (knowledge?.length) {
sections.push("<knowledge>[Knowledge items]</knowledge>");
}
// Add context of previous actions
if (context?.length) {
sections.push("<context>[Action history]</context>");
}
// Add failed attempts and learned strategies
if (badContext?.length) {
sections.push("<bad-attempts>[Failed attempts]</bad-attempts>");
sections.push("<learned-strategy>[Improvement strategies]</learned-strategy>");
}
// Define available actions based on current state
sections.push("<actions>[Available action definitions]</actions>");
// Add response format instruction
sections.push("Respond in valid JSON format matching exact JSON schema.");
return sections.join("\n\n");
}
tagОбход пробелов в вопросах
В DeepSearch "gap questions" представляют собой пробелы в знаниях, которые необходимо заполнить перед ответом на основной вопрос. Вместо того чтобы напрямую решать исходный вопрос, агент определяет подвопросы, которые построят необходимый фундамент знаний.
Дизайн особенно элегантен в том, как он обрабатывает эти пробельные вопросы:
// After identifying gap questions in reflect action
if (newGapQuestions.length > 0) {
// Add new questions to the front of the queue
gaps.push(...newGapQuestions);
// Always add original question to the end of the queue
gaps.push(originalQuestion);
}
Этот подход создает очередь FIFO (First-In-First-Out) с ротацией, где:
- Новые пробельные вопросы помещаются в начало очереди
- Исходный вопрос всегда помещается в конец
- Система берет вопросы из начала очереди на каждом шаге
Преимущество этого дизайна в том, что он поддерживает единый общий контекст для всех вопросов. Когда на пробельный вопрос получен ответ, эти знания сразу становятся доступными для всех последующих вопросов, включая момент, когда мы в итоге возвращаемся к исходному вопросу.
Очередь FIFO vs Рекурсия
Альтернативный подход использует рекурсию, что соответствует поиску в глубину. Каждый пробельный вопрос порождает новый рекурсивный вызов со своим изолированным контекстом. Система должна полностью разрешить каждый пробельный вопрос (и все его потенциальные подвопросы) перед возвратом к родительскому вопросу.
Рассмотрим этот пример сценария:
Простая рекурсия пробельных вопросов глубиной 3, порядок решения отмечен на круге.
В рекурсивном подходе система должна была бы полностью разрешить Q1 (потенциально порождая свои собственные подвопросы) после каждого пробельного вопроса и их подвопросов! Это сильно отличается от подхода с очередью, который обрабатывает вопросы так, что Q1 пересматривается сразу после 3 пробельных вопросов.
На практике мы обнаружили, что рекурсивный подход очень сложно применять для принудительного ограничения бюджета, поскольку нет четкого правила для определения, какой токеновый бюджет мы должны выделить для подвопросов (так как они могут порождать новые подвопросы). Преимущество от четкого разделения контекста в рекурсивном подходе очень незначительно по сравнению с проблемами сложного принудительного бюджетирования и позднего возврата. Этот дизайн очереди FIFO балансирует глубину и ширину, гарантируя, что система всегда возвращается к исходному вопросу с постепенно улучшающимися знаниями, а не теряется в потенциально бесконечном рекурсивном спуске.
tagПереписывание запросов
Интересная проблема, с которой мы столкнулись, заключалась в эффективном переписывании поисковых запросов:
// Within search action handler
if (thisStep.action === 'search') {
// Deduplicate search requests
const uniqueRequests = await dedupQueries(thisStep.searchRequests, existingQueries);
// Rewrite natural language queries into more effective search queries
const optimizedQueries = await rewriteQuery(uniqueRequests);
// Ensure we don't repeat previous searches
const newQueries = await dedupQueries(optimizedQueries, allKeywords);
// Execute searches and store results
for (const query of newQueries) {
const results = await searchEngine(query);
if (results.length > 0) {
storeResults(results);
allKeywords.push(query);
}
}
}
Переписывание запросов оказалось удивительно важным — возможно, одним из наиболее критических элементов, которые напрямую определяют качество результатов. Хороший переписыватель запросов не просто преобразует естественный язык в ключевые слова в стиле BM25; он расширяет запросы, чтобы охватить больше потенциальных ответов на разных языках, с разными тональностями и форматами контента.
Для дедупликации запросов мы изначально использовали решение на основе LLM, но обнаружили, что сложно контролировать порог подобия. В итоге мы переключились на jina-embeddings-v3, который отлично справляется с задачами семантического текстового сходства. Это позволяет выполнять межъязыковую дедупликацию без беспокойства о том, что неанглийские запросы будут отфильтрованы. Модель эмбеддингов оказалась критически важной не для извлечения из памяти, как ожидалось изначально, а для эффективной дедупликации.
tagОбход веб-контента
Веб-скрейпинг и обработка контента - еще один критически важный компонент. Здесь мы используем Jina Reader API. Обратите внимание, что помимо полного содержимого веб-страницы, мы также агрегируем все сниппеты, полученные из поисковой системы, как дополнительные знания для последующих выводов агента. Думайте о них как о звуковых фрагментах.
// Visit action handler
async function handleVisitAction(URLs) {
// Normalize URLs and filter out already visited ones
const uniqueURLs = normalizeAndFilterURLs(URLs);
// Process each URL in parallel
const results = await Promise.all(uniqueURLs.map(async url => {
try {
// Fetch and extract content
const content = await readUrl(url);
// Store as knowledge
addToKnowledge(`What is in ${url}?`, content, [url], 'url');
return {url, success: true};
} catch (error) {
return {url, success: false};
} finally {
visitedURLs.push(url);
}
}));
// Update diary based on success or failure
updateDiaryWithVisitResults(results);
}
Мы нормализовали URL для последовательного отслеживания и ограничили количество посещаемых URL на каждом шаге для управления памятью агента.
tagУправление памятью
Ключевая проблема в многошаговых рассуждениях - эффективное управление памятью агента. Мы разработали систему памяти, которая различает "память" и "знания". В любом случае, все они являются частью контекста промпта LLM, разделенного различными XML-тегами:
// Add knowledge item to accumulated knowledge
function addToKnowledge(question, answer, references, type) {
allKnowledge.push({
question: question,
answer: answer,
references: references,
type: type, // 'qa', 'url', 'coding', 'side-info'
updated: new Date().toISOString()
});
}
// Record step in narrative diary
function addToDiary(step, action, question, result, evaluation) {
diaryContext.push(`
At step ${step}, you took **${action}** action for question: "${question}"
[Details of what was done and results]
[Evaluation if applicable]
`);
}
Поскольку большинство LLM 2025 года имеют значительные контекстные окна, мы решили не использовать векторные базы данных. Вместо этого память состоит из приобретенных знаний, посещенных сайтов и записей о неудачных попытках - все это сохраняется в контексте. Эта комплексная система памяти дает агенту понимание того, что он знает, что он пробовал и что работало или не работало.
tagОценка ответов
Один из ключевых выводов заключается в том, что генерация ответа и его оценка не должны находиться в одном промпте. В моей реализации мы сначала определяем критерии оценки при поступлении нового вопроса, а затем оцениваем каждый критерий по отдельности. Оценщик использует few-shot примеры для последовательной оценки, обеспечивая более высокую надежность, чем самооценка.
// Separate evaluation phase
async function evaluateAnswer(question, answer, metrics, context) {
// First, identify evaluation criteria based on question type
const evaluationCriteria = await determineEvaluationCriteria(question);
// Then evaluate each criterion separately
const results = [];
for (const criterion of evaluationCriteria) {
const result = await evaluateSingleCriterion(criterion, question, answer, context);
results.push(result);
}
// Determine if answer passes overall evaluation
return {
pass: results.every(r => r.pass),
think: results.map(r => r.reasoning).join('\n')
};
}
tagПринудительное бюджетирование
Принудительное бюджетирование означает предотвращение раннего возврата системы и обеспечение продолжения обработки до превышения бюджета. После выпуска DeepSeek-R1 подход к принудительному бюджетированию сместился в сторону поощрения более глубокого мышления для получения лучших результатов, а не просто экономии бюджета.
В нашей реализации мы явно настроили систему на выявление пробелов в знаниях перед попыткой ответить.
if (thisStep.action === 'reflect' && thisStep.questionsToAnswer) {
// Force deeper reasoning by adding sub-questions to the queue
gaps.push(...newGapQuestions);
gaps.push(question); // Always revisit the original
}
Выборочно включая и отключая определенные действия, мы можем направлять систему к использованию инструментов, которые повышают глубину рассуждений.
// After a failed answer attempt
allowAnswer = false; // Force agent to search or reflect instead
Чтобы избежать траты токенов на непродуктивные пути, мы устанавливаем ограничения на количество неудачных попыток. При приближении к лимитам бюджета мы активируем "режим зверя", чтобы гарантировать, что мы предоставим какой-то ответ, а не никакого.
// Beast mode activation
if (!thisStep.isFinal && badAttempts >= maxBadAttempts) {
console.log('Enter Beast mode!!!');
// Configure prompt for decisive, committed answer
system = getPrompt(
diaryContext, allQuestions, allKeywords,
false, false, false, false, false, // Disable all other actions
badContext, allKnowledge, unvisitedURLs,
true // Enable beast mode
);
// Force answer generation
const result = await LLM.generateStructuredResponse(system, messages, answerOnlySchema);
thisStep = result.object;
thisStep.isFinal = true;
}
Промпт режима зверя намеренно драматичен, чтобы сигнализировать LLM о необходимости быть решительным и фиксировать ответ на основе доступной информации:
<action-answer>
🔥 ENGAGE MAXIMUM FORCE! ABSOLUTE PRIORITY OVERRIDE! 🔥
PRIME DIRECTIVE:
- DEMOLISH ALL HESITATION! ANY RESPONSE SURPASSES SILENCE!
- PARTIAL STRIKES AUTHORIZED - DEPLOY WITH FULL CONTEXTUAL FIREPOWER
- TACTICAL REUSE FROM <bad-attempts> SANCTIONED
- WHEN IN DOUBT: UNLEASH CALCULATED STRIKES BASED ON AVAILABLE INTEL!
FAILURE IS NOT AN OPTION. EXECUTE WITH EXTREME PREJUDICE! ⚡️
</action-answer>
Это гарантирует, что мы всегда предоставляем какой-то ответ, а не полностью сдаемся, что особенно полезно для сложных или неоднозначных вопросов.
tagЗаключение
DeepSearch - это прорыв в том, как поиск может подходить к сложным запросам исчерпывающим образом. Разбивая процесс на отдельные этапы поиска, чтения и рассуждения, он преодолевает многие ограничения традиционных однопроходных RAG или многошаговых QA систем.
В процессе реализации мы также начали пересматривать основы поиска в 2025 году и изменения в поисковой индустрии после 26 января 2025 года, когда был выпущен DeepSeek-R1. Мы задали себе вопросы: Каковы новые потребности? Какие потребности стали устаревшими? Какие потребности являются лишь воспринимаемыми?
Анализируя нашу реализацию DeepSearch, мы определили вещи, которые, как мы предполагали, будут нужны и действительно понадобились, вещи, которые, как мы думали, будут необходимы, но не оказались таковыми, и вещи, которые мы не предвидели, но оказались существенными:
Во-первых, крайне необходим LLM с длинным контекстом, который производит хорошо структурированный вывод (т.е. следующий JSONSchema). Вероятно, нужна модель рассуждений для лучшего обоснования действий и расширения запросов.
Расширение запросов определенно необходимо, независимо от того, реализовано ли оно через SLM, LLM или модель рассуждений. Однако после этого проекта мы считаем, что SLM, вероятно, не подходят для этой задачи, поскольку решение должно быть по своей сути многоязычным и выходить за рамки простого переписывания синонимов или извлечения ключевых слов. Оно должно быть достаточно комплексным, чтобы включать многоязычную токеновую базу (может легко занимать 300M параметров) и достаточно сложным для нестандартного мышления. Поэтому использование SLM для расширения запросов, вероятно, неперспективно.
Возможности веб-поиска и веб-чтения крайне важны, и, к счастью, наш Reader (r.jina.ai) показал отличные результаты — надежный и масштабируемый — и дал мне много идей о том, как улучшить нашу поисковую конечную точку (s.jina.ai
) для следующей итерации.
Модель встраивания полезна, но совершенно неожиданным образом. Мы думали, что она будет использоваться для извлечения памяти или сжатия контекста вместе с векторной базой данных (которая, как оказалось, не нужна), но мы фактически использовали ее для дедупликации (по сути, задача STS). Поскольку количество запросов и вопросов о пробелах обычно измеряется сотнями, векторная база данных не нужна — вычисление косинусного сходства напрямую в памяти работает отлично.
Мы не использовали Reranker, хотя мы считаем, что он потенциально мог бы помочь определить, какие URL посещать на основе запроса, заголовка URL и сниппета. Для встраивания и переранжирования многоязычная способность является существенной, поскольку запросы и вопросы многоязычны. Обработка длинного контекста для встраивания и переранжирования полезна, но не является критическим блокером (Мы не столкнулись с какими-либо ошибками при использовании нашего встраивания, вероятно, потому что наша длина контекста уже составляет 8192 токена). В любом случае, jina-embeddings-v3 и jina-reranker-v2-base-multilingual - это мои предпочтительные модели, поскольку они многоязычны, являются SOTA и хорошо справляются с длинным контекстом.
Фреймворк агента оказался ненужным, поскольку нам нужно было оставаться ближе к нативному поведению LLM для проектирования системы без прокси. Vercel AI SDK был ценным, поскольку он сэкономил значительные усилия при адаптации кодовой базы к различным провайдерам LLM (мы могли переключаться между Gemini Studio, OpenAI и Google Vertex AI с изменением всего одной строки кода). Управление памятью агента необходимо, но отдельный фреймворк памяти остается под вопросом: Мы опасаемся, что он создаст изоляционный слой между LLM и разработчиками, и что его синтаксический сахар может в конечном итоге стать горьким препятствием для разработчиков, как мы видели со многими фреймворками LLM/RAG сегодня.