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

By Cyprian AaronsUpdated 2026-04-21
loan-approvallanggraphpythoninvestment-banking

A loan approval agent in investment banking is a workflow system that gathers borrower data, checks policy and regulatory constraints, scores risk, and routes decisions to the right human approver when needed. It matters because credit teams need faster turnaround without losing control over compliance, auditability, and decision consistency.

Architecture

  • Input normalization layer

    • Converts borrower profile, financial statements, collateral details, and deal terms into a structured state object.
    • This is where you validate required fields before any downstream decisioning.
  • Policy and compliance checks

    • Enforces hard rules like KYC completeness, sanctions screening status, exposure limits, and jurisdiction-specific constraints.
    • In investment banking, this must be deterministic and logged.
  • Risk scoring node

    • Produces a risk grade from financial ratios, leverage, DSCR, historical repayment behavior, and sector exposure.
    • Keep this separate from policy checks so you can explain why a deal was rejected.
  • Decision router

    • Routes to approve, reject, or escalate for manual review.
    • This is the core LangGraph strength: explicit branching instead of hidden agent behavior.
  • Audit trail state

    • Stores every intermediate output, rule hit, and final recommendation.
    • Required for model governance and internal credit committee review.
  • Human review node

    • Sends borderline or high-value deals to a credit officer.
    • Use this for exceptions, overrides, and regulated approvals.

Implementation

  1. Define the graph state and node functions

Use a typed state object so every step in the graph has a predictable contract. For loan approval workflows, keep the state small but auditable: applicant data, compliance flags, risk score, decision, and rationale.

from typing import TypedDict, Literal
from langgraph.graph import StateGraph, START, END

class LoanState(TypedDict):
    applicant_name: str
    amount: float
    kyc_complete: bool
    sanctions_clear: bool
    dscr: float
    leverage_ratio: float
    risk_score: int
    decision: Literal["approve", "reject", "review"]
    rationale: str

def compliance_check(state: LoanState) -> dict:
    if not state["kyc_complete"]:
        return {"decision": "reject", "rationale": "KYC incomplete"}
    if not state["sanctions_clear"]:
        return {"decision": "reject", "rationale": "Sanctions screening failed"}
    return {}

def risk_score(state: LoanState) -> dict:
    score = 0
    if state["dscr"] < 1.25:
        score += 50
    if state["leverage_ratio"] > 4.0:
        score += 40
    if state["amount"] > 10_000_000:
        score += 10
    return {"risk_score": score}

def decide(state: LoanState) -> dict:
    if state.get("decision") == "reject":
        return {}
    if state["risk_score"] >= 60:
        return {"decision": "review", "rationale": "High risk requires credit officer review"}
    return {"decision": "approve", "rationale": "Passed policy checks and risk threshold"}

graph = StateGraph(LoanState)
graph.add_node("compliance_check", compliance_check)
graph.add_node("risk_score", risk_score)
graph.add_node("decide", decide)

graph.add_edge(START, "compliance_check")
graph.add_edge("compliance_check", "risk_score")
graph.add_edge("risk_score", "decide")
graph.add_edge("decide", END)

loan_agent = graph.compile()
  1. Run the workflow with real input

This pattern keeps the agent deterministic. You can store the input payload in your case management system and replay it later for audit or model validation.

result = loan_agent.invoke({
    "applicant_name": "Acme Manufacturing Ltd",
    "amount": 15_000_000,
    "kyc_complete": True,
    "sanctions_clear": True,
    "dscr": 1.18,
    "leverage_ratio": 4.6,
    "risk_score": 0,
    "decision": "review",
    "rationale": ""
})

print(result["decision"])
print(result["rationale"])
print(result["risk_score"])
  1. Add conditional routing for manual review

In production loan ops, you rarely want a single linear path. Borderline deals should branch to a human review node only when thresholds are hit.

from langgraph.graph import END

def route_after_risk(state: LoanState) -> str:
    if not state["kyc_complete"] or not state["sanctions_clear"]:
        return END
    if state["risk_score"] >= 60:
        return "review"
    return END

def human_review(state: LoanState) -> dict:
    # Replace with queue integration to CRM / loan origination system
    return {"decision": "review", "rationale": f"Manual review required for {state['applicant_name']}"}

graph = StateGraph(LoanState)
graph.add_node("compliance_check", compliance_check)
graph.add_node("risk_score", risk_score)
graph.add_node("decide", decide)
graph.add_node("review", human_review)

graph.add_edge(START, "compliance_check")
graph.add_edge("compliance_check", "risk_score")
graph.add_conditional_edges("risk_score", route_after_risk)
graph.add_edge("review", END)
graph.add_edge("decide", END)

loan_agent = graph.compile()
  1. Persist checkpoints for auditability

For investment banking workflows, checkpointing matters because you need replayable decisions. LangGraph supports this through checkpointers when compiling the graph; wire it to durable storage so reviewers can inspect each step later.

Production Considerations

  • Use durable checkpoints

    • Store graph state in Postgres or another controlled datastore.
    • This gives you replayable decisions for audit committees and internal model risk teams.
  • Separate policy logic from LLM reasoning

    • Hard compliance rules should be code-first.
    • Do not let an LLM override sanctions status, KYC gaps, or lending limits.
  • Log every decision input and output

    • Capture raw inputs, normalized features, rule hits, final recommendation, reviewer overrides.
    • Investment banking teams will ask why a deal was approved or rejected six months later.
  • Respect data residency and access controls

    • Keep borrower PII inside approved regions.
    • Apply role-based access so only authorized credit staff can see sensitive deal data.

Common Pitfalls

  • Mixing deterministic rules with free-form generation

    • If an LLM writes the final approval text without guardrails, you get inconsistent outcomes.
    • Keep approvals driven by explicit nodes like compliance_check and risk_score.
  • Not modeling escalation paths

    • Many teams build approve/reject logic only.
    • Real loan ops needs review for exceptions like high ticket size, covenant waivers, or sector concentration limits.
  • Skipping audit metadata

    • If you only store the final decision, you cannot explain it later.
    • Persist intermediate outputs like DSCR thresholds hit, sanctions result, reviewer override reason.

A good loan approval agent is not “smart” in the vague sense. It is controlled software with explicit branches, traceable decisions, and enough structure to satisfy both credit officers and compliance teams.


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