Metadata-Version: 2.4
Name: documa
Version: 0.5.0
Summary: LLM-ready document understanding package.
Author: Documa contributors
License-Expression: MIT
Project-URL: Repository, https://github.com/AllanYiin/Documa
Project-URL: Documentation, https://github.com/AllanYiin/Documa#readme
Keywords: pdf,document-understanding,rag,mcp,llm
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Text Processing :: Markup
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: filelock>=3.12
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-cov>=5; extra == "dev"
Requires-Dist: pytest-regressions>=2.5; extra == "dev"
Requires-Dist: jsonschema>=4.21; extra == "dev"
Provides-Extra: pdf
Requires-Dist: PyMuPDF>=1.24; extra == "pdf"
Provides-Extra: ocr
Requires-Dist: rapidocr-onnxruntime>=1.3; extra == "ocr"
Provides-Extra: docx
Requires-Dist: python-docx>=1.1; extra == "docx"
Provides-Extra: pptx
Requires-Dist: python-pptx>=1.0; extra == "pptx"
Provides-Extra: html
Requires-Dist: beautifulsoup4>=4.12; extra == "html"
Provides-Extra: msg
Requires-Dist: extract-msg>=0.49; extra == "msg"
Provides-Extra: ipynb
Requires-Dist: nbformat>=5.10; extra == "ipynb"
Provides-Extra: documents
Requires-Dist: PyMuPDF>=1.24; extra == "documents"
Requires-Dist: python-docx>=1.1; extra == "documents"
Requires-Dist: python-pptx>=1.0; extra == "documents"
Requires-Dist: beautifulsoup4>=4.12; extra == "documents"
Requires-Dist: extract-msg>=0.49; extra == "documents"
Requires-Dist: nbformat>=5.10; extra == "documents"
Provides-Extra: mcp
Requires-Dist: mcp>=1.0; extra == "mcp"
Provides-Extra: tokens
Requires-Dist: tiktoken>=0.7; extra == "tokens"
Provides-Extra: anthropic-tokens
Requires-Dist: anthropic>=0.30; extra == "anthropic-tokens"
Provides-Extra: demo
Requires-Dist: tiktoken>=0.7; extra == "demo"
Requires-Dist: openai>=1.0; extra == "demo"
Dynamic: license-file

# Documa

<p align="center">
  <img src="https://raw.githubusercontent.com/AllanYiin/Documa/main/assets/documa-logo.png" alt="Documa logo" width="320">
</p>

Documa 是給 agent 用的 **document evidence runtime**：讓 agent 用一小部分的 token 讀懂大文件、回答時指得出出處，而且引用可以被機器驗證。

如果你正在評估「有沒有比把整份文件塞進 context 更省、比 grep 原始文字更準」的文件讀取機制，這份 README 就是為你寫的。

## 問題：agent 讀文件的三種常見方式，各有一筆 token 帳

以一份 69 頁的 PDF（Basel III 流動性架構，全文 **49,570 tokens**）為例：

| 方式 | Token 成本 | 痛點 |
| --- | --- | --- |
| 整份塞進 context | 49,570（每輪對話都攤提） | 貴；長文件超過 context；模型在中段迷路（lost in the middle） |
| grep / 逐行搜尋原始文字 | 每個 hit 便宜（~20-40 tokens/行），但**路徑長**：命中後要反覆擴窗 Read 前後文，跨頁表格、雙欄版面直接斷裂 | 沒有結構、沒有頁碼出處、CJK 無詞界時 grep 幾乎失效 |
| 向量 RAG | 檢索本身便宜 | 需要 embedding 前置成本與基礎設施；chunk 邊界破壞表格與章節；引用難以回溯到頁面座標 |
| **Documa 漸進式區塊讀取** | 大綱 2,813 + 搜尋 1,181 + 讀 2-3 個區塊 ~1,500 ≈ **5-6k tokens（約全文的 12%）** | 前置一次 `documa process`（確定性、離線、無 ML 依賴） |

