Skip to content

feat: 支持 PubMed/arXiv 学术搜索、全文获取与引用审计 - #90

Open
openjiuwen-sync-bot[bot] wants to merge 3 commits into
openJiuwen-ai:devfrom
openjiuwenai:sync/pr-353
Open

feat: 支持 PubMed/arXiv 学术搜索、全文获取与引用审计#90
openjiuwen-sync-bot[bot] wants to merge 3 commits into
openJiuwen-ai:devfrom
openjiuwenai:sync/pr-353

Conversation

@openjiuwen-sync-bot

@openjiuwen-sync-bot openjiuwen-sync-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown

Paired: GitHub #90GitCode !353

变更概述

本 PR 增加 PubMed 和 arXiv 学术搜索能力,并完善学术全文在信息收集、写作选择和最终引用过程中的审计链路。

主要改动

  • 支持 query 级搜索引擎路由,可按查询选择 PubMed 或 arXiv
  • 支持 PubMed ESearch、PubMed EFetch 和 PMC 全文获取
  • 支持 arXiv Atom API、HTML 全文获取及 PDF 回退
  • 增加 PubMed/arXiv 进程内统一限速和有限重试
    • 最多尝试 3 次
    • 重试 429、500、502、503、504、连接错误和超时
    • 支持 Retry-After
    • 默认按 1 秒、2 秒退避
    • arXiv 全文下载并发限制为 2
  • 标准化学术搜索结果并传递全文、DOI、PMCID 等来源信息
  • 去重时即使 Tavily 结果成为代表文档,也保留学术来源及全文元数据
  • 日期过滤覆盖 PubMed 和 arXiv 发表日期
  • 增加学术全文审计链路:
    • returned
    • entered
    • selected
    • cited
  • 审计事件按 conversation_id 严格隔离
  • 支持 PubMed 原始 URL 与 PMC 引用匹配
  • 支持 arXiv abs、HTML 和 PDF URL 匹配
  • 无会话上下文时跳过正式审计事件,不影响正常搜索

稳定性与兼容性

  • 全文获取失败时保留摘要结果,不导致整个搜索失败
  • 普通非临时 4xx 错误不重试
  • arXiv PDF 备用 URL 仅在 404 或内容解析失败时使用
  • PDF 文本解析移出异步事件循环,并在达到长度限制后提前停止
  • 限速状态在同一进程的同步、异步和多个 wrapper 实例之间共享
  • 已合并最新 upstream/dev

What type of PR is this?

/kind

