新增 scripts/http_utils.py 统一封装 HTTP 请求的限流重试(429/5xx 指数退避, 优先遵守服务端 Retry-After),search_arxiv/crossref/semantic_scholar.py 和 verify_citation.py 都已接入,不再各自裸调 urllib。 新增 scripts/search_openalex.py 作为第四个检索源:免费、无需 API key,覆盖面 比单独的 Crossref 更广,还能拿到开放获取PDF直链;已接入 literature_search.py 的主检索流程。verify_citation.py 的跨源标题核查同步改为同时查 Semantic Scholar 和 OpenAlex 两个独立源、任一命中相似度达标即通过,不再单点依赖 S2—— 这是针对"S2 被限流导致整批候选退化成 unverified"这个实际发生过的问题的直接 修复,已用真实网络请求验证:复测中 S2 确实当场返回了 429,靠 OpenAlex 兜底 最终判定仍然是 verified。 顺带修了 archive_references.py 的 slugify(),之前中文主题名会被正则全部 过滤掉、退化成通用的 "references",导致不同中文主题的归档目录互相冲突。 scripts/tests/ 下补了 37 个 unittest(全部 mock 网络请求,不发真实请求), 覆盖 verify_citation 的三档判定和双源核查合并逻辑、archive_references 的 bib 解析边界情况(嵌套花括号、中文主题名)、literature_search 的候选去重 合并与 BibTeX 生成、http_utils 的重试逻辑。用标准库 unittest 而不是 pytest, 和这些脚本本身不引入第三方依赖的原则保持一致。 CLAUDE.md 的下一步计划里,这一项已标记为完成。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
12 KiB
name, description
| name | description |
|---|---|
| literature-search-verify | Search academic literature across arXiv, Semantic Scholar, Crossref, OpenAlex, and other connected paper-search MCP tools, and independently verify every candidate citation before it is treated as real. Use this whenever the user asks to find papers, search literature on a topic, build a reading list, compile related-work references, check whether a citation or bibliography entry actually exists, or prepare references to import into Zotero or a .bib file — especially in academic writing contexts where a fabricated citation would be a real problem. Also covers guiding the user to the Zotero Connector browser extension for Chinese-language sources (CNKI/知网, Wanfang/万方, VIP/维普) that have no public API and cannot be reached by search tools. |
文献检索 + 反幻觉引用核查
为什么需要这个技能
大语言模型在编造论文引用这件事上非常擅长——生成的标题、作者、期刊名读起来都很像真的,但可能根本不存在,或者张冠李戴(把A论文的结论安在B论文头上)。这在正式学术写作里是不可接受的:一篇论文只要有一条编造的引用被发现,审稿人对全篇的信任都会崩塌。
所以这个技能的核心不是"搜索",而是"搜索之后不轻信"——每一条打算真正拿去引用的文献,都必须经过独立交叉验证,验证不通过的必须明确标出来,而不是悄悄丢弃或者悄悄当作真的用。
工作流程
第一步:明确检索范围
在开始搜索前,搞清楚(不确定就直接问,一句话就够):
- 核心关键词/研究方向(可以中英文混合,比如"UAV磁补偿 Tolles-Lawson"这类)
- 大致的时间范围(比如"近5年"还是不限)
- 是否需要限定顶会/顶刊,还是什么来源都要
第二步:检索——直接调用脚本,不要自己现编API调用
scripts/ 目录下已经写好了能直接跑的检索脚本,不依赖任何第三方Python包,也不需要装MCP工具:
# 始终从项目根目录调用(脚本本身不关心cwd,但--bib-out等输出路径按项目根目录约定拼)。
# 一次性搞定:检索 arXiv + Semantic Scholar + Crossref,自动去重、逐条验证,
# 并把通过验证的条目写成BibTeX文件——这是应该默认调用的入口
mkdir -p output/literature-search-verify/uav_aeromagnetic_compensation
python3 .claude/skills/literature-search-verify/scripts/literature_search.py \
"UAV magnetic compensation Tolles-Lawson" \
--max-per-source 8 \
--bib-out output/literature-search-verify/uav_aeromagnetic_compensation/refs.bib
草稿路径(检索报告JSON、下载的PDF、中间生成的bib)统一落在项目根目录下的
output/literature-search-verify/,不要写回技能自己的目录里(.claude/skills/
应该只放技能代码,不放运行时产生的草稿数据)。如果项目里以后还有别的技能也
需要草稿区,各自建 output/<skill-name>/ 子目录,互不混放。
同一个技能内部也要按主题分开,不要所有检索会话都堆在同一层:开始一个新
主题的检索时,先按"和第六步归档时同样的规则"把主题名转成 slug(小写、非
字母数字换成下划线,比如"UAV aeromagnetic compensation" → uav_aeromagnetic_compensation),
建 output/literature-search-verify/<topic-slug>/ 子目录,这一整个主题下
不管跑多少轮检索、多少条不同的query,原始JSON/bib草稿都写进这一个子目录里
(文件名可以随意区分轮次,比如q1.json/q2.json),不要用不带主题区分的
通用文件名散落在output/literature-search-verify/根下。这样同一个主题的
草稿和第六步归档产出的references/<topic-slug>/目录能通过同一个slug对上号,
之后回来补充检索同一主题时也知道去哪个子目录续。
正常情况下只需要跑这一条命令,它内部会依次调用 search_arxiv.py、search_semantic_scholar.py、search_crossref.py、search_openalex.py 做检索,再对每条合并后的候选文献跑 verify_citation.py 做交叉验证,输出一份JSON报告(每条候选都带verdict字段)。如果只是想单独查一个来源,或者针对某一条文献单独复核,再分别调用对应的单个脚本(用法见每个脚本文件开头的docstring)。OpenAlex(search_openalex.py)是在 arXiv/Semantic Scholar/Crossref 之外新增的第四个源,免费、无需API key,覆盖面比单独的 Crossref 更广(聚合了包括 IEEE 在内的绝大多数出版商元数据),还能拿到开放获取PDF直链;可以设置OPENALEX_MAILTO环境变量为一个邮箱地址进入OpenAlex的"polite pool"换取更稳的限额,不设置也能正常用。
所有脚本的网络请求都经过 http_utils.py 统一封装:遇到 429(限流)或 5xx 会按指数退避自动重试几次(优先遵守服务端返回的Retry-After),不需要每次手动重试;verify_citation.py的跨源标题核查更是同时查 Semantic Scholar 和 OpenAlex 两个独立源、任一命中即算通过,不会再出现"S2一限流,整批候选的跨源核查全部退化成skipped"的情况(这是实际发生过的问题,现在已经用两个源+重试解决)。即便如此,如果这些脚本因为网络原因跑不动(比如内网/代理限制导致连不上这几个学术API域名),literature_search.py 会把每个来源的报错单独记在search_errors里而不是直接崩溃——这时候老实告诉用户"检索脚本连不上网络,以下是报错信息",不要退回去凭记忆编文献。如果用户这边确实连不上这几个学术API域名,才退回到 web_search 工具,并在结果里明确标注"来自通用网络搜索的补充结果,未经过脚本的交叉验证流程,置信度较低"。
如果用户已经连了 paper-search-mcp / scholar_mcp_server 这类MCP工具,可以补充用来扩大覆盖面(比如它们能覆盖PubMed、能直接下载PDF),但不能替代verify_citation.py的交叉验证这一步——MCP搜到的候选一样要过一遍验证,不能因为是工具搜出来的就默认可信。
第三步:理解验证结果——这是最关键的一步
literature_search.py(或单独调用verify_citation.py)对每条候选文献做的核查是:
- arXiv ID 独立核实:如果有 arXiv ID,反查一次 arXiv API,确认这个ID真的存在且标题对得上——一个编造的ID在这一步会直接暴露。
- DOI 独立核实:如果有 DOI,反查一次 Crossref,确认这个 DOI 真的能解析出对应文献。
- 跨源标题复核(双源):不管有没有ID,单独拿标题分别去 Semantic Scholar 和 OpenAlex 各搜一次,要求返回的标题跟候选标题高度相似(相似度≥0.9)——这一步专门用来抓"标题作者读起来很像真的,但其实是编出来的"这种情况。两个源里任一个命中相似度达标就算通过;只有当两个源都明确没找到匹配(不是因为网络问题被跳过)时才算这一项核查失败——避免某个源覆盖不全(比如论文太新还没被其中一个索引收录)被误判成"编造"。
每条候选最后会带一个verdict:
- verified:至少一项独立核查通过,而且没有任何一项核查明确失败
- suspect:至少一项核查明确失败(比如DOI查不到、跨源标题对不上)——这种情况下不要用这条文献,即使标题看起来很合适
- unverified:所有核查项都因为网络等原因被跳过(
skipped),不代表验证通过,只代表"没能验证"——同样不能当成已核实的文献直接使用,要跟用户说清楚原因
呈现给用户时按这三档分组说明,suspect和unverified都要明确标出来,不要因为报告里有个"看起来还行"的标题就含糊地当真的用。
原则:找不到真实存在的相关文献时,直接说"没找到符合条件的文献",不要为了凑数编一条出来。这条原则没有例外。
第四步:输出
按 verified / suspect / unverified 分组呈现结果,每条包含标题、作者年份、venue、标识符、一句话相关性说明。
literature_search.py 传了 --bib-out 参数时,会自动把所有 verified 的条目写成BibTeX文件,citation key 用"姓氏+年份"约定,可以直接导入 Zotero(配合 Better BibTeX 插件)。这些 key 也是后续paper-writing-grounded技能里\cite{}要用到的,两个技能之间通过这些key保持一致,不需要额外对照。
第五步:提醒中文文献的检索缺口
MCP 检索工具覆盖的是 arXiv/Semantic Scholar/Crossref 这类有公开 API 的英文为主的库,知网、万方、维普这类中文数据库没有公开 API,搜不到很正常,不是技能出错。遇到用户明显需要中文文献的场景,主动提醒:装好 Zotero Connector 浏览器插件,在浏览器里正常登录学校账号搜索、打开文献页面,点一下 Connector 图标就能把元数据和 PDF 存进 Zotero——这部分需要用户手动完成,不要尝试用检索工具"模拟"或"猜测"中文文献的存在。
第六步:归档
<project-root>/output/literature-search-verify/ 只是脚本运行时的草稿区——里面
混着每一轮探索性检索的原始JSON(包括被过滤掉的噪声,比如"Tolles""Lawson"被当成
人名匹配出的无关文献),不适合作为最终交付物,而且随着会话增多会越堆越乱、也不
方便下次会话或用户直接翻阅。
所以每次整理出一份稳定可信的参考文献列表(不管是第一轮检索还是后续多轮补充检索合并后的结果)之后,调用归档脚本把它固化到项目级目录,而不是留在草稿区里:
python3 .claude/skills/literature-search-verify/scripts/archive_references.py \
"UAV aeromagnetic compensation" \
--bib output/literature-search-verify/uav_aeromagnetic_compensation/uav_aeromagnetic_compensation_final.bib \
--project-root . \
--pdfs-dir output/literature-search-verify/uav_aeromagnetic_compensation/pdfs \
--suspect "某条可疑文献标题|不建议引用的具体原因" \
--notes "检索覆盖了哪些方向、哪些方向搜了但没结果、中文文献缺口提醒等"
这会在 <project-root>/references/<按主题自动生成的slug>/ 下生成:
references.bib—— 传入的bib文件原样拷贝过去pdfs/(如果传了--pdfs-dir且里面有PDF)—— 一并拷贝过去README.md—— 自动从bib里解析出条目列表(标题/年份/venue/DOI/note)生成索引,--suspect和--notes里的内容会分别整理进"不要引用"和"检索覆盖说明"两个小节
几个要点:
--bib传的必须是已经过滤掉无关噪声、只保留verified条目的干净bib文件,不要把literature_search.py直接吐出来的、可能夹杂噪声的原始bib不加甄别地拿去归档。- 同一个
topic名字多次调用会往同一个归档目录里覆盖更新(bib和README会被覆盖,pdfs按文件名去重合并),所以后续检索到更多文献后可以直接对同一个topic重新跑一遍归档脚本来更新,不需要手动合并。 - 这一步做完之后可以明确告诉用户归档目录的路径,方便他们后续在
paper-writing-grounded阶段直接引用。
维护者备注:单元测试
scripts/tests/ 下有针对核心正确性逻辑的单元测试(不发真实网络请求,全部用
mock):verify_citation.py 的三档判定和双源标题核查合并逻辑、
archive_references.py 的 bib 解析(含嵌套花括号、中文主题名slugify等边界
情况)、literature_search.py 的候选去重合并与BibTeX生成、http_utils.py
的限流重试。改动这几个脚本后应该跑一遍:
python3 -m unittest discover -s .claude/skills/literature-search-verify/scripts/tests -t .claude/skills/literature-search-verify/scripts
只用标准库unittest,不引入pytest等第三方测试框架,和脚本本身"不依赖第三方
包"的原则保持一致。
和 paper-writing-grounded 技能的配合
这个技能负责把"真实存在、经过核实的文献"整理好并生成 BibTeX;写作阶段的 paper-writing-grounded 技能会直接消费这里产出的 citation key,正文引用只能来自这里核实过的条目,不会凭空生成新的引用。两个技能配合使用时,建议先跑完这个技能、拿到稳定的参考文献列表,再进入写作。