Skip to content

代码导读:Hybrid 的两路上下文如何交给报告写作器

返回 GPT Researcher 剖面

本篇只追踪 assafelovic/gpt-researcher@6f998577d547b1e54ec662dac63583aa11e3b84b 的普通 ResearchReportReportSource.Hybrid、非 Granite 的 PromptFamily 路径:调用者未给 source_urls,也未给 write_report() 外部上下文。设想任务是“根据本地材料和网页资料写一份技术调研”;这只是帮助读代码的输入例子,不是运行记录。关键问题是:两路材料什么时候仍是原始文档,什么时候变成供 LLM 写作的字符串?提示词家族选择

沿调用链走九步

  1. 入口先确定分支所需的状态。 GPTResearcher.__init__() 保存 report_sourcesource_urlsdocument_urlsquery_domainsvisited_urlscontext。其中 visited_urls 是已见 URL 的集合,context 初始可为空;两者并不是“已核实事实”的集合。随后调用 conduct_research();只有 report_type == DeepResearch 且对应研究器存在时才转入深度研究分支。本篇的普通报告继续调用 ResearchConductor.conduct_research(),将返回值赋给 self.context构造状态 · 研究入口
  2. source_urls 比 Hybrid 优先。 ResearchConductor.conduct_research() 先检查是否提供了特定来源 URL,随后才比较 report_source。所以传了 source_urls 的请求不会自动进入下面的 Hybrid 并发分支;要研究指定 URL 的行为,应另读 _get_context_by_urls()。在本文前提下,控制流落到 ReportSource.Hybrid来源选择
  3. 装载文档,但装载成功不是先决条件。 Hybrid 有 document_urls 时用 OnlineDocumentLoader,否则从配置的 doc_pathDocumentLoader;若设置了 vector_store,还会把装载结果写入它。接着 asyncio.gather() 同时发起两次 _get_context_by_web_search():一次传 document_data,一次传空列表。document_data 为空时,第一路并不会因此终止,见第 5 步的回退。Hybrid 两路及可选写入
  4. 两路各自规划子问题。 尽管函数名叫 _get_context_by_web_search(),传入文档的一路也调用 plan_research();规划函数会先用首个 retriever 做初始搜索,再生成研究提纲。非子专题报告把原始 query 追加到子问题列表,并用 asyncio.gather() 并发处理各子问题。因此“本地文档路径”不是一条完全离线的纯文档路径。规划时的初始检索 · 子问题执行与空结果
  5. 一个子问题决定用文档,还是再去取网页。 _process_sub_query() 只有在 scraped_data 为假时才调用 _scrape_data_by_urls()。所以非空的文档列表会直接送入相似内容提取;空文档列表和第二条网页路径则会搜索、抓取网页。URL 路径按 retriever 的 requires_scraping 选择重新抓取或复用 raw_content;待抓取 URL 会进入 visited_urls 去重,抓取结果还可能写入可选向量库。这里的副作用是检索、抓取、记录 URL 和可选存储,不是对材料做事实核验。子问题的关键条件 · URL 与正文的选择
  6. 原始材料在此变成“相关上下文”。 ContextManager.get_similar_content_by_query() 把页面列表交给 ContextCompressor。材料总字符数低于 COMPRESSION_THRESHOLD(默认 8000)且文档数不超过上限时,快路径直接格式化前几份文档;否则用拆分与 embedding 相似度过滤。格式化字符串包含 SourceTitleContent,但保留来源字段不等于验证内容真假或引用是否准确。压缩入口 · 两种压缩路径 · 上下文格式
  7. 两路汇合后才成为研究器的写作输入。 每路先把非空子问题结果连接为字符串;本文范围内的 PromptFamily.join_local_web_documents() 包装成“local documents”和“web sources”两段,再写到 researcher.context。Granite 家族可覆写拼接格式,不能把这两个标签当成所有配置的输出。curate_sources 若开启,后面还会额外筛选并重新格式化上下文;它不是默认必经的独立证据审查。子问题汇总 · 本篇拼接格式 · Granite 覆写 · 保存及可选筛选
  8. 写作器只收到整理后的上下文。 GPTResearcher.write_report() 在本篇前提下把 self.context 交给 ReportGenerator.write_report()。后者以字符串去空白检查,非空才调用 generate_report()generate_report() 将上下文放入提示并请求 LLM,失败时尝试另一种消息组织,再失败则记录异常并返回当前 report。因此源码能证明有提示生成链,却不能证明生成内容的事实或引文正确。写作交接 · 空串保护 · LLM 调用与重试
  9. “两路都空”暴露一个保护边界。 _get_context_by_web_search() 无有效子问题结果时返回 [];本文范围内的 join_local_web_documents() 即使收到两个空列表,也仍生成含两个段落标签的非空字符串。写作器检查的是 _ctx.strip(),不是“至少一份带正文的来源”。由这些条件推断:这道保护不能单独保证该 Hybrid 路径的报告有材料;是否在真实配置中触发、最终会写出什么,尚未运行验证。另一个失败分支是单个子问题异常被记日志并变成空串;asyncio.gather() 外部的规划、装载等异常是否被上层捕获,需要沿具体调用方另查,不能宣称系统会自动恢复。空子问题与异常 · 子问题错误返回 · 拼接与保护 · 写作器判断

读完应能判断什么

Hybrid 是“两次上下文获取并发、随后拼接”的实现策略,不是“本地事实与网页事实交叉验证”。尤其第一路文档为空时也可能走网页抓取,第二路则始终从空文档输入启动;只看两段标签,不能反推来源类别或质量。上述是固定 commit 的源码事实与明确标注的静态推断;没有运行检索器、加载本地文件、调用模型,也没有验证真实报告的引用。来源台账(仓库开放后提供)已识别该提交的仓库根许可证,但子目录与第三方素材仍需权益终审;本篇不复刻上游源码。


在线预览稿:书稿仍在校稿,系统篇以文内固定源码版本为准;静态阅读不等于运行验收。发现错误或有改进建议?按本章填写邮件,或查看反馈说明