Self-checklist:(请自检,在[ ]内打上x,我们将检视你的完成情况,否则会导致pr无法合入

    • 设计:PR对应的方案是否已经经过Maintainer评审,方案检视意见是否均已答复并完成方案修改
    • 测试:PR中的代码是否已有UT/ST测试用例进行充分的覆盖,新增测试用例是否随本PR一并上库或已经上库
    • 验证:PR描述信息中是否已包含对该PR对应的Feature、Refactor、Bugfix的预期目标达成情况的详细验证结果描述
    • 接口:是否涉及对外接口变更,相应变更已得到接口评审组织的通过,API对应的注释信息已经刷新正确
    • 文档:是否涉及官网文档修改,如果涉及请及时提交资料到Doc仓

Linked Closing Issues:

@openjiuwen-collaboration-bot

openjiuwen-collaboration-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown

head_sha: 6efdefae1fa0f066e46eeb3c30320cb6104c547d

变更摘要

本 PR 为系统新增了 PubMed 和 arXiv 学术搜索能力,支持从文献检索到全文获取的完整链路,并建立了贯穿"返回→进入→选中→引用"四个阶段的全文审计机制。核心改动包括:新增 PubMedSearchAPIWrapperArxivSearchAPIWrapper 两个学术搜索引擎 wrapper,实现 ESearch/EFetch/PMC 全文获取及 Atom API/HTML/PDF 全文回退;引入 ServiceRequestControl 进程内统一限速与有限重试(最多 3 次,覆盖 429/5xx/连接超时,支持 Retry-After),arXiv 下载并发限制为 2;在搜索结果归一化、证据构建、去重等环节完整传递学术来源元数据(academic_sourcedoipmcidfull_text 等);新增 academic_full_text_audit.py 审计模块,按 conversation_id 严格隔离审计事件,并支持 PubMed/arXiv URL 变体与引用的匹配。

主要改动

  • 新增 PubMed/arXiv 学术搜索引擎与全文获取:在 scholarly_search/pubmed.pyarxiv.py 中分别实现 PubMedSearchAPIWrapperArxivSearchAPIWrapper,支持 PubMed ESearch 精确 PMID 查询与 EFetch 摘要获取、PMC 全文 XML 解析,以及 arXiv Atom API 查询、HTML 全文提取(_parse_arxiv_html)和 PDF 文本解析(_extract_pdf_text),PDF 解析移至异步事件循环外执行并在达到字符限制后提前停止。

  • 进程内统一限速与重试机制:在 scholarly_search/common.py 中新增 ServiceRequestControl(共享限速调度器)和 ServiceConcurrencyLimiter(arXiv 下载并发限制为 2),实现 async_request_with_retry / sync_request_with_retry,最多 3 次尝试,重试 429/500/502/503/504、连接错误和超时,支持 Retry-After 响应头解析,限速状态在同一进程的同步/异步及多个 wrapper 实例间共享。

  • 学术搜索结果归一化与全文元数据传递:在 collector_function.py_normalize_web_search_item 中为 PubMed/arXiv 结果附加 academic_sourceacademic_source_iddoipmcidfull_text 等字段,新增 _parse_scholarly_published_date 支持日/月/年三种精度的出版日期解析;build_evidence_atom 优先使用可用全文、传递 evidence_content_typeevidence_content_charsdeduplicate_doc_infos 在 Tavily 结果成为代表文档时保留被丢弃文档的学术来源及全文元数据。

  • 学术全文审计链路:新增 academic_full_text_audit.py,实现 returned(全文返回)、entered(进入收集器)、selected(被写作选中)、cited(被最终引用)四个审计事件,通过 _citation_identities 支持 PubMed 原始 URL/PMC URL 及 arXiv abs/HTML/PDF URL 变体与引用的匹配,按 conversation_id 严格隔离,无会话上下文时静默跳过。

  • 搜索错误重试策略细化InfoRetrievalNode._direct_search_with_retry 支持 retryable 参数,non-retryable 错误不再重试;run_web_search 异常时统一返回 retryable=True;学术引擎默认 max_web_search_results=1 且查询路由自动启用日期过滤。

@openjiuwen-collaboration-bot

openjiuwen-collaboration-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown

head_sha: 95fe3191f7e96ea7020969181c13fcd2f929c195

代码审查

Now I've completed the review. Let me provide the closing summary.


审查结论

已逐一审查全部 8 个变更文件,每个文件的审查结果如下:

文件 审查结论
doc_prefilter.py 1 个 P2 问题 — 去重时元数据与正文不一致
report.py 1 个 P3 问题_write_subsection_reports 中死代码(重复定义)
collector_evidence.py 无问题
collector_function.py 无问题
info_collector.py 无问题
main_graph_nodes.py 无问题
workflow.py 无问题
arxiv.py 无问题

按优先级统计: P0: 0 | P1: 0 | P2: 1 | P3: 1

整体风险判断: 中等偏低。P2 问题涉及去重逻辑中元数据完整性,在 Tavily 结果成为代表文档的特定场景下会被触发,可能导致下游审计数据不一致。P3 问题为纯粹的死代码,不影响运行行为。其余变更(全文获取、重试逻辑、日期过滤、审计链路接入)实现质量良好,无安全或并发方面的明显缺陷。


Now I have a complete picture. Let me compile the final review.


审查总结

变更文件审查结果

文件 审查结果
deepsearch/openjiuwen_deepsearch/framework/openjiuwen/tools/search_api/scholarly_search/common.py 无问题
deepsearch/openjiuwen_deepsearch/framework/openjiuwen/tools/search_api/scholarly_search/pubmed.py 2 个问题(P2 × 1,P3 × 1)
deepsearch/openjiuwen_deepsearch/framework/openjiuwen/tools/web_search.py 无问题
deepsearch/openjiuwen_deepsearch/utils/academic_full_text_audit.py 无问题
deepsearch/tests/info_collector/algorithm/test_collector_evidence.py 无问题
deepsearch/tests/info_collector/algorithm/test_collector_function.py 无问题
deepsearch/tests/info_collector/test_academic_search_routing.py 无问题
deepsearch/tests/report/test_doc_prefilter.py 无问题

按优先级汇总

  • P2 × 1:PMC XML 解析中表格内嵌 <p> 文本重复
  • P3 × 1:full_text_timeout_seconds 属性命名误导(作用于全部 PubMed API 请求,非仅全文)

整体风险评估

中低风险。核心的重试、限速、全文获取和审计链路设计合理,边界处理较完善。主要问题是 _parse_pmc_xml 中的内容重复缺陷,在包含复杂表格的 PMC 全文中会降低全文质量,但对于大多数 PubMed 摘要型使用场景影响有限。建议在合并前修复 P2 的内容重复问题,P3 可根据实际情况在下个迭代处理。


审查总结

已审查全部 3 个变更文件:

文件 审查结果
deepsearch/tests/tools/search_api/test_scholarly_search.py 1 个 P3 发现(限流测试时间容差过大)
deepsearch/tests/tools/test_web_search.py 无问题
deepsearch/tests/utils/test_academic_full_text_audit.py 无问题

按优先级统计

  • P0: 0
  • P1: 0
  • P2: 0
  • P3: 1(限流测试中 4 处断言使用 >= 0.04 而理论最小值为 0.05,允许 20% 偏差,为可选的测试精度改进)

总体风险评估:低风险

本次变更全部为测试代码新增/修改,覆盖了 PubMed/arXiv 学术搜索、全文获取、请求重试与限流、以及学术全文审计链路的完整功能验证。测试设计严谨:正确使用 pytest.mark.asyncio 标记异步测试、通过 autouse=True fixture 管理共享状态清理、使用 caplog / session_id_ctx 验证审计事件与跨会话隔离。未发现逻辑错误、安全隐患或可靠性问题。

类型 数量
🔴 阻塞 0
🟡 建议 4

💬 仅评论

@CLAassistant

CLAassistant commented Aug 11, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@openjiuwen-collaboration-bot

Copy link
Copy Markdown

head_sha: 6efdefae1fa0f066e46eeb3c30320cb6104c547d

任务名称 结果 日志操作
静态检查 ❌FAILED 点此跳转
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 ✅SUCCESS 点此跳转
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck ✅SUCCESS N/A

@openjiuwen-collaboration-bot

Copy link
Copy Markdown

head_sha: 1628d6d26d73a2625662fb85fb7c29f1bde59f58

任务名称 结果 日志操作
静态检查 ❌FAILED 点此跳转
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 ✅SUCCESS 点此跳转
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck ✅SUCCESS N/A

@openjiuwen-collaboration-bot

Copy link
Copy Markdown

head_sha: 95fe3191f7e96ea7020969181c13fcd2f929c195

任务名称 结果 日志操作
静态检查 ❌FAILED 点此跳转
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 ✅SUCCESS 点此跳转
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck ✅SUCCESS N/A

caption = self._joined_text(element.find("caption"))
value = " ".join(part for part in (label, caption) if part)
if value:
parts.append(value)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: 95fe3191f7e96ea7020969181c13fcd2f929c195

🟡 Medium Priority

changed line: pubmed.py_parse_pmc_xmlfor element in body.iter(): 循环(第 388 行)。body.iter() 遍历 <body> 的所有后代节点,包括嵌套在 <td> 内部的 <p> 元素。当处理 <tr> 时,_joined_text(cell) 通过 "".join(element.itertext()) 递归提取 <td> 的全部文本(含嵌套 <p>),形成表格行文本(如 "Value 1 | Value 2")。随后 body.iter() 继续遍历到嵌套的 <p>,其标签匹配 "p",再次被提取为独立段落。虽然后续 dict.fromkeys(parts) 可消除完全相同的字符串,但当 <td> 包含混合内容(如 <td>Label: <p>text</p></td>)时,表格行文本为 "Label: text",独立 <p> 文本为 "text",二者不同,无法去重,导致内容重复浪费 LLM 上下文窗口。

failure mode: 对于包含表格的 PMC 全文,解析输出的 full_text 中同一内容出现两次(一次在表格行中,一次作为独立段落),降低全文质量,影响后续写作模型的理解准确性。

suggested fix: 将 body.iter() 改为仅遍历 <body> 的直接子元素,对 <sec> 递归处理其直接子元素;或在遍历时跟踪当前元素是否位于已处理的容器(<table-wrap><fig><tr>)内部,若是则跳过。

建议:改为仅遍历 body 的直接子元素,对 section/table-wrap/fig 等容器分别处理其内容,避免深度递归遍历导致表格内嵌元素被重复提取。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: 71e8b5a39b665d8bee999312ea1221671602d823

已在 71e8b5a 修复。解析 table-wrap 和 fig 时会记录其结构化文本子节点,后续遍历不再单独提取其中的

,避免 caption 和表格单元格内容重复;表格行内容仍正常保留。已补充表格、图片 caption 和嵌套单元格段落的回归测试。

"url": "https://export.arxiv.org/abs/hep-th/9901001",
"evidence_content_type": "full_text",
"evidence_content_chars": 100,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: 95fe3191f7e96ea7020969181c13fcd2f929c195

🟡 Medium Priority

4 个测试中对 emit_cited_academic_full_text_events 的第三参数使用了两种不一致的格式:

  • 第 159 行(test_cited_helper_matches_pubmed_document_to_pmc_citation):传入 [pubmed],即 List[Dict]pubmed 本身是 dict,[pubmed] 是一个扁平的单文档列表)。
  • 第 179 行(test_cited_helper_matches_arxiv_url_variants):传入 [arxiv],同样是 List[Dict]
  • 第 201 行(test_cited_helper_matches_legacy_arxiv_url_variants):传入 [document],同样是 List[Dict]

