# Bioc Fulltext Fetch

> 从 PubMed/PMC 论文取结构化全文并按章节切分为 RAG 友好的 chunk。当用户需要批量抽取论文全文、按 Methods/Results 等章节分段、提取表格上下文、构建文献向量库时，使用本 skill。支持 PMID（标题+摘要）和 PMCID（全文，开放获取子集）。token 级 RAG chunking 不跨章节，支持缓存与限流。

- **Type:** Skill
- **Install:** `agentstack add skill-ethanyoq-skill-hub-bioc-fulltext-fetch`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [EthanYoQ](https://agentstack.voostack.com/s/ethanyoq)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [EthanYoQ](https://github.com/EthanYoQ)
- **Source:** https://github.com/EthanYoQ/Skill-hub/tree/mine/skills/06-data-search/bioc-fulltext-fetch

## Install

```sh
agentstack add skill-ethanyoq-skill-hub-bioc-fulltext-fetch
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# 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`](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 结构化版本。

```python
doc = fetch_abstract_bioc("39523456")
# doc.passages → [{"infons": {"section_type": "TITLE"}, "text": "..."},
#                 {"infons": {"section_type": "ABSTRACT"}, "text": "..."}]
# doc.infons["mesh"] → ["Leukemia, Myeloid, Acute", "FLT3 Mutation", ...]
```

### 5.2 `fetch_fulltext_bioc(pmcid: str) -> BioCDocument`

取 PMC OA 全文，按章节切分（`section_type` ∈ {TITLE, ABSTRACT, INTRO, METHODS, RESULTS, DISCUSS, CONCL, REF, FIG, TABLE, ...}）。

```python
doc = fetch_fulltext_bioc("PMC10234567")
# 不可用时抛 NotOpenAccessError，调用方应回退到 fetch_abstract_bioc
```

### 5.3 `extract_paragraphs(doc: BioCDocument) -> list[Paragraph]`

按 BioC `passage` 提取段落数组，保留 section 标签 + 字符 offset。

```python
paragraphs = extract_paragraphs(doc)
# [{"section_label": "METHODS", "text": "Flow cytometry was performed...",
#   "offset_start": 2890, "offset_end": 3401}, ...]
```

要点：跳过 `REF` / `FIG_LABEL` 纯标签段；合并同 section 内  list[TableContext]`

提取每个表格的上下文（前后 200 字），帮 RAG 给表打语义标签。

```python
tables = extract_tables_context(doc, window=200)
# [{"table_id": "T1", "caption": "Patient baseline characteristics",
#   "before_context": "...", "after_context": "...", "section": "RESULTS"}, ...]
```

表格本体（cell 数据）不解析；只取 caption + 周围文本。

### 5.5 `to_rag_chunks(doc: BioCDocument, chunk_size: int = 512, overlap: int = 50) -> list[Chunk]`

把 BioC 文档切成 RAG 入库 chunk。token 单位、不跨章节、段落优先、可追溯 `chunk_id`。

```python
chunks = to_rag_chunks(doc, chunk_size=512, overlap=50)
# {
#   "chunk_id": "PMC10234567_METHODS_2890",
#   "text": "...",
#   "metadata": {"pmcid": "...", "pmid": "...", "section": "METHODS",
#                "offset_start": 2890, "offset_end": 3401, "doi": "...",
#                "year": 2024, "is_oa": True, "title": "...", "journal": "..."}
# }
```

切片策略详见 [`references/chunking-strategies.md`](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. 失败模式与避坑（必读）

1. **OA 不可用**：PMC OA 子集仅占 PMC 的 ~25%，大量 PMID 没有可用全文。**必须**在调用方实现 abstract 兜底路径，不要假设 fetch_fulltext_bioc 总能成功。
2. **章节标签不统一**：不同期刊的 `section_type` 命名有差异（`Methods` vs `Materials and Methods` vs `Subjects and Methods`），BioC 已部分归一化但仍有例外（综述偶尔出现 `INTRO_RESULTS` 合并段）。下游过滤时用集合而非精确匹配。
3. **公式与 unicode 丢失**：BioC unicode 端点把数学符号转 Unicode，但希腊字母 / 上下标偶尔丢失。关键公式建议从 `BioC_xml` 端点取，或解析时检测 `?` 占位符。
4. **chunk 跨章节切碎**：朴素的 token 切片会让 Methods 末尾和 Results 开头被切到同一 chunk，破坏 section 过滤检索。`to_rag_chunks` 默认按 section 分组，不要绕过。
5. **缓存命中但 TTL 过期**：批量任务跨天运行时，第二天命中缓存的旧条目可能与新条目混用（DOI 元数据已更新）。检查 `fetched_at` 而非只看文件存在。
6. **BioC API 超时**：单篇大文档（> 1MB BioC JSON）在网络抖动时易超时。timeout 不要低于 30s；批量任务必须有失败隔离（`failed.jsonl`），不能让一篇阻断整批。
7. **大文档内存**：单篇 PMC 全文 BioC JSON 可达 1–3 MB，1000 篇并发会吃 3 GB RAM。批量处理用流式读写或分批 flush。
8. **不要走 PubTator3 全文端点替代**：PubTator3 的 BioC 输出绑定标注模型版本，体积更大；本 skill 保持纯净结构化全文，标注由 `pubtator-entity-search` 后置叠加。

## 9. RAG 集成

LangChain / LlamaIndex / 元数据过滤检索 / pubtator 实体叠加 / medical-evidence-grading 联用的完整代码示例见 [`references/rag-integration.md`](references/rag-integration.md)。

## 10. 环境与依赖

```toml
[project]
dependencies = [
  "httpx>=0.27",       # 异步 HTTP
  "aiolimiter>=1.1",   # RPS 限流
  "tiktoken>=0.7",     # token 计数
  "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

- [`references/bioc-format-spec.md`](references/bioc-format-spec.md) — BioC XML/JSON 格式规范、字段定义、`section_type` 取值
- [`references/rag-integration.md`](references/rag-integration.md) — LangChain Chroma + LlamaIndex 完整代码、批量入库要点、向量库选型
- [`references/chunking-strategies.md`](references/chunking-strategies.md) — token 切片、不跨章节策略、overlap 设计、chunk_id 设计、评测指标

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [EthanYoQ](https://github.com/EthanYoQ)
- **Source:** [EthanYoQ/Skill-hub](https://github.com/EthanYoQ/Skill-hub)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-ethanyoq-skill-hub-bioc-fulltext-fetch
- Seller: https://agentstack.voostack.com/s/ethanyoq
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
