How to Build a loan approval Agent Using LangGraph in Python for insurance
A loan approval agent for insurance decides whether an application can move forward, needs human review, or must be declined based on policy rules, risk signals, and document completeness. In insurance, this matters because loan decisions often touch regulated data, underwriting constraints, and audit requirements, so the agent has to be deterministic where it counts and traceable everywhere else.
Architecture
- •
Input normalization layer
- •Converts raw application payloads into a typed state object.
- •Validates required fields like applicant identity, premium history, claim history, and loan amount.
- •
Policy and eligibility node
- •Applies hard rules first: minimum tenure, active policy status, outstanding claims, overdue premiums.
- •This is where you keep non-negotiable compliance logic.
- •
Risk scoring node
- •Produces a structured risk assessment from underwriting signals.
- •Can call an LLM or a rules engine, but the output must be constrained to a fixed schema.
- •
Decision router
- •Routes to
approve,reject, ormanual_review. - •Uses explicit thresholds and rule outcomes rather than free-form generation.
- •Routes to
- •
Audit trail store
- •Persists every state transition, input snapshot, and final decision.
- •Needed for internal audit, regulator review, and dispute handling.
- •
Human review handoff
- •Escalates borderline cases to an underwriter or loan officer.
- •Keeps the agent from making unsupported decisions on edge cases.
Implementation
1) Define the graph state and decision schema
Use TypedDict for graph state and keep the final decision structured. For insurance workflows, do not let the model emit arbitrary text as the decision object.
from typing import TypedDict, Literal, Optional
from langgraph.graph import StateGraph, START, END
Decision = Literal["approve", "reject", "manual_review"]
class LoanState(TypedDict):
applicant_id: str
policy_active: bool
premium_overdue_days: int
outstanding_claims: int
loan_amount: float
risk_score: Optional[float]
decision: Optional[Decision]
reason: Optional[str]
2) Add deterministic policy checks first
Hard rules should run before any model-based reasoning. This keeps compliance logic stable and makes audit easier.
def policy_check(state: LoanState) -> LoanState:
if not state["policy_active"]:
return {**state, "decision": "reject", "reason": "Policy is inactive"}
if state["premium_overdue_days"] > 30:
return {**state, "decision": "reject", "reason": "Premium overdue exceeds threshold"}
if state["outstanding_claims"] > 0:
return {**state, "decision": "manual_review", "reason": "Outstanding claim requires review"}
return state
3) Add a scoring node and route based on thresholds
In production you can replace this with a model call wrapped in strict validation. The key pattern is that LangGraph nodes return updated state dictionaries and routing is handled explicitly with add_conditional_edges.
def score_risk(state: LoanState) -> LoanState:
# Replace with a real model or rules engine.
# Keep output bounded and auditable.
amount_factor = min(state["loan_amount"] / 100000.0, 1.0)
overdue_factor = min(state["premium_overdue_days"] / 60.0, 1.0)
claims_factor = min(state["outstanding_claims"] * 0.25, 1.0)
score = round((amount_factor * 0.4) + (overdue_factor * 0.4) + (claims_factor * 0.2), 3)
return {**state, "risk_score": score}
def route_decision(state: LoanState) -> str:
if state.get("decision") in ("reject", "manual_review"):
return state["decision"]
score = state.get("risk_score", 1.0)
if score < 0.3:
return "approve"
if score < 0.7:
return "manual_review"
return "reject"
def finalize_approval(state: LoanState) -> LoanState:
return {**state, "decision": "approve", "reason": f"Approved with risk score {state['risk_score']}"}
def finalize_rejection(state: LoanState) -> LoanState:
return {**state, "decision": "reject", "reason": f"Rejected with risk score {state['risk_score']}"}
def send_to_human_review(state: LoanState) -> LoanState:
return {**state, "decision": "manual_review", "reason": f"Manual review required; risk score {state['risk_score']}"}
4) Wire the LangGraph workflow
This is the core LangGraph pattern: build nodes with StateGraph, connect them with edges, compile once, then invoke with an input state.
workflow = StateGraph(LoanState)
workflow.add_node("policy_check", policy_check)
workflow.add_node("score_risk", score_risk)
workflow.add_node("finalize_approval", finalize_approval)
workflow.add_node("finalize_rejection", finalize_rejection)
workflow.add_node("send_to_human_review", send_to_human_review)
workflow.add_edge(START, "policy_check")
def after_policy(state: LoanState) -> str:
if state.get("decision") == "reject":
return "finalize_rejection"
if state.get("decision") == "manual_review":
return "send_to_human_review"
return "score_risk"
workflow.add_conditional_edges(
"policy_check",
after_policy,
)
workflow.add_conditional_edges(
"score_risk",
route_decision,
)
workflow.add_edge("finalize_approval", END)
workflow.add_edge("finalize_rejection", END)
workflow.add_edge("send_to_human_review", END)
app = workflow.compile()
result = app.invoke({
"applicant_id": "A-10291",
"policy_active": True,
"premium_overdue_days": 5,
"outstanding_claims": 0,
"loan_amount": 25000.0,
})
print(result)
Production Considerations
- •
Auditability
- •Persist every input payload and final state transition.
- •Store
decision,reason, timestamps, model version, and rule version for regulator review.
- •
Data residency
- •Keep policyholder data in-region if your insurance jurisdiction requires it.
- •If you call external models or APIs, make sure they do not move personal data outside approved boundaries.
- •
Guardrails
- •Enforce schema validation on all node outputs.
- •Never allow an LLM to directly emit a final approval without passing through deterministic policy checks.
- •
Monitoring
- •Track approval rate by product line, manual review rate, false rejects, and override frequency.
- •Alert on drift in premium delinquency patterns or sudden changes in risk-score distribution.
Common Pitfalls
- •
Letting the LLM make the final call
- •Avoid this by making the graph route through hard-coded thresholds and compliance nodes first.
- •The model should assist scoring or summarization, not override policy.
- •
Skipping human review paths
- •Insurance workflows always have edge cases: disputed claims، recent lapses، identity mismatches.
- •Add a real manual-review branch in the graph instead of forcing binary approve/reject outcomes.
- •
Weak traceability
- •If you cannot explain why an application was rejected six months later, your workflow is not production-ready.
- •Log node outputs separately so you can reconstruct each decision path end to end.
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