How to Build a loan approval Agent Using LlamaIndex in Python for payments
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
SimpleDirectoryReaderfor local files or custom loaders for APIs and object storage.
- •
Indexing layer
- •Builds a searchable knowledge base over underwriting policies and customer artifacts.
- •
VectorStoreIndexworks well for semantic retrieval across unstructured documents.
- •
Retrieval and reasoning layer
- •Uses
RetrieverQueryEngineor anAgentRunnerto fetch relevant evidence before deciding. - •The model should never “guess” approval criteria without retrieved context.
- •Uses
- •
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
- •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)
- •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)
- •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)
- •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
- •
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.
- •
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.
- •
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.
- •
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
- •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