Documa 的核心主張：**讓 LLM 的每一次工具呼叫都只換回「做下一步決策所需」的最小資訊**，並在收尾時給出可驗證的頁碼級引用。

## 機制：四步漸進式揭露

```
process/ingest ──► block tree（大綱+梗概）──► search（排序過的候選）──► read（有界讀取）──► cite/verify
     一次性              ~2.8k tokens              ~1.2k tokens            每塊數百 tokens        出處可機器驗證
```

1. **Ingest** — `documa ingest report.pdf` 把文件處理成 parser-neutral 的 IR 與 block tree，拿到穩定的 `document_id`（同內容自動去重）。支援 PDF、Word、PowerPoint、HTML、email、notebook、Markdown。
2. **Overview** — `documa_block_tree` 回傳章節大綱；`include_sketches=true` 直接附上 ingest 時算好的每節梗概（sketch）與讀取成本，總覽類問題常常**零讀取**就能回答。
3. **Search + Read** — `documa_search_blocks` 用 BM25-lite + coverage/proximity/intent 排序（TOC 與頁眉頁腳自動降權、近似重複去除），每個 hit 附帶結構路徑、頁碼、建議讀取量與**可直接照發的下一步工具呼叫**（`recommended_next.actions[]`）；`documa_read_block` 以 `max_chars`/`max_tokens` 有界讀取，讀不完給續讀 cursor。
4. **Cite + Verify** — `documa_cite_block` 回溯到「第幾頁、頁面上哪個 bbox」，`documa_verify_citations` 機器檢查引用的區塊真實存在——答案不只便宜，還可稽核。

跨文件同理：`documa_search_collection`（SQLite FTS5，含 CJK 逐字索引）一次回答「哪幾份文件提到 X」，每份文件一列精確命中數 rollup。

### 回應層的 token 工程（v0.5.0）

省 token 不只靠「少讀」，也靠**每個回應本身就瘦**：

- 回應只傳一份 compact JSON（不重複傳 structuredContent + pretty text）。
- Block id 去掉重複的文件 GUID 前綴，envelope 宣告一次 `block_id_prefix`；空欄位一律不序列化；常數（如 `page_ref_kind`）上提到 envelope。
- 搜尋預設 `nav` profile：每個 hit 只回路由決策需要的欄位；citation/selection 細節留給 `evidence`，診斷留給 `debug`。
- 有 token counter 時，搜尋回應自動套 2,000 token 上限，超出時按「可有可無 → 低排名結果」順序優雅裁減並回報 `dropped_results`。
- Token 一律由真實 tokenizer 計算（tiktoken 自動偵測；Claude 可走 Anthropic count-tokens API），**禁止 chars/4 估算**。

同一份 69 頁 PDF 的實測（compact JSON tokens，v0.4.0 → v0.5.0）：

| 回應 | v0.4.0 | v0.5.0 |
| --- | --- | --- |
| `block_tree max_depth=2` | 9,756 | **2,813（-71%）** |
| `list_blocks depth=1` | 3,476 | **1,092（-69%）** |
| `search_blocks` 5 hits | 1,344 | 1,181 |
| MCP wire（傳輸層） | 每回應兩份 | **單份（再省一半）** |

固定成本也可控：MCP 工具面分 `agent`（16 工具，schema 約 3.3k tokens，涵蓋完整 evidence 工作流）/`advanced`/`admin` 三個 profile，plugin 預設最小的 `agent`。

可重現的量測入口：`python benchmarks/token_economy/run_agent_benchmark.py`（真實 tokenizer 計分：Tokens-to-Supported-Answer、Evidence Recall、Search Path Length、Budget Correctness）。

## 15 分鐘評估路徑

需要 Python 3.10+。以下以 PowerShell 為例（macOS/Linux 改路徑分隔符即可）：

```powershell
python -m pip install -e ".[documents,mcp]"   # documents = PDF/DOCX/PPTX/HTML/MSG/IPYNB 依賴
```

**1. 處理一份你自己的 PDF**（挑一份你熟悉內容的長文件，才能評估答案品質）：

```powershell
documa process .\your.pdf --out .\out\eval --export-format block-json
```

**2. 看大綱與梗概**（評估點：不讀內文能否掌握文件結構）：

```powershell
documa block-tree .\out\eval\documa.ir.json --max-depth 2
```

**3. 問一個具體問題**（評估點：hit 的排序品質與每 hit 的 token 成本）：

```powershell
documa search-blocks .\out\eval\documa.ir.json --query "你關心的主題"
```

**4. 只讀命中的區塊**（評估點：有界讀取 + 續讀 cursor）：

```powershell
documa block .\out\eval\documa.ir.json --id "<搜尋結果的 block id>" --read --max-chars 1500
```

**5. 產生可驗證引用**：

```powershell
documa cite-block .\out\eval\documa.ir.json --id "<block id>"
```

回傳類似：

```json
{
  "page_label": "PDF p.2",
  "grounding": "visual",
  "bboxes": [{ "page": 2, "x0": 56.0, "y0": 240.0, "x1": 486.0, "y1": 328.0 }],
  "citation_string": "[PDF p.2, bbox(56,240,486,328)]"
}
```

**6.（選配）看完整離線 demo**——不呼叫任何 LLM，輸出一份 trace 展示「搜尋→選塊→讀取→合成答案」全程與逐步 token 用量：

```powershell
documa block-demo .\your.pdf --question "這份文件的主要風險是什麼？" --out .\out\eval-demo
```

**多文件評估**：把幾份文件 `documa ingest` 進同一個 store，然後：

```powershell
documa search-collection --query "違約金" --group-by-document
```

## 接進你的 agent

同一套能力有四個入口，行為一致：

| 入口 | 使用方式 |
| --- | --- |
| **MCP** | `documa-mcp`（需 `[mcp]` extra）。`DOCUMA_MCP_PROFILE=agent` 只暴露 evidence 工作流的 16 個工具。repo 內附三個現成 plugin：Claude Code（`plugins/claude-code-documa`）、Codex（`plugins/codex-documa`）、OpenClaw（`plugins/openclaw-documa`），各含引導 LLM 走短搜尋路徑的 `documa-evidence` skill。 |
| **OpenAI function calling** | `from documa.interfaces import openai_tool_schemas, call_documa_tool` |
| **CLI** | 上面評估路徑用的指令；適合 shell-based agent 或人工檢查。 |
| **Python API** | `documa.adapters` + `documa.pipeline.run_default_pipeline`，或直接 `call_documa_tool(name, args)`。 |

Python 最小範例：

```python
from documa.interfaces import call_documa_tool

result = call_documa_tool(
    "documa_search_blocks",
    {"ir_path": "out/eval/documa.ir.json", "query": "資本適足率", "limit": 5},
)
# result["structuredContent"]["results"] 每筆含 block_id/path/page/score/snippet/read_chars，
# recommended_next.actions[] 是可直接照發的下一步呼叫
```

給 LLM 的回應約定（plugin skill 已內建教學）：回應在 envelope 宣告一次 `block_id_prefix` 後發短 block id，回傳工具時原樣帶回即可；空欄位不出現＝空值；`page` 是引用標籤、`page_refs` 是實體頁碼。

## 設計性質（評估 checklist）

- **確定性、可離線**：core 無 ML/LLM 依賴、無網路呼叫，同輸入同輸出；適合放進 CI 與資安敏感環境。
- **出處可驗證**：每個 block 帶頁碼與 bbox 來源鏈；`documa_verify_citations` 做引用存在性檢查（明確不是語義驗證——語義驗證的 `AnswerSupportChecker` 介面在 core、實作在 examples）。
- **CJK 完整支援**：關鍵詞抽取用 CJK n-gram + 邊界熵新詞發現；collection FTS 逐字索引讓中文子詞查詢可用；snippet 視 CJK/ASCII 分別以字元/詞窗置中。
- **格式統一**：PDF（`.pdf`）、Word（`.docx`）、PowerPoint（`.pptx`）、HTML、email（`.eml`/`.msg`，含 mailbox 批次 `documa ingest-mailbox`）、Jupyter（`.ipynb`）、Markdown/text 全部進同一種 IR，下游只依賴 IR。
- **IR 是 semver 契約**：`ir_version` minor 只允許 additive 欄位；schema 由 dataclass 生成並有 CI 同步閘門（`documa validate-ir` 可驗檔）。
- **品質有 gold 基準**：`documa benchmark --mode quality` 對 13 個 gold case 計分（表格 TEDS、閱讀順序 NED、關係連結 F1）；雙欄/三欄/sidebar 閱讀順序 1.0。兩個已知缺口（footnote 與 caption 連結）**蓄意保留為 failed**，不調門檻掩蓋。
- **OCR 選配不混料**：`documa[ocr]`（RapidOCR，CPU）處理掃描件；所有 OCR 產物標記 `origin: "ocr"` 與信心值，永不冒充原生文字。
- **索引皆可拋棄重建**：collection index（SQLite FTS5）與 retrieval sidecar 都是版本化衍生物，來源真相只在 IR + registry。

## 什麼時候不該用 Documa

- 文件很短（幾頁以內）而且只問一次——直接塞 context 更簡單，前置處理不划算。
- 你需要**語義**相似檢索（同義改寫、跨語言概念對齊）——Documa 檢索是 lexical/statistical 的；它預留了 hybrid/vector adapter 邊界，但目前不內建 embeddings。
- 你要的是視覺／版面渲染判斷（「第 3 頁有沒有蓋章」）、完整 UI 文件管理系統，或生產級 OCR 產品。
- 你只要最底層的 PDF 文字抽取，且已滿意 PyMuPDF / pdfplumber。

## 升級注意（v0.5.0）

- v0.5.0 前建立的 collection index 與 search sidecar 會被 `documa doctor --store-dir` 標為 stale，首次搜尋前請 `documa index-collection` 重建（或重跑 `process`）。
- 工具回應形狀有 breaking 變更：搜尋列移除 `read_ref`/`ir_document_id`/`bbox_refs`（讀取對＝`document_id` + `block_id`）、`block_tree` 預設不含 per-node citation、空欄位不再序列化。細節見 [CHANGELOG](CHANGELOG.md)。

## 深入閱讀

| 主題 | 位置 |
| --- | --- |
| 架構分層與各 stage 演進 | [docs/documa/architecture.md](docs/documa/architecture.md) |
| IR 相容性契約 | [docs/spec/ir-compatibility.md](docs/spec/ir-compatibility.md) |
| Gold 標註格式與品質門檻 | [fixtures/pdf/gold/README.md](fixtures/pdf/gold/README.md) |
| Token 經濟 benchmark | [benchmarks/token_economy/](benchmarks/token_economy/run_agent_benchmark.py) |
| Plugin 安裝與 skill | [plugins/README.md](plugins/README.md) |

## 開發與測試

```powershell
python -m pip install -e ".[dev,documents,mcp]"
python -m pytest                      # 全套（含 snapshot 回歸）
documa doctor                         # 環境診斷
documa benchmark --mode quality       # gold 品質計分
```

Snapshot 回歸測試把 3 份真實 PDF 的完整 pipeline 輸出與 golden files 比對；只有「預期內的輸出變更」才能 `pytest --force-regen` 重建，且 commit 訊息必須說明原因。

新增 parser adapter：在 `src/documa/adapters/` 實作並於 `registry.py` 註冊副檔名，adapter 只回傳 IR、不外洩 parser 原生物件。新增 public tool：同步更新 `interfaces/tools.py`、`tool_schemas.py`、`mcp_server.py`、`cli.py` 與測試。

## License

MIT.
