How to Build a loan approval Agent Using LangGraph in Python for wealth management
A loan approval agent for wealth management takes a client’s application, pulls the right financial context, checks policy constraints, and produces a decision path that a banker or advisor can review. It matters because wealth clients expect fast decisions, but the bank still needs traceability, compliance checks, and consistent underwriting across jurisdictions and product lines.
Architecture
- •
Input normalization node
- •Converts raw application data into a structured loan request.
- •Validates required fields like income, assets under management, liabilities, jurisdiction, and product type.
- •
Policy retrieval node
- •Pulls underwriting rules from a policy store.
- •Handles product-specific limits such as LTV, debt-to-income thresholds, and minimum liquidity requirements.
- •
Risk assessment node
- •Scores the applicant using deterministic checks first.
- •Optionally enriches with portfolio exposure or relationship data from internal systems.
- •
Compliance and suitability node
- •Checks AML/KYC status, sanctions flags, residency constraints, and suitability rules for wealth clients.
- •Ensures the decision is explainable and auditable.
- •
Decision node
- •Produces approve / reject / manual-review outcomes.
- •Generates reasons that can be stored in an audit log.
- •
Human review handoff
- •Routes edge cases to an underwriter or relationship manager.
- •Preserves the full state for escalation and override tracking.
Implementation
1) Define the state and helper functions
LangGraph works best when you keep state explicit. For loan approval, that means one object carrying the application, policy results, risk score, compliance flags, and final decision.
from typing import TypedDict, Literal, Optional
from langgraph.graph import StateGraph, START, END
class LoanState(TypedDict):
applicant_id: str
jurisdiction: str
requested_amount: float
annual_income: float
liquid_assets: float
debt_obligations: float
kyc_passed: bool
aml_cleared: bool
policy_limit: float
risk_score: float
decision: Optional[Literal["approve", "reject", "manual_review"]]
reason: str
def load_policy(state: LoanState) -> dict:
# In production this would query a policy service or rules engine.
limit_by_jurisdiction = {"US": 500000.0, "UK": 350000.0}
return {"policy_limit": limit_by_jurisdiction.get(state["jurisdiction"], 250000.0)}
def assess_risk(state: LoanState) -> dict:
dti = state["debt_obligations"] / max(state["annual_income"], 1)
liquidity_ratio = state["liquid_assets"] / max(state["requested_amount"], 1)
score = max(0.0, 100.0 - (dti * 60.0) + (liquidity_ratio * 10.0))
return {"risk_score": round(score, 2)}
2) Build deterministic checks as graph nodes
For wealth management, don’t let an LLM make the core credit decision. Use LangGraph to orchestrate deterministic checks first, then let an LLM summarize or explain if needed.
def compliance_check(state: LoanState) -> dict:
if not state["kyc_passed"] or not state["aml_cleared"]:
return {"decision": "reject", "reason": "KYC/AML failed"}
return {}
def decide(state: LoanState) -> dict:
if state.get("decision") == "reject":
return {}
if state["requested_amount"] > state["policy_limit"]:
return {"decision": "manual_review", "reason": "Requested amount exceeds policy limit"}
if state["risk_score"] >= 75:
return {"decision": "approve", "reason": "Meets policy and risk thresholds"}
if state["risk_score"] >= 55:
return {"decision": "manual_review", "reason": "Borderline risk profile"}
return {"decision": "reject", "reason": "Risk score below threshold"}
3) Wire the graph with StateGraph, add_node, add_edge, and compile
This is the actual LangGraph pattern you want in production: explicit nodes with predictable transitions.
def route_after_compliance(state: LoanState) -> str:
return END if state.get("decision") == "reject" else "risk"
graph = StateGraph(LoanState)
graph.add_node("policy", load_policy)
graph.add_node("compliance", compliance_check)
graph.add_node("risk", assess_risk)
graph.add_node("decide", decide)
graph.add_edge(START, "policy")
graph.add_edge("policy", "compliance")
graph.add_conditional_edges("compliance", route_after_compliance)
graph.add_edge("risk", "decide")
graph.add_edge("decide", END)
app = graph.compile()
result = app.invoke({
"applicant_id": "CUST-10091",
"jurisdiction": "US",
"requested_amount": 300000.0,
"annual_income": 220000.0,
"liquid_assets": 1200000.0,
"debt_obligations": 45000.0,
"kyc_passed": True,
"aml_cleared": True,
"policy_limit": 0.0,
"risk_score": 0.0,
"decision": None,
"reason": ""
})
print(result)
4) Add human review for wealth-specific exceptions
Wealth management has exceptions that retail lending usually doesn’t: pledged assets, relationship pricing, cross-border residency issues, and discretionary overrides. Model those as a separate branch instead of burying them in one big function.
def manual_review_required(state: LoanState) -> bool:
return state["decision"] == "manual_review"
# If you want a review branch later:
# graph.add_node("review", human_review_handler)
# graph.add_conditional_edges("decide", manual_review_required)
That structure keeps your audit trail clean. Every transition is visible in the graph instead of hidden inside a single prompt or monolithic service.
Production Considerations
- •
Audit logging
- •Persist every input field, node output, final decision, and override reason.
- •Wealth management teams will need this for model governance and regulatory exams.
- •
Data residency
- •Keep client financial data in-region based on jurisdiction.
- •If you enrich from external systems or use hosted models for summaries, make sure no restricted client data leaves approved boundaries.
- •
Guardrails
- •Hard-code compliance failures so they always short-circuit to reject or manual review.
- •Never allow an LLM to override KYC/AML status or policy limits.
- •
Monitoring
- •Track decision distribution by jurisdiction, advisor team, product type, and exception rate.
- •Watch for drift in manual review volume; it usually means your policy thresholds are stale or upstream data quality has slipped.
Common Pitfalls
- •
Using the LLM as the decision maker
Don’t ask a model to “approve this loan” from raw text alone. Use deterministic rules for eligibility and let the model handle explanation only.
- •
Not separating compliance from credit risk
KYC/AML failure is not a low-risk signal; it is usually an immediate stop condition. Keep compliance checks as their own node so they can short-circuit execution.
- •
Ignoring jurisdiction-specific policy
A loan acceptable in one region may violate residency rules or lending caps in another. Encode jurisdiction into state early and fetch policies per region before scoring.
- •
Skipping human review paths
Wealth clients often have complex balance sheets that automated rules won’t classify well. Build a manual review branch from day one so exceptions do not get forced into approve/reject buckets.
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