
现在才 2 月,Deep Search 就已经成为 2025 年的新搜索标准,以 Google 和 OpenAI 为首的主要玩家通过他们的 Deep Research 发布引领潮流(是的,我们也自豪地在同一天发布了开源的 node-deepresearch
)。Perplexity 紧随其后推出了他们的 Deep Research,X AI 则将他们自己的 Deep Search 功能整合到 Grok3 中,本质上创造了另一个 Deep Research 变体。虽然深度搜索的概念并不革命性——在 2024 年它本质上被称为 RAG 或多跳 QA——但在 2025 年 1 月底 Deepseek-r1 发布后,它获得了显著的发展势头。上周末,百度搜索和腾讯微信搜索已将 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 年都相对不受重视?事实上,斯坦福 NLP 实验室在 2024 年初就发布了 STORM 项目,用于基于网络的长篇报告生成。那么,是不是仅仅因为"Deep Search"听起来比多跳 QA、RAG 或 STORM 更酷?让我们诚实点——有时候,一次品牌重塑就足以让行业突然接受一直存在的东西。
我们认为真正的转折点是 OpenAI 在 2024 年 9 月发布的 o1-preview
,它引入了推理时计算(test-time compute)的概念,并逐渐改变了行业观点。推理时计算指的是在推理阶段(LLM 生成输出的阶段)使用更多计算资源,而不是在预训练或后训练阶段。众所周知的例子包括思维链(Chain-of-Thought,CoT)推理和 "Wait"
注入(即预算强制),这使模型能够进行更广泛的内部深思熟虑,比如评估多个潜在答案、进行更深入的规划,并在得出最终回答之前进行自我反思。
这种推理时计算概念和推理模型教育用户接受延迟满足——用较长的等待时间换取更高质量、可立即行动的结果,就像斯坦福棉花糖实验中,能够抵制立即吃掉一个棉花糖以获得之后两个棉花糖的孩子表现出更好的长期结果。Deepseek-r1 进一步强化了这种用户体验,不管喜欢与否,大多数用户都已经接受了这一点。
这标志着与经典搜索要求的重大转变,在过去,如果无法在 200ms 内响应就会导致你的解决方案失败。在 2025 年,经验丰富的搜索开发者和 RAG 工程师优先考虑 top-1 精确率和召回率而不是延迟,用户已经习惯了较长的处理时间——只要他们能看到系统在 <thinking>
。
在 2025 年,显示推理过程已成为标准做法,许多聊天界面现在都在专门的 UI 区域中渲染 <think>
内容。
在本文中,我们将通过研究我们的开源实现来讨论 DeepSearch 和 DeepResearch 的原理。我们将介绍关键设计决策并强调潜在的注意事项。
tag什么是 Deep Search?
DeepSearch 通过搜索、阅读和推理的迭代循环运行,直到找到最优答案。搜索操作利用搜索引擎探索互联网,而阅读操作详细分析特定网页(例如 Jina Reader)。推理操作评估当前状态,并决定是否将原始问题分解为更小的子问题或尝试不同的搜索策略。

虽然网上存在各种定义,但在开发 node-deepresearch
项目时,我们遵循了这种直接的方法。实现非常简单——其核心是一个主 while 循环,带有 switch-case 逻辑来指导下一步操作。
与 2024 年的 RAG 系统不同,后者通常只运行一次搜索-生成过程,DeepSearch 则通过管道执行多次迭代,需要明确的停止条件。这些条件可以基于 token 使用限制或失败尝试的次数。
在 search.jina.ai 尝试 deep search,观察 <thinking>
中的内容,看看你是否能发现循环发生的位置
看待 DeepSearch 的另一个角度是将其视为配备了各种网络工具(如搜索器和阅读器)的 LLM 代理。该代理通过分析当前观察和过去的行动来决定下一步——决定是提供答案还是继续探索网络。这创建了一个状态机架构,其中 LLM 控制状态之间的转换。在每个决策点,你有两种方法:你可以精心设计提示词让标准生成模型产生特定操作,或者利用像 Deepseek-r1 这样的专门推理模型来自然地推导出下一步操作。然而,即使使用 r1,你也需要定期中断其生成过程,将工具输出(例如搜索结果、网页内容)注入上下文中,并提示它继续其推理过程。
最终,这些只是实现细节——无论你是精心设计提示词还是直接使用推理模型,它们都符合 DeepSearch 的核心设计原则:搜索、阅读和推理的持续循环。
tag那么什么是 DeepResearch?
DeepResearch 在 DeepSearch 的基础上增加了生成长篇研究报告的结构化框架。它通常从创建目录开始,然后系统地将 DeepSearch 应用于每个所需部分——从引言到相关工作和方法论,一直到结论。每个部分都是通过将特定研究问题输入 DeepSearch 来生成的。最后阶段是将所有部分整合到单个提示中,以提高整体叙述的连贯性。

