Restore Tesseract in runtime images (#6916)
Some checks are pending
Run tests and pre-commit / Run tests and pre-commit hooks (push) Waiting to run
Run tests and pre-commit / Frontend Lint and Build (push) Waiting to run
Run tests and pre-commit / pip Package Smoke Tests (3.11) (push) Waiting to run
Run tests and pre-commit / pip Package Smoke Tests (3.13) (push) Waiting to run
Publish Fern Docs / run (push) Waiting to run

Co-authored-by: Suchintan Singh <suchintan@skyvern.com>
This commit is contained in:
Suchintan 2026-06-29 23:46:01 -04:00 committed by GitHub
parent 1451bd491f
commit 46f22cdad7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 106 additions and 4 deletions

View file

@ -0,0 +1,62 @@
from __future__ import annotations
from collections.abc import Iterable
DEFAULT_TESSERACT_OCR_LANGUAGE_PACKS = (
"eng",
"spa",
"fra",
"deu",
"ita",
"por",
"nld",
"pol",
"rus",
"ukr",
"ara",
"chi-sim",
"chi-tra",
"jpn",
"kor",
"hin",
)
def tesseract_ocr_packages(language_packs: Iterable[str] = DEFAULT_TESSERACT_OCR_LANGUAGE_PACKS) -> list[str]:
return [f"tesseract-ocr-{language_pack}" for language_pack in language_packs]
def tesseract_language_arg(language_packs: Iterable[str] = DEFAULT_TESSERACT_OCR_LANGUAGE_PACKS) -> str:
return "+".join(language_pack.replace("-", "_") for language_pack in language_packs)
DEFAULT_FLAT_FILL_OCR_LANGUAGES = tesseract_language_arg()
def main() -> None:
import argparse
parser = argparse.ArgumentParser(description="Print Tesseract language package metadata.")
parser.add_argument(
"--apt-packages",
action="store_true",
help="Print Debian OCR language packages for apt-get install.",
)
parser.add_argument(
"--tesseract-languages",
action="store_true",
help="Print the Tesseract -l language argument.",
)
args = parser.parse_args()
if args.apt_packages:
print(" ".join(tesseract_ocr_packages()))
return
if args.tesseract_languages:
print(DEFAULT_FLAT_FILL_OCR_LANGUAGES)
return
parser.error("expected --apt-packages or --tesseract-languages")
if __name__ == "__main__":
main()

View file

@ -32,6 +32,7 @@ from skyvern.forge.sdk.db.exceptions import NotFoundError
from skyvern.forge.sdk.experimentation.llm_prompt_config import get_llm_handler_for_prompt_type
from skyvern.forge.sdk.schemas.files import FileInfo
from skyvern.forge.sdk.utils.pdf_parser import render_pdf_pages_as_images, validate_pdf_file
from skyvern.forge.sdk.utils.tesseract_languages import DEFAULT_FLAT_FILL_OCR_LANGUAGES
from skyvern.forge.sdk.workflow.context_manager import WorkflowRunContext
from skyvern.forge.sdk.workflow.loop_download_filter import filter_downloaded_files_for_current_iteration
from skyvern.forge.sdk.workflow.models._jinja import render_templates_in_json_value
@ -60,6 +61,7 @@ FLAT_FILL_OCR_TIMEOUT_SECONDS = 60
# Outer budget across all pages so a flat PDF can't pin a worker for the full per-page timeout x max pages.
FLAT_FILL_OCR_TOTAL_TIMEOUT_SECONDS = 300
FLAT_FILL_MAX_PAGES = 25
FLAT_FILL_OCR_LANGUAGES = os.getenv("FLAT_FILL_OCR_LANGUAGES", DEFAULT_FLAT_FILL_OCR_LANGUAGES).strip()
@dataclass(frozen=True)
@ -511,10 +513,7 @@ class PdfFillBlock(Block):
async with aiofiles.open(image_path, "wb") as f:
await f.write(image_bytes)
process = await asyncio.create_subprocess_exec(
"tesseract",
image_path,
"stdout",
"tsv",
*self._tesseract_command(image_path),
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.DEVNULL,
)
@ -532,6 +531,14 @@ class PdfFillBlock(Block):
)
return anchors
@staticmethod
def _tesseract_command(image_path: str) -> list[str]:
command = ["tesseract", image_path, "stdout"]
if FLAT_FILL_OCR_LANGUAGES:
command.extend(["-l", FLAT_FILL_OCR_LANGUAGES])
command.append("tsv")
return command
@staticmethod
def _tsv_int(row: dict[str, str], key: str) -> int | None:
try:

View file

@ -20,7 +20,9 @@ from pypdf.generic import (
)
from skyvern.forge import app
from skyvern.forge.sdk.utils.tesseract_languages import tesseract_language_arg, tesseract_ocr_packages
from skyvern.forge.sdk.workflow.context_manager import WorkflowRunContext
from skyvern.forge.sdk.workflow.models import pdf_fill_block
from skyvern.forge.sdk.workflow.models.block import PdfFillBlock, extract_file_url_from_block_output
from skyvern.forge.sdk.workflow.models.parameter import OutputParameter
from skyvern.forge.sdk.workflow.models.pdf_fill_block import FlatPdfAnchor, FlatPlacement, PdfFieldInventory
@ -589,6 +591,37 @@ def test_parse_tesseract_tsv_skips_malformed_numeric_rows() -> None:
assert [a.text for a in anchors] == ["City:"]
def test_tesseract_command_includes_configured_languages(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(pdf_fill_block, "FLAT_FILL_OCR_LANGUAGES", "eng+spa+fra")
assert PdfFillBlock._tesseract_command("/tmp/page.png") == [
"tesseract",
"/tmp/page.png",
"stdout",
"-l",
"eng+spa+fra",
"tsv",
]
def test_tesseract_command_allows_empty_language_override(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(pdf_fill_block, "FLAT_FILL_OCR_LANGUAGES", "")
assert PdfFillBlock._tesseract_command("/tmp/page.png") == ["tesseract", "/tmp/page.png", "stdout", "tsv"]
def test_tesseract_language_helpers_share_package_defaults() -> None:
language_packs = ("eng", "spa", "chi-sim", "chi-tra")
assert tesseract_ocr_packages(language_packs) == [
"tesseract-ocr-eng",
"tesseract-ocr-spa",
"tesseract-ocr-chi-sim",
"tesseract-ocr-chi-tra",
]
assert tesseract_language_arg(language_packs) == "eng+spa+chi_sim+chi_tra"
def test_escape_pdf_text() -> None:
assert PdfFillBlock._escape_pdf_text("A (NM) \\ B\nC") == r"A \(NM\) \\ B C"