这两种格式语义不同:List[List[Dict]] 按引用分组文档,List[Dict] 是扁平文档列表。至少有一组测试的调用方式与被测函数的真实签名不符。如果函数实际期望 List[List[Dict]],则第 159/179/201 行的测试在对 dict 进行迭代时可能迭代到 dict 的 key("academic_source""url" 等字符串),导致引用匹配逻辑静默跳过,测试成为误通过(false positive);反之若函数期望 List[Dict],则第 130 行的 [[pubmed], [arxiv]] 会使函数拿到 list 而非 dict,同样可能静默失败。

建议:确认 emit_cited_academic_full_text_events 的实际签名:若期望 List[List[Dict]],则将第 159/179/201 行的 [doc] 改为 [[doc]];若期望 List[Dict],则将第 130 行的 [[pubmed], [arxiv]] 改为 [pubmed, arxiv]。统一后所有测试使用相同格式。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: 71e8b5a39b665d8bee999312ea1221671602d823

经核对,此处无需修改。生产数据 all_classified_contents 可以是 List[List[Dict]],综合测试已经使用 [[pubmed], [arxiv]] 覆盖该结构。_iter_documents() 会递归展开 Dict、List[Dict]、List[List[Dict]] 和 tuple。其他测试使用 [document] 是为了单独验证 PMC/arXiv URL 身份匹配;两种输入最终产生相同的文档序列,不存在误通过。

