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