Skip to content

Recipe: Office Files to Page Images

Goal

Let a model see Word, Excel and PowerPoint files as they look on the page: charts, pictures, diagrams and layout that the text readers can only count. Show the pages to the model, or save them as image files for a report or another step.

Prerequisites

  • The docs extra (pymupdf, Pillow and the Office readers).
  • LibreOffice where the tools run. It turns each file into a PDF, which pymupdf then renders. On Debian or Ubuntu:

    apt-get install libreoffice-core libreoffice-writer libreoffice-calc libreoffice-impress
    

    The Deployment page has a Dockerfile with it. Install fonts for the documents you expect, such as fonts-noto-cjk for Chinese, Japanese or Korean.

PDFs render without LibreOffice, with pymupdf alone.

Steps

Tool calls are written as Python calls; the model sends the same arguments as JSON. The results are from a run in a Linux container with LibreOffice.

1. Read the text first, and look for media

The text readers are fast and cost no image tokens. They also say when a file holds something they cannot read:

read_powerpoint(path="decks/q3_review.pptx")
{
  "slides": [
    {"slide": 1, "text": ["Q3 review", "Revenue and margin"], "image_count": 0, "chart_count": 0, "has_media": false},
    {"slide": 2, "text": ["Revenue by region"], "image_count": 0, "chart_count": 1, "has_media": true},
    {"slide": 3, "text": ["Next steps", "Hire two analysts"], "image_count": 0, "chart_count": 0, "has_media": false}
  ],
  "slide_count": 3,
  "has_media": true
}

Slide 2 is a chart with a title: its numbers are not in the text. read_excel reports has_charts, has_images and has_media the same way. read_word ends with a line such as:

[media_summary] embedded_images=1 — this document contains images; if text looks incomplete, render it with read_document_pages_as_images.

2. Show the model the pages that matter

read_document_pages_as_images(path="decks/q3_review.pptx", pages="2")

This returns one image per page, for the model to look at. In the run above, a standard 4:3 slide came back as a 1440 × 1080 PNG, and the LibreOffice step took about 1.5 seconds per file.

Tool Use it for
read_document_pages_as_images(path, pages, scale=2.0, max_pages=20) The pages you name: "2", "1-3", "1-3,7"
render_document_thumbnail(path, max_pages=5) A quick look at the first pages, at 72 dpi (a Letter page is 612 × 792)
render_document_page(path, page=1, scale=2.0) One page

Every image is brought within DPTTOOLS_IMAGE_MAX_EDGE_PX (1568 px) and DPTTOOLS_IMAGE_MAX_MB (3 MB) before the model sees it. A tall spreadsheet page rendered at 1109 × 1568 for that reason. A higher scale doesn't add detail past that limit. To read small print, crop the page with Image Tools and read_image the crop.

Ask for page ranges rather than "all": each page costs image tokens, and pages past max_pages are dropped without an error.

3. Save page images as files

When the images are an output rather than something for the model to look at, write them to disk with convert_document:

convert_document(path="decks/q3_review.pptx", to="png", page="1-2", outdir="exports")
{
  "output_files": ["/workspace/exports/q3_review-p1.png", "/workspace/exports/q3_review-p2.png"],
  "pages": 2,
  "engine": "libreoffice+pymupdf"
}
  • Pages are rendered at 144 dpi and named <name>-p<N>.png; to="jpg" writes JPEGs.
  • outdir must already exist. Without it, the files go next to the source.
  • An existing file is never replaced unless you pass overwrite=True:

    Error: '/workspace/exports/q3_review-p1.png' (and 1 more) already exists. Pass overwrite=True to replace it, or choose another outdir.
    
  • convert_document(path="sales.xlsx", to="pdf") makes a PDF instead, with pages set to its page count.

To check a saved page, read_image(path="exports/q3_review-p2.png") shows it to the model.

4. Give an agent the tools

from doc_processing_toolkit import Agent
from doc_processing_std_tools import document_processing_server

agent = Agent(
    model="claude-sonnet-5",
    instructions=(
        "Read Office files with read_word, read_excel or read_powerpoint first. When a result reports "
        "charts, images or media, look at those pages with read_document_pages_as_images, a few "
        "pages at a time."
    ),
    tools=[{"type": "fastmcp", "server": document_processing_server,
            "allowed_tools": ["get_file_overview", "read_word", "read_excel", "read_powerpoint",
                              "read_document_pages_as_images", "convert_document", "read_image"]}],
)
result = agent.run_sync("What does the revenue chart in decks/q3_review.pptx show?")

Image results reach the model in-process only with toolkit 0.5.1 or later (Compatibility); out of process they work with any version. A LibreOffice run takes a second or more, so a busy agent is better served out of process, where the composed launcher runs it on a worker thread.

What to validate

  • The text readers report media: chart_count and has_media (PowerPoint), has_charts (Excel), the [media_summary] line (Word).
  • read_document_pages_as_images returns one image per requested page, each within the image limits.
  • convert_document(..., to="png", page="1-2") writes exactly those pages and lists them in output_files.
  • Running the same conversion again fails, naming the existing file, until overwrite=True.
  • Without LibreOffice, rendering an Office file returns an Error: with the install command, and PDFs still render.

Test sources

  • tests/integration/test_libreoffice_live.py: real conversions (DOCX to PNG and PDF, XLSX to CSV, two at once, render_document_page on a real DOCX). Skipped without LibreOffice; run it in the Linux image with tests/integration/run_in_docker.py.
  • tests/unit/test_soffice.py: finding LibreOffice, per-run profiles, timeouts, and the no-overwrite rule.
  • tests/unit/test_document_processing.py: TestReadPowerPoint, TestReadExcel, TestReadWord (media reporting), and the three render tools.
  • Documents server: every target convert_document supports.
  • Images: how images are fitted to the model limits.