"full_text_truncated",
):
if full_text_key in discarded:
representative[full_text_key] = discarded[full_text_key]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: 95fe3191f7e96ea7020969181c13fcd2f929c195

🟡 Medium Priority

changed line 275-284 → 去重时若 discarded(被淘汰文档)有全文而 representative(代表文档)仅有摘要,代码将 evidence_content_type(设为 "full_text")与 evidence_content_chars(全文字符数)从 discarded 拷贝到 representative。但 representative 的 original_content 仍保留原有摘要文本,导致元数据与正文不一致。

受影响的行为/契约:下游消费者若通过 evidence_content_type == "full_text" 判断是否为全文,或以 evidence_content_chars 估算内容长度,将获得错误信息——元数据声称"全文"但 original_content 实际存放的是摘要。

触发条件:去重时同一 URL 下 Tavily/web 结果被选为代表文档,而 arXiv/PubMed 的全文结果被淘汰。

建议:方案 A:拷贝 evidence_content_type/evidence_content_chars 的同时也拷贝 original_content。方案 B:不拷贝 evidence_content_type 和 evidence_content_chars,仅保留 academic_source/doi/pmcid 等来源溯源字段,避免元数据与正文不一致。推荐方案 B(仅保留溯源信息,不改变内容类型元数据),更贴合 PR 中"保留学术来源及全文元数据"的意图且不会引入不一致。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: 71e8b5a39b665d8bee999312ea1221671602d823

