Deepseek引入的深度搜索成为2025年新标准。各大公司纷纷推出深度研究产品,AI工程师通过整合长期思考与推理,显著提升了搜索系统的性能与深度。
要点:
- 深度搜索DeepSearch经过搜索、阅读和推理的迭代循环,直到找到最佳答案。
- 深度研究DeepResearch在深度搜索DeepSearch 的基础上添加了一个用于生成长篇研究报告的结构化框架。
虽然现在才2月,但深度搜索已经成为2025年的新搜索标准。
-
以Google和OpenAI为首的主要公司通过发布他们的“深度研究”引领了这一潮流(是的,我们也在同一天自豪地发布了开源的node-deepresearch)。
-
Perplexity紧随其后推出了他们的深度研究功能,
-
X AI则将深度搜索整合到了Grok3中,本质上创造了另一个深度研究的变体。
虽然深度搜索的概念并不算革命性——在2024年它被称为RAG或多跳QA——但在2025年1月底Deepseek-r1发布后,它获得了巨大的发展动力。上周末,百度搜索和腾讯微信搜索已经将Deepseek-r1整合到了他们的搜索引擎中。
AI工程师发现,通过将长期思考和推理过程整合到搜索系统中,他们可以实现比以往更高的检索准确性和深度。
发布时间表:
- 2025年1月20日:DeepSeek发布DeepSeek-r1,开源。
- 2025年2月2日:Google发布DeepResearch,专有。
- 2025年2月2日:OpenAI发布DeepResearch,专有。
- 2025年2月2日:Jina AI发布DeepSearch (node-deepresearch),开源。
- 2025年2月4日:Hugging Face发布Open Deep Research,开源。
- 2025年2月15日:Perplexity发布DeepResearch,专有。
- 2025年2月17日:X AI发布Grok3 with DeepSearch,专有。
- 2025年2月22日:百度搜索整合DeepSeek-r1,专有。
- 2025年2月23日:腾讯微信搜索整合DeepSeek-r1,专有。
但为什么这种转变发生在现在,而深度研究在2024年并没有受到太多关注?
事实上,斯坦福NLP实验室在2024年初就发布了STORM项目,用于生成基于网络的长篇报告。
那么,是不是仅仅因为“深度搜索”听起来比多RAG或STORM更酷?让我们诚实点——有时候,一次品牌重塑就足以让行业突然接受一直存在的东西。
我们认为真正的转折点是OpenAI在2024年9月发布的o1-preview,它引入了“推理时计算”的概念,并逐渐改变了行业的看法。
推理时计算指的是在推理阶段(即LLM生成输出的阶段)使用更多的计算资源,而不是在预训练或后训练阶段。众所周知的例子包括“思维链”(Chain-of-Thought,CoT)推理和“等待”注入(即预算强制),这使得模型能够进行更广泛的内部思考,比如评估多个潜在答案、进行更深入的规划,并在得出最终答案之前进行自我反思。
这种推理时计算的概念教育用户接受延迟满足——用较长的等待时间换取更高质量、可立即使用的结果,就像斯坦福棉花糖实验中,能够抵制立即吃掉一个棉花糖以获得之后两个棉花糖的孩子表现出更好的长期结果。
Deepseek-r1进一步强化了这种用户体验,不管用户是否喜欢,大多数用户都已经接受了这一点。
这标志着与经典搜索要求的重大转变:
-
在过去,如果无法在200毫秒内响应,就会导致你的解决方案失败。
-
而在2025年,经验丰富的搜索开发者和RAG工程师优先考虑的是top-1精确率和召回率,而不是延迟。
用户已经习惯了较长的处理时间——只要他们能看到系统在“思考”。
在2025年,显示推理过程已成为标准做法,许多聊天界面现在都在专门的UI区域中渲染“思考”内容。
在本文中,我们将通过研究我们的开源实现来讨论深度搜索和深度研究的原理。我们将介绍关键设计决策并强调潜在的注意事项。
什么是深度搜索?
深度搜索通过搜索、阅读和推理的迭代循环运行,直到找到最优答案。搜索操作利用搜索引擎探索互联网,而阅读操作详细分析特定网页(例如Jina Reader)。推理操作评估当前状态,并决定是否将原始问题分解为更小的子问题或尝试不同的搜索策略。
深度搜索——持续搜索、阅读网页、推理,直到找到答案(或超出token预算)。虽然网上存在各种定义,但在开发node-deepresearch项目时,我们遵循了这种直接的方法。实现非常简单——其核心是一个主while循环,带有switch-case逻辑来指导下一步操作。
与2024年的RAG系统不同,后者通常只运行一次搜索-生成过程,深度搜索则通过管道执行多次迭代,需要明确的停止条件。这些条件可以基于token使用限制或失败尝试的次数。
看待深度搜索的另一个角度是:
将其视为配备了各种网络工具(如搜索器和阅读器)的LLM智能体代理。
这个智能体代理通过分析当前观察和过去的行动来决定下一步——决定是提供答案还是继续探索网络。
这创建了一个状态机架构,其中LLM控制状态之间的转换。在每个决策点,你有两种方法:
-
你可以精心设计提示词让标准生成模型产生特定操作,
-
或者利用像Deepseek-r1这样的专门推理模型来自然地推导出下一步操作。
然而,即使使用DeepSeek r1,你也需要定期中断其生成过程,将工具输出(例如搜索结果、网页内容)注入上下文中,并提示它继续其推理过程。
最终,这些只是实现细节——无论你是精心设计提示词还是直接使用推理模型,它们都符合深度搜索的核心设计原则:搜索、阅读和推理的持续循环。
那么什么是深度研究?
深度研究在深度搜索的基础上增加了生成长篇研究报告的结构化框架。它通常从创建目录开始,然后系统地将深度搜索应用于每个所需部分——从引言到相关工作和方法论,一直到结论。每个部分都是通过将特定研究问题输入深度搜索来生成的。最后阶段是将所有部分整合到单个提示中,以提高整体叙述的连贯性。
深度搜索作为深度研究的基础构建块。通过深度搜索迭代构建每个章节,然后在生成最终长报告前改进整体连贯性。在我们2024年的“Research”项目中,我们执行了多次连贯性改进,每次迭代都会考虑所有其他章节。然而,随着当今LLM上下文窗口显著增大,这种方法似乎显得多余——单次连贯性修订就足够了。
深度搜索 vs 深度研究
虽然很多人经常将深度搜索和深度研究混为一谈,但在我们看来,它们解决的是完全不同的问题。深度搜索作为一个原子构建块,是深度研究所依赖的核心组件。另一方面,深度研究专注于制作高质量、可读性强的长篇研究报告,这涉及一系列不同的要求:通过图表和表格来整合有效的可视化内容,用适当的章节标题来组织内容,确保子章节之间的逻辑流畅,在整个文档中保持术语一致性,消除章节之间的冗余,制作连接前后内容的流畅过渡。这些元素与核心搜索功能基本无关,这就是为什么我们发现深度搜索作为公司重点更有意思。
最后,下表总结了深度搜索和深度研究之间的差异。值得注意的是,这两个系统都从长上下文和推理模型中获益良多。这可能看起来有些反直觉,特别是对深度搜索而言——虽然深度研究需要长上下文能力(因为它产生长报告)是显而易见的。原因在于深度搜索必须存储先前的搜索尝试和网页内容以做出关于下一步的明智决定,这使得长上下文窗口对其有效实现同样至关重要。
深度搜索 :
-
通过迭代搜索实现信息的准确性和完整性
-
简洁的答案,附带URL作为参考
-
状态机架构,具有明确的转换条件;通过失败尝试持续进行直到解决
-
局部优化(最佳的下一个搜索/阅读操作)
-
受限于搜索质量和推理能力
深度研究:
- 在文档规模上实现内容的组织、连贯性和可读性
- 长篇结构化报告,包含多个部分、图表、表格和参考文献
- 多层次架构,管理微观(搜索)和宏观(文档)问题;管理复杂信息层次的结构化方法
- 全局优化(章节组织、术语一致性、过渡)
- 受限于深度搜索质量,加上组织复杂性和叙述连贯性挑战
了解深度搜索实现
深度研究的核心在于其循环推理方法。与大多数RAG系统试图一次性回答问题不同,我们实现了一个迭代循环,持续搜索信息、阅读相关来源并进行推理,直到找到答案或用完token预算。以下是这个大型while循环的简化核心:
// 主推理循环
while (tokenUsage < tokenBudget && badAttempts <= maxBadAttempts) {
// 跟踪进度
step++; totalStep++;
// 从空白队列中获取当前问题或使用原始问题
const currentQuestion = gaps.length > 0 ? gaps.shift() : question;
// 生成提示,包含当前上下文和允许的操作
system = getPrompt(diaryContext, allQuestions, allKeywords,
allowReflect, allowAnswer, allowRead, allowSearch, allowCoding,
badContext, allKnowledge, unvisitedURLs);
// 让LLM决定下一步操作
const result = await LLM.generateStructuredResponse(system, messages, schema);
thisStep = result.object;
// 执行选定的操作(回答、反思、搜索、访问、编码)
if (thisStep.action === 'answer') {
// 处理回答操作...
} else if (thisStep.action === 'reflect') {
// 处理反思操作...
} // ... 其他操作的处理
}
一个关键的实现细节是在每个步骤中选择性地禁用某些操作,以确保更稳定的结构化输出。例如,如果内存中没有URL,我们会禁用访问操作;或者如果上一个答案被拒绝,我们会阻止代理立即再次调用回答操作。这种约束使代理保持在一个富有成效的路径上,避免因重复调用相同操作而导致的失败。
我们使用XML标签来定义各个部分,这样可以生成更稳健的系统提示和生成内容。我们还发现,将字段约束直接放在JSON schema的description字段中会产生更好的结果。虽然有人可能会说大多数提示都可以用DeepSeek-R1这样的推理模型来自动化,但上下文长度限制和对高度特定行为的需求使得显式方法在实践中更可靠。
function getPrompt(params...) {
const sections = ;
// 添加系统指令的头部
sections.push("You are an advanced AI research agent specialized in multistep reasoning...");
// 如果有累积的知识,添加知识部分
if (knowledge?.length) {
sections.push("
[Knowledge items]
");
}
// 添加上下文中的先前操作
if (context?.length) {
sections.push("
[Action history]
");
}
// 添加失败尝试和学习策略
if (badContext?.length) {
sections.push("
[Failed attempts]
");
sections.push("
[Improvement strategies]
");
}
// 定义基于当前状态的可用操作
sections.push("
[Available action definitions]
");
// 添加响应格式指令
sections.push("Respond in valid JSON format matching exact JSON schema.");
return sections.join("\n\n");
}
知识空白问题
在深度搜索中,“知识空白问题”代表在回答主要问题之前需要填补的知识缺口。与直接解决原始问题不同,代理会识别出能够构建必要知识基础的子问题。
这种设计在处理这些知识空白问题时特别优雅:
// 在反思操作中识别出空白问题后
if (newGapQuestions.length > 0) {
// 将新问题添加到队列前面
gaps.push(...newGapQuestions);
// 始终将原始问题添加到队列末尾
gaps.push(originalQuestion);
}
这种方法创建了一个带轮转的FIFO(先进先出)队列,其中:
- 新的知识空白问题被推到队列前面
- 原始问题总是被推到队尾
- 系统在每个步骤从队列前面提取问题
这种设计的优秀之处在于它为所有问题维护了一个共享的上下文。当一个知识空白问题得到回答时,这些知识立即可用于所有后续问题,包括当我们最终重新访问原始问题时。
FIFO队列 vs 递归
另一种方法是使用递归,这对应于深度优先搜索。每个知识空白问题都会产生一个具有自己独立上下文的新递归调用。系统必须完全解决每个知识空白问题(及其所有潜在的子问题)才能返回到父问题。
在实践中,我们发现递归方法很难应用预算限制,因为没有明确的经验法则来决定应该为子问题分配多少token预算(因为它们可能会产生新的子问题)。与复杂的预算限制和延迟返回问题相比,递归方法中清晰的上下文分离带来的好处非常有限。这种FIFO队列设计平衡了深度和广度,确保系统总是带着逐步改进的知识返回到原始问题,而不是陷入潜在的无限递归下降。
查询重写
我们遇到的一个有趣挑战是如何有效地重写搜索查询:
// 在搜索操作处理程序中
if (thisStep.action === 'search') {
// 去重搜索请求
const uniqueRequests = await dedupQueries(thisStep.searchRequests, existingQueries);
// 将自然语言查询重写为更有效的搜索查询
const optimizedQueries = await rewriteQuery(uniqueRequests);
// 确保不重复之前的搜索
const newQueries = await dedupQueries(optimizedQueries, allKeywords);
// 执行搜索并存储结果
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模型最终成为了关键,不是最初预期的用于内存检索,而是用于高效去重。
爬取网页内容
网页抓取和内容处理是另一个关键组件。这里我们使用Jina Reader API。请注意,除了完整的网页内容外,我们还会聚合搜索引擎返回的所有片段作为代理后续推理的额外知识。可以将它们视为简要信息。
// 访问操作处理程序
async function handleVisitAction(URLs) {
// 标准化URL并过滤掉已经访问过的
const uniqueURLs = normalizeAndFilterURLs(URLs);
// 并行处理每个URL
const results = await Promise.all(uniqueURLs.map(async url => {
try {
// 抓取并提取内容
const content = await readUrl(url);
// 存储为知识
addToKnowledge(`What is in ${url}?`, content, [url=, 'url');], 'url');
return {url, success: true};
} catch (error) {
return {url, success: false};
} finally {
visitedURLs.push(url);
}
}));
// 根据成功或失败更新日记
updateDiaryWithVisitResults(results);
}[/url]
[我们对URL进行标准化以便一致追踪,并限制每个步骤访问的URL数量以管理代理内存。
内存管理
多步推理的一个关键挑战是如何有效管理代理内存。我们设计的内存系统区分了什么算作“记忆”与什么算作“知识”。无论如何,它们都是LLM提示上下文的一部分,用不同的XML标签分隔:
// 将知识项添加到累积知识中
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()
});
}
// 在日记中记录步骤
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]
`);
}
](, 'url');)
由于2025年大多数LLM都有很大的上下文窗口,我们选择不使用;)[url=https://www.jdon.com/67254.html\]向量数据库\[/url\]。相反,内存由获得的知识、访问过的网站和失败尝试的记录组成——所有这些都保存在上下文中。这个全面的内存系统让代理能够意识到它所知道的、尝试过的以及哪些成功或失败了。
答案评估
一个关键见解是答案生成和评估不应该在同一个提示中。在我的实现中,当新问题到达时,我们首先确定使用哪些评估标准,然后逐一评估每个标准。评估器使用少量示例进行一致性评估,确保比自我评估更可靠。
// 独立的评估阶段
async function evaluateAnswer(question, answer, metrics, context) {
// 首先,根据问题类型确定评估标准
const evaluationCriteria = await determineEvaluationCriteria(question);
// 然后分别评估每个标准
const results = ;
for (const criterion of evaluationCriteria) {
const result = await evaluateSingleCriterion(criterion, question, answer, context);
results.push(result);
}
// 确定答案是否通过整体评估
return {
pass: results.every(r => r.pass),
think: results.map(r => r.reasoning).join('\n')
};
}
预算强制
预算强制意味着防止系统过早返回,确保它继续处理直到预算超限。自DeepSeek-R1发布以来,预算强制的方法已转向鼓励更深入的思考以获得更好的结果,而不是单纯节省预算。
在我们的实现中,我们明确配置系统在尝试回答之前识别知识缺口。
if (thisStep.action === 'reflect' && thisStep.questionsToAnswer) {
// 通过将子问题添加到队列中来强制更深层次的推理
gaps.push(...newGapQuestions);
gaps.push(question); // 始终重新访问原始问题
}
通过有选择地启用和禁用某些操作,我们可以引导系统使用增强推理深度的工具。
// 在失败的回答尝试后
allowAnswer = false; // 强制代理进行搜索或反思
为避免在无效路径上浪费token,我们对失败尝试次数设置了限制。当接近预算限制时,我们激活“野兽模式”以确保我们提供某个答案而不是没有答案。
// 野兽模式激活
if (!thisStep.isFinal && badAttempts >= maxBadAttempts) {
console.log('Enter Beast mode!!!');
// 配置提示以生成决定性的、承诺的答案
system = getPrompt(
diaryContext, allQuestions, allKeywords,
false, false, false, false, false, // 禁用所有其他操作
badContext, allKnowledge, unvisitedURLs,
true // 启用野兽模式
);
// 强制生成答案
const result = await LLM.generateStructuredResponse(system, messages, answerOnlySchema);
thisStep = result.object;
thisStep.isFinal = true;
}
野兽模式提示故意做得很戏剧化,以向LLM表明它需要果断并基于可用信息做出回答:
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
SANCTIONED
- WHEN IN DOUBT: UNLEASH CALCULATED STRIKES BASED ON AVAILABLE INTEL!
FAILURE IS NOT AN OPTION. EXECUTE WITH EXTREME PREJUDICE! ⚡️
这确保我们始终提供某个答案而不是完全放弃,这对于困难或模糊的问题特别有用。
结论
深度搜索是搜索如何深入处理复杂查询的一个飞跃。通过将过程分解为搜索、阅读和推理的离散步骤,它克服了传统单次RAG或多跳QA系统的许多限制。
在实施过程中,我们还开始回顾2025年的搜索基础以及2025年1月26日DeepSeek-R1发布后搜索行业的变化。我们问自己:有哪些新需求?哪些需求已经过时?哪些仅仅是感知的需求?
回顾我们的深度搜索实现,我们识别出了预期需要且确实需要的东西,我们认为必要但实际上不需要的东西,以及我们没有预料到但结果证明是必要的东西:
首先,一个能产生结构良好输出的长上下文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框架那样。
网友评论:
最近,我发现自己对经典的 RAG 模式有些失望,这种模式就是查找相关文档并将它们转储到上下文中,以便单次调用 LLM。
我认为 DeepSearch 的这个定义有助于解释原因。RAG 是关于回答超出模型知识范围的问题。DeepSearch 模式提供了一种基于工具的经典 RAG 替代方案:我们为模型提供额外的工具来运行多个搜索(可以是基于向量的、FTS 或甚至像 ripgrep 这样的系统),并循环运行几个步骤以尝试找到答案。
我认为 DeepSearch 比 DeepResearch 有趣得多,后者对我来说更像是一个表示层的东西。将多个搜索结果汇总到一份“报告”中看起来更令人印象深刻,但我仍然担心报告格式会误导人们对所进行的“研究”的质量的印象。