在我们 2024 年的"Research"项目中,我们执行了多次连贯性改进,每次迭代都会考虑所有其他章节。然而,随着当今 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 系统试图一次性回答问题不同,我们实现了一个迭代循环,持续搜索信息、阅读相关来源并进行推理,直到找到答案或用完 token 预算。以下是这个大型 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
操作;或者如果上一个答案被拒绝,我们会阻止 agent 立即再次调用 answer
。这种约束使 agent 保持在一个富有成效的路径上,避免因重复调用相同操作而导致的失败。
tagSystem Prompt
我们使用 XML 标签来定义各个部分,这样可以生成更稳健的系统提示和生成内容。我们还发现,将字段约束直接放在 JSON schema 的 description
字段中会产生更好的结果。虽然有人可能会说大多数提示都可以用 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 中,"知识空白问题"代表在回答主要问题之前需要填补的知识缺口。与直接解决原始问题不同,agent 会识别出能够构建必要知识基础的子问题。
这种设计在处理这些知识空白问题时特别优雅:
// 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(先进先出)队列,其中:
- 新的知识空白问题被推到队列前面
- 原始问题总是被推到队尾
- 系统在每个步骤从队列前面提取问题
这种设计的优秀之处在于它为所有问题维护了一个共享的上下文。当一个知识空白问题得到回答时,这些知识立即可用于所有后续问题,包括当我们最终重新访问原始问题时。
FIFO 队列 vs 递归
另一种方法是使用递归,这对应于深度优先搜索。每个知识空白问题都会产生一个具有自己独立上下文的新递归调用。系统必须完全解决每个知识空白问题(及其所有潜在的子问题)才能返回到父问题。
考虑这个示例场景:
一个简单的 3 层深度知识空白问题递归,圆圈中标注了解决顺序。
在递归方法中,系统必须在每个知识空白问题及其子问题之后完全解决 Q1(可能会产生自己的子问题)!这与队列方法形成鲜明对比,在队列方法中,Q1 在 3 个知识空白问题之后就会被重新访问。
在实践中,我们发现递归方法很难应用预算限制,因为没有明确的经验法则来决定应该为子问题分配多少 token 预算(因为它们可能会产生新的子问题)。与复杂的预算限制和延迟返回问题相比,递归方法中清晰的上下文分离带来的好处非常有限。这种 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,它在语义文本相似性任务上表现出色。这使得跨语言去重成为可能,而不用担心非英语查询会被过滤。embedding 模型最终成为了关键,不是最初预期的用于内存检索,而是用于高效去重。
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]
`);
}
由于 2025 年大多数 LLM 都有很大的上下文窗口,我们选择不使用向量数据库。相反,内存由获得的知识、访问过的网站和失败尝试的记录组成——所有这些都保存在上下文中。这个全面的内存系统让代理能够意识到它所知道的、尝试过的以及哪些成功或失败了。
tag答案评估
一个关键见解是答案生成和评估不应该在同一个提示中。在我的实现中,当新问题到达时,我们首先确定使用哪些评估标准,然后逐一评估每个标准。评估器使用少量示例进行一致性评估,确保比自我评估更可靠。
// 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
为避免在无效路径上浪费 token,我们对失败尝试次数设置了限制。当接近预算限制时,我们激活"野兽模式"以确保我们提供某个答案而不是没有答案。
// 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 年的搜索基础以及 2025 年 1 月 26 日 DeepSeek-R1 发布后搜索行业的变化。我们问自己:有哪些新需求?哪些需求已经过时?哪些仅仅是感知的需求?
回顾我们的 DeepSearch 实现,我们识别出了预期需要且确实需要的东西,我们认为必要但实际上不需要的东西,以及我们没有预料到但结果证明是必要的东西:
首先,一个能产生结构良好输出的长上下文 LLM 是非常必要的(即遵循 JSONSchema)。可能需要一个推理模型来实现更好的行动推理和查询扩展。
查询扩展绝对是必要的,无论是通过 SLM、LLM 还是推理模型实现。然而,在这个项目之后,我们认为 SLM 可能不适合这个任务,因为解决方案必须本质上是多语言的,并且超越简单的同义词重写或关键词提取。它需要足够全面以包含多语言 token 基础(很容易占用 300M 参数),并且足够复杂以实现跳出框架的思考。因此使用 SLM 进行查询扩展可能是行不通的。
网页搜索和网页阅读能力至关重要,值得庆幸的是我们的 Reader (r.jina.ai) 表现出色——强大且可扩展——同时也让我对如何改进我们的搜索端点(s.jina.ai
)产生了许多想法供下一次迭代使用。
嵌入模型是有用的但方式完全出乎意料。我们原以为它会用于内存检索或与向量数据库一起进行上下文压缩(事实证明并不需要),但我们实际上将它用于去重(本质上是一个 STS 任务)。由于查询和缺口问题的数量通常在数百个范围内,不需要向量数据库——直接在内存中计算余弦相似度就足够了。
我们没有使用重排序器,尽管我们相信它可能有助于根据查询、URL 标题和片段来确定访问哪些 URL。对于嵌入和重排序来说,多语言能力是必不可少的,因为查询和问题都是多语言的。长上下文处理对嵌入和重排序有益但不是关键障碍(我们没有遇到任何来自嵌入使用的错误,可能是因为我们的上下文长度已经是 8192-token)。无论如何,jina-embeddings-v3 和 jina-reranker-v2-base-multilingual 是我首选的模型,因为它们是多语言的、SOTA 的并且能很好地处理长上下文。
代理框架证明是不必要的,因为我们需要更贴近 LLM 原生行为来设计系统而不需要代理。Vercel AI SDK 很有价值,因为它在使代码库适应不同的 LLM 提供商方面节省了大量工作(我们可以通过仅改变一行代码就从 Gemini Studio 切换到 OpenAI 再到 Google Vertex AI)。代理内存管理是必要的,但专用内存框架仍然值得商榷:我们担心它会在 LLM 和开发者之间创建一个隔离层,而且它的语法糖最终可能会成为开发者的障碍,正如我们在今天看到的许多 LLM/RAG 框架那样。