已在 71e8b5a 修复。摘要文档成为代表文档时,只继承 academic_source、academic_source_id、DOI、PMCID 等学术溯源字段,不再继承全文类型、字符数、格式和全文 URL。只有代表文档与被淘汰文档本身都是全文时,才补齐缺失的全文元数据。已补充回归测试。

search_results=[],
error=f"Error when run web search {resolved_name}: {e}")
error=f"Error when run web search {resolved_name}: {e}",
retryable=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: 95fe3191f7e96ea7020969181c13fcd2f929c195

🟡 Medium Priority

changed line: web_search.py:208 — 在 except Exception 分支中新增 retryable=True

affected behavior: run_web_search 捕获来自下游 wrapper(如 PubMedSearchAPIWrapper、ArxivSearchAPIWrapper)的所有异常并统一标记为可重试。wrapper 内部通过 async_request_with_retry 已对可重试状态码(429/5xx)做了内部重试,非瞬态错误(如 400/401/403)会立即向上抛出 httpx.HTTPStatusError。该异常被此处的 except Exception 捕获后也获得 retryable=True

failure mode: info_collector 层的 _direct_search_with_retry 根据 retryable 字段决定是否重试整个搜索(见 info_collector.py:551: tool_result_raw.get("retryable", True) is not False)。当 wrapper 因 400 等非瞬态错误失败后,info_collector 会再次重试,浪费 API 配额并延迟错误暴露。虽然修改前缺少 retryable 字段时默认也是 True(行为等价),但本 PR 将该字段显式化,却未根据异常类型区分重试策略,与 PR 描述中"普通非临时 4xx 错误不重试"的意图不一致。

suggested fix: 在 except Exception 分支中根据异常类型设置 retryable:仅对连接错误、超时或 5xx/429 响应标记为 retryable=True,其余设为 retryable=False。可复用 common.py 中已有的 is_transient_connection_errorhttp_status_code 辅助函数。

建议:在 except 分支中区分瞬态/非瞬态异常,对非瞬态错误设置 retryable=False。可导入 common.py 的 is_transient_connection_error 和 http_status_code 辅助判断。

