AI (artificial intelligence) Learning · 04 / 08

DAG (Directed Acyclic Graph) Orchestration

The invoice pipeline as a directed acyclic graph: its steps, the models and humans in it, retry and timeout policies, and how it behaves when things fail.

Why a DAG

The pipeline is a directed acyclic graph: every step runs once per invoice, depends only on earlier steps, and the branch taken depends on the data. The orchestrator gives each step a retry policy, a timeout and a run history, and it stops a failed step from hiding inside a prompt. See ADR-001.

The graph

Invoice pipeline as a directed acyclic graph Seven pipeline steps run left to right: ingest, normalize, classify, extract, validate, enrich and match. A tolerance check sends clean matches to approval routing and exceptions to an agent and then a human. Approved invoices are posted to the ERP, scheduled for payment and archived. Ingest email · upload Normalize hash · dedupe Classify invoice? (LLM) Extract fields + schema Validate schema · totals Enrich vendor · bank Match 3-way PO · receipt Tolerance? rules engine Agent RAG + MCP tools no Human review approve · reject Approval route amount bands yes Post to ERP idempotent write Schedule payment run Archive audit evidence Non-invoice documents route to a review queue after Classify. deterministic step LLM step agent with tools human decision
The DAG fixes the order and the retries. Only the two shaded LLM (Large Language Model) steps and the agent use a model.

Two steps use a model for extraction. Classification uses a smaller model. The exception agent and the human reviewer are the only steps that handle non-routine cases.

Node specification

NodeKindInputOutputRetryTimeout
Ingestdeterministicemail connector or portal uploadraw document + metadata3 × exponential2 min
Normalizedeterministicraw documentpages, text, SHA-256 (Secure Hash Algorithm, 256-bit)3 ×2 min
ClassifyLLM (small)page textdocument_type + confidence2 ×30 s
ExtractLLM (frontier)pages + invoice.v3 schemavalidated JSON (JavaScript Object Notation), per-field confidence2 ×3 min
Validatedeterministicextracted JSONpass / fail with reasonsnone10 s
Enrichtool (MCP)vendor_idvendor status, bank, terms3 ×30 s
Match 3-waydeterministicinvoice, PO (Purchase Order), GRN (Goods Receipt Note) via MCP (Model Context Protocol)match table, tolerance result3 ×1 min
Resolve exceptionagent (MCP + RAG (Retrieval-Augmented Generation))match table, clausesresolution draft with citations1 ×10 min
Human reviewhumanresolution draftapprove, reject or querynone (SLA (Service-Level Agreement) 1 business day)—
Approval routedeterministicamount, cost centre, exception typeapprover rolenone10 s
Post to ERP (Enterprise Resource Planning)tool (MCP)approved invoiceERP document ID (identifier)3 × with idempotency key2 min
Schedule paymenttool (MCP)ERP document ID, due datepayment run entry3 ×1 min
Archive auditdeterministicall outputs and traceimmutable audit record5 ×2 min

Retry counts are attempts including the first. Timeouts are per attempt. Human review has a service-level target rather than a timeout.

The definition

The DAG is code, so it is versioned and reviewed like any other change. The sketch below is illustrative: the same structure maps onto Airflow, Dagster, Prefect or Temporal, and the graph and retry policies would stay the same.

pipeline/ap_invoice.py · illustrative
@dag(id="ap_invoice", schedule="on_event:invoice.received", max_active_runs=50)
def ap_invoice():
    doc      = ingest()
    pages    = normalize(doc)
    kind     = classify(pages)            # LLM, small tier
    if kind.document_type != "invoice":
        route_to_review_queue(doc, kind)
        return

    fields   = extract(pages)             # LLM, frontier tier; schema-validated
    checked  = validate(fields)
    vendor   = enrich(checked)            # MCP: vendor.lookup
    match    = match_three_way(checked)   # MCP: po.get, grn.search; rules only

    if match.within_tolerance:
        decision = approval_route(checked, match)
    else:
        draft    = resolve_exception(checked, match)   # agent, MCP tools, RAG
        decision = human_review(draft)                 # blocks until a person decides
        decision = approval_route(checked, match, decision)

    if decision.approved:
        erp_doc = post_to_erp(checked, decision, idempotency_key=doc.sha256)  # MCP: invoice.post
        schedule_payment(erp_doc)                                              # MCP: payment.schedule
    archive_audit(doc, fields, match, decision)

Behaviour under failure

Build this for your business

This example uses a generic invoicing system, but the same pattern — orchestrated steps, governed tools, grounded knowledge and a documented lifecycle — fits most back-office processes. We can scope a pilot on your own documents and systems.

Book a discovery call