How to Build a loan approval Agent Using LlamaIndex in Python for payments

By Cyprian AaronsUpdated 2026-04-21
loan-approvalllamaindexpythonpayments

A loan approval agent for payments takes applicant data, transaction history, policy rules, and supporting documents, then produces a decision recommendation with evidence. It matters because payment products live or die on approval speed, fraud control, and compliance traceability; if your agent cannot explain why a loan was approved or rejected, you do not have a production system.

Architecture

  • Document ingestion layer

    • Pulls KYC docs, bank statements, payroll records, repayment history, and policy PDFs into LlamaIndex.
    • Use SimpleDirectoryReader for local files or custom loaders for APIs and object storage.
  • Indexing layer

    • Builds a searchable knowledge base over underwriting policies and customer artifacts.
    • VectorStoreIndex works well for semantic retrieval across unstructured documents.
  • Retrieval and reasoning layer

    • Uses RetrieverQueryEngine or an AgentRunner to fetch relevant evidence before deciding.
    • The model should never “guess” approval criteria without retrieved context.
  • Decision engine

    • Applies hard rules first: income thresholds, delinquency limits, KYC status, sanctions checks.
    • Then uses the LLM to summarize risk and produce a recommendation.
  • Audit and traceability layer

    • Stores retrieved chunks, prompts, outputs, and final decisions.
    • Required for compliance review, dispute handling, and internal model governance.
  • Security and residency controls

    • Keeps sensitive payment data in approved regions and redacts PII before logging.
    • This is non-negotiable for PCI-adjacent workflows and regulated lending environments.

Implementation

  1. Install dependencies and load your policy + applicant data

Use LlamaIndex’s actual loaders to ingest underwriting docs. In payments systems, keep raw customer files in a controlled bucket or file store with region restrictions.

from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
from llama_index.core.settings import Settings
from llama_index.llms.openai import OpenAI

# Configure your LLM
Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)

# Load underwriting policies and sample applicant documents
policy_docs = SimpleDirectoryReader("./data/policies").load_data()
applicant_docs = SimpleDirectoryReader("./data/applicants/applicant_001").load_data()

all_docs = policy_docs + applicant_docs

# Build the index
index = VectorStoreIndex.from_documents(all_docs)
  1. Create a retriever-backed query engine for evidence-first decisions

This pattern forces the model to answer from retrieved context instead of inventing approval logic. For loan approvals in payments, that’s how you keep explanations auditable.

query_engine = index.as_query_engine(similarity_top_k=5)

response = query_engine.query(
    """
    Evaluate this applicant against the underwriting policy.
    Return:
    1) approve/reject/review recommendation,
    2) key reasons,
    3) cited evidence from the documents,
    4) any compliance concerns.
    """
)

print(response)
  1. Wrap hard rules around the LLM output

Do not let the model make final credit decisions alone. Use deterministic checks for compliance-sensitive gates like KYC status, overdue balances, debt-to-income ratio, or fraud flags.

def hard_rule_checks(applicant: dict) -> list[str]:
    issues = []

    if not applicant.get("kyc_passed", False):
        issues.append("KYC failed")
    if applicant.get("sanctions_hit", False):
        issues.append("Sanctions screening hit")
    if applicant.get("delinquency_days", 0) > 30:
        issues.append("Recent delinquency > 30 days")
    if applicant.get("dti_ratio", 0) > 0.45:
        issues.append("DTI above threshold")

    return issues


def make_decision(applicant: dict, llm_text: str) -> dict:
    issues = hard_rule_checks(applicant)

    if issues:
        return {
            "decision": "reject",
            "reason": issues,
            "llm_summary": llm_text,
        }

    return {
        "decision": "review",
        "reason": ["Passed hard rules; needs human review"],
        "llm_summary": llm_text,
    }


applicant_profile = {
    "kyc_passed": True,
    "sanctions_hit": False,
    "delinquency_days": 0,
    "dti_ratio": 0.32,
}

final_decision = make_decision(applicant_profile, str(response))
print(final_decision)
  1. Add an agent layer when you need multi-step tool use

If your flow needs more than retrieval—say pulling transaction summaries from an internal API—use FunctionTool with an agent runner. This keeps the LLM inside a controlled tool boundary.

from llama_index.core.tools import FunctionTool
from llama_index.core.agent import ReActAgent

def get_payment_history(customer_id: str) -> str:
    # Replace with your internal service call
    return f"Customer {customer_id} has no chargebacks and two on-time repayments."

payment_history_tool = FunctionTool.from_defaults(fn=get_payment_history)

agent = ReActAgent.from_tools(
    [payment_history_tool],
    llm=Settings.llm,
    verbose=True,
)

result = agent.chat(
    "Fetch payment history for customer_001 and assess whether repayment behavior supports approval."
)

print(result)

Production Considerations

  • Log every decision path

    • Persist prompt input, retrieved node IDs, model output, rule checks, and final disposition.
    • Auditors will ask why a loan was approved; “the model said so” is not acceptable.
  • Redact sensitive data before indexing or logging

    • Mask account numbers, national IDs, card data, salary details where not needed.
    • Keep raw PII out of general observability tools.
  • Control data residency

    • Store documents and embeddings in approved regions only.
    • If your lending policy says customer data must stay in-country, your vector store must follow that rule too.
  • Put human review on edge cases

    • Route borderline decisions to underwriters when confidence is low or policy conflicts exist.
    • Payments products often need conservative thresholds because false approvals are expensive.

Common Pitfalls

  1. Letting the LLM decide without hard rules

    • Mistake: asking the model to approve or reject directly from free text.
    • Fix: run deterministic compliance checks first, then use LLM output as decision support.
  2. Indexing sensitive data blindly

    • Mistake: dumping full bank statements and identity docs into logs or shared indexes.
    • Fix: tokenize/redact sensitive fields before ingestion; separate regulated data stores from analytics stores.
  3. No audit trail for retrieved evidence

    • Mistake: storing only the final answer.
    • Fix: persist source document names, chunk IDs, timestamps, rule outputs, and prompt versions so every decision is reconstructible.
  4. Ignoring region-specific compliance

    • Mistake: using a hosted vector DB or LLM endpoint in the wrong jurisdiction.
    • Fix: validate vendor regions against your lending policy, PCI obligations, and local banking regulations before go-live.

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