GPT Researcher 源码拆解:研究型 Agent 如何检索、筛选证据与生成报告

GPT Researcher 源码拆解:研究型 Agent 如何检索、筛选证据与生成报告

摘要:从 GPT Researcher 真实源码出发,拆解研究规划、检索抓取、BM25 与模型筛选、深度研究及报告生成。结合 SDK 示例、Linux 部署与二次开发,说明研究型 Agent 如何组织证据,以及为什么有引用仍需审核。

一篇带着十几个链接的 AI 报告,不一定比没有链接的回答可靠。真正需要理解的是:链接从哪里来,原文是否被读过,哪些段落进入了模型,证据为空时系统会做什么。

GPT Researcher 值得学习的,不是"给模型加一个搜索工具",而是把研究任务拆成可替换的工程环节。本文沿着真实源码分析这条流水线,再给出安装、Linux 部署和二次开发的路径。

文章目录

  • [GPT Researcher 源码拆解:研究型 Agent 如何检索、筛选证据与生成报告](#GPT Researcher 源码拆解:研究型 Agent 如何检索、筛选证据与生成报告)
    • [一、项目定位:它是研究应用,不是万能 Agent 框架](#一、项目定位:它是研究应用,不是万能 Agent 框架)
    • 二、先看目录:应该从哪些文件读起
    • 三、一条研究请求,实际经历了什么
      • [3.1 入口对象负责组装,而不是完成所有工作](#3.1 入口对象负责组装,而不是完成所有工作)
      • [3.2 规划不是凭空拆问题:先做初始搜索](#3.2 规划不是凭空拆问题:先做初始搜索)
      • [3.3 来源选择决定了后面的可信边界](#3.3 来源选择决定了后面的可信边界)
      • [3.4 子查询并行,但并行不意味着成本免费](#3.4 子查询并行,但并行不意味着成本免费)
    • 四、真正的关键:筛选什么内容让模型读
      • [4.1 搜索摘要、网页原文、模型上下文是三层对象](#4.1 搜索摘要、网页原文、模型上下文是三层对象)
      • [4.2 最新源码提供五种筛选模式](#4.2 最新源码提供五种筛选模式)
      • [4.3 BM25 为什么能作为有效回退](#4.3 BM25 为什么能作为有效回退)
      • [4.4 筛选的评估应该固定检索结果](#4.4 筛选的评估应该固定检索结果)
    • 五、深度研究与多角色研究:别混成一个概念
    • 六、写作阶段的保护:没证据,就不该装作有证据
    • [七、最小可运行示例:先 SDK,再考虑完整应用](#七、最小可运行示例:先 SDK,再考虑完整应用)
      • [7.1 创建环境与安装](#7.1 创建环境与安装)
      • [7.2 新建 research_demo.py](#7.2 新建 research_demo.py)
      • [7.3 用指定 URL 做更可控的研究](#7.3 用指定 URL 做更可控的研究)
    • [八、Linux 服务器部署:端口与文件比启动命令更重要](#八、Linux 服务器部署:端口与文件比启动命令更重要)
      • [8.1 先固定源码](#8.1 先固定源码)
      • [8.2 公网访问应该走 HTTPS 入口](#8.2 公网访问应该走 HTTPS 入口)
    • 九、二次开发:从研究工具变成你的业务服务
      • [9.1 第一层:业务包装,不急着改底层](#9.1 第一层:业务包装,不急着改底层)
      • [9.2 第二层:替换资料入口](#9.2 第二层:替换资料入口)
      • [9.3 第三层:增加证据审查,而不是只换 Prompt](#9.3 第三层:增加证据审查,而不是只换 Prompt)
    • 十、技术选型与常见问题
    • 十一、总结与阅读资料

一、项目定位:它是研究应用,不是万能 Agent 框架

GPT Researcher 是 Apache-2.0 许可的开源研究型 Agent 项目。它围绕一个具体目标组织能力:从问题出发,检索外部信息或本地资料,整理相关上下文,再生成报告。

例如,你要调查"团队应该使用 pgvector 还是 Milvus"。普通聊天可能直接给出比较表;研究型应用还需要查最新文档、弄清规模和运维约束、记录来源,并把证据组织成回答。这些步骤的输入、错误和成本并不相同,不能全部藏进一个长 Prompt。

它与三类系统的边界如下:

系统 主要职责 GPT Researcher 的关系
搜索引擎/API 返回候选网页或摘要 为研究提供入口,不代替研究
RAG 应用 在给定知识集合中检索并回答 可作为资料来源;研究还需要规划和报告组织
LangGraph 等编排库 提供状态和执行控制机制 可用于构建复杂研究工作流,不等于现成研究产品

"自主研究"也不是事实准确性的承诺。网页可能过时、抓取可能失败,模型仍可能曲解原文。本文把工程机制和质量边界分开讨论。

二、先看目录:应该从哪些文件读起

不要按目录字母顺序通读。更有效的顺序是"入口 → 主流程 → 上下文 → 写作 → 外围服务"。

text 复制代码
gpt_researcher/
  agent.py                  对外 GPTResearcher 类与组件组装
  skills/researcher.py       研究规划、来源分流、子查询调度
  skills/browser.py          抓取管理
  skills/context_manager.py 上下文管理
  skills/writer.py           报告生成与空证据保护
  skills/deep_research.py    递归深入研究
  context/select.py         选择上下文筛选策略
  context/lexical.py        BM25 本地筛选
  retrievers/               搜索服务适配
  scraper/                  页面抓取实现
  config/                   默认值与配置加载
backend/                    Web 应用后端
frontend/                   Web 界面
multi_agents/               更复杂的多角色研究流程

这里有一个易混淆的名字:gpt_researcher/skills/ 是项目内部 Python 功能模块,不应直接理解为所有文件都是可安装的 SKILL.md 包。阅读项目时,要看实际加载机制,而不是只看目录名。

前后端是应用包装层;GPTResearcher 才是适合嵌入自己后端的核心接口。这意味着你可以不部署完整 UI,只将它作为知识调研服务的一部分。

三、一条研究请求,实际经历了什么

3.1 入口对象负责组装,而不是完成所有工作

agent.py 在初始化时创建研究执行器、写作器、上下文管理器、抓取管理器和来源整理器。它保存问题、来源 URL、已访问 URL、上下文以及成本信息。

下面是依据源码整理的调用关系示意,不是可替代原类的完整实现:

python 复制代码
# 调用关系示意:省略日志、模型选择、图片等分支
async def conduct_research(self):
    self.context = await self.research_conductor.conduct_research()
    return self.context

async def write_report(self, custom_prompt=""):
    return await self.report_generator.write_report(
        ext_context=self.context,
        custom_prompt=custom_prompt,
    )

这个分离很实用:研究完成后,你可以先检查来源,再决定是否生成报告;也可以保存研究上下文,使用不同写作模板生成技术综述、采购说明或内部评估。一次检索与一次写作不是同一个生命周期。

入口源码

3.2 规划不是凭空拆问题:先做初始搜索

ResearchConductor.plan_research() 可以接收已经取得的初始搜索结果,再调用规划函数生成子查询。在网页研究路径中,初始搜索结果还会进入上下文处理,避免"规划看过的资料,写作时反而丢失"。

这与纯 Prompt 拆题不同。对于陌生问题,先搜索可以提供真实术语和议题范围。例如调查数据库,不应先假设所有产品都有同一种索引;应让官方材料帮助规划比较维度。

规划输出也需要约束。子问题太多会导致重复检索,太少会遗漏核心事实。建议先把输入写清楚:对象、时间范围、预期读者、比较维度、允许来源,而不是输入"全面分析一下"。

3.3 来源选择决定了后面的可信边界

源码根据 source_urls、report_source 等设置走不同分支:指定 URL、网页研究、本地资料、混合资料等。指定 URL 可以减少无关搜索,但无法保证网页可访问或内容充足。

complement_source_urls=True 表示允许在指定来源之外补充搜索。对严格的内部审计,这可能不合适;对公开技术调研,则可以提高覆盖面。

visited_urls 是运行内的去重信息,不是一个自动持久化、跨用户共享的缓存数据库。源码刻意保留调用者传入的空集合对象,便于父子研究共享去重状态。把它当成永久缓存,会误判资料是否已经更新。

3.4 子查询并行,但并行不意味着成本免费

网页研究路径使用 asyncio.gather() 汇总子查询上下文。异步的收益主要是等待搜索和网络响应时重叠工作;并不是把 CPU 计算变成多核,也不是降低服务商计费。

要区分四种限制:

  • 子查询数量决定工作量;
  • 抓取 Worker 数量影响网页访问并发;
  • 深度研究的 Semaphore 约束其查询执行;
  • 模型/API 的 RPM、TPM 和账户配额来自服务商。

只改一个"并发数"不会同时控制所有环节。网页研究汇总路径会捕获异常并返回空结果;因此上层业务不能仅凭"函数没有抛异常"认定研究成功。

研究执行源码

四、真正的关键:筛选什么内容让模型读

4.1 搜索摘要、网页原文、模型上下文是三层对象

搜索服务返回候选结果;抓取器读取页面正文;上下文筛选器进一步挑选相关段落。搜索成功不等于抓取成功,抓取成功也不等于写作模型读到了整个网页。

因此,"给报告加来源列表"只能证明来源被记录过,不能证明每个结论都由对应段落支持。

4.2 最新源码提供五种筛选模式

context/select.py 的 resolve_context_filter() 决定使用哪种策略:

模式 如何选择内容 工程取舍
auto 有 TypeSafe Key 使用 Jev,否则 keyword 默认便于启动
keyword 本地 BM25 排序 无额外筛选 API,但依赖词项匹配
jev 为段落评估回答问题的有用性 需要服务凭据、考虑外发资料与费用
embeddings 向量相似度筛选 需要嵌入模型,相似不等于能回答
none 不做筛选 更易超上下文和增加噪声

小资料集有直接传递的快速路径。Jev 或 embeddings 路径失败时会回退到 keyword。这不是"总能找到正确证据",只是避免筛选服务失败就完全失去已有资料。

这里的 Memory/嵌入模型是懒加载:只有相关路径使用时才创建,不是初始化每个研究对象都强制调用 Embedding API。

策略选择源码

4.3 BM25 为什么能作为有效回退

BM25 不是"大模型理解"。它根据词项在段落中的频率、在资料集合中的稀有程度和段落长度计算相关性:

text 复制代码
score(q,d) = Σ IDF(t) × tf(t,d)×(k1+1)
                         / [tf(t,d)+k1×(1-b+b×|d|/avgdl)]

高频但到处都出现的词贡献较小;与查询重叠、但比较稀有的技术词贡献较大;长度归一化避免长文仅因词多就占优势。

项目 lexical.py 将文本切块后排序,还采用相对阈值:只有达到最高分一定比例的块才保留,并设置最大数量。它不是强行填满 Top-K;在没有任何词项命中时,则回退到开头的块。

这带来两个边界:相对高分不代表事实真实;零匹配的回退也不代表与问题相关。中文资料尤其要用实际语料检查词项切分效果,不能把 Unicode 支持等同于专业中文分词效果。

BM25 实现

4.4 筛选的评估应该固定检索结果

如果今天用 A 策略,明天用 B 策略,同时搜索结果和模型都变了,就无法知道差异来自哪里。仓库的 evals/context_filter/ 使用录制与重放思路比较过滤策略,这是值得复用的实验设计。

自己的评估至少应固定问题集、原始页面、模型版本和报告模板,再比较相关段落覆盖、引用正确性、输入 Token、失败率和端到端耗时。官方样本集的表现不能直接迁移成你的中文业务效果,本文不引用它作为独立验证的性能承诺。

五、深度研究与多角色研究:别混成一个概念

skills/deep_research.py 的重点是递归探索:生成查询、执行查询、整理发现,再在剩余深度内继续研究。源码中有广度、深度和并发限制,并在没有成功查询结果时停止向下递归。

工程上可以把它理解为受预算约束的搜索树。若每层产生多个分支,深度提高一层可能显著增加搜索和模型调用。并且不同层的并发限制不能简单理解为"整个系统只有这么多个请求"。

multi_agents/ 则提供更复杂的多角色工作流组织。本文最小示例使用核心 SDK 普通报告路径,不应把它的行为说成已经包含编辑、审稿、人类批准等完整流程。

选择普通研究还是深度研究,取决于证据是否已有明确边界:官方文档比较通常先用普通模式;需要持续追踪新线索的问题再考虑深度模式。深入探索不是质量无限提升按钮。

六、写作阶段的保护:没证据,就不该装作有证据

skills/writer.py 在调用报告模型前会检查上下文是否为空;为空时返回明确的无法收集来源说明,而不是继续生成看起来有依据的报告。这是重要的失败显式化设计。

但要注意:这是字符串返回,不是统一业务异常或强类型状态。接入自己的后端时,应该在研究阶段先检查上下文,并将"无来源""研究失败""写作失败"分成明确状态,不依赖匹配英文提示句来判断结果。

历史 Issue #1572 曾报告空上下文下出现虚构来源。本文核对的当前 writer 已有空上下文保护;不能仅凭旧 Issue 就说最新源码没有保护,也不能反过来说所有引用幻觉已经解决。

业务上还应增加两道检查:输出链接是否属于已收集来源;关键判断是否能定位到具体原文段落。前者是集合约束,后者是证据支持性,难度不同。

写作器源码

七、最小可运行示例:先 SDK,再考虑完整应用

7.1 创建环境与安装

以下命令面向 Linux,先确保 python3.12 已安装:

bash 复制代码
python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install 'gpt-researcher==0.16.0' python-dotenv

新建 .env,只在本地填写自己的凭据,不提交到 Git:

dotenv 复制代码
OPENAI_API_KEY=替换为你自己的密钥
TAVILY_API_KEY=替换为你自己的搜索密钥
LANGUAGE=chinese
CONTEXT_FILTER=keyword
MAX_SEARCH_RESULTS_PER_QUERY=3
MAX_ITERATIONS=2

LANGUAGE 控制报告语言,不保证检索资料全部是中文。关键词筛选无需额外筛选模型,但研究规划与写报告仍然使用语言模型。

如果换成兼容接口,需要同时核对模型名、接口地址、输出参数以及工具/结构化输出支持。不要把任意服务都当成完全兼容的替代品。模型配置可使用 FAST_LLM、SMART_LLM、STRATEGIC_LLM,它们采用项目支持的 provider:model 形式;具体供应商和模型选择以当前官方配置文档为准。

7.2 新建 research_demo.py

python 复制代码
import asyncio
from pathlib import Path

from dotenv import load_dotenv
from gpt_researcher import GPTResearcher

load_dotenv()

async def main() -> None:
    researcher = GPTResearcher(
        query=(
            "为已有 PostgreSQL 的小型团队比较 pgvector 与 Milvus。"
            "围绕数据规模、过滤检索、备份和部署复杂度进行研究。"
            "区分官方事实与工程建议,不编造性能数字。"
        ),
        report_type="research_report",
        query_domains=["github.com", "milvus.io"],
        verbose=True,
    )
    context = await researcher.conduct_research()
    if not context or not str(context).strip():
        raise RuntimeError("没有取得研究上下文,请检查检索与抓取日志")

    report = await researcher.write_report(
        custom_prompt="使用中文;列明资料来源;证据不足处明确说明。"
    )
    if not report.strip():
        raise RuntimeError("报告内容为空,请检查写作模型日志")

    output = Path("outputs")
    output.mkdir(exist_ok=True)
    (output / "report.md").write_text(report, encoding="utf-8")

if __name__ == "__main__":
    asyncio.run(main())
bash 复制代码
python research_demo.py

这里的域名列表是搜索约束而不是安全防火墙;搜索服务支持方式也要检查。report.md 是输出文件,不代表结果已经审核。读者运行后应先打开来源链接检查,再将报告用于决策。

7.3 用指定 URL 做更可控的研究

python 复制代码
researcher = GPTResearcher(
    query="总结 pgvector 的索引选择与查询注意事项,用中文说明。",
    report_type="research_report",
    source_urls=["https://github.com/pgvector/pgvector"],
    complement_source_urls=False,
)

这适合教学复现和限定文档的比较。但 GitHub 页面抓取是否完整要看日志;严谨做法是保存文档快照、抓取时间和内容摘要,而不是只保存一个会变化的 URL。

八、Linux 服务器部署:端口与文件比启动命令更重要

8.1 先固定源码

bash 复制代码
git clone https://github.com/assafelovic/gpt-researcher.git
cd gpt-researcher
git checkout 3582a6841fe7dc801e2a34e34ee408641aea9bce
mkdir -p my-docs outputs logs

官方根目录 Compose 的后端端口是 8000,Next.js 前端端口是 3000。它的前端使用 Dockerfile.dev 并挂载源码,后端使用 root 用户,不能原样称为生产加固方案。

为了先验证后端,可以只启动官方的后端 service。将 Compose 文件中的后端端口映射改为以下内容,避免直接暴露到公网:

yaml 复制代码
ports:
  - "127.0.0.1:8000:8000"

上面是编辑官方 service 的片段,不是可独立启动的完整 Compose 文件。采用片段时不要用简单 override 追加一个端口并保留原来的公网映射,应检查最终合并配置。

官方 Compose 显式传递一部分环境变量。你新增的 LANGUAGE、CONTEXT_FILTER 等 SDK 环境设置,必须也传入容器,不能以为宿主 .env 的所有键都会自动成为容器环境变量:

yaml 复制代码
environment:
  LANGUAGE: ${LANGUAGE:-chinese}
  CONTEXT_FILTER: ${CONTEXT_FILTER:-keyword}
  MAX_SEARCH_RESULTS_PER_QUERY: ${MAX_SEARCH_RESULTS_PER_QUERY:-3}
bash 复制代码
docker compose config --quiet
docker compose up -d --build gpt-researcher
docker compose ps
docker compose logs --tail=100 gpt-researcher
curl -I http://127.0.0.1:8000/

curl -I 只检查 HTTP 可达性;即使某路由返回 405,也应继续按应用接口验证,而不是立即认定模型可用。网页能打开不代表搜索密钥、模型调用和文件导出都正常。

8.2 公网访问应该走 HTTPS 入口

优先通过 SSH 隧道做个人访问:

bash 复制代码
ssh -N -L 8000:127.0.0.1:8000 your-user@your-server

需要团队访问时,再设计域名、TLS、身份验证和配额。云安全组与主机防火墙是两层;容器发布端口是第三层。不要为了排障将 8000、3000 和所有内部端口全部开放。

若使用应用 WebSocket,反向代理还需要处理 Upgrade/Connection 头和长连接超时。前端 NEXT_PUBLIC_GPTR_API_URL 必须是浏览器能访问到的后端地址,不能填只在 Docker 网络内有效的 service 名。

outputs、my-docs、logs 的持久化与权限要分别验证:容器重建后报告是否还在,敏感文档是否可被无关用户访问,日志是否泄漏完整研究内容。

官方 Compose

九、二次开发:从研究工具变成你的业务服务

9.1 第一层:业务包装,不急着改底层

把提交研究请求与研究执行分开:API 返回任务 ID,后台 Worker 执行,数据库记录状态,完成后保存报告和来源。至少区分 queued、running、succeeded、failed、cancelled。

每次任务创建一个研究对象,不要跨用户共享上下文或修改进程级环境变量来切换请求配置。添加超时、预算和用户配额;重复提交用幂等键约束;重试前确认上次是否已经生成报告,以免重复消费费用。

9.2 第二层:替换资料入口

如果场景是企业内部调研,优先接入经过权限过滤的内部知识检索,而不是把数据库凭据直接交给模型。检索器应返回稳定结构,保留来源、标题和内容,明确内容是否还需要抓取。

当前 Release 记录了通过 gpt_researcher.retrievers entry point 加载第三方检索器的能力。真正编写插件前,应阅读适配器的构造和返回值契约;本文不提供未经完整验证的插件骨架,避免"能 import 但不能被调用"的假示例。

9.3 第三层:增加证据审查,而不是只换 Prompt

建议保存以下业务证据结构,它是二次开发建议,不是上游已实现的全部字段:

json 复制代码
{
  "task_id": "research-001",
  "source_url": "https://example.org/official-doc",
  "fetched_at": "2026-10-13T00:00:00Z",
  "content_hash": "由抓取内容计算",
  "passage": "与结论直接相关的原文片段",
  "claim": "报告中的待验证判断",
  "review_status": "unverified"
}

这样可回答两个不同问题:系统当时读到了什么,以及这些资料能否支持结论。不要用"模型再次说正确"替代证据审核。

十、技术选型与常见问题

需求 建议
有限来源的技术调研、报告助手 GPT Researcher 很适合先做验证
复杂文件交付、代码执行、长期协作 需要更完整的 harness 与执行环境,可参考 DeerFlow
高吞吐的固定业务 API 先判断是否真的需要研究 Agent;确定性检索/规则可能更简单
法律、医疗、投资等高风险决策 必须有专业审核,不应依赖自动报告直接决策

为什么有链接,仍然可能答错? 链接记录、段落筛选、判断生成是不同环节。真实来源可能被误读,也可能不支持输出的结论。

为什么只搜到结果却写不出报告? 继续检查正文抓取、筛选后的上下文、模型输出与超时,而不是只检查搜索 API 是否返回 200。

是否必须装向量数据库? 普通网页研究在本文版本中不要求。选择 embeddings 或外部知识库接入时,才按具体路径准备依赖。

为什么中文报告仍引用英文资料? 输出语言和检索语言不是同一个设置;需要明确资料范围和搜索策略。

能否把开源项目作为自己的项目? 可以在遵守 Apache-2.0 许可和依赖许可的前提下二次开发;项目经历应该描述你增加的权限、任务管理、评估和部署能力,不能把上游代码归为自己的原创。

十一、总结与阅读资料

GPT Researcher 最值得借鉴的,是研究阶段的组件边界,以及"已有网页如何变成模型上下文"这一中间层。把检索、抓取、筛选、写作和审核分别观察,才能知道质量问题发生在哪里。

要做自己的产品,先用 SDK 完成一个来源可检查的小任务,再补任务状态、持久化、权限、成本约束和评估。不要以"容器启动了"代替"研究服务可以可靠交付"。

资料采集日期:2026-10-12~2026-10-13。示例需要读者自备模型与搜索凭据;本文未虚构模型执行结果或云服务器截图。

相关推荐
数据皮皮侠4 小时前
中国产品级二氧化碳排放数据集
大数据·人工智能·搜索引擎·智慧城市·制造
旋生万物4 小时前
螺旋数不是玄学:Python 实现广义复数与等角螺线
开发语言·人工智能·python·云原生·aigc·软件工程·后端开发
边界智能4 小时前
国庆假期 AI Agent 行业复盘|智能体的考核标准被改写
人工智能·ai·智能体
李福春4 小时前
技术思考问题16:AI智能体模型Meta-Muse-Wonder越聪明,刹车应该谁来踩?
人工智能·agent安全·幂等设计·深圳同盟·腾讯云架构师技术同盟·智能体权限·工具授权分级
素男4 小时前
不够好,正好:便利贴那管「失败」的胶
人工智能·agent·self-becoming·ai长期记忆·ai自我介绍
AI码上来4 小时前
全模态、全双工、端到端:GPT-Live 之后被用坏了
人工智能·实时音视频
Solange__4 小时前
工业产线怎么用AI生成3D场景?从现场采集、部件重建到数字孪生交付的完整方案
人工智能·游戏·3d·ai·语言模型·自然语言处理·关卡设计
小小程序猴14 小时前
用工程视角建模企业AI培训周期:三段账本估算器(Python实现)
开发语言·人工智能·python
酒旅Agent开发实战4 小时前
10000个MCP Server之后:生态繁荣还是碎片化?
人工智能·大模型·酒店预订·ai agent·mcp
javaDocker5 小时前
授权、协议与提示注入:Personal Agent 的安全基线该抄哪几份作业
人工智能·安全·金融·架构