Suggested change
retryable=True)
return dict(search_engine=resolved_name,
search_results=[],
error=f"Error when run web search {resolved_name}: {e}",
retryable=is_transient_connection_error(e) or http_status_code(e) in RETRYABLE_HTTP_STATUSES)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: 71e8b5a39b665d8bee999312ea1221671602d823

已在 71e8b5a 修复。run_web_search 复用现有的 is_transient_connection_error、http_status_code 和 RETRYABLE_HTTP_STATUSES 分类异常:连接错误、超时、429 和可重试 5xx 返回 retryable=True;400/401/403 及未知异常返回 retryable=False。已增加 400、429、503、连接错误和未知异常测试。

@openjiuwen-collaboration-bot

Copy link
Copy Markdown

head_sha: e39d49b35990b45183950da12be99457171ed79b

任务名称 结果 日志操作
静态检查 ✅SUCCESS 点此跳转
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 ✅SUCCESS 点此跳转
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck ✅SUCCESS N/A

@openjiuwen-collaboration-bot

Copy link
Copy Markdown

head_sha: 71e8b5a39b665d8bee999312ea1221671602d823

任务名称 结果 日志操作
静态检查 ✅SUCCESS 点此跳转
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 ✅SUCCESS 点此跳转
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck ✅SUCCESS N/A

@openjiuwen-collaboration-bot

Copy link
Copy Markdown

head_sha: ebd4e5757d56802968d27a87d74a82db1dcec283

任务名称 结果 日志操作
静态检查 ✅SUCCESS 点此跳转
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 ✅SUCCESS 点此跳转
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck ✅SUCCESS N/A

@@ -0,0 +1,66 @@
# Academic search full-text enrichment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ebd4e5757d56802968d27a87d74a82db1dcec283

和代码仓其他 feature 文档统一,改中文

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: a17193b124dcbe221bb9d7f5923947be340752d8

已修改。academic-search-full-text.md 已整体改为中文,并删除了本次不进入正式代码的实验审计相关描述。文档现在只说明学术搜索、官方全文获取、通用全文字段协议、配置开关及功能边界。

- 存在时间范围时,初始 query 与 supervisor 补搜 query 由 LLM 自然表达该范围;每条 query 最多 5 个主题关键词,
时间短语不计入该限制。`source_date` 表示资料发表时间,`content_date` 表示事实或数据时间。
- 时间约束采用召回优先的 best-effort:只删除 Tavily 返回的可确认发表日期且明确越界的文档,日期未知、其他 web 引擎和 local 文档继续进入证据。
- 时间约束采用召回优先的 best-effort:删除 Tavily、PubMed 和 arXiv 返回的可确认发表日期且明确越界的文档,日期未知、其他 web 引擎和 local 文档继续进入证据。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ebd4e5757d56802968d27a87d74a82db1dcec283

PubMed 和 arXiv 的时间约束不在本次修改范围内,去除

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: a17193b124dcbe221bb9d7f5923947be340752d8

已修改。已删除 PubMed/arXiv 日期过滤及相关日期解析逻辑,info-collector-subgraph.md 也恢复为当前实际边界:只有 Tavily wrapper 提供可确认的 source_date/source_date_type=published,Collector 才执行发表日期过滤。PubMed/arXiv 返回的书目日期不进入该过滤协议。

current_index, current_doc = current
if _representative_sort_key((index, doc)) > _representative_sort_key(current):
grouped[key] = (index, doc)
representative, discarded = doc, current_doc

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ebd4e5757d56802968d27a87d74a82db1dcec283

为什么需要修改 doc_prefilter?从下面的逻辑看这个文件的修改是为了审计仍能识别该文档是 PubMed/arXiv 全文,不需要有这个识别项

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ecc8715fb660d72dec50a85ff2d693b9c19683f6

已修改。实验审计不进入正式代码,因此已撤销 doc_prefilter.py 中为 PubMed/arXiv 保留代表文档及合并审计元数据的专用逻辑。

