How to Build a document extraction Agent Using CrewAI in Python for payments

By Cyprian AaronsUpdated 2026-04-21
document-extractioncrewaipythonpayments

A document extraction agent for payments reads invoices, remittance advice, bank statements, SWIFT messages, or payment instructions and turns them into structured fields your system can trust. That matters because payment ops lives and dies on accuracy: one bad account number, invoice total, or beneficiary name can trigger delays, failed settlements, compliance issues, or fraud reviews.

Architecture

  • Document intake layer

    • Accepts PDFs, images, email attachments, or OCR text from your ingestion pipeline.
    • Normalizes file metadata like source system, customer ID, jurisdiction, and retention policy.
  • Extraction agent

    • Uses CrewAI Agent to interpret the document and extract payment-relevant fields.
    • Focuses on structured output: invoice number, amount, currency, due date, payer/payee names, IBAN/SWIFT, reference IDs.
  • Validation toolset

    • Applies deterministic checks before anything enters downstream payment workflows.
    • Verifies totals, date formats, currency codes, checksum rules for IBANs where applicable.
  • Review workflow

    • Routes low-confidence or policy-sensitive documents to a human reviewer.
    • Keeps an audit trail of model output, tool calls, and final approved values.
  • Persistence and audit store

    • Saves raw document references plus extracted JSON and validation results.
    • Supports compliance needs like traceability, retention controls, and data residency boundaries.

Implementation

1) Define the extraction schema and the agent

Use a strict output shape. For payments, loose free-text answers are a bad idea because you need predictable downstream parsing.

from pydantic import BaseModel
from crewai import Agent

class PaymentDocumentFields(BaseModel):
    document_type: str
    invoice_number: str | None = None
    amount: float | None = None
    currency: str | None = None
    due_date: str | None = None
    payer_name: str | None = None
    payee_name: str | None = None
    iban: str | None = None
    swift_bic: str | None = None
    payment_reference: str | None = None

extraction_agent = Agent(
    role="Payment Document Extraction Specialist",
    goal="Extract payment-critical fields from financial documents with high precision",
    backstory=(
        "You process invoices and payment instructions for a regulated payments team. "
        "You must return only validated structured data and flag uncertainty explicitly."
    ),
    verbose=True,
)

2) Add a deterministic validation tool

CrewAI agents work better when they can call real tools for checks instead of hallucinating validation logic. Keep these checks boring and explicit.

import re
from crewai.tools import BaseTool

class ValidatePaymentFieldsTool(BaseTool):
    name: str = "validate_payment_fields"
    description: str = "Validate extracted payment fields for basic formatting and completeness."

    def _run(self, iban: str | None = None, swift_bic: str | None = None,
             currency: str | None = None, amount: float | None = None) -> dict:
        errors = []

        if currency and not re.fullmatch(r"[A-Z]{3}", currency):
            errors.append("currency must be ISO-4217 format")

        if amount is not None and amount <= 0:
            errors.append("amount must be positive")

        if iban and not re.fullmatch(r"[A-Z]{2}[0-9A-Z]{13,32}", iban.replace(" ", "")):
            errors.append("iban format invalid")

        if swift_bic and not re.fullmatch(r"[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?", swift_bic):
            errors.append("swift_bic format invalid")

        return {"valid": len(errors) == 0, "errors": errors}

3) Build the task and run the crew

This pattern keeps extraction separate from validation. In production you usually want extraction first, then validation as a second step so you can inspect failures cleanly.

from crewai import Task, Crew

extract_task = Task(
    description=(
        "Extract structured payment fields from the provided document. "
        "Return all values in a JSON-compatible structure matching the schema."
    ),
    expected_output="Structured payment document fields with confidence notes for any uncertain values.",
    agent=extraction_agent,
)

validation_task = Task(
    description=(
        "Validate the extracted payment fields using the validation tool. "
        "Return whether the record is safe to pass downstream."
    ),
    expected_output="Validation result with errors list if any.",
    agent=extraction_agent,
)

crew = Crew(
    agents=[extraction_agent],
    tasks=[extract_task],
    verbose=True,
)

result = crew.kickoff(inputs={
    "document_text": """
Invoice #INV-20491
Amount Due: EUR 12,450.00
Due Date: 2026-05-10
Bill To: Acme Trading Ltd
Pay To: Nord Bank Services GmbH
IBAN: DE89370400440532013000
SWIFT/BIC: COBADEFFXXX
Reference: PAY-77821
"""
})

print(result)

4) Wrap it in a service boundary

Do not let the agent directly touch core banking or payment rails. Put it behind an API that stores raw input separately from extracted output and enforces residency rules.

def process_payment_document(document_text: str) -> dict:
    crew_result = crew.kickoff(inputs={"document_text": document_text})
    return {
        "status": "extracted",
        "raw_result": str(crew_result),
    }

Production Considerations

  • Deployment

    • Run the agent in a private network segment with no direct internet access unless required by your model provider.
    • Keep OCR/extraction services close to your data store to reduce cross-border transfer risk.
  • Monitoring

    • Track extraction accuracy by field type, not just overall success.
    • Alert on spikes in missing IBANs, mismatched amounts, or unusually high human-review rates.
  • Guardrails

    • Reject outputs that fail schema validation before they reach AP/AR or payment initiation systems.
    • Store every prompt/result pair for auditability; regulators will ask how a value was derived.
  • Compliance

    • Classify documents by jurisdiction and apply retention/deletion policies accordingly.
    • Mask PII where possible and log access to sensitive documents for audit trails.

Common Pitfalls

  1. Using free-form text instead of strict structure

    • If the model returns prose like “the invoice seems to be around twelve thousand,” your workflow breaks.
    • Fix it by enforcing Pydantic-like schemas and validating every field before persistence.
  2. Skipping deterministic checks

    • LLMs are not reliable validators for IBAN formats, currency codes, or amount math.
    • Fix it by using tools like ValidatePaymentFieldsTool after extraction.
  3. Letting the agent make payment decisions

    • Extraction is not authorization.
    • Fix it by limiting the agent to classification and field extraction; keep approval logic in your payments engine with human review for exceptions.

Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides