| name | bioc-fulltext-fetch |
| description | 从 PubMed/PMC 论文取结构化全文并按章节切分为 RAG 友好的 chunk。当用户需要批量抽取论文全文、按 Methods/Results 等章节分段、提取表格上下文、构建文献向量库时,使用本 skill。支持 PMID(标题+摘要)和 PMCID(全文,开放获取子集)。token 级 RAG chunking 不跨章节,支持缓存与限流。 |
| keywords | ["bioc","nlm-api","pmc-open-access","structured-fulltext","medical-literature","literature-mining","chunking-for-rag"] |
| license | MIT |
BioC Fulltext Fetch
封装 NLM BioC API,把 PubMed / PMC 文献转成 RAG 友好的结构化全文 chunk。只做全文抽取与切片,不做检索、不做实体标注、不做向量化。
1. 何时使用
触发场景:
- "把这批 PMID 的全文取下来准备入 ChromaDB / 我有 PMCID 列表要按章节切分做 RAG"
- "提取 Methods 章节段落 / 找出表格周围的语义文本"
- "Build vector index from PMC open access papers"
- "PMC1234567 的全文段落 + 章节标签"
- "把 100 篇血液 IFI PMID 入向量库"
不要使用本 skill:
- 用户只要标题/摘要纯文本 → 用
pubmed-eutils efetch
- 用户要做语义检索找文献 → 用
pubmed-search / europepmc-search
- 用户要实体标注(基因/疾病/化合物)→ 用
pubtator-entity-search(可在本 skill 输出之上叠加,不替代)
- 用户要做证据等级评分 → 用
medical-evidence-grading(其内部会调用本 skill 抽 Methods 段落)
2. 跨 skill 协作(显式)
| 上游 / 下游 skill | 协作方式 |
|---|
pubmed-eutils.elink_pubmed_to_pmc() | 上游,把 PMID 列表转换成 PMCID 列表,喂给本 skill |
europepmc-search.get_full_text_links() | 替代源,当 PMC OA 不可用时回退到 Europe PMC 的开放获取链接 |
pubtator-entity-search | 后置叠加,在本 skill 产的 chunk 上做实体标注,不替代本 skill |
medical-evidence-grading | 下游,调 extract_paragraphs 取 Methods 段落做 sample size / study design 检测 |
典型 pipeline:
europepmc-search ──► PMID list ──► pubmed-eutils.elink ──► PMCID list
│
▼
bioc-fulltext-fetch (本 skill)
│
├─► [可选] pubtator-entity-search 实体叠加
├─► [可选] medical-evidence-grading 证据评分
└─► LangChain / LlamaIndex 入库
3. 两个 BioC 端点
| 端点 | 输入 | 覆盖 | 内容 |
|---|
| BioC PubMed | PMID | 全 PubMed (~37M) | 标题 + 摘要 + MeSH(结构化) |
| BioC PMC OA | PMCID | PMC 开放获取子集(~10M,约占 PMC 25%) | 全文章节、段落、表格、图注 |
摘要级: https://www.ncbi.nlm.nih.gov/research/bionlp/RESTful/pubmed.cgi/BioC_json/{PMID}/unicode
全文级: https://www.ncbi.nlm.nih.gov/research/bionlp/RESTful/pmcoa.cgi/BioC_json/{PMCID}/unicode
格式细节、section_type 取值、字符 offset 一致性见 references/bioc-format-spec.md。
4. 推荐工作流
PMID list
│
▼
[可选] elink PMID→PMCID (调 pubmed-eutils)
│
├─ PMCID 存在且 OA ──► fetch_fulltext_bioc(pmcid) ──► extract_paragraphs / extract_tables_context ──► to_rag_chunks
│
└─ 不可用/非 OA ─────► fetch_abstract_bioc(pmid) ─────────────────────────────────────────────────► to_rag_chunks
5. 核心能力(5 个函数 · 跨平台签名)
5.1 fetch_abstract_bioc(pmid: str) -> BioCDocument
取标题 + 摘要 + MeSH 的 BioC 结构化版本。
doc = fetch_abstract_bioc("39523456")
5.2 fetch_fulltext_bioc(pmcid: str) -> BioCDocument
取 PMC OA 全文,按章节切分(section_type ∈ {TITLE, ABSTRACT, INTRO, METHODS, RESULTS, DISCUSS, CONCL, REF, FIG, TABLE, ...})。
doc = fetch_fulltext_bioc("PMC10234567")
5.3 extract_paragraphs(doc: BioCDocument) -> list[Paragraph]
按 BioC passage 提取段落数组,保留 section 标签 + 字符 offset。
paragraphs = extract_paragraphs(doc)
要点:跳过 REF / FIG_LABEL 纯标签段;合并同 section 内 < 50 字符短 passage;offset 来自 BioC passage.offset。
5.4 extract_tables_context(doc: BioCDocument, window: int = 200) -> list[TableContext]
提取每个表格的上下文(前后 200 字),帮 RAG 给表打语义标签。
tables = extract_tables_context(doc, window=200)
表格本体(cell 数据)不解析;只取 caption + 周围文本。
5.5 to_rag_chunks(doc: BioCDocument, chunk_size: int = 512, overlap: int = 50) -> list[Chunk]
把 BioC 文档切成 RAG 入库 chunk。token 单位、不跨章节、段落优先、可追溯 chunk_id。
chunks = to_rag_chunks(doc, chunk_size=512, overlap=50)
切片策略详见 references/chunking-strategies.md。
6. 速率限制 + 缓存
- 速率:BioC 不强制 RPS,但建议 ≤ 5 RPS(批量 > 100 篇时降到 3);用
asyncio.Semaphore(5) 或 aiolimiter
- 缓存:
~/.cache/bioc/{id}.json + SQLite 索引 index.db(key, file_path, fetched_at, is_oa, doi, year)
- TTL:默认 30 天(
BIOC_CACHE_TTL_DAYS),force_refresh=True 绕过
7. 错误处理
所有错误继承 BioCFetchError 基类。
| 错误 | 类型 | 处理 |
|---|
| 404 (PMC OA) | NotOpenAccessError | 调用方回退 abstract |
| 404 (PubMed) | NotFoundError | PMID 不存在,跳过 |
| 429 | RateLimitError | 指数退避 1s/2s/4s,最多 3 次 |
| 5xx | BioCServerError | 重试 3 次后抛出 |
| JSON 解析失败 | BioCParseError | 记录原始响应,跳过 |
| 超时(默认 30s) | BioCTimeoutError | 重试 1 次 |
8. 失败模式与避坑(必读)
- OA 不可用:PMC OA 子集仅占 PMC 的 ~25%,大量 PMID 没有可用全文。必须在调用方实现 abstract 兜底路径,不要假设 fetch_fulltext_bioc 总能成功。
- 章节标签不统一:不同期刊的
section_type 命名有差异(Methods vs Materials and Methods vs Subjects and Methods),BioC 已部分归一化但仍有例外(综述偶尔出现 INTRO_RESULTS 合并段)。下游过滤时用集合而非精确匹配。
- 公式与 unicode 丢失:BioC unicode 端点把数学符号转 Unicode,但希腊字母 / 上下标偶尔丢失。关键公式建议从
BioC_xml 端点取,或解析时检测 ? 占位符。
- chunk 跨章节切碎:朴素的 token 切片会让 Methods 末尾和 Results 开头被切到同一 chunk,破坏 section 过滤检索。
to_rag_chunks 默认按 section 分组,不要绕过。
- 缓存命中但 TTL 过期:批量任务跨天运行时,第二天命中缓存的旧条目可能与新条目混用(DOI 元数据已更新)。检查
fetched_at 而非只看文件存在。
- BioC API 超时:单篇大文档(> 1MB BioC JSON)在网络抖动时易超时。timeout 不要低于 30s;批量任务必须有失败隔离(
failed.jsonl),不能让一篇阻断整批。
- 大文档内存:单篇 PMC 全文 BioC JSON 可达 1–3 MB,1000 篇并发会吃 3 GB RAM。批量处理用流式读写或分批 flush。
- 不要走 PubTator3 全文端点替代:PubTator3 的 BioC 输出绑定标注模型版本,体积更大;本 skill 保持纯净结构化全文,标注由
pubtator-entity-search 后置叠加。
9. RAG 集成
LangChain / LlamaIndex / 元数据过滤检索 / pubtator 实体叠加 / medical-evidence-grading 联用的完整代码示例见 references/rag-integration.md。
10. 环境与依赖
[project]
dependencies = [
"httpx>=0.27",
"aiolimiter>=1.1",
"tiktoken>=0.7",
"tenacity>=9.0",
"platformdirs>=4.0",
]
环境变量:BIOC_CACHE_DIR(默认 ~/.cache/bioc)、BIOC_CACHE_TTL_DAYS(30)、BIOC_MAX_RPS(5)、BIOC_TIMEOUT_SEC(30)。
11. 测试要点
| 测试 | 预期 |
|---|
fetch_abstract_bioc("39523456") | 返回 title + abstract passages |
fetch_fulltext_bioc("PMC10234567") | 返回章节切分的全文 |
fetch_fulltext_bioc("PMC1") 非 OA | 抛 NotOpenAccessError |
extract_paragraphs(doc) | 段落 ≥ 3,每段有 section_label |
extract_tables_context(doc) | 上下文左右各 ≤ 200 字 |
to_rag_chunks(doc, 512, 50) | chunk token ≤ 512、overlap ≈ 50、不跨章节 |
| 缓存命中 | 第二次调用 < 50ms,无网络请求 |
| 429 退避 | 模拟 429 后能在 3 次内成功 |
最低单元测试覆盖率 80%,集成测试用 5 篇真实 PMCID 跑端到端。
12. References