pdf2md · git:20260714.5777904 · 2026-07-14 · sha256 d2f7104627e021e1

pdf2md git:20260714.5777904A

Immutable. This exact content is served forever at /api/v1/blob/d2f7104627e021e1.

---
name: pdf2md
description: Convert PDF files to Markdown — extracts text with pdfplumber and describes images/diagrams via a local vision-language model. Use when the user asks to extract PDF content, convert presentation slides to text, transcribe conference talks from PDFs, or OCR diagrams and charts from documents.
---

# PDF -> Markdown (pdf2md)

Extracts PDF content into Markdown: text as-is + image/diagram meaning via a Vision Language Model.

## When to Use

- User asks to "extract from PDF", "convert PDF to text/Markdown", "transcribe slides"
- Presentation decks (text + graphics + diagrams)
- Conference talks, workshops, technical reports
- PDFs where plain text extraction produces garbled output

## When NOT to Use

- PDFs that are purely text (use pdfplumber directly — faster, no VL needed)
- One-page forms or invoices (use pdfplumber tables)
- Filling PDF forms (use well known `pdf` skill instead)
- Scanned documents without selectable text (this skill works, but OCR-only tools may be faster)

## Dependencies

Install once per environment:

```bash
pip install pdfplumber pypdfium2 pillow requests
```

## VL Model Setup

Default: **LM Studio** with `qwen/qwen3-vl-8b` loaded on `localhost:1234`.

Any OpenAI-compatible endpoint works — Ollama, vLLM, cloud providers. To switch:

```bash
python scripts/extract_pdfs.py --source "<path>" --model "llama3.2-vision" --api-url "http://localhost:11434/v1/chat/completions"
```

## Workflow

### Step 1: Verify VL model is reachable

```bash
python -c "import requests; r = requests.get('http://localhost:1234/v1/models'); print([m['id'] for m in r.json()['data']])"
```

If you don't see your VL model, load it in LM Studio before proceeding.

### Step 2: Test on 1-2 PDFs first

```bash
python scripts/extract_pdfs.py --source "<path-to-pdfs>" --first 2
```

Check the output in `_markdown/`. If quality is poor (too much "water", missing text, or style descriptions leaking through), edit the prompt in `references/vl-prompt.md` and the `VL_PROMPT` variable in the script, then re-run with `--force`.

### Step 3: Run on everything

```bash
python scripts/extract_pdfs.py --source "<path-to-pdfs>"
```

Already-processed PDFs are skipped automatically. Use `--force` to overwrite.

### Step 4: If something fails

- **VL call fails (3 retries exhausted)**: page is left with fallback text from pdfplumber, or marked `[VL error]`
- **Rendering fails**: page is skipped, counted as error
- **Output file corrupted**: delete the `.md` and re-run — the script will re-process it
- **Model runs out of VRAM**: reduce `RENDER_SCALE` in the script (1.5 or 1.0)

## Script Arguments

| Argument | Default | Description |
|----------|---------|-------------|
| `--source` | *(required)* | Directory with PDF files (recursive) |
| `--output` | `_markdown/` inside source | Output directory for .md files |
| `--model` | `qwen/qwen3-vl-8b` | VL model ID |
| `--api-url` | `http://localhost:1234/v1/chat/completions` | API endpoint |
| `--first N` | *(all)* | Process only first N PDFs |
| `--files "s1,s2"` | *(all)* | Filter by filename substring |
| `--force` | `false` | Overwrite existing output |

## Output Structure

```
_source/
  _markdown/
    subfolder/
      presentation1.md
      presentation2.md
```

Folder structure mirrors the source. Each `.md` file:

```markdown
# Presentation Title

---
## Page 1 / 40

**[VL slide description]**

<content extracted by VL model>

---
## Page 2 / 40
...
```

## VL Prompt Rules (hard-won lessons)

1. **Never** describe fonts, colors, margins, backgrounds — only content
2. **Never** write intros like "This slide shows..." — start with content
3. **Never** invent "Conclusions" or "Overall assessment" sections
4. Extract ALL text verbatim
5. For diagrams/charts: describe MEANING, not appearance
6. Concise, dense, no filler — every sentence must carry information
7. Do NOT limit output tokens — let the model decide

See `references/vl-prompt.md` for the full prompt template and tuning guide.

## Performance

| GPU | Sec/page | 1000 pages |
|-----|---------|------------|
| RTX 3090/4090 | ~7-10s | ~2 hours |
| M1/M2 Mac (LM Studio) | ~15-20s | ~4-5 hours |
| CPU-only | ~60-120s | Not recommended |

## See Also

- [VL Prompt (references/vl-prompt.md)](references/vl-prompt.md) — prompt template + iteration history + tuning tips
- [Script (scripts/extract_pdfs.py)](scripts/extract_pdfs.py) — the extraction tool

---

> **Evolution rule:** If you are dissatisfied with the extraction quality or the workflow, identify what to change (prompt, script parameters, dependencies) and offer to update this skill. The prompt lives in `references/vl-prompt.md`; the extraction logic in `scripts/extract_pdfs.py`.