Develop Custom Strategies for Document AI
The built-in strategies cover common cases: passage_level for retrieval and document_level for metadata cataloging. Write your own strategy when a use case needs a different pipeline: a custom chunking scheme, a specialized extraction pass, or per-page reasoning. A custom strategy is a single Python function that you pass inline as the script field on a /process request. It runs on the existing service immediately, with no registration step and no redeployment required.
This guide is for data scientists and developers who author processing logic. If you only need to call an existing strategy, see Document AI REST API Developer Guide.
Your script runs inside the shared Document AI service, in the same processing environment as every other run on the cluster. Submit custom strategies only on a single-tenant cluster, and submit only code you trust.
The strategy contract
A strategy is one Python function with a fixed signature:
def process_document(document, settings):
...
return Artifact(document=document, chunks=chunks, errors=errors)document: The input file. It carries the raw content, and after preparation, the rendered page images.settings: The parsed ingestion settings for this run, giving you the same knobs the built-in strategies read. The recognized settings blocks areprepare,parser,chunker,document_extractor,passage_extractor,embedder, andhierarchy, pluscustom. Thecustomblock is a free-form bucket for strategy-owned knobs the framework has no type for. It's delivered to your function verbatim assettings.custom, with its keys and casing preserved rather than normalized tosnake_case. Equivalents written likedocumentExtractorare also accepted, and a key outside this set is silently ignored rather than rejected.- Return value: An
Artifactthat references the document and carries the produced chunks and any non-fatal errors.
Your function composes the same building blocks the built-in strategies use. The Document AI service handles everything else: file download and conversion, batching, retries, scaling, logging, and persistence of the returned artifact.
Building blocks
The following functions are available to your script. You don't import them; they're provided in your script's namespace.
| Block | Purpose |
|---|---|
prepare_document(document, settings) | Validates the file, converts office formats to PDF, and renders page images. Call it first. |
parse_document(document, settings) | Reads each page with a vision language model and returns text, table, and image components in reading order. |
chunk(components, settings) | Merges parsed components into token-budgeted chunks with configurable overlap. |
extract_from_document(document, settings) | Extracts document-level tags and entities; populates the document's extraction result. |
extract_from_chunks(chunks, settings) | Extracts passage-level tags and entities; populates each chunk's extraction result. |
embed(chunks, settings) | Produces an embedding for each chunk's embed text. |
detect_hierarchy(document, components, chunks, settings) | Detects the document's section hierarchy from the parsed components and enriches each chunk with its section context. |
completion(messages, options) | A direct language model call with chat-style messages, for custom reasoning steps. Pass a JSON response format in options so the reply shape stays predictable. |
The settings argument is optional on every block; omit it to use the block's defaults.
Output contract
Persistence reads specific fields from the artifact you return; anything else is dropped at the boundary:
- A chunk is persisted as a
Genai.Vector.SourcePassageonly if itsembeddingis set, so pass your chunks throughembedbefore returning them. - Tags and entities reach the target application only through the extraction results on the document and on each chunk. Populate those results using the extract blocks, or write to the same result types yourself.
- Append non-fatal problems to the artifact's
errorslist: the run still completes, and the list gives callers a single place to see what degraded.
Write the strategy
The following example is a working strategy that matches the built-in passage_level pipeline. It prepares the file, parses it, chunks the text, enriches each chunk with its section context, embeds the chunks, and runs document-level extraction. Start from this shape and change the steps you need:
def process_document(document, settings):
document = prepare_document(document, settings.prepare)
parsed = parse_document(document, settings.parser)
chunks = chunk(parsed.components, settings.chunker)
chunks = extract_from_chunks(chunks, settings.passage_extractor)
chunks = detect_hierarchy(document, parsed.components, chunks, settings.hierarchy)
chunks = embed(chunks, settings.embedder)
document = extract_from_document(document, settings.document_extractor)
return Artifact(
document=document,
chunks=chunks,
errors=parsed.errors,
)Two constraints apply while authoring:
- Imports: your script sees the building blocks listed in this guide, the shared data classes they exchange, and the Python standard library. Anything beyond that must already be pinned in the service's image. Installing additional packages isn't supported, and there's no separate model client to import: model access goes through the
completionblock. - Configuration: behavior a caller should vary per request belongs in the known settings blocks: parsing, chunking, extraction, and embedding. Behavior that's fixed for the strategy stays as a literal in your code.
Submit the strategy
Pass the function source as the script field on /process instead of strategyName; the two are mutually exclusive:
curl -X POST "<docai-base-url>/process" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d @process-request.json{
"source": { "files": ["azure://<container>/<path>/contract.pdf"] },
"script": "<your Python source>",
"ingestionSettings": "{ \"chunker\": { \"maxTokens\": 512 } }",
"sourceApp": "<target-app-id>"
}| Field | Required | Description |
|---|---|---|
script | Yes | Python source as plain text; don't encode it. Must define a top-level process_document(document, settings). |
ingestionSettings | No | Settings passed to your strategy as settings; see The strategy contract. |
Submission checks that the script defines a top-level process_document that accepts two positional parameters; a script that fails this check is rejected before a job is created. Behavior isn't validated at submission: a logic error inside the function only surfaces when a run executes.
Because the script ships inline with each request, your application is the registry: version and store the source wherever you keep your application code. There's no separate registration step, and nothing persists in Document AI between requests.
Test and iterate
The test loop is write, run, read:
- Submit one small test document with your
scriptset, instead ofstrategyName. - Read the run with the
/runs/<runId>endpoint. A script that fails to load or raises an error reports it on the run; a successful run returns its results for inspection. - Edit the script and resubmit. Because each run carries its own copy of the script, editing it never affects a run that's already in flight.
GET /strategies/list lists only the built-in strategies (passage_level and document_level); your custom strategy isn't registered, so it never appears there.