Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ explicitly under **Changed**, with what to do about it.

## [Unreleased]

### Added

- **The agent image's document tools can now render PDF pages and handle images.** `/opt/doctools` gains PyMuPDF, Pillow and numpy, pinned. Agents on a network allowlist cannot reach PyPI, so tools they used to install on demand (`uv run --with pymupdf`) have to be in the image. OCR is still tesseract through `doc2text`.

## [0.9.0] - 2026-10-06

### Added
Expand Down
11 changes: 10 additions & 1 deletion deploy/agents-box/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,18 @@ RUN apt-get update \
&& apt-get install -y --no-install-recommends poppler-utils tesseract-ocr tesseract-ocr-eng \
&& rm -rf /var/lib/apt/lists/* \
&& pdftotext -v >/dev/null 2>&1 && tesseract --version >/dev/null 2>&1
# PyMuPDF (render a PDF page to an image, read a PDF's layout), Pillow and numpy
# (image handling). Baked in because the agents' network is an allowlist and PyPI
# is NOT on it: before this, an agent that needed them ran `uv run --with pymupdf …`
# ~60 times (2026-08-28..09-22) and fetched them at runtime. OCR stays tesseract
# (above); RapidOCR was left out on purpose (+285 MB, unused since 2026-09-09).
# Pins are what `uv` resolves under its 14-day exclude-newer cutoff, checked on
# 2026-10-20 with a render + import test on Python 3.13.5.
RUN uv venv /opt/doctools --python 3.13 \
&& uv pip install --python /opt/doctools/bin/python \
"openpyxl==3.1.5" "xlrd==2.0.2" "python-docx==1.2.0" "pypdf==6.1.1"
"openpyxl==3.1.5" "xlrd==2.0.2" "python-docx==1.2.0" "pypdf==6.1.1" \
"pymupdf==1.28.2" "pillow==13.0.0" "numpy==2.5.4" \
&& /opt/doctools/bin/python -c "import fitz, PIL, numpy, pypdf, docx, openpyxl, xlrd"
COPY deploy/agents-box/scripts/doc2text /usr/local/bin/doc2text

# openpyxl AGAIN, into hermes' own venv this time. hermes ships a bundled
Expand Down
39 changes: 39 additions & 0 deletions tests/test_agents_box_doctools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""The agent image's document tools are baked in, pinned, and import-checked at build time.

Agents run on a network allowlist that does not include PyPI, so a library the agent used to fetch with
`uv run --with pymupdf` (≈60 runs, 2026-08-28..09-22) must be in the image. The Dockerfile has no test run,
so this reads the text: every library is pinned with `==` and one RUN imports them all, so a bad pin fails the
build instead of the agent's first PDF.

Run: uv run --extra dev python -m pytest tests/test_agents_box_doctools.py -q
"""
import re
from pathlib import Path

DOCKERFILE = Path(__file__).resolve().parents[1] / "deploy" / "agents-box" / "Dockerfile"
BAKED = {"openpyxl": "openpyxl", "xlrd": "xlrd", "python-docx": "docx", "pypdf": "pypdf",
"pymupdf": "fitz", "pillow": "PIL", "numpy": "numpy"}


def doctools_run():
df = DOCKERFILE.read_text()
start = df.index("RUN uv venv /opt/doctools")
return df[start:df.index("\n\n", start)].replace("\\\n", " ")


def test_every_document_library_is_pinned_exactly():
run = doctools_run()
for pkg in BAKED:
assert re.search(rf'"{re.escape(pkg)}==\d[\w.]*"', run), f"{pkg} must be pinned with =="


def test_the_build_imports_every_library_it_installs():
run = doctools_run()
probe = run[run.index("/opt/doctools/bin/python -c"):]
for module in BAKED.values():
assert re.search(rf"\b{module}\b", probe), f"the build-time import must cover {module}"


def test_ocr_is_tesseract_and_rapidocr_stays_out():
df = DOCKERFILE.read_text()
assert "tesseract-ocr" in df and "rapidocr" not in df.lower().replace("rapidocr was left out", "")
Loading