Configuring workflows
A workflow on a search store is a named instance of a workflow class, usually the DocumentAI preset. It is configured by two JSON documents:
- Client setup
- Inspecting what exists
- The parameter model
- Recipe: text-first ingestion
- Recipe: vision (OCR) ingestion
- Recipe: per-page routing
- Recipe: figure descriptions (
figure_extractor+image_describer) - Inference providers
- Sampling temperature
- Choosing a chunker
- Embeddings (
vectorize) - Enrichments (
page_enrichments,chunk_enrichments) - Cleaning page headers and footers
- The input schema
- Updating a workflow
- Running and watching
- Related pages
-
parameters— overrides for the workflow’s module configuration. The workflow class defines defaults for every module. Specify only the fields that you want to change. -
schema— the JSON schema of the run input. It is currently required at creation and is planned for deprecation. See The input schema.
This page describes the parameters that DocumentAI accepts and how to manage workflows. All examples on this page were tested against a live deployment.
Client setup
Each example below is shown for three clients. All three call the same REST API at https://sherlock.{ingressDomain}/api/v1; the SDK and the CLI wrap the requests shown in the curl tab. Every request requires a bearer token. The deployment’s IAM sidecar validates the token and adds the caller’s user identity to the request, and Sherlock authorizes the request against that identity.
-
curl
-
Python SDK
-
Platform CLI
Log in once with the platform CLI, then export the deployment URL and a token:
pharia --env aleph-alpha iam login # one-time OAuth login via your browser
export SHERLOCK_URL="https://sherlock.{ingressDomain}"
export AA_TOKEN="$(pharia --env aleph-alpha iam token)"
Pass -H "Authorization: Bearer $AA_TOKEN" on every call. A platform token from any other source also works; the API only reads the header. Requests with a JSON body use --json (curl 7.82.0 or later), which sends the body and sets the Content-Type and Accept headers to application/json. To read the body from a file, use --json @body.json.
Install the client from the Aleph Alpha package index:
pip install sherlock-client --extra-index-url https://alephalpha.jfrog.io/artifactory/api/pypi/holmes/simple
Then create a client for the deployment and a handle for the store whose workflows you are configuring:
import os
from sherlock import SyncSherlock
client = SyncSherlock(
url="https://sherlock.{ingressDomain}",
token=os.environ["AA_TOKEN"],
)
store = client.v1.search_stores("<store-id>")
url and token default to the SHERLOCK_BASE_URL and SHERLOCK_TOKEN environment variables, so SyncSherlock() without arguments works when those variables are set. Sherlock is the async client and has the same API. Use either client as a context manager (with SyncSherlock(...) as client:) to close the connection pool when you finish.
The pharia CLI has a sherlock subcommand that covers the full workflow lifecycle. Log in once; every subsequent command uses the cached token:
pharia --env aleph-alpha iam login
JSON flags accept inline JSON, @file, or @- for stdin. Select the environment with the global --env flag. To reach a different tenant, pass --base-url or set PHARIA_SHERLOCK_URL.
Inspecting what exists
List your stores, the workflows on a store, and the current configuration of a single workflow:
-
curl
-
Python SDK
-
Platform CLI
curl "$SHERLOCK_URL/api/v1/search-stores" \
-H "Authorization: Bearer $AA_TOKEN"
curl "$SHERLOCK_URL/api/v1/search-stores/<store-id>/workflows" \
-H "Authorization: Bearer $AA_TOKEN"
curl "$SHERLOCK_URL/api/v1/search-stores/<store-id>/workflows/<workflow-id-or-name>" \
-H "Authorization: Bearer $AA_TOKEN"
for s in client.v1.search_stores.list():
print(s.id, s.name)
for workflow in store.workflows.list():
print(workflow.id, workflow.name, workflow.workflow_name)
current = store.workflows("<workflow-id-or-name>").get()
print(current.parameters)
pharia --env aleph-alpha sherlock stores list
pharia --env aleph-alpha sherlock workflows list <store-id>
pharia --env aleph-alpha sherlock workflows get <store-id> <workflow-id-or-name>
To read a single workflow, pass either its ID or its name.
The parameter model
DocumentAI consists of modules. Each top-level key in parameters configures one module:
| Group | Purpose |
|---|---|
|
Reader for PDF files. |
|
Readers for Word ( |
|
One reader per file type. The workflow routes each document to the matching reader. |
|
Uses a vision model to annotate rasterized PDF pages. Required when |
|
Optional modules that extract figures from PDF files and describe them with a vision model. |
|
Splits pages into chunks. One of |
|
Optional lists of enrichment modules that add metadata to pages or chunks, for example keywords or generated questions. |
|
Embeds chunks and writes vectors to the vector store. |
Groups with more than one implementation (pdf_reader, docx_reader, xlsx_reader, chunker) select the implementation with a "kind" field. In addition to its own parameters, every module accepts the execution fields batch_size, max_in_flight, and timeout.
Recipe: text-first ingestion
This is the standard configuration for digitally created documents. It extracts the embedded text, chunks it recursively, and embeds the chunks through the platform’s inference gateway.
{
"pdf_reader": {"kind": "pdf_text"},
"chunker": {"kind": "recursive", "chunk_size": 4096, "chunk_overlap": 600},
"vectorize": {"provider": "pharia"}
}
Create a workflow with these parameters and the standard run-input schema (see The input schema):
-
curl
-
Python SDK
-
Platform CLI
curl "$SHERLOCK_URL/api/v1/search-stores/<store-id>/workflows" \
-H "Authorization: Bearer $AA_TOKEN" \
--json '{
"name": "text-ingestion",
"workflow_name": "DocumentAI",
"parameters": {
"pdf_reader": {"kind": "pdf_text"},
"chunker": {"kind": "recursive", "chunk_size": 4096, "chunk_overlap": 600},
"vectorize": {"provider": "pharia"}
},
"schema": {
"type": "object",
"additionalProperties": true,
"properties": {
"source": {
"anyOf": [{"type": "string"}, {"type": "array", "items": {"type": "string"}}],
"description": "Source path, connector URI, or list thereof"
}
}
}
}'
workflow = store.workflows.create(
name="text-ingestion",
workflow_name="DocumentAI",
parameters={
"pdf_reader": {"kind": "pdf_text"},
"chunker": {"kind": "recursive", "chunk_size": 4096, "chunk_overlap": 600},
"vectorize": {"provider": "pharia"},
},
schema={
"type": "object",
"additionalProperties": True,
"properties": {
"source": {
"anyOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}},
],
"description": "Source path, connector URI, or list thereof",
}
},
},
)
print(workflow.id)
pharia --env aleph-alpha sherlock workflows create <store-id> \
--name text-ingestion \
--workflow-name DocumentAI \
--parameters @parameters.json \
--schema @input-schema.json
Recipe: vision (OCR) ingestion
For scanned documents or complex layouts, rasterize each PDF page and annotate it with a vision model. This pipeline uses two modules. pdf_rasterizer renders each page to an image. vision_reader converts each image into one structured page, in a separate stage that processes pages in parallel. pdf_rasterizer does not extract text, so a vision_reader with an explicit provider and model is required; there is no default endpoint. A run without a vision_reader, or with one that lacks provider or model, fails immediately and is not retried.
{
"pdf_reader": {"kind": "pdf_rasterizer", "pdf_backend": "pdfboss"},
"vision_reader": {"provider": "pharia_next", "model": "rednote-hilab/dots.mocr", "timeout": 120},
"chunker": {"kind": "recursive", "chunk_size": 4096, "chunk_overlap": 600}
}
Other pdf_rasterizer fields: pdf_backend (pdf_oxide default, or pdfboss), dpi (default 150), and image_format (default png).
Other vision_reader fields: max_tokens, temperature (see Sampling temperature; use 0 for reproducible OCR), ignore_headers_footers (default true), prompt, and extra_body. prompt replaces the built-in layout-extraction prompt. By default, dots-OCR models receive a dots-format prompt and other models receive a normalized-coordinate prompt. extra_body contains vendor-specific payload fields; for example, {"chat_template_kwargs": {"thinking": false}} disables a reasoning model’s thinking mode. The chunker processes the annotated pages the same way as text-extracted pages.
Recipe: per-page routing
Many PDFs mix digitally created pages with scanned pages, screenshots, or pages whose fonts do not map to readable characters. pdf_text returns empty or garbled text for those pages, and pdf_rasterizer sends every page to the vision model, including the pages that pdf_text reads correctly. smart_pdf_router decides per page. Pages with a usable text layer are extracted as text. Only the other pages are rendered and annotated by vision_reader. A mostly digital document therefore costs close to pdf_text, and its scanned pages are still read.
{
"pdf_reader": {"kind": "smart_pdf_router"},
"vision_reader": {"provider": "pharia_next", "model": "rednote-hilab/dots.mocr", "temperature": 0, "timeout": 120},
"chunker": {"kind": "recursive", "chunk_size": 4096, "chunk_overlap": 600}
}
The router reads the structure of each page (fonts, images, and annotations) without rendering it. A page goes to the vision model when one of these rules applies:
-
The page draws images and no fonts, for example a scan.
-
The page draws images, and its text layer has fewer than
min_text_charsnon-whitespace characters (default80). An example is a screenshot whose text layer contains only a caption or a page number. -
More than
max_unmapped_shareof the page’s non-whitespace text (default0.02) is unmapped glyph codes. These are runs of two or more private-use characters or U+FFFD, which fonts without a character map produce. A single symbol, such as a bullet, does not count. -
More than
max_rtl_shareof the page’s non-whitespace text is Arabic or Hebrew letters. The default1disables this rule, because the text layer returns right-to-left text in reading order. Set a lower value only if a document’s right-to-left text extracts in the wrong order.
All other pages keep their text layer. A page that draws neither fonts nor images becomes an empty page and is not rendered.
vision_reader is required, as in Recipe: vision (OCR) ingestion. A run without it fails before it processes any page and is not retried, even if no page of the document would go to the vision model.
Other smart_pdf_router fields:
-
rasterizer— the rendering settings for the routed pages, with the same fields aspdf_rasterizer, for example{"dpi": 200, "image_format": "png"}. -
pdf_backend— the backend for the structure probe and text extraction. Onlypdfboss(the default) supports the probe. -
pages_per_part(default10) — the number of pages that one work item processes. -
max_in_flight(default4) — the number of text extractions that run at the same time.
pdf_router is the basic version of the router. It sends a page to the vision model only when the page draws images and has no extractable text. smart_pdf_router with min_text_chars: 1 and max_unmapped_share: 1 behaves the same way.
Each page records its routing decision in its metadata under pdf_router: route (text or raster), the fonts, images, and annotations counts, and, for each share rule whose limit is below 1, the page’s measured share (unmapped_share, rtl_share). Use these values to check why a page went to the vision model.
|
Version availability
|
Recipe: figure descriptions (figure_extractor + image_describer)
Text extraction and OCR do not produce searchable text for the content of charts, diagrams, and photos. The figure pipeline generates text descriptions for them in two stages. A figure_extractor produces one image crop per figure. image_describer sends each crop to a vision model and stores the returned description.
|
Not yet active
Current releases accept these parameter groups but do not run the figure-extraction stage yet, so the pipeline produces no descriptions. The parameters below are stable and are documented ahead of activation. |
{
"pdf_reader": {"kind": "pdf_rasterizer", "dpi": 200},
"vision_reader": {"provider": "pharia_next", "model": "rednote-hilab/dots.mocr"},
"figure_extractor": {"kind": "bbox_figure_extractor", "dpi": 200},
"image_describer": {
"provider": "pharia_next",
"model": "rednote-hilab/dots.mocr",
"max_tokens": 4096,
"temperature": 0,
"extra_body": {"chat_template_kwargs": {"thinking": false}}
}
}
Two extractors implement figure_extractor. Select one with kind:
| Kind | Strategy | Pair with |
|---|---|---|
|
Extracts the images that are embedded in the PDF file. |
|
|
Renders each page again and crops it at the figure bounding boxes that the vision reader detected. Its |
|
image_describer fields: provider (default pharia_next) and model (default moonshotai/kimi-k2.6) identify the vision deployment. prompt replaces the built-in description prompt. max_tokens (default 2048), temperature, and extra_body configure the model call. max_in_flight (default 4) limits the number of concurrent describe calls.
The platform stores descriptions append-only, keyed by page, image, model, and prompt. A re-run with the same model and prompt has no effect. A different model or prompt adds new descriptions and does not change existing ones.
Inference providers
Modules that call models (vision_reader, vectorize, image_describer, …) select their client by name. The provider parameter is a lookup key. The endpoint, credentials, and retry policy for each provider are defined in the server-side deployment configuration. Workflow parameters never contain secrets or URLs.
The providers below are available only when the data platform runs on the Aleph Alpha platform. On-premise installations use different providers.
| Provider | Route |
|---|---|
|
The platform inference gateway (default). Routes to the legacy backend. |
|
The same gateway, fixed to the next-generation inference backend. This is the planned long-term route. |
|
A direct connection to the next-generation backend that bypasses the gateway. A temporary route until service-account authentication is available. |
|
External OpenAI-compatible endpoints, when the deployment configures them. |
You can select a provider only if the deployment configures it. If you select an unconfigured provider, the run fails.
Sampling temperature
Every module that calls a chat or vision model accepts a temperature field (0.0--2.0):
-
vision_readerandimage_reader— page annotation and image-file reading -
image_describer— figure descriptions -
pdf_readerwithkind: "parsy_ocr"— OCR extraction -
page_enrichments/chunk_enrichmentsentries that call an LLM, such aspage_questions
Embeddings (vectorize) and non-LLM modules (chunkers, keyword enrichment) have no temperature.
If you do not set temperature (the default), the request omits the field and the model server’s default applies. 0 is treated as a value and sent to the model; it is not treated as unset. Use 0 for reproducible extraction, which is the usual setting for OCR and structured layout parsing:
{
"vision_reader": {"provider": "pharia_next", "model": "rednote-hilab/dots.mocr", "temperature": 0}
}
|
Version availability
|
Choosing a chunker
| Kind | Strategy | Fields (defaults) |
|---|---|---|
|
Fixed-size word groups with no overlap. Fast, but it can break sentences. |
|
|
Character-based with overlap. It prefers sentence boundaries. |
|
|
Recursive separator hierarchy: paragraphs before line breaks before spaces and punctuation. |
|
|
Splits at top-level markdown blocks. It never splits fenced code, and it prefixes each chunk with its heading breadcrumb. |
|
Smaller chunks give more precise matches but contain less surrounding context. Overlap preserves context across chunk boundaries.
Embeddings (vectorize)
The vectorize module calls an OpenAI-compatible embeddings endpoint through a named provider (default pharia; see Inference providers).
The search store defines the vector schema. If you do not set model and dimension, the module takes them from the store’s models entry, so {"provider": "pharia"} is usually sufficient. If you set them, they must match a vector configured on the store. If they do not match, the run fails instead of writing vectors that search cannot find.
Enrichments (page_enrichments, chunk_enrichments)
Both groups take a list of enrichment modules, selected by kind. Enrichments add metadata rows for pages or chunks. They do not change the page and chunk content.
| Kind | What it does | Fields (defaults) |
|---|---|---|
|
Extracts the top keyword lemmas per item with spaCy. No model call. |
|
|
Asks an LLM for questions that each page can answer. Useful for HyDE-style retrieval and Q&A dataset generation. Page-level only. |
|
{
"page_enrichments": [
{"kind": "keyword", "language": "de"},
{"kind": "page_questions", "num_questions": 3, "temperature": 0.7}
]
}
Removing repeated page headers and footers is not an enrichment. vision_reader does it at read time; see Cleaning page headers and footers.
Cleaning page headers and footers
Repeated headers and footers, such as running titles and page numbers, add irrelevant text to chunks and search hits. vision_reader removes them: with ignore_headers_footers (default true), it skips the header and footer regions that the layout model classifies; see Recipe: vision (OCR) ingestion.
Affected pages record the number of dropped lines as metadata.headers_footers_removed.
The input schema
To create a workflow, you currently must send schema: the JSON schema of the input that each run accepts.
|
Deprecation ahead
The |
For DocumentAI, the standard schema declares the source property:
{
"type": "object",
"additionalProperties": true,
"properties": {
"source": {
"anyOf": [{"type": "string"}, {"type": "array", "items": {"type": "string"}}],
"description": "Source path, connector URI, or list thereof"
}
}
}
Updating a workflow
-
curl
-
Python SDK
-
Platform CLI
curl -X PUT "$SHERLOCK_URL/api/v1/search-stores/<store-id>/workflows/<workflow-id>" \
-H "Authorization: Bearer $AA_TOKEN" \
--json '{
"parameters": {
"pdf_reader": {"kind": "pdf_rasterizer", "pdf_backend": "pdfboss"},
"vision_reader": {"provider": "pharia_next", "model": "rednote-hilab/dots.mocr", "timeout": 120},
"chunker": {"kind": "recursive", "chunk_size": 4096, "chunk_overlap": 600}
}
}'
This body switches the workflow created above to the vision configuration. It contains the whole parameters document, with pdf_reader changed and a vision_reader group added.
store.workflows("<workflow-id>").update(
parameters={
"pdf_reader": {"kind": "pdf_rasterizer", "pdf_backend": "pdfboss"},
"vision_reader": {
"provider": "pharia_next",
"model": "rednote-hilab/dots.mocr",
"timeout": 120,
},
"chunker": {"kind": "recursive", "chunk_size": 4096, "chunk_overlap": 600},
},
)
pharia --env aleph-alpha sherlock workflows update <store-id> <workflow-id> \
--parameters @parameters.json
|
Updates replace, not merge
|
The change applies only to future runs. The platform does not automatically reprocess content that is already indexed.
Running and watching
To start a run, specify a source (file IDs from Ingesting files). Then stream its progress:
-
curl
-
Python SDK
-
Platform CLI
curl "$SHERLOCK_URL/api/v1/search-stores/<store-id>/workflows/<workflow-id>/runs" \
-H "Authorization: Bearer $AA_TOKEN" \
--json '{"input": {"source": ["files://<file-id>"]}}'
curl -N "$SHERLOCK_URL/api/v1/search-stores/<store-id>/workflows/<workflow-id>/runs/<run-id>/event-stream" \
-H "Accept: text/event-stream" \
-H "Authorization: Bearer $AA_TOKEN"
curl "$SHERLOCK_URL/api/v1/search-stores/<store-id>/workflows/<workflow-id>/runs" \
-H "Authorization: Bearer $AA_TOKEN"
runs = store.workflows("<workflow-id>").runs
run = runs.execute(input={"source": ["files://<file-id>"]})
for event in run.stream():
print(event.event, event.data)
for past in runs.list():
print(past.id, past.status)
pharia --env aleph-alpha sherlock runs execute <store-id> <workflow-id> \
--input '{"source": ["files://<file-id>"]}'
pharia --env aleph-alpha sherlock runs watch <store-id> <workflow-id> <run-id>
pharia --env aleph-alpha sherlock runs list <store-id> <workflow-id>
The event stream sends server-sent events until the run completes or fails. Deleting a workflow also deletes all its runs and their derived data.
Related pages
-
Workflows and presets — the built-in presets and how to select one.
-
For workflow authors — how to write workflow classes and modules in code.