136 lines
8.1 KiB
Markdown
136 lines
8.1 KiB
Markdown
---
|
|
name: research
|
|
description: 查 paper_server 文献库(基于 OpenAlex 元数据 + Sci-Hub 下载的内部部署)。用户要查文献、找 DOI、拉 PDF、做文献综述、写带引文的申报书 / 研究方案 / 调研报告时使用。
|
|
---
|
|
|
|
# Research
|
|
|
|
paper_server 是内部部署的 Django 文献库:元数据来自 OpenAlex,PDF / XML 由 Sci-Hub / OpenAlex 异步抓取。**库里主语料是英文**(OpenAlex 主索引英文文献),少量中文。本 skill 给你四个 helper(`search` / `get_paper` / `fetch_pdf` / `fetch_xml`),用 `run_python` 调用,**不要**自己 `httpx` 裸调 API。
|
|
|
|
## 何时用
|
|
|
|
- 用户要查 / 找 / 看 / 推荐文献
|
|
- 要 DOI、要某篇 PDF、要 abstract
|
|
- 写申报书 / 研究方案 / 调研报告的"国内外现状"段需要真实文献支撑
|
|
- 配合 `proposal` skill 的「立项依据」起草 —— 先 `search` 拿候选,看 abstract 决定要不要引
|
|
|
|
## 何时不用
|
|
|
|
- 用户只问通识(直接答即可,不需要文献支撑)
|
|
- 用户已经给了具体文献清单(直接用,不要二次校验)
|
|
|
|
## 准备
|
|
|
|
```python
|
|
from skills.research.paper import search, get_paper, fetch_pdf, fetch_xml
|
|
```
|
|
|
|
(import 路径由 `run_python` 注入的 `PYTHONPATH` 提供,直接写就行,不必折腾 `sys.path`)
|
|
|
|
## 关键:keyword 优先用英文
|
|
|
|
`search(keyword=...)` 走 paper_server SearchFilter,模糊匹配 **title / first_author / first_author_institution**(目前不含 abstract)。库里 95%+ 文献 title 是英文,中文 keyword 命中率很低。
|
|
|
|
**用户输入中文 → 先转成专业英文术语再 search**:
|
|
|
|
| 用户原话 | 不要这样 | 这样 |
|
|
|---|---|---|
|
|
| 水泥水化 | `search("水泥水化")` | `search("cement hydration")` |
|
|
| 钢筋锈蚀 | `search("钢筋锈蚀")` | `search("steel reinforcement corrosion")` |
|
|
| 混凝土碳化 | `search("混凝土碳化")` | `search("concrete carbonation")` |
|
|
| 锂离子电池电解液 | `search("锂离子电池电解液")` | `search("lithium-ion battery electrolyte")` |
|
|
|
|
转译策略:
|
|
- 用领域内标准英文术语(查不准就先英文 keyword 试一次看返回 title 是不是相关)
|
|
- 多词术语用空格分隔(`SearchFilter` 默认空格视作 AND,要更宽用单词)
|
|
- 不确定时同义词都试一遍:`search("CO2 absorption")` 没结果 → 试 `search("carbon dioxide capture")`
|
|
- 中文期刊 paper 也可中文 keyword 单独搜一次(`search("水泥") + filter 中文期刊` 命中率不算低,但远不如英文主搜)
|
|
|
|
## 四个函数
|
|
|
|
### `search(keyword="", year=None, year_gte=None, year_lte=None, doi="", first_author="", publication_name="", has_pdf=None, is_oa=None, limit=10) -> list[dict]`
|
|
|
|
搜文献,返回精简列表(每条含 16 字段:id / doi / title / first_author / first_author_institution / publication_year / publication_date / publication_name / has_fulltext_pdf / has_fulltext_xml / has_abstract / is_oa / type / **abstract** / **pdf_url** / **xml_url**)。
|
|
|
|
- `keyword`: SearchFilter,匹配 title / first_author / first_author_institution(**英文为主**,见上节)
|
|
- `year` / `year_gte` / `year_lte`: 精确年份 / 范围(做"近 5 年文献"用 `year_gte=2020`)
|
|
- `doi`: 精确 DOI(命中 0 / 1 条)
|
|
- `first_author` / `publication_name`: 精确作者 / 期刊
|
|
- `has_pdf=True` 仅返 PDF 已下好的;`False` 仅返没 PDF 的;`None` 都返
|
|
- `is_oa=True` 仅返开放获取(OA);`False` 仅返非 OA;`None` 都返
|
|
- `limit`: 默认 10,上限 50
|
|
|
|
```python
|
|
# 找近 5 年水泥水化研究,且 PDF 已下好
|
|
papers = search(keyword="cement hydration", year_gte=2020, has_pdf=True, limit=10)
|
|
for p in papers:
|
|
print(p["title"], p["publication_year"])
|
|
if p["abstract"]:
|
|
print(p["abstract"][:200]) # 看摘要前 200 字判断是否切题
|
|
```
|
|
|
|
### `get_paper(id_or_doi) -> dict`
|
|
|
|
取单条完整 metadata + abstract。`id_or_doi` 既接受 paper_server 内部 id,也接受 DOI(自动解析)。
|
|
|
|
**list 端点已带 abstract,正常工作流不需要调本函数** —— 仅在用户给单个 id / DOI 想拿全字段(含 OpenAlex 原始字段如 `o_keywords` 等)时用。
|
|
|
|
```python
|
|
paper = get_paper("10.1016/j.cemconres.2020.106156")
|
|
print(paper["title"])
|
|
print(paper["abstract"])
|
|
```
|
|
|
|
### `fetch_pdf(id_or_doi, working_dir) -> str`
|
|
|
|
下载 PDF 到 `<working_dir>/papers/<safe_doi>.pdf`,返回相对路径 `papers/<safe_doi>.pdf`(safe_doi 把 `/` 换成 `_`)。已存在跳过下载直接复用。走 paper_server 的 `/resm/paper/<id>/pdf/` 端点(有 `has_fulltext_pdf` 预检)。
|
|
|
|
`has_fulltext_pdf=False` 时抛 `RuntimeError` —— 服务器侧还没下到 PDF。
|
|
|
|
```python
|
|
rel = fetch_pdf("10.1016/j.cemconres.2020.106156", working_dir=r"D:/projects/zcbot/workspace/users/<uid>/<wd>")
|
|
# rel == "papers/10.1016_j.cemconres.2020.106156.pdf"
|
|
```
|
|
|
|
### `fetch_xml(id_or_doi, working_dir) -> str`
|
|
|
|
下载 XML 到 `<working_dir>/papers/<safe_doi>.xml`,对称 `fetch_pdf`。**走 paper_server 的 media 静态直链**(由 list/retrieve 返回的 `xml_url` 字段提供),paper_pdf_view 只覆盖 PDF,XML 没对应 API。已存在跳过下载直接复用。
|
|
|
|
`has_fulltext_xml=False` 或 `xml_url` 空(publication_date 缺失时会空)→ 抛 `RuntimeError`。
|
|
|
|
**为什么 XML 优先 PDF**:XML 已结构化 —— 章节标题 / 摘要 / 段落 / 参考文献 / 图表 caption 都有标签,LLM 读取无需 OCR 或 PDF 文本抽取的不确定性;文献综述 / 引文清单 / 章节定位场景比 PDF 友好得多。能拿 XML 就别拿 PDF;只有需要看具体公式 / 图表内容 / 表格数据时才下 PDF。
|
|
|
|
## 标准工作流
|
|
|
|
1. **(若用户输入中文)转专业英文术语**
|
|
2. **search**:按 keyword + filter(年份范围 / OA / has_pdf 等)缩窄候选,`limit=10` 起
|
|
3. **直接看返回里的 abstract**(list 端点已带):
|
|
- abstract 非空 → 看前 200-400 字判断切题
|
|
- abstract 空(`has_abstract=False`)→ 仅凭 title + 期刊 + 年份判断,信号弱时下条候选
|
|
4. **下全文(若需要)** —— **优先级**:
|
|
- `has_fulltext_xml=True` → `fetch_xml`(LLM 友好,结构化)
|
|
- 仅 `has_fulltext_pdf=True` → `fetch_pdf`(回退,需要 PDF 文本抽取)
|
|
- 两者都 False → 仅凭 abstract 写综述,告诉用户哪几篇没全文
|
|
5. **read 全文**:fetch 返回相对路径,用主 agent 的 `read` 工具读取(zcbot 已内置 PDF 文本抽取;XML 直接当文本读)
|
|
|
|
## 错误处理
|
|
|
|
- 网络超时 / paper_server 不可达:`httpx.ConnectError` / `httpx.TimeoutException` —— 告诉用户"paper_server 暂时连不上",不要重试堆栈刷屏
|
|
- `doi 未命中` / `doi 命中多条`:`get_paper` / `fetch_pdf` / `fetch_xml` 内部 `_resolve_to_id` 抛 `ValueError` —— DOI 拼写错或库里没收录,改 keyword 重搜
|
|
- `has_fulltext_pdf=False` / `has_fulltext_xml=False`:`fetch_pdf` / `fetch_xml` 抛 `RuntimeError` —— 服务器还没下到对应格式;若另一格式存在则换用,都没就只能用 abstract
|
|
- `xml_url` 空(publication_date 缺失,paper 落到 unknown 目录):`fetch_xml` 抛 `RuntimeError(xml_url unavailable...)` —— 改试 `fetch_pdf`
|
|
- 文件 disk 缺失(`has_fulltext_pdf=True` 但 paper_server 侧文件丢了 → HTTP 404):helper 透传 `httpx.HTTPStatusError`,告诉用户换一篇
|
|
- `abstract` 字段为空字符串:正常情况,不是错;告诉用户"这篇没收录摘要"即可
|
|
- search 命中 0 条:先尝试同义词 / 缩短 keyword / 放宽 filter,3 次还是 0 条告诉用户库里没覆盖,**不要凭训练数据脑补文献**
|
|
|
|
## 反模式
|
|
|
|
- 用 `httpx` / `requests` 裸调 paper_server API(走 helper,免得 base_url / auth / 字段名漂移时四处改)
|
|
- 用户输中文直接 `search(中文)` —— 转英文术语,见上节
|
|
- `search(limit=50)` 一次拉满后 dump 给 LLM 全文(只 print 前 5-10 条精简就够,要全部让用户自己 `print(papers)`)
|
|
- 已经看到 abstract 还 `get_paper` 一遍(list 已带 abstract,重复调白费 roundtrip)
|
|
- 没看 abstract 就 `fetch_pdf` / `fetch_xml`(80% 场景 abstract 够用,下载全文慢且费带宽)
|
|
- `has_fulltext_xml=True` 时盲走 `fetch_pdf`(XML 对 LLM 更友好,先试 fetch_xml)
|
|
- 编造 DOI / title / 作者 —— 不在 paper_server 库里就**明确告诉用户"未命中"**,不要凭训练数据脑补
|
|
- 把 `fetch_pdf` 返回的相对路径当绝对路径用(它是相对 `working_dir` 的)
|