--- name: research description: 查 paper_server 文献库(基于 OpenAlex 元数据 + Sci-Hub 下载的内部部署)。用户要查文献、找 DOI、拉 PDF、做文献综述、写带引文的申报书 / 研究方案 / 调研报告时使用。 --- # Research paper_server 是内部部署的 Django 文献库:元数据来自 OpenAlex,PDF 由 Sci-Hub 异步抓取。本 skill 给你三个 helper(`search` / `get_paper` / `fetch_pdf`),用 `run_python` 调用,**不要**自己 `httpx` 裸调 API。 ## 何时用 - 用户要查 / 找 / 看 / 推荐文献 - 要 DOI、要某篇 PDF、要 abstract - 写申报书 / 研究方案 / 调研报告的"国内外现状"段需要真实文献支撑 - 配合 `proposal` skill 的「立项依据」起草 —— 先 `search` 拿候选,`get_paper` 看 abstract 决定要不要引 ## 何时不用 - 用户只问通识(直接答即可,不需要文献支撑) - 用户已经给了具体文献清单(直接用,不要二次校验) - paper_server 没覆盖的领域(本库主打中文工程 / 材料 / 土木 / 化学;前沿 AI / 数学 / 纯理论 paper 命中率低 —— 命中 0 条直接告诉用户,不要瞎编) ## 准备 ```python from skills.research.paper import search, get_paper, fetch_pdf ``` (import 路径由 `run_python` 注入的 `PYTHONPATH` 提供,直接写就行,不必折腾 `sys.path`) ## 三个函数 ### `search(keyword="", year=None, doi="", has_pdf=None, limit=10) -> list[dict]` 搜文献,返回精简列表(每条只含 id / doi / title / first_author / publication_year / publication_name / has_fulltext_pdf / has_abstract / type)。 - `keyword`: paper_server 的 search 字段,匹配 title / first_author / first_author_institution - `year`: 精确年份(目前只支持 exact) - `doi`: 精确 DOI(命中 0 / 1 条) - `has_pdf=True` 仅返已下好 PDF 的;`False` 仅返没 PDF 的;`None` 都返 - `limit`: 默认 10,上限 50 ```python # 找最近 5 篇关于"水泥水化"且已下好 PDF 的 papers = search(keyword="水泥水化", has_pdf=True, limit=5) for p in papers: print(p["title"], p["doi"], p["publication_year"]) ``` ### `get_paper(id_or_doi) -> dict` 取单条完整 metadata + abstract。`id_or_doi` 既接受 paper_server 内部 id,也接受 DOI(自动解析)。 ```python paper = get_paper("10.1016/j.cemconres.2020.106156") print(paper["title"]) print(paper["abstract"]) # 没 abstract 时是空串,不会抛 ``` ### `fetch_pdf(id_or_doi, working_dir) -> str` 下载 PDF 到 `/papers/.pdf`,返回相对路径 `papers/.pdf`(safe_doi 把 `/` 换成 `_`)。已存在跳过下载直接复用。 ```python rel = fetch_pdf("10.1016/j.cemconres.2020.106156", working_dir=r"D:/projects/zcbot/workspace/users//") # rel == "papers/10.1016_j.cemconres.2020.106156.pdf" ``` `has_fulltext_pdf=False` 时抛 `RuntimeError` —— 这时该 paper 服务器侧还没下到 PDF,告诉用户不要硬等。 ## 标准工作流 1. **search**:按关键词 / 年份缩窄候选(`limit=10` 起,不够再扩) 2. **筛选**:扫返回列表,挑相关的(看 title + first_author + year),`has_fulltext_pdf=True` 的优先 3. **get_paper**:对每篇候选拿 abstract,确认确实切题再继续 4. **fetch_pdf**:确定要全文细读时才下载(用户没说要全文就别下,abstract 已足够覆盖大多数引用场景) 5. **read PDF**:`fetch_pdf` 返回相对路径,用主 agent 的 `read` 工具读 PDF 提取关键段(zcbot 已内置 PDF 文本提取) ## 错误处理 - 网络超时 / paper_server 不可达:`httpx.ConnectError` / `httpx.TimeoutException` —— 直接告诉用户"paper_server 暂时连不上",不要重试堆栈刷屏 - `doi 未命中` / `doi 命中多条`:`get_paper` / `fetch_pdf` 内部 `_resolve_to_id` 抛 `ValueError` —— DOI 拼写错或库里真没收录,改 keyword 重搜 - `has_fulltext_pdf=False`:`fetch_pdf` 抛 `RuntimeError(reason=...)` —— 服务器还没下到 PDF;告诉用户"这篇 paper_server 还没下到 PDF,可读 abstract 或换一篇" - `abstract` 字段为空字符串:正常情况,不是错;告诉用户"这篇没收录摘要"即可 ## 反模式 - 用 `httpx` / `requests` 裸调 paper_server API(走 helper,免得 base_url / auth / 字段名漂移时四处改) - `search(limit=50)` 一次拉满后扔给 LLM 全文 dump(只 print 前 5-10 条精简就够,要全部让用户自己 `print(papers)`) - 没看 abstract 就 `fetch_pdf`(80% 场景 abstract 够用,下载 PDF 慢且费带宽) - 编造 DOI / title / 作者 —— 不在 paper_server 库里就**明确告诉用户"未命中"**,不要凭训练数据脑补 - 把 `fetch_pdf` 返回的相对路径当绝对路径用(它是相对 `working_dir` 的)