DeepSeek+DeepResearch:把科研文献调研变成一场多轮对话 简介这份PDF由清华大学科研团队分享聚焦DeepSeekDeepResearch这一AI科研辅助方案面向高校科研人员、数据分析师和AI工具使用者。内容先梳理多个自研模型的差异化定位有主打低延迟高吞吐的实时推理模型有轻量化、适合边缘设备的模型也有面向医疗、法律等垂直领域优化或支持长文本、多模态的组件随后展示从数据采集、清洗、分析、挖掘到可视化的全流程并给出爬虫任务、文件数据读取、长文本集成三类实测如提取网页网址并去重筛选、编写Python脚本抓取内容、读取春运数据表格等。每个任务都配有提示词、输出结果和结论实测中DeepSeek R1与OpenAI o3mini响应更快、采集更完整Claude 3.5 sonnet与Kimi k1.5则可能需多次调试这些信息能帮助读者快速选择合适模型、规避调试坑。资源共1个PDF压缩包大小5.66MB已有374人学习下载适合希望将AI接入数据科研全流程的入门与进阶用户。1. 把科研里最耗时的环节变成多轮对话这套组合到底在解决什么某高校团队把 DeepSeek 和 DeepResearch 组合成一套面向科研场景的对话式研究方案到了第四弹核心卖点就一句话把科研里最耗精力的信息调研、方向比对和综述梳理变成一段多轮对话。你负责描述问题智能体负责建检索任务、读摘要、汇总证据、给出可追问的结论。我第一次听这个说法也不太信直到自己搭了一套最小实现跑通才意识到“像聊天一样简单”指的不是替你完成实验而是把大量阅读、筛选和逻辑串联工作从你手上接走。这个方案适合正在做文献综述、开题论证、方法选型的硕博学生和研发工程师。下面按我复现这套组合的完整路径讲。2. DeepSeek 与 DeepResearch 各自解决什么问题为什么组合起来才叫“科研聊天”2.1 DeepSeek 是语言推理底座不是搜索引擎DeepSeek 在这套方案里承担的是“理解与生成”角色。你给它一段研究问题描述它能把问题里的隐含条件拆出来比如“光催化产氢效率受什么因素影响最大”它会先区分影响因素的类型再判断应该找实验对比类文献还是综述类文献最后把这个问题组织成可以回答的形式。这种能力来自大模型本身的预训练积累而不是某个具体数据库。但把 DeepSeek 单独拿来当科研工具会遇到一个很实际的问题它的知识截止时间是固定的你没法靠它获取最新文献也没法让它为某个论断给出可验证的出处。很多人在这一步就放弃了觉得大模型做科研是“玄学”生成的引用看着像真的一查却是幻觉。这也是 DeepResearch 存在的意义。它不是一个独立的大模型而是一套“研究智能体”工作模式把任务拆成多个检索子任务逐个去外部数据库和网页拿证据再把证据读回来交给语言模型做综合。DeepSeek 负责“想”DeepResearch 负责“去找和验证”两者组合起来才构成一个能对话、能落地、能给出证据链条的科研助手。2.2 DeepResearch 把问题变成“检索-阅读-综合”流水线DeepResearch 的典型执行路径是四步规划plan、检索search、阅读read、综合summarize。规划阶段把一个大问题拆成若干小问题检索阶段为每个小问题生成查询词阅读阶段判断返回结果的摘要和全文是否真的回答问题综合阶段把所有证据归纳成一段可读的结论并标注来源。这套流水线解决的是科研工作里最容易被低估的“信息组织成本”。我读研时做文献调研花在搜索和筛摘要上的时间永远比真正读全文的时间多。DeepResearch 的思路就是把这一步交给程序它不怕一天翻两百个网页也不怕为同一篇论文反复调整关键词。它缺的是对问题语义的把握能力而这正是 DeepSeek 补上的位置。所以“让科研像聊天一样简单”这句话的技术本质是把科研中的语言密度工作交给一个擅长语言、又配备工具调用能力的智能体循环。聊天只是交互外壳背后是一次次有目的、有状态、有校验的检索与推理迭代。2.3 关键技术选型API 直连、本地部署与 Agent 框架怎么选我实现这套方案时没有从零写模型训练代码而是把重点放在“怎么编排”上。选型上主要看三个维度数据隐私、成本、可控度。方案适合人群成本数据可控度开发量API 直连大模型服务个人研究者、小课题组低按 token 计费中数据经过服务商最低本地部署开源模型数据敏感的实验室或公司高需要 GPU 资源高全部本地中API 直连 自研 Agent 编排有开发能力的团队中中高检索逻辑可控高本地部署 自研 Agent 编排企业级科研平台最高最高最高我个人给大多数人的建议是第一版先用 API 直连 自研 Agent 编排把流程跑通再根据隐私要求和预算决定要不要把模型层换成本地部署。原因是 DeepResearch 的价值核心在编排逻辑不在模型本身先验证流程能解决你的问题再花钱上硬件这是最不浪费投入的路径。注意无论选哪种方案务必确认模型服务的接口协议。多数服务商兼容 chat/completions 协议但模型名、超时策略、上下文长度上限都可能不同接入前先读对应文档。3. 跑通最小方案用代码把一条科研问题变成可执行任务3.1 准备工作环境变量与服务地址我先假设你用 API 直连方式搭建。这套方法不绑定任何具体厂商只需要服务商提供一个兼容 chat/completions 的接口地址、一个 API Key 和一个模型名。我把这三样都放进环境变量避免代码里写死也方便后面切换部署方式。export LLM_API_URLhttps://your-provider.example.com/v1/chat/completions export LLM_API_KEYsk-your-key export LLM_MODEL_NAMEdeepseek-chat export LLM_MAX_TOKENS4096设置环境变量之后写一个通用调用函数。用 requests 而不是某个封装库是为了保持最小依赖也方便你之后替换成内网地址。这个函数是整套方案的“管道”所有和大模型相关的请求都会经过它。# llm_client.py # 一个最小的大模型调用函数兼容多数 chat/completions 服务 import os import requests def chat_completion(messages, temperature0.2, max_tokens2048): messages: [{role: user/system, content: ...}] temperature: 越低越稳定越高越发散 max_tokens: 控制单次生成长度防止跑飞 url os.getenv(LLM_API_URL) headers {Authorization: Bearer os.getenv(LLM_API_KEY)} payload { model: os.getenv(LLM_MODEL_NAME), messages: messages, temperature: temperature, max_tokens: max_tokens, } resp requests.post(url, headersheaders, jsonpayload, timeout120) resp.raise_for_status() return resp.json()[choices][0][message][content]这个函数里最值得关注的是 temperature。做文献筛选和任务拆解时我会把它压在 0.2 到 0.4 之间让输出尽量贴近指令只有需要发散思路、生成候选研究方向时才调到 0.7 以上。timeout 设 120 秒是因为科研场景的 prompt 往往带长文本短超时会误杀正常请求。如果你拿到的服务地址有网络隔离要求只需要改环境变量里的 URL不用动任何业务代码。3.2 用 DeepSeek 做文献初筛完整脚本与参数解释科研任务里最高频的场景是“从一批论文里筛出和某个主题相关的部分”。我拿一个 CSV 文件举例里面每行是标题和摘要。用 DeepSeek 做语义筛选比单纯用关键词匹配精准得多因为模型能判断“Perovskite solar cells”和“钙钛矿电池”是不是同一类内容也能根据上下文排除关键词相同但主题完全无关的条目。# filter_papers.py # 从 CSV 读入文献标题与摘要用大模型按主题筛选并输出 JSON import os import csv import json from llm_client import chat_completion def load_papers(csv_path): rows [] with open(csv_path, newline, encodingutf-8) as f: reader csv.DictReader(f) for r in reader: rows.append({id: r[id], title: r[title], abstract: r[abstract]}) return rows def filter_by_topic(papers, topic, top_n10): sys_prompt 你是文献筛选助手。只输出 JSON不输出解释。 user_prompt ( f从以下文献列表中找出与「{topic}」直接相关度最高的 {top_n} 条。\n 按相关度从高到低排序。输出 JSON 数组每项包含 id、title、与 relevance 字段relevance 取值 high/medium/low。\n\n f{json.dumps(papers, ensure_asciiFalse)} ) raw chat_completion( messages[ {role: system, content: sys_prompt}, {role: user, content: user_prompt}, ], temperature0.2, max_tokens1500, ) return json.loads(raw) if __name__ __main__: papers load_papers(local_papers.csv) result filter_by_topic(papers, 光催化 产氢, top_n10) print(json.dumps(result, ensure_asciiFalse, indent2))这段代码的思路是让模型输出严格 JSON再用 json.loads 解析这样筛出来的结果可以直接接进后续的 DeepResearch 检索流程。max_tokens 设 1500 是为了容纳 10 条结果的输出空间如果改成 top_n20要相应调到 2500 左右。这里有个老手才会注意的细节CSV 里的 abstract 字段不要截断。有一次我把摘要截成前 300 字筛选结果立刻出现偏差因为很多论文的关键方法讨论在中后段。宁可少传几篇也不要截断每篇的摘要这个经验后来成了我筛文献的固定习惯。3.3 让 DeepResearch 把问题拆成检索子任务文献筛选只是单点能力。DeepResearch 真正的第一步是把一个模糊的研究问题拆成可执行检索的子任务。比如问题“二维材料在气体传感中的应用现状”直接丢给搜索引擎会返回一堆无关内容但如果拆成“二维材料气体传感的敏感机理”“不同二维材料的响应时间对比”“最新综述文献”每个子任务的目标和关键词都明确搜回来的内容质量会显著提升。# plan_tasks.py # 把研究问题拆成 DeepResearch 可执行的检索子任务 import json from llm_client import chat_completion def plan_research_tasks(question): sys_prompt 你是科研规划助手。把用户的研究问题拆成检索子任务输出 JSON。 user_prompt f研究问题{question} 请拆成 5 到 8 个检索子任务每个子任务包含以下字段 - goal这个子任务要搞清楚的结论 - query适合学术数据库的具体检索词组合 - depthread_abstract读摘要或 read_full_text读全文 - required这个子任务对回答主问题是否必须true/false 输出 JSON 数组不要输出解释。 raw chat_completion( messages[ {role: system, content: sys_prompt}, {role: user, content: user_prompt}, ], temperature0.4, max_tokens2000, ) tasks json.loads(raw) # 把非必须任务排到后面必要时先跳过 tasks.sort(keylambda t: not t.get(required, False)) return tasks if __name__ __main__: q 二维材料在气体传感中的应用现状与瓶颈 for i, t in enumerate(plan_research_tasks(q), 1): print(f{i}. {t[goal]}) print(f query: {t[query]})拆解任务时 temperature 我调到 0.4比筛选文献稍高一点。原因是任务拆解难有标准答案稍微放开一些约束能让模型给出更多样、更发散的检索角度排序时把 required 为 false 的任务放末尾是保证即便检索预算不够核心问题也能先被覆盖。拆完任务就涉及一个关键选择是每个子任务独立发起一轮检索还是像聊天一样连续追问。我在第四弹方案里用的是后者这也是“对话感”的主要来源。下一章讲怎么把这种连续交互组织成一个稳定的执行循环。4. 把多轮对话变成科研项目Agent 主循环与状态管理4.1 一个能跑的极简 DeepResearch 主循环我实现 DeepResearch 的方式是一个带状态的主循环先读问题拆任务然后逐个执行“检索-阅读-记录证据”最后综合成报告。这里的核心不是调用大模型而是维护证据状态。如果每一轮都把上一轮所有内容重新发给模型token 消耗会迅速失控而且模型会记住太多无关信息反而抓不住主线。# deep_research.py # 极简 DeepResearch 主循环规划 - 检索 - 阅读 - 综合 import json from llm_client import chat_completion from plan_tasks import plan_research_tasks from filter_papers import load_papers def search_abstracts(query): 检索接口换成你的学术搜索 API 或本地向量库 # 这里只做演示实际接入学术数据库后返回 [{id, title, abstract, source}] raise NotImplementedError(接入你的检索源) def summarize_evidence(evidence, question): sys_prompt 你是科研证据整理助手。用 200 字以内总结证据如何回答问题并标注不确定之处。 user_prompt ( f研究问题{question}\n\n f证据列表{json.dumps(evidence, ensure_asciiFalse)}\n\n 总结时区分‘证据支持’和‘证据缺失’两部分。 ) return chat_completion( messages[{role: system, content: sys_prompt}, {role: user, content: user_prompt}], temperature0.3, max_tokens800, ) def write_report(state): user_prompt ( f研究问题{state[question]}\n\n f证据笔记{json.dumps(state[notes], ensure_asciiFalse)}\n\n 请写成结构化研究报告分节呈现并标注未解决的开放问题。 ) return chat_completion( messages[{role: user, content: user_prompt}], temperature0.4, max_tokens3000, ) def research_agent(question, max_rounds6): state { question: question, evidence: [], notes: [], } tasks plan_research_tasks(question) for task in tasks[:max_rounds]: abstracts search_abstracts(task[query]) evidence [a for a in abstracts if a[abstract]] # 只保留有摘要的证据 state[evidence].extend(evidence) note summarize_evidence(evidence, task[goal]) state[notes].append({ goal: task[goal], note: note, source_count: len(evidence), }) return write_report(state)这个循环的每个阶段都有独立函数替换起来非常方便。search_abstracts 是唯一需要你真正接入外部服务的点其他部分都是通用逻辑。每个中间结果只存“压缩后的笔记”不存原始全文这是状态管理的关键。如果某个子任务检索到的摘要加全文有几十万 token直接全部留到下一轮既烧钱又会让模型混乱。笔记压缩到 200 字以内既保留信息又能把多轮上下文的长度控制住。4.2 三个必调的参数轮数上限、证据条数、摘要压缩第一版跑通之后参数调优才是决定好不好用的分水岭。我踩过的参数坑主要集中在这三个上面。参数默认值调低的影响调高的影响max_rounds 检索轮数6速度快、覆盖窄、容易漏关键子问题覆盖广、耗时翻倍、token 成本上升evidence 条数/轮10结论依据少、判断片面混杂低质量内容、上下文变乱摘要压缩长度200 字信息丢失、无法追溯推理过程token 消耗大、主线被稀释我的经验是探索性研究比如“这个方向值不值得做”把 max_rounds 提到 8每轮证据条数压到 8验证性研究比如“已有结论是否可靠”把 max_rounds 压到 4但证据条数提到 15因为关键在于覆盖足够多的对比实验数据。evidence 条数这个参数特别容易翻车。搜索引擎返回的前几条往往高度同质比如同一课题组的工作占了前五名。如果不做去重综合出来的结论会有很强偏向性。我处理的办法是在 search_abstracts 里对来源域名和标题做一次简单去重让后续模型看到的证据尽量来自不同团队。4.3 Prompt 模板与多轮追问技巧主循环能跑之后真正提升体验的是交互层设计。我把“聊天”的体验拆成三层第一层是用户描述问题第二层是智能体展示它拆出的子任务和每轮检索发现第三层是用户可以在任意子任务结果上追问“这个结论的样本量是多少”“换一种材料体系还成立吗”。# follow_up.py # 在单条证据或结论上继续追问 def ask_follow_up(state, sub_goal, follow_up_question): relevant [ e for e in state[evidence] if sub_goal.lower() in e.get(matched_goal, ).lower() ] user_prompt ( f关于子任务「{sub_goal}」用户追问{follow_up_question}\n f现有证据{json.dumps(relevant[:5], ensure_asciiFalse)}\n 如果证据足以回答请直接回答并注明依据如果不足以回答 请明确说要补检索并生成补检索的 query。 ) return chat_completion( messages[{role: user, content: user_prompt}], temperature0.3, max_tokens1000, )这个追问函数的设计原则是不把整个对话历史传进去只传“与当前子任务相关的证据”。这样可以避免模型被用户早先闲聊的内容带偏也让每轮追问的响应质量和 token 消耗都可控。如果模型判断证据不足它会主动要求补检索这样的回复比硬撑着编一个答案有价值得多。5. 落地避坑四条最常见的翻车现场与排查思路5.1 引用幻觉模型给出不存在的论文现象报告里列出了一篇论文标题、作者、年份都齐全但在学术数据库里怎么都搜不到。我第一次遇到时以为是自己检索方式不对换了好几个学术库才发现这篇论文根本不存在。模型只是按概率生成了“长得像参考文献”的文本。原因总结环节只把证据文本喂给了模型没有把“引用列表”和“证据来源”绑定。模型在生成报告时并不清楚哪些结论有真实出处于是自创了引用。解决我在写报告和总结的 prompt 里强制加一条约束每条结论后面必须标注证据编号证据编号来自输入 JSON 里的 source 字段输出后跑一个校验脚本把没有对应证据编号的引用标记出来。注意永远不要跳过校验步骤直接看报告。引用校验不是可选项在这个方案里是必需环节。5.2 任务漂移聊到一半跑题了现象用户问的是“钙钛矿电池的稳定性”对话进行到第四轮模型开始大篇幅介绍钙钛矿的晶体结构。内容没错但对当前研究问题已经没有直接价值而且占了上下文空间导致后面的结论变得空洞。原因多轮对话没有任务状态管理模型把每轮输入都当成新的、独立的指令历史上下文里相关性低的信息没有被过滤。问题出在我的实现里只传了“全部历史”没有传“当前子任务”。解决改成显式状态管理后每一轮只传三部分内容原始研究问题、当前子任务目标、与当前子任务相关的证据摘要。这个改动也把 token 消耗降了接近一半。5.3 检索过时用旧数据做新判断现象智能体回答“当前钙钛矿电池最高效率”时给出的是两年前的数据。数据本身没错但它不能代表当前研究现状用户如果直接引用进开题报告就会被导师指出来。原因检索接口返回结果默认按相关度排序不按时间排序模型在总结时也没有区分“统计时间”和“发表时间”。模型看到的证据虽然是新的但对时效性要求高的指标会按旧数据给结论。解决我的做法是在 query 后面强制追加年份限定词让检索接口优先返回近三年的文献同时在总结 prompt 里要求模型对每个关键数据标注“数据来源年份”然后在报告里集中排查那些年份过旧的论断。只加年份限定还不够可视化排查这一步我是用脚本实现的。5.4 Token 失控每轮重发全文现象跑完一次完整研究循环后对账发现 token 消耗是预期的三倍多。进一步排查发现原因是多轮检索时每一轮都把上一轮的原始论文全文重新塞进了上下文。原因我把检索结果直接拼进了 messages本意是让模型“记住”前面看到的内容但这种方式每次都会把同样一批全文重复发送而且超过模型上下文窗口后前面的内容还会被截断模型既花了钱也没用好上下文。解决改为“每轮只保留压缩笔记”的策略后上下文长度基本稳定在 3000 token 左右。原始全文不再进入模型上下文而是存到外部文件或向量库里只在用户明确追问某篇论文时才按需加载。这也是第四章那个状态设计的核心出发点。6. 进阶把 DeepResearch 接到你的本地文献库和验证流程上6.1 引用回溯验证让报告经得起推敲我现在的固定习惯是每份报告生成后跑一遍引用回溯脚本。原理很简单——把报告里的每条引用抽出来去检索接口重新查一遍查不到的一律标记成“待人工确认”。这个脚本只有几十行但它能拦住绝大多数幻觉引用。# validate_citations.py # 从报告文本中抽取引用标记回查检索源确认存在性与准确性 import re from filter_papers import load_papers def validate_citations(report_text, source_papers): # 约定报告中的引用格式为 [cite: id] cited_ids re.findall(r\[cite:\s*([A-Za-z0-9_\-])\], report_text) valid, missing [], [] for cid in cited_ids: if cid in {p[id] for p in source_papers}: valid.append(cid) else: missing.append(cid) return valid, missing约定一种显式的引用标记格式很重要。如果你不约定模型会生成风格各异的引用校验就无从下手。我一般把这份校验结果作为报告的一部分直接发给用户让他知道哪些结论有据可依、哪些还在悬空。6.2 把本地 PDF 文献库做成检索源如果你所在课题组有大量本地 PDF比如几百篇领域经典文献那么用公开学术搜索接口做检索源并不是最优解因为很多经典文献的全文不在开放数据库里。我后面自己加了本地向量检索把 PDF 转成纯文本按段落切块用向量模型做索引这样 search_abstracts 的第一步是先查本地库查不到再走外部接口。这个方法把 DeepResearch 的适用场景从“调研一个陌生方向”扩展到了“把某个小领域吃透”。本地库的好处是内容完全可控不依赖外部服务的更新频率也能做精细化去重。唯一要注意的是 PDF 里公式和图表的段落会被切碎检索召回时会有噪声所以在切块时最好把图表标题和公式单独过滤掉。6.3 形成自己的科研对话模板跑通以后我给自己准备了三个固定模板开题方向调研、方法对比选型、论文投稿前相关工作总结。每个模板其实就是一段更具体的 system prompt加一套预设的检索子任务。模板的好处是减少重复调试也更方便统计每个环节的耗时和 token 成本。我的教训是不要指望模型默认理解“科研”这个词。你必须把问题的边界、需要的证据类型、结论的形式都写在模板里否则智能体会给你一篇像综述的散文而不是可以直接推进工作的报告。多轮对话的边界也要写清楚什么时候该补检索什么时候该停止都需要在 prompt 里明确否则会出现“问一句答三页”的现象。这套流程我从第一次跑通到现在大概调了一个月越往后越意识到问题定义比工具本身更重要。希望帮到你。本文还有配套的精品资源点击获取