本地Deep Research实战:用开源模型搭建隐私安全的AI研究代理
“Deep Research”这个词最近在AI圈子里被反复刷屏从ChatGPT的Deep Research到各种套壳工具大家都在秀那份“AI帮你查了几十个来源生成一份长报告”的能力。但大多数这类能力都绑在云端大模型上要么付费要么数据全往外送要么排队等到地老天荒。我前阵子看到LearningCircuit/local-deep-research这个项目名字看着是个偏教学向的仓库实际做下来的感觉是它把“深度研究”这件看起来很玄的事拆成了可以完全跑在本地的工程流程。核心思路就是用本地大模型主导规划、调用搜索引擎、抓取网页内容、反复迭代追问最后自动拼出一份结构化研究报告。整个过程可以做到不把任何数据交给第三方花销就是一台能跑14B左右量化模型的家用机器或Mac。这篇就围绕这个项目把它的设计思路、核心模块、实际操作和踩坑经验完整过一遍对想自己做本地AI研究代理的同学应该很有用。1. 从场景切入为什么需要local-deep-research1.1 深度研究能力到底是什么原理先理解一个基础概念。深度研究Deep Research本质上不是一个单一模型能力而是一个多步骤Agent工作流。普通聊天模型只做“你说一句它答一句”深度研究则是让模型进入一个循环先理解用户的复杂问题把问题拆成子问题每个子问题去找资料再阅读抓取到的网页从中提取与子问题相关的答案根据这些答案判断还有哪些信息缺口再发起新一轮搜索直到收集到的材料足以支撑结论最后把所有材料组织成一篇有逻辑结构的长文报告。这个循环的核心机制是“迭代”。它不会只搜索一次就输出而是像人做调研一样先搜一轮,看看结果发现信息不够再搜第二轮读更细的内容。LearningCircuit/local-deep-research项目最值得学习的地方就是它把这一整套工作流用最小可实现的方式落地到了本地。1.2 为什么一定要本地化运行市面上开源的全流程深度研究工具也不少OpenAI那个Deep Research是闭源的还有一些基于云端API的Agent框架。这些方案解决的是“效果”问题但没有解决“隐私、成本、可控性”这组问题。隐私完全可控研究问题的输入、搜索记录的中间过程、生成的报告都不出本机。我经常研究一些偏行业竞争策略的内容这些文本留在自己机器上更安心。按量成本变成固定成本云端深度研究按次收费或者靠API调用量计费一次深度研究可能烧掉几美元。本地跑一次性配好环境后续每次研究的“边际成本”几乎为零。模型自由替换云端工具绑死厂商本地工具可以随时切换Qwen、Llama、Mistral等不同底座模型哪个研究场景更适合就用哪个。离线可用没有外网的环境下只要搜索引擎服务也在本地比如自建SearXNG研究流程依然可以运转。1.3 LearningCircuit/local-deep-research项目的核心价值LearningCircuit这个组织名很有意思“Circuit”本意是电路但放在这里更像“回路、闭环”。这个仓库的价值不在代码量有多大而在于它把深度研究系统的抽象层次整理得很清楚从规划器、搜索器、阅读器、记忆池到报告器每个模块职责单一且都留了清晰的替换接口。它最打动我的一点是克制没有上来就堆一个重型框架而是用最朴素的Python模块组织整套流程。这意味着初学者也容易看懂进阶用户可以轻易替换任意一个环节。对我这种更喜欢读代码、改代码的人来说这种“最小系统清晰扩展点”的设计比一个几百个依赖的大工程舒服太多。2. 核心架构与技术选型解析2.1 整体工作流设计仔细梳理项目之后它的核心流程大致分为五个阶段。这里我用自己在实际运行时看到的日志来解释更直观接收研究问题。规划器把主问题拆成多个子问题。比如输入“比较本地11B与14B模型的性价比”规划器可能会拆成“主流11B/14B开源模型有哪些”“各自量化后显存占用多少”“评测指标对比”“实际部署生态差别”等子任务。搜索器遍历子问题调用搜索引擎API拿到一批网页标题、链接、摘要。阅读器逐个抓取网页正文做冗余过滤和摘要提炼把有价值的内容写入本地记忆池往往就是一份临时文本或向量库。判断器结合记忆池内容决定是否进入下一轮更细粒度的搜索或直接进入报告器。报告器阅读记忆池中已整理的材料按固定结构生成研究报告。这个流水线的关键是第五步。很多早期Agent做不好深度研究问题就出在只会一轮搜索就硬写而好的流程里模型每次读到新东西后都会反思“我目前还缺哪块信息”然后带着这个缺口发起下一轮搜索。整个系统质量的上限很大程度上取决于这个“缺口判断”是否准确。2.2 模型选型本地LLM怎么搭配本地深度研究的不同环节对模型能力的要求差异很大项目配置里通常会给不同环节指定不同模型。我觉得这是设计中很成熟的一点。规划器模型需要较强的逻辑拆解能力至少要把开放式问题拆成独立可检索的子问题。8B到14B的模型基本胜任Qwen2.5 14B在这个任务上表现比较稳。阅读器模型任务是从抓取网页中提炼事实不需要太多长程推理反而更吃指令跟随能力和上下文长度。1B到7B的小模型就能跑用太大反而拖慢速度。实测小模型生成的摘要容易被无关信息带偏所以建议至少用7B。判断器模型它在决定是否继续搜索这个环节是整套系统最依赖推理能力的建议直接用14B甚至32B。判断失误会导致漏检信息或无限循环。报告器模型产出最终长文报告需要长上下文综合能力和中文表达组织能力。32B档位会有明显质量优势资源紧张时14B也能出可读结果但结构完整性和论据组织会更松散。2.3 搜索模块能卡多少脖子本地深度研究最容易被忽略的瓶颈是搜索服务。项目在线搜索既可以接公网Search API也可以接SearXNG这种自托管元搜索服务。我从实用角度说说差别。如果直接接Search API比如Serper.dev或者Tavily代码逻辑简单返回结果结构化程度高但你的搜索请求会经过第三方这相当于泄露了一部分研究意图。如果严格追求端到端本地化更推荐自己本地起一个SearXNG容器它可以通过聚合上游公开搜索服务返回结果然后项目只请求你本机的SearXNG地址。这样整条链路中真正带上你研究意图的请求只存在于本机与SearXNG之间。2.4 上下文窗口与记忆管理的取舍深度研究报告动不动几千字中间阅读器读入的网页正文总量早就超出上下文窗口了所以不可能把全部原始材料一次性喂给报告器。项目的处理方式是把所有检索结果先做“分段摘要”再按研究子问题分组存入记忆池最后报告器只读取这些高密度摘要而不是原始网页。这里有个很实用的经验摘要的粒度直接决定报告质量。摘要太粗会丢细节报告就会变成空话套话摘要太细又会信息冗余模型容易迷失在次要内容里。我自己配置时每个网页提取出的正文会按300字左右一个小段做切片再让阅读器对每个切片输出一个50到80字的核心要点。这样既保住细节又足够凝练报告器输入端的Token利用效率会高很多。3. 实操从零跑通一套本地深度研究系统3.1 环境准备与依赖安装实测环境是Ubuntu 22.04 NVIDIA RTX 4090 24G模型均采用Ollama运行。如果是Mac用户只要内存达到16G以上也可以跑只是模型档位需要往下降一些。基础依赖如下# Python 3.10 python3 -m venv .venv source .venv/bin/activate pip install ollama requests trafilatura pyyaml markdown # 如果要用SearXNG建议用Docker直接起 docker run -d -p 8888:8080 -e BASE_URLhttp://localhost:8888 searxng/searxngSearXNG起来之后最好先在本机浏览器确认它能返回搜索结果再进入下一步。这一步很多人会漏真正跑项目时才发现搜索接口不可用排查起来比较费时。3.2 拉取模型与配置逐项解读用Ollama先拉取两个模型一个负责规划和判断一个负责阅读和报告ollama pull qwen2.5:14b ollama pull qwen2.5:7b然后在项目根目录创建配置文件config.yamlmodel: ollama_base_url: http://localhost:11434 planner_model: qwen2.5:14b judge_model: qwen2.5:14b reader_model: qwen2.5:7b reporter_model: qwen2.5:14b temperature: 0.3 max_tokens: 4096 timeout: 120 search: provider: searxng query_url: http://localhost:8888/search max_results: 8 research: language: zh max_iterations: 3 max_sources: 20 min_answer_length: 150 output_dir: output关键参数里我最想提醒的是max_iterations也就是最大迭代轮数。第一轮是粗筛资料第二轮做补漏第三轮基本是查缺补漏。超过3轮在14B模型下容易陷入重复搜索结果却没增量。真需要更深入研究宁可加大max_sources也不要盲目拉高迭代轮数。temperature给0.3左右是比较稳的区间搜索和判断环节需要确定性温度太高模型会自由发挥导致判断失准。3.3 核心模块代码解析项目代码结构比较清楚我挑三个关键模块片段说一下。规划器模块planner.pyimport ollama SUB_QUESTION_PROMPT 你是研究规划助理。针对下面的研究主题拆解出最多5个需要独立检索才能回答的子问题。 要求子问题必须具体、可检索、之间没有重叠。 主题{question} 只输出子问题列表每行一个不要额外解释。 def plan(question, modelqwen2.5:14b, base_urlhttp://localhost:11434): client ollama.Client(hostbase_url) resp client.chat(modelmodel, messages[ {role: user, content: SUB_QUESTION_PROMPT.format(questionquestion)} ]) lines resp[message][content].strip().splitlines() questions [line.lstrip(1234567890.-、 ).strip() for line in lines if line.strip()] return questions这个模块的重点是让模型只输出“问题列表”不做分析。很多人让模型既拆解又解释结果模型写了一大段话解析逻辑反而难写。用纯列表形式后续好处理。搜索与阅读模块search_read.pyimport requests, trafilatura def search(query, query_urlhttp://localhost:8888/search, max_results8): params {q: query, format: json} resp requests.get(query_url, paramsparams, timeout30) data resp.json() results [] for item in data.get(results, [])[:max_results]: results.append({ title: item.get(title, ), url: item.get(url, ), content: item.get(content, )[:300] }) return results def read_page(url): try: downloaded trafilatura.fetch_url(url) if downloaded is None: return text trafilatura.extract(downloaded, include_commentsFalse, include_tablesTrue) return (text or ).strip() except Exception as exc: print(f[read_page] error: {url}, {exc}) return SearXNG的JSON格式返回字段很多但最常用就是title、url、content。content字段通常是搜索引擎抓取的摘要可以直接拿来做粗筛。读网页用trafilatura而不是requestsBeautifulSoup是因为trafilatura对正文提取效果远好于人肉写规则能自动跳过页头页尾导航栏。报告合成模块reporter.pyREPORT_PROMPT 你是资深研究分析师。请根据下面的研究素材输出一份结构化的中文研究报告。 要求 1. 开头给出摘要结论 2. 按主题分小节展开每个小节要有数据或事实支撑 3. 最后列出参考文献来源 研究主题{question} 素材 {material} def write_report(question, material, modelqwen2.5:14b, base_urlhttp://localhost:11434): client ollama.Client(hostbase_url) resp client.chat(modelmodel, messages[ {role: user, content: REPORT_PROMPT.format(questionquestion, materialmaterial)} ]) return resp[message][content]报告器提示词里最重要的一点是“每个小节要有数据或事实支撑”。没有这句模型很容易凭印象编结构最后写出一篇看起来通顺但实际什么都没有的“空文章”。加了这个约束模型会倾向把素材里的具体数字、日期、描述带进来。3.4 跑一个真实研究任务全流程我把配置准备好后输入了一个测试问题“本地运行开源大模型的推理框架有哪些主流选择各自优缺点是什么”运行后系统输出日志大致如下[规划] 拆解出4个子问题: 1. 主流的本地推理框架有哪些 2. 这些框架在性能上有何差异 3. 它们的部署难度和生态支持如何 4. 不同硬件条件下如何选择 [第1轮] 检索子问题: 主流的本地推理框架有哪些 [阅读] 已提取 www.xxx.com 的核心要点: ... [阅读] 已提取 www.yyy.com 的核心要点: ... 当前信息缺口: 缺少框架共享显存机制与批量推理性能对比 [第2轮] 检索: 本地推理框架 批量推理 性能对比 [阅读] ... 当前信息缺口: 缺少在纯CPU环境下的部署细节 [第3轮] 检索: llama.cpp CPU 部署 内存需求 [阅读] ... 素材完整度评估: 80%可以生成报告 [报告] 已生成报告输出到 output/xxx.md最终报告大约2800字覆盖了llama.cpp、Ollama、LM Studio、vLLM等主流框架列了量化支持、显存占用、并发能力、易用性等维度的对比并附带参考链接。整体可用度相当高只需要再人工做一轮事实确认。3.5 扩展方式与后续优化方向这个项目因为是模块化设计扩展点非常清晰。我试过把记忆池从纯文本文件换成ChromaDB向量库这样报告器可以按子问题相关性做检索式读取而不是把所有摘要一股脑全塞进去。效果上对超长研究的质量提升明显推荐动手能力强的同学试试。另一个值得改造的方向是对接本地知识库。不改任何Agent逻辑只需要把搜索器的数据源从SearXNG换成本地文档检索接口它就从“网络深度研究”变成了“私有文档深度阅读助手”。这种改造非常灵活也正好体现模块化架构的收益。4. 常见问题与排查技巧实录4.1 模型推理慢到没法用怎么办本地大模型的速度瓶颈主要在显存带宽和算力模型越大越慢。如果你发现一次深度研究要跑半小时以上优先检查模型档位是否过高。我踩过的坑是报告器用14B模型在4090上生成2000字报告大约需要3分钟还能忍但如果换成32B模型一次生成可能要10到15分钟严重拖慢整体节奏。优化方法是把阅读器降到7B判断器和规划器保持14B报告器根据实际效果按需切换。此外每轮搜索的网页数量不要贪多max_results设为5到8就够设置太高既拖慢时间又容易把不相关内容塞进摘要。4.2 生成的报告空洞、像在凑字数这个问题我遇到太多次了。绝大多数原因是报告器输入端素材质量太差。如果你发现报告虽然结构正确但事实性内容稀疏多半是前端的摘要粒度太粗。解决办法是检查阅读器提示词在提取摘要时明确要求输出的事实信息类型。例如“必须包含具体数字、型号、版本号、性能参数”等没有量化信息的摘要就不要进记忆池。还有一个取巧的办法在素材里人工拼接每个来源的原文标题和URL让模型在写报告时更容易把“这段话是来自某篇文章”的上下文带出来一定程度上能减少空泛编造。4.3 搜索工具总报错或抓不到内容SearXNG最常见的问题是JSON格式没打开或者反爬导致结果为空。先访问http://localhost:8888/config确认搜索API和JSON格式都已经启用。另外一个很容易被忽略的点是SearXNG默认可能会拦截脚本请求需要在SearXNG设置里关闭“限制外部HTTP请求”否则requests验证不过。如果抓取网页正文经常返回空多半是网页本身是JavaScript动态渲染的trafilatura抓不到。这时候要么换来源网站要么接一个无头浏览器工具做渲染抓取但代价是速度和资源占用会大幅提升。对我个人来说直接换来源比上无头浏览器更划算。4.4 上下文爆掉与记忆混乱14B模型的上下文窗口通常有16K到32K如果max_sources开得很大摘要累积起来照样会撑爆上下文。项目如果自带上下文压缩逻辑最好如果没有最简单做法是分模块处理判断器和报告器共用一个“压缩视角”报告器输入端只保留二级摘要也就是“对摘进一步再汇总”而不是保留全部原始摘要。调试的时候可以在每轮迭代后打印当前记忆池的总字符数。我习惯控制在6000到8000字之间超过就按“信息量评分”淘汰掉低价值段落。这个评分可以简单用来源权威性加内容长度来近似虽然粗糙但实测够用。4.5 迭代太多次反而降低质量max_iterations拉满不一定是好事。到第三轮之后新搜索的结果往往是旧信息的重复甚至会把自媒体低质量内容带进来。我的建议是默认最多3轮且每到新一轮迭代之前先让判断器基于当前信息缺口输出“本轮搜索目标”再围绕这个目标去搜索而不是沿用原始子问题去查。这样迭代精度高很多质量自然也就上来了。我自己跑完这个项目之后最深的体会是本地深度研究的价值不只是“省钱”或“隐私”而是它把一个原本黑盒的Agent流程拆到了可以逐环节干预的程度。规划不合理就改规划器摘要太粗就调摘要提示词报告结构不好就换报告模型。这种自由度是云端闭源方案完全给不了的。如果你也在折腾本地Agent建议把LearningCircuit/local-deep-research这套最小流程跑通再顺着自己的需求去改它会是一个非常值得作为起点的作品。