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

By Cyprian AaronsUpdated 2026-04-21
loan-approvalllamaindexpythonlending

A loan approval agent helps a lending team turn borrower data, policy rules, and internal knowledge into a consistent decision workflow. In practice, it can pre-screen applications, retrieve the right policy clauses, explain why a file is approvable or not, and produce an audit trail that compliance teams can review.

Architecture

  • Application intake layer

    • Normalizes borrower inputs: income, debt, employment, requested amount, jurisdiction, and product type.
    • Pulls structured fields from your LOS or CRM before the agent runs.
  • Policy retrieval layer

    • Indexes underwriting manuals, credit policy docs, regulatory guidance, and product eligibility rules.
    • Uses LlamaIndex retrieval to fetch only the relevant passages for each application.
  • Decision engine

    • Combines application facts with retrieved policy context.
    • Produces a recommendation such as approve, refer, or decline with explicit reasons.
  • Guardrail and validation layer

    • Enforces hard rules like minimum credit score thresholds, debt-to-income caps, and prohibited attributes.
    • Blocks unsupported decisions and forces human review when confidence is low.
  • Audit logging layer

    • Stores inputs, retrieved evidence, model output, and final decision.
    • Gives compliance teams traceability for fair lending reviews and adverse action workflows.
  • Human review workflow

    • Routes edge cases to an underwriter.
    • Captures override reasons so the system learns from exceptions without silently drifting.

Implementation

1. Load lending policy documents into a LlamaIndex index

Use SimpleDirectoryReader to load policy files and VectorStoreIndex to make them retrievable. For lending systems, keep these docs versioned by product and jurisdiction.

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
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
documents = SimpleDirectoryReader(
    input_dir="./lending_policies"
).load_data()

# Build searchable index
policy_index = VectorStoreIndex.from_documents(documents)
policy_retriever = policy_index.as_retriever(similarity_top_k=3)

2. Define the application payload and retrieve relevant policy context

Keep borrower data structured. The agent should not guess on core financial fields; those should come from validated upstream systems.

from dataclasses import dataclass

@dataclass
class LoanApplication:
    applicant_id: str
    income_monthly: float
    existing_debt_monthly: float
    requested_amount: float
    credit_score: int
    employment_months: int
    state: str
    product_type: str

def build_query(app: LoanApplication) -> str:
    return (
        f"Loan approval review for {app.product_type} in {app.state}. "
        f"Credit score={app.credit_score}, monthly income={app.income_monthly}, "
        f"monthly debt={app.existing_debt_monthly}, requested amount={app.requested_amount}, "
        f"employment months={app.employment_months}. "
        "Return applicable underwriting rules and any exceptions."
    )

application = LoanApplication(
    applicant_id="A12345",
    income_monthly=8500,
    existing_debt_monthly=2100,
    requested_amount=25000,
    credit_score=712,
    employment_months=26,
    state="TX",
    product_type="personal_loan",
)

query = build_query(application)
nodes = policy_retriever.retrieve(query)
policy_context = "\n\n".join([node.node.get_content() for node in nodes])

3. Run a structured decision prompt through LlamaIndex’s query engine pattern

For production lending flows, use a fixed prompt that forces the model to return a constrained output. Here we use PromptTemplate with a SummaryIndex-style query flow over the retrieved context.

from llama_index.core import Document
from llama_index.core.indices.list import SummaryIndex
from llama_index.core.prompts import PromptTemplate

decision_prompt = PromptTemplate(
    """
You are a lending decision assistant.

Use only the provided policy context and application data.
Do not use protected characteristics or infer anything about them.
Return:
1) decision: approve | refer | decline
2) reasons: bullet list
3) required_actions: bullet list if refer or decline

Application:
{application_data}

Policy Context:
{policy_context}
"""
)

doc = Document(text=policy_context)
summary_index = SummaryIndex.from_documents([doc])
query_engine = summary_index.as_query_engine(response_mode="compact")

response = query_engine.query(
    decision_prompt.format(
        application_data=str(application),
        policy_context=policy_context,
    )
)

print(response)

4. Add hard guardrails before finalizing the outcome

The model should recommend; your code should decide whether that recommendation is allowed. This is where you enforce lending rules like DTI ceilings or minimum score thresholds.

def hard_rule_check(app: LoanApplication) -> list[str]:
    violations = []

    dti = app.existing_debt_monthly / app.income_monthly
    if dti > 0.45:
        violations.append(f"DTI too high: {dti:.2f}")

    if app.credit_score < 660:
        violations.append(f"Credit score below minimum: {app.credit_score}")

    if app.employment_months < 12:
        violations.append(f"Insufficient employment history: {app.employment_months} months")

    return violations

violations = hard_rule_check(application)

if violations:
    final_decision = "refer"
else:
    final_decision = "approve"

audit_record = {
    "applicant_id": application.applicant_id,
    "decision": final_decision,
    "model_response": str(response),
    "rule_violations": violations,
}
print(audit_record)

Production Considerations

  • Deploy regionally for data residency

    • Keep borrower PII in-region if your jurisdiction requires it.
    • Separate the vector store by country or business unit when policies differ across regions.
  • Log every retrieval and decision path

    • Store which policy chunks were used for each outcome.
    • This matters for adverse action notices, internal audits, and dispute handling.
  • Add explicit compliance guardrails

    • Block protected attributes from prompts and retrieval filters.
    • Validate that explanations reference permissible factors only: income stability, DTI, payment history, collateral quality.
  • Monitor drift in both model behavior and policy content

    • Track approval rates by product, branch, state, and risk band.
    • Re-index when underwriting manuals change so stale rules do not keep influencing decisions.

Common Pitfalls

  1. Using the LLM as the source of truth for eligibility

    • Don’t let the model invent thresholds or exceptions.
    • Keep hard underwriting rules in code and use LLM output only for interpretation and explanation.
  2. Mixing protected attributes into prompts

    • Never pass race, religion, marital status, age-inference signals, or similar fields into the reasoning prompt.
    • If you need fair lending analysis, handle it in separate offline compliance workflows with strict access control.
  3. Skipping versioning on policies and prompts

    • A loan decision without document versioning is hard to defend later.
    • Version underwriting docs, prompt templates, retrievers settings, and model names together so every decision is 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