Skip to main content

docpipe-custom-operator

Create, load, package, or modify an external custom operator for docpipe. Use when a consumer needs an AbstractOperator supplied through DOCPIPE_CUSTOM_OPERATORS, DocpipeFlowManager, package entry points, S3, or register_operator_provider without modifying the built-in DOCPIPE_OPERATORS registry. Do not use for built-in operators or ingest source connectors.

Source facts

Repository
docling-project/docling-pipelines
Last source activity
August 31, 2026 at 08:05
Detected SKILL.md language
English
Stars
47
Forks
39

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
2 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
docpipe-custom-operator
description
Create, load, package, or modify an external custom operator for docpipe. Use when a consumer needs an AbstractOperator supplied through DOCPIPE_CUSTOM_OPERATORS, DocpipeFlowManager, package entry points, S3, or register_operator_provider without modifying the built-in DOCPIPE_OPERATORS registry. Do not use for built-in operators or ingest source connectors.
# Docpipe Custom Operator Deliver a consumer-owned operator that satisfies docpipe’s PyArrow and metadata contracts, is discoverable through the selected custom loading mechanism, and runs in a valid flow without modifying the built-in registry. ## Scope and placement A custom operator is external to `src/docpipe` and must set: ```python owner: str | None = DocpipeConstants.OWNER_CUSTOM ``` Custom discovery rejects an operator that claims `OWNER_DOCPIPE`. Do not import the class into `src/docpipe/core/operators/operator_registry.py` and do not add it to `DOCPIPE_OPERATORS`. Choose a consumer-owned location. When adding a maintained example to this repository, use `examples/custom_operators/`; otherwise use the user’s external project or requested path. Read these sources before implementing: - `src/docpipe/core/operators/abstract_operator.py` for the runtime contract; - `src/docpipe/core/orchestration/operator_loader/validator.py` for discovery validation; - `examples/custom_operators/hello_operator.py` for a small current example; - `docs/guides/CUSTOM_OPERATORS_GUIDE.md` for filesystem, package, and S3 loading; - `docs/guides/EXTERNAL_OPERATOR_INTEGRATION.md` only for host provider hooks and owner-priority behavior. Read [references/examples.md](references/examples.md) and select the example matching the requested loading mode and operator shape. Do not load package or S3 examples for a simple local-file operator. ## Choose the loading mode Use the smallest mechanism that fits distribution needs. ### Local file or directory Use during development. `DOCPIPE_CUSTOM_OPERATORS` accepts comma-separated source paths: ```bash export DOCPIPE_CUSTOM_OPERATORS="/absolute/path/operator.py,/absolute/path/operators" ``` The filesystem loader scans Python modules and validates discovered classes. Prefer one operator class per module so discovery is deterministic. ### Installed package Use for reuse across applications. Put operators in an importable package, expose a stable module, and optionally publish `docpipe.operators` entry points. Verify both package installation metadata and module import; the package adapter uses both entry points and module inspection. ### Python library registration Call before execution: ```python manager.register_custom_operators(package_names=["my_company.operators"]) ``` ### Host application provider Use `register_operator_provider()` when a host application owns a set of operator classes and wires them at startup. Return a `frozenset` and optionally filter it by orchestrator. Register owner priority only when the host intentionally needs conflict resolution beyond the reserved custom and docpipe tiers. ### S3 source Use only for an existing remote-operator deployment workflow. Keep credentials outside source paths, use the supported S3 loader configuration, and do not download or execute untrusted operator code. ## Required class contract Implement a class that: - inherits `AbstractOperator`; - defines non-empty `short_name` and a valid `OperatorCategory`; - sets `owner = DocpipeConstants.OWNER_CUSTOM`; - calls `super().__init__(config)` when overriding `__init__`; - defines `transform()` directly on the class; - accepts a `pa.Table` and returns `tuple[list[pa.Table], dict]`; - implements `get_metadata()` as `@staticmethod` for its public parameters and output features; - declares upstream columns with `get_required_features()`; - uses `create_base_metadata()` and inherited failure/skip recording; - keeps persistence, orchestration, and concrete infrastructure adapters outside transform logic. Use keyword-only parameters whenever a function has two or more parameters excluding `self` or `cls`. Do not copy the legacy optional `file_name` argument from old examples unless a real caller requires it; make it keyword-only when required. ## Transform semantics Define before coding: - required columns and accepted Arrow types; - output columns, types, and whether existing columns are replaced; - row ordering, document identifier, and branch behavior; - empty-table, missing-column, null-value, and row-failure behavior; - processed, skipped, and failed metadata accounting. For valid zero-row input, return an empty output with a stable schema unless the operator’s documented contract requires failure. Never use `if table:` on `pa.Table`. Use docpipe ports such as `LLMInferencePort` or `LLMEmbeddingPort` for external behavior. Inject or construct dependencies at an appropriate boundary; do not make transform logic import a concrete built-in adapter. ## Metadata and validation Keep the runtime configuration and `get_metadata()` synchronized. - Put user inputs under `OperatorConstants.Config.ATTRIBUTES` with label, description, type, required flag, default, and bounds or valid values. - Put produced columns under `OperatorConstants.Config.FEATURES` with stable names and types. - Use `AttributeDataTypes` and existing constants instead of free-form strings when available. - Implement `get_static_required_features()` when discovery needs requirements without instance configuration. - Override `validate(errors, warnings, available_features)` for cross-field and upstream-feature validation. - Ensure the class `short_name`, flow `type`, metadata, tests, and docs agree. ## Logging and safety - Use `get_logger()` from docpipe infrastructure. - Use lazy `%s` parameters in `logger.*()` calls; no f-strings. - Do not log secrets, full document content, or non-ASCII/emoji text. - Preserve actionable document identifiers and job context in errors. - Treat imported custom code as executable code; do not load an untrusted path or package merely to inspect it. ## Verify discovery before flow execution For a local source: ```bash source .venv/bin/activate DOCPIPE_CUSTOM_OPERATORS=/absolute/path/to/operator.py \ docling-pipelines --list-operators --verbose ``` Confirm: - the expected `short_name` is present; - owner is `custom`; - category, availability, attributes, and features are correct; - no duplicate short name exists in the same custom load batch; - an intended override wins according to owner priority. An importable module that is absent from `--list-operators` is not complete. ## Automated verification Keep tests with the consumer project. For an example maintained here, place focused tests under an appropriate `tests/unit/` custom-loader or example area without changing global registries during teardown. Cover at least: - happy-path output schema and exact values; - typed zero-row input; - invalid and conflicting config; - missing and null required columns; - row-level or dependency failures and metadata accounting; - discovery validation, owner, and duplicate `short_name` behavior; - package/provider registration when that loading mode is used. Mock external services and use `patch.object` or injected dependencies. Reuse `tests/fixtures/custom_operators/` for loader behavior when appropriate. ## Runnable flow validation Create a temporary flow with exactly one `ingest_source` root, the minimum upstream operators required by the custom operator, and the custom `short_name` as its `type`. Use `force_ingest: true`, keep validation enabled, and set micro-batching explicitly for deterministic behavior. Validate and run with the same custom source configuration: ```bash source .venv/bin/activate DOCPIPE_CUSTOM_OPERATORS=/absolute/path/to/operator.py \ docling-pipelines --flow-file /path/to/flow.json --validate DOCPIPE_CUSTOM_OPERATORS=/absolute/path/to/operator.py \ docling-pipelines --flow-file /path/to/flow.json ``` Use `.skills/feature-testing-workflow/SKILL.md` to inspect job statistics and actual Parquet values without depending on pytest. Verify feature values, input/output document parity, and failure/skip counts rather than relying only on process exit status. ## Packaging and documentation For a distributable package: - provide normal package metadata and a stable import path; - declare compatible docpipe/Python versions; - declare only required dependencies; - add `docpipe.operators` entry points when package discovery needs them; - document the selected loading mechanism and required environment variables; - verify behavior in a clean environment where the package is installed rather than imported from the working tree. For a repository example, update `examples/custom_operators/README.md`, add or update one focused example flow, and add the change under `## [Unreleased]` in `CHANGELOG.md`. Do not add an external operator to built-in operator reference pages as though it ships with docpipe. Finish by reporting the source location, loading mode, discovery evidence, flow-validation result, runtime feature evidence, and any unverified external dependency.
View on GitHub