How to Build a loan approval Agent Using AutoGen in Python for banking

By Cyprian AaronsUpdated 2026-04-21
loan-approvalautogenpythonbanking

A loan approval agent automates the first pass of credit decisions by gathering applicant data, checking policy rules, analyzing risk signals, and producing a decision recommendation with an audit trail. In banking, this matters because it reduces manual review load, speeds up turnaround time, and enforces consistent underwriting logic without replacing human approval where policy requires it.

Architecture

  • Applicant intake service

    • Accepts structured loan applications from your core banking or CRM system.
    • Normalizes fields like income, debt, employment status, requested amount, and jurisdiction.
  • Policy and compliance agent

    • Checks hard rules: minimum income, debt-to-income thresholds, KYC completeness, sanctions flags.
    • Enforces jurisdiction-specific constraints and rejects incomplete applications early.
  • Risk analysis agent

    • Evaluates credit risk using internal scorecards or external bureau data.
    • Produces a reasoned recommendation: approve, decline, or refer to manual review.
  • Decision orchestrator

    • Coordinates multi-agent conversation using AutoGen.
    • Ensures each agent only sees the data it needs.
  • Audit logger

    • Persists prompts, tool calls, decisions, timestamps, and final rationale.
    • Required for model governance and regulator review.
  • Human override workflow

    • Routes borderline cases to a loan officer.
    • Keeps final authority with humans for high-risk or regulated cases.

Implementation

1) Install AutoGen and define your agents

For banking workflows, use explicit agents with narrow responsibilities. The pattern below uses AssistantAgent for policy/risk reasoning and UserProxyAgent as the orchestrator that can execute controlled tool functions.

from autogen import AssistantAgent, UserProxyAgent
import os

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

llm_config = {
    "config_list": config_list,
    "temperature": 0,
}

policy_agent = AssistantAgent(
    name="policy_agent",
    llm_config=llm_config,
    system_message=(
        "You are a banking policy checker. "
        "Only evaluate application completeness and rule compliance. "
        "Do not make final credit decisions."
    ),
)

risk_agent = AssistantAgent(
    name="risk_agent",
    llm_config=llm_config,
    system_message=(
        "You are a credit risk analyst. "
        "Assess risk using the provided application data. "
        "Return approve, decline, or refer with concise reasons."
    ),
)

orchestrator = UserProxyAgent(
    name="loan_orchestrator",
    human_input_mode="NEVER",
    code_execution_config=False,
)

2) Add deterministic banking checks before any LLM reasoning

Do not send raw applications straight into the model. Run hard checks first so compliance failures are caught deterministically and auditable.

def validate_application(app: dict) -> list[str]:
    errors = []

    required_fields = ["applicant_id", "income", "monthly_debt", "loan_amount", "country"]
    for field in required_fields:
        if field not in app or app[field] in (None, "", []):
            errors.append(f"Missing required field: {field}")

    if app.get("income", 0) <= 0:
        errors.append("Income must be positive")

    dti = app.get("monthly_debt", 0) / max(app.get("income", 1), 1)
    if dti > 0.45:
        errors.append(f"Debt-to-income too high: {dti:.2f}")

    if app.get("country") not in {"KE", "UG", "TZ"}:
        errors.append("Unsupported jurisdiction for this workflow")

    return errors


application = {
    "applicant_id": "A-10091",
    "income": 250000,
    "monthly_debt": 80000,
    "loan_amount": 500000,
    "country": "KE",
}

errors = validate_application(application)
if errors:
    print({"decision": "decline", "reasons": errors})

3) Use AutoGen to produce a structured recommendation

Once the application passes hard checks, let the agents reason over the case. Keep the prompt narrow and force structured output so downstream systems can parse it cleanly.

from autogen import GroupChat, GroupChatManager

prompt = f"""
Application:
{application}

Rules:
- If DTI > 0.45 decline.
- If income is stable and no compliance issues exist, consider approve.
- If data is incomplete or borderline, refer to manual review.

Return JSON with keys:
decision
reasons
confidence
"""

groupchat = GroupChat(
    agents=[policy_agent, risk_agent],
    messages=[],
    max_round=4,
)

manager = GroupChatManager(groupchat=groupchat, llm_config=llm_config)

result = orchestrator.initiate_chat(
    manager,
    message=prompt,
)

print(result.summary)

4) Wrap the decision in an auditable service boundary

In production you want one function that validates input, runs AutoGen only when needed, and stores a decision record. That record should include inputs, rule failures, model output, and reviewer identity if a human intervened.

import json
from datetime import datetime

def decide_loan(app: dict) -> dict:
    validation_errors = validate_application(app)
    audit_record = {
        "timestamp": datetime.utcnow().isoformat(),
        "application_id": app["applicant_id"],
        "validation_errors": validation_errors,
        "model_output": None,
        "decision": None,
    }

    if validation_errors:
        audit_record["decision"] = {"status": "decline", "reason": validation_errors}
        return audit_record

    chat_result = orchestrator.initiate_chat(manager, message=json.dumps(app))
    
    audit_record["model_output"] = str(chat_result.summary)
    audit_record["decision"] = {
        "status": "refer_review",
        "reason": ["Model recommendation captured; final approval requires policy gateway"],
    }
    
    return audit_record

Production Considerations

  • Data residency

    • Keep applicant PII inside approved regions.
    • If you use hosted models, confirm where prompts and logs are processed and stored.
  • Auditability

    • Persist every rule check and every agent response.
    • Regulators will ask why a decision was made; “the model said so” is not enough.
  • Guardrails

    • Block prohibited attributes like race, religion, health status, and other protected classes from prompts.
    • Use deterministic pre-checks for eligibility before any LLM call.
  • Monitoring

    • Track approval rate drift, manual review rate, false declines, latency per case, and model refusal rate.
    • Alert when decision distribution changes materially by product line or geography.

Common Pitfalls

  • Letting the LLM make final credit decisions

    • Avoid this by keeping final authority in a policy engine or human reviewer.
    • Use AutoGen for analysis and explanation, not uncontrolled approval logic.
  • Sending raw PII into every agent

    • Minimize fields per agent.
    • Tokenize account numbers and redact unnecessary identifiers before chat starts.
  • Skipping deterministic underwriting rules

    • Do not rely on prompt instructions alone for compliance thresholds.
    • Enforce hard rules in Python first so they are testable and reproducible.

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