How to Build a KYC verification Agent Using AutoGen in Python for retail banking

By Cyprian AaronsUpdated 2026-04-21
kyc-verificationautogenpythonretail-banking

A KYC verification agent automates the first pass of customer identity checks for retail banking: it collects documents, extracts identity data, compares it against policy rules and external signals, and produces an auditable decision or escalation. It matters because onboarding speed and compliance are usually in tension; a good agent reduces manual review load without weakening AML/KYC controls, auditability, or data residency requirements.

Architecture

  • Customer intake layer

    • Accepts onboarding payloads: name, DOB, address, ID document images, and consent flags.
    • Normalizes inputs before any model call.
  • Document extraction agent

    • Uses an LLM to extract structured fields from OCR text or document metadata.
    • Should only process approved document types for the bank’s market.
  • Policy/rules agent

    • Checks extracted fields against KYC policy: age thresholds, jurisdiction restrictions, expiry dates, mismatch tolerance.
    • Keeps deterministic rules outside the model.
  • Risk review agent

    • Flags anomalies like inconsistent addresses, suspicious document patterns, or missing consent.
    • Escalates to a human reviewer when confidence is low.
  • Audit logger

    • Writes every decision, prompt summary, and tool output to immutable storage.
    • Needed for regulator review and internal QA.
  • Human-in-the-loop router

    • Sends uncertain cases to compliance analysts.
    • Prevents auto-approval when evidence is incomplete.

Implementation

1) Install AutoGen and define your banking agents

For a retail banking KYC flow, keep the LLM responsible for extraction and narrative reasoning, while deterministic checks stay in Python. The pattern below uses AssistantAgent for extraction/review and UserProxyAgent as the orchestrator that can execute local validation logic.

import os
import json
from typing import Dict, Any

from autogen import AssistantAgent, UserProxyAgent

llm_config = {
    "model": "gpt-4o-mini",
    "api_key": os.environ["OPENAI_API_KEY"],
    "temperature": 0,
}

extractor = AssistantAgent(
    name="kyc_extractor",
    llm_config=llm_config,
    system_message=(
        "You extract KYC fields from customer onboarding data. "
        "Return only valid JSON with keys: full_name, date_of_birth, address, "
        "document_type, document_number, expiry_date."
    ),
)

reviewer = AssistantAgent(
    name="kyc_reviewer",
    llm_config=llm_config,
    system_message=(
        "You assess KYC risk for retail banking. "
        "Be strict. If data is incomplete or inconsistent, recommend manual review."
    ),
)

user_proxy = UserProxyAgent(
    name="bank_orchestrator",
    human_input_mode="NEVER",
    code_execution_config=False,
)

2) Add deterministic policy checks before any decision

This is where most banking teams should spend their effort. The model can help interpret text, but policy enforcement should be explicit Python so it is testable and auditable.

def validate_kyc_policy(data: Dict[str, Any]) -> Dict[str, Any]:
    issues = []

    required = ["full_name", "date_of_birth", "address", "document_type", "document_number", "expiry_date"]
    for field in required:
        if not data.get(field):
            issues.append(f"missing_{field}")

    if data.get("document_type") not in {"passport", "national_id", "driver_license"}:
        issues.append("unsupported_document_type")

    # Example rule: expiry date must exist and be in ISO format handled elsewhere
    if data.get("expiry_date") == "":
        issues.append("missing_expiry_date")

    return {
        "approved_for_auto_pass": len(issues) == 0,
        "issues": issues,
    }

3) Run the AutoGen conversation and force structured output

A practical pattern is: extract → validate → review → decide. The extractor produces JSON; your app parses it; then the reviewer gets only the facts needed to make a recommendation.

customer_payload = {
    "full_name": "Amina Yusuf",
    "date_of_birth": "1994-02-11",
    "address": "12 King Street, Lagos",
    "document_type": "passport",
    "document_number": "P12345678",
    "expiry_date": "2030-08-15",
}

prompt = f"""
Extract KYC fields from this onboarding payload and return JSON only:

{json.dumps(customer_payload)}
"""

extract_result = user_proxy.initiate_chat(
    extractor,
    message=prompt,
)

raw_text = extract_result.chat_history[-1]["content"]
kyc_data = json.loads(raw_text)

policy_result = validate_kyc_policy(kyc_data)

review_prompt = f"""
KYC extracted data:
{json.dumps(kyc_data)}

Policy result:
{json.dumps(policy_result)}

Decision rules:
- APPROVE only if policy passes and no red flags exist.
- MANUAL_REVIEW if any issue exists.
- REJECT only if there is clear fraud or invalid identity evidence.
Return one of: APPROVE, MANUAL_REVIEW, REJECT with a short reason.
"""

decision_result = user_proxy.initiate_chat(
    reviewer,
    message=review_prompt,
)

decision_text = decision_result.chat_history[-1]["content"]
print(decision_text)

4) Persist audit logs for compliance review

Retail banking KYC needs traceability. Store the input payload hash, extracted fields, policy result, final decision, model version, timestamp, and reviewer rationale in an append-only store.

import hashlib
from datetime import datetime

def audit_record(input_payload: Dict[str, Any], extracted: Dict[str, Any], policy: Dict[str, Any], decision: str) -> Dict[str, Any]:
    payload_hash = hashlib.sha256(json.dumps(input_payload, sort_keys=True).encode()).hexdigest()
    return {
        "timestamp_utc": datetime.utcnow().isoformat(),
        "payload_hash": payload_hash,
        "model_name": llm_config["model"],
        "extracted": extracted,
        "policy": policy,
        "decision": decision,
    }

record = audit_record(customer_payload, kyc_data, policy_result, decision_text)
print(json.dumps(record, indent=2))

Production Considerations

  • Data residency

    • Keep PII processing inside the approved region or VPC boundary.
    • If your bank operates across jurisdictions, route EU/UK customer data to region-specific deployments and separate logs accordingly.
  • Auditability

    • Log every prompt template version and model version alongside the decision.
    • Make sure compliance can reconstruct why a case was approved or escalated six months later.
  • Guardrails

    • Never let the LLM make final approval decisions without deterministic checks.
    • Block unsupported document types at the API edge before they hit the model.
  • Monitoring

    • Track manual-review rate, false positive rate on mismatches, OCR failure rate, and latency per step.
    • Alert on sudden shifts by geography or product channel; that often signals upstream fraud changes or broken extraction prompts.

Common Pitfalls

  • Using the model as the source of truth

    • Mistake: asking the agent to “decide KYC” end-to-end.
    • Fix: keep approvals gated by Python policy code and human review thresholds.
  • Skipping structured outputs

    • Mistake: parsing free-form text from the LLM with regex.
    • Fix: force JSON-only responses and validate with json.loads() plus schema checks before downstream use.
  • Ignoring jurisdiction-specific requirements

    • Mistake: one global workflow for all customers.
    • Fix: parameterize rules by country/region so residency limits, acceptable IDs, retention periods, and escalation paths match local regulation.

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