How to Build a document extraction Agent Using CrewAI in Python for payments
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
Agentto 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.
- •Uses CrewAI
- •
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
- •
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.
- •
Skipping deterministic checks
- •LLMs are not reliable validators for IBAN formats, currency codes, or amount math.
- •Fix it by using tools like
ValidatePaymentFieldsToolafter extraction.
- •
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
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
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