f"selected_content len: {len(classified_content)}"
)
classified_content = current_inputs.get("classified_content", [])
emit_selected_academic_full_text_events(logger, classified_content)

@openjiuwen-collaboration-bot openjiuwen-collaboration-bot Bot Aug 14, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ebd4e5757d56802968d27a87d74a82db1dcec283

审计只是为了预实验你是否有真实调用学术论文,事实上这个审计函数没有上正式代码仓的必要,审计相关内容需要去除,其他文件也看下

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ecc8715fb660d72dec50a85ff2d693b9c19683f6

已修改。已移除 returned → entered → selected → cited 审计链路、审计工具及对应测试。

content = str(full_text if use_full_text else record.get("content") or "")[
:MAX_COLLECTOR_DOC_CONTENT_LENGTH
]
evidence_content_type = "full_text" if use_full_text else "abstract"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ebd4e5757d56802968d27a87d74a82db1dcec283

开启网页正文增强后,PubMed/arXiv 的 HTTP URL 也会进入候选列表。apply_enrichment_to_doc 会替换 original_content 和 source_id,却不更新 evidence_content_type、evidence_content_chars 及全文来源字段,导致摘要或网页正文被错误标记为 full_text,并被审计为已使用官方全文。
需要修改一下网页正文增强的逻辑,排除掉你的垂域搜索的 URL

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ecc8715fb660d72dec50a85ff2d693b9c19683f6

已修改。通用网页增强模块没有硬编码 PubMed/arXiv 域名。学术 wrapper 通过通用字段 skip_webpage_enrichment=True 声明结果已经由上游处理;Collector 和 Evidence 仅传递严格布尔值 True,网页增强候选构建遇到该标记时直接跳过,从而避免再次抓取页面并覆盖官方全文字段。


logger = logging.getLogger(__name__)

SCHOLARLY_MONTH_NAMES = {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ebd4e5757d56802968d27a87d74a82db1dcec283

这个应该只服务于PubMed?从职责划分上不适合放在这里,从长期维护看,会让通用 Collector 逐渐耦合 PubMed/arXiv 的细节

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ecc8715fb660d72dec50a85ff2d693b9c19683f6

已修改。Collector 已不再判断 PubMed/arXiv 来源,而是消费通用全文协议。仅当 full_text_status 为 available、unavailable 或 failed 时传递全文字段;available 但正文为空时降级为 unavailable;full_text_truncated 和 skip_webpage_enrichment 只接受真正的布尔值 True。后续其他全文提供者遵守同一协议即可接入,无需修改 Collector。

def apply_full_text_extension_config(wrapper: Any, extension: dict | None) -> None:
ext = extension or {}
if "scholarly_fetch_full_text" in ext:
wrapper.fetch_full_text = bool(ext["scholarly_fetch_full_text"])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ebd4e5757d56802968d27a87d74a82db1dcec283

不要用 bool() 解析字符串开关

extension 是开放字典,配置可能传入字符串 false;但 Python 中 bool('false') 为 True,因此用户关闭全文抓取的配置会被忽略。请校验布尔类型,或显式解析 true/false 字符串。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ecc8715fb660d72dec50a85ff2d693b9c19683f6

已修改。增加了严格布尔配置解析,只接受原生 bool,或忽略大小写和首尾空格的 "true"/"false"。其他值会抛出 ValueError,不会再通过 bool("false") 得到错误结果。

academic_config["search_engine_name"] = engine_name
academic_config["search_url"] = ""
academic_config["search_api_key"] = bytearray()
academic_config["max_web_search_results"] = 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ebd4e5757d56802968d27a87d74a82db1dcec283

这个配置原因是为什么

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ecc8715fb660d72dec50a85ff2d693b9c19683f6

已补充注释。学术结果还可能继续抓取官方全文,单条结果的网络和解析成本明显高于普通网页搜索,因此注册学术 wrapper 时不继承主搜索引擎较大的结果数,而是显式限制为 1。wrapper 仍允许调用方显式覆盖该配置。


from __future__ import annotations

import asyncio

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ebd4e5757d56802968d27a87d74a82db1dcec283

这两个学术搜索引擎修改后,是否测试过这两个搜索工具的耗时,评论区贴一下

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ecc8715fb660d72dec50a85ff2d693b9c19683f6

分别完成一次启用学术搜索的 PubMed 和 arXiv E2E 实验。PubMed 实验共调用 PubMed 37 次,平均单次观测延迟 12.91 秒、中位数 10.02 秒、最长 34.50 秒;arXiv 实验,共调用 arXiv 105 次,平均单次观测延迟 38.52 秒、中位数 32.34 秒、最长 188.64 秒。实验表明学术搜索及官方全文处理成本明显高于普通搜索,因此学术 wrapper 默认每个 query 只返回 1 条结果,避免进一步扩大请求和全文解析成本。

from __future__ import annotations

import asyncio
import logging

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ebd4e5757d56802968d27a87d74a82db1dcec283

SEG 有提到,垂域搜索加个配置开关,默认关闭。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ecc8715fb660d72dec50a85ff2d693b9c19683f6

已修改。新增 web_search_engine_config.extension.scholarly_search_enabled,默认值为 false。关闭时不会实例化 PubMed/arXiv wrapper,同时会清理 query 级学术路由;只有显式启用后才注册学术引擎并允许路由。配置行为、严格布尔解析及启停路由均已补充文档和测试。

except Exception as exc:
row.update(_full_text_fields())
row["full_text_status"] = "failed"
logger.warning("Unable to enrich PubMed result %s from PMC: %s", row.get("source_id"), exc)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ebd4e5757d56802968d27a87d74a82db1dcec283

看下错误日志的情况,是否出现大量 warning 日志

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ecc8715fb660d72dec50a85ff2d693b9c19683f6

已检查 PubMed 日志。PubMed 实验共调用 37 次,Unable to enrich PubMed result ... from PMC warning 为 0,没有发现该日志大量输出。当前每个 query 最多增强 1 条学术结果,因此单次调用不会批量产生 warning。


@staticmethod
def _exact_pmid(query: str) -> str:
match = re.fullmatch(r"\s*(?:PMID\s*:\s*)?(\d+)\s*", str(query or ""), re.IGNORECASE)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ebd4e5757d56802968d27a87d74a82db1dcec283

pubmed.py::_exact_pmid 用 re.fullmatch(r"\s*(?:PMID\s*:\s*)?(\d+)\s*") 拦截查询。"2024"(纯年份)、数值型数据集 ID 等查询会被当作 PMID 直接走 efetch,跳过 esearch。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head_sha: ecc8715fb660d72dec50a85ff2d693b9c19683f6

已修改。现在只有显式格式 PMID: 38132429 才会直接进入 EFetch。2024 或其他普通纯数字查询会继续走 ESearch,避免把年份等通用查询误判为 PMID。同步和异步路径均使用相同判断,并有回归测试覆盖。

@openjiuwen-collaboration-bot

Copy link
Copy Markdown

head_sha: a17193b124dcbe221bb9d7f5923947be340752d8

任务名称 结果 日志操作
静态检查 ❌FAILED 点此跳转
禁用词扫描 N/A N/A
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 ✅SUCCESS 点此跳转
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck ✅SUCCESS N/A

@openjiuwen-collaboration-bot

Copy link
Copy Markdown

head_sha: ecc8715fb660d72dec50a85ff2d693b9c19683f6

任务名称 结果 日志操作
静态检查 ✅SUCCESS 点此跳转
禁用词扫描 N/A N/A
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 ✅SUCCESS 点此跳转
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck ✅SUCCESS N/A

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants