> ## Documentation Index
> Fetch the complete documentation index at: https://docs.eigenpal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Parse Document

> Extract text from documents (PDF, DOCX, images) using OCR or vision models

`ai.parse` turns a document into text. It is almost always the first step in a
document-understanding workflow: point it at an uploaded file, get back clean
text plus per-page content, then feed that into [`ai.extract`](/steps/ai/extract),
[`ai.split`](/steps/ai/split), or a [`transform.script`](/steps/transform/script).

## When to use it

* **You have a PDF, image, or Office document and need its text.** `ai.parse`
  handles PDFs, images (PNG/JPG), and Office formats (DOCX, PPTX, XLSX, ODT, and
  more), normalizing them to one text representation.
* **You need per-page boundaries.** The `pages` array carries page-scoped text so
  downstream steps can cite evidence by page or split a document into sections.

## How parsing is chosen

The step picks a strategy based on the document and your config:

* **Native text** (`nativeText: true`) pulls embedded text straight from a PDF.
  Fastest, uses no credits, and falls back to OCR/VLM when the PDF has no text
  layer (a scan).
* **OCR** (`ocrModel`) runs optical character recognition for scanned PDFs and
  images.
* **Vision LLM** (`llmModel`) reads page images with a vision model. Best for
  complex layouts where OCR struggles. `pagesPerBatch` and `maxConcurrency`
  tune how page images are batched across requests.

Office formats are converted to PDF first (via headless LibreOffice) so they get
true per-page boundaries instead of a single blob.

## Example

```yaml theme={null}
- name: parse
  type: ai.parse
  with:
    input: '{{ input.document }}'
    nativeText: true
    outputFormat: markdown
```

`markdown` output keeps headings and tables, which makes the downstream
`ai.extract` prompt far more reliable than unstyled `plain` text.

## Configuration

Configuration goes inside the step's `with:` block.

<ParamField path="input" type="string" required>
  Storage reference or template expression for the document
</ParamField>

<ParamField path="ocrModel" type="string">
  OCR provider ID for PDF/image parsing
</ParamField>

<ParamField path="llmModel" type="string">
  LLM provider ID for vision-based parsing
</ParamField>

<ParamField path="maxConcurrency" type="number" default="3">
  Max concurrent VLM batch requests
</ParamField>

<ParamField path="pagesPerBatch" type="number" default="5">
  Number of page images per VLM request
</ParamField>

<ParamField path="pdfRenderScale" type="number" default="1">
  Scale factor for rendering PDF pages before VLM parsing. Higher values produce sharper images at larger payload sizes.
</ParamField>

<ParamField path="imageQuality" type="integer" default="85">
  JPEG quality for rendered PDF page images sent to VLM parsing. Higher values reduce compression artifacts at larger payload sizes.
</ParamField>

<ParamField path="prompt" type="string">
  Custom extraction prompt
</ParamField>

<ParamField path="languages" type="array<string>">
  OCR language hints
</ParamField>

<ParamField path="outputFormat" type="&#x22;plain&#x22; | &#x22;markdown&#x22; | &#x22;djot&#x22; | &#x22;html&#x22;" default="markdown">
  Format for extracted text. `markdown` (default) keeps structure and is best for LLM extraction; `plain` is unstyled text; `djot`/`html` preserve more layout. Only the native (Kreuzberg) parser respects this, OCR/VLM always emit markdown.
</ParamField>

<ParamField path="nativeText" type="boolean" default="false">
  Extract native/embedded text from PDFs without OCR/VLM. Faster and uses no credits. Falls back to OCR/VLM if the PDF has no embedded text.
</ParamField>

<ParamField path="describeFigures" type="boolean">
  Opt-in (default off). After text extraction, detect which pages contain figures with an in-worker layout model, then caption those pages with a vision model and append `&lt;figure>description&lt;/figure>` to their text, so image-only pages (property photos, signatures, charts) become findable by text-based steps like ai.split. Note: the layout scan runs over all pages, and the caption step and its vision calls are billed. Skipped for plaintext.
</ParamField>

<ParamField path="figureInstructions" type="string">
  Custom instruction for the figure-description pass, e.g. "Describe each figure; label a handwritten signature as `&lt;figure>signature&lt;/figure>` and a stamp as `&lt;figure>stamp&lt;/figure>`; for property photos note the room or exterior shown." Applied only when describeFigures runs.
</ParamField>

## Output

<ResponseField path="text" type="string" required>
  Extracted text content (combined from all pages)
</ResponseField>

<ResponseField path="pages" type="array<object>">
  Per-page content

  <Expandable title="pages properties">
    <ResponseField path="pageIndex" type="number" required>
      0-based page index
    </ResponseField>

    <ResponseField path="text" type="string" required>
      Extracted text for this page
    </ResponseField>

    <ResponseField path="pageName" type="string">
      Page/sheet name (e.g., Excel sheet name)
    </ResponseField>

    <ResponseField path="confidence" type="number">
      Overall page confidence
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField path="metadata" type="record<string, unknown>">
  Document metadata
</ResponseField>
