How to Build a loan approval Agent Using AutoGen in Python for fintech
A loan approval agent automates the first pass of a credit decision: it ingests an application, checks policy rules, pulls together risk signals, and produces a recommendation with an audit trail. For fintech teams, that matters because you want faster decisions without turning every application into a manual ops task, while still keeping compliance, explainability, and data handling under control.
Architecture
A production loan approval agent usually needs these components:
- •Application intake layer
- •Normalizes borrower data from API payloads, CRM records, or document extraction.
- •Policy/rules engine
- •Encodes hard constraints like minimum income, maximum DTI, jurisdiction-specific limits, and blacklist checks.
- •LLM orchestration layer
- •Uses AutoGen agents to reason over the application and produce a structured recommendation.
- •Risk scoring tool
- •Calls internal models or external scoring services for credit risk, fraud flags, and affordability signals.
- •Audit logger
- •Persists every input, tool call, intermediate decision, and final recommendation for compliance review.
- •Human review fallback
- •Routes borderline or high-risk cases to an underwriter instead of auto-approving.
Implementation
1) Set up the AutoGen agents
For this pattern, use AssistantAgent for reasoning and UserProxyAgent as the executor/orchestrator. The assistant should not make raw decisions on its own; it should summarize evidence and return a structured recommendation.
from autogen import AssistantAgent, UserProxyAgent
llm_config = {
"model": "gpt-4o-mini",
"api_key": "YOUR_OPENAI_API_KEY",
"temperature": 0,
}
loan_underwriter = AssistantAgent(
name="loan_underwriter",
llm_config=llm_config,
system_message=(
"You are a loan approval assistant for a fintech lender. "
"Use only the provided application data and tool outputs. "
"Return a JSON object with fields: decision, reason_codes, "
"missing_info, and review_required. Do not invent facts."
),
)
operator = UserProxyAgent(
name="operator",
human_input_mode="NEVER",
code_execution_config=False,
)
2) Add deterministic policy checks before the LLM call
Do not ask the model to infer basic eligibility rules. Hard rules belong in Python so they are testable and auditable.
def policy_check(app):
reasons = []
if app["country"] != "US":
reasons.append("unsupported_jurisdiction")
if app["income_monthly"] < 3000:
reasons.append("income_below_minimum")
dti = app["monthly_debt_payments"] / max(app["income_monthly"], 1)
if dti > 0.45:
reasons.append("dti_above_threshold")
if app.get("sanctions_match", False):
reasons.append("sanctions_hit")
return {
"eligible": len(reasons) == 0,
"reasons": reasons,
"dti": round(dti, 4),
}
3) Register tools for risk scoring and audit logging
AutoGen agents can call Python functions as tools through register_function. This is the cleanest way to keep external dependencies outside the prompt.
import json
from datetime import datetime
from autogen import register_function
def get_risk_score(application_id: str) -> dict:
# Replace with internal model service or feature store lookup
return {
"application_id": application_id,
"risk_score": 612,
"fraud_flag": False,
"score_version": "v1.3.2",
}
def write_audit_log(event: dict) -> str:
payload = {
**event,
"timestamp_utc": datetime.utcnow().isoformat() + "Z",
}
print(json.dumps(payload))
return "ok"
register_function(
get_risk_score,
caller=loan_underwriter,
executor=operator,
name="get_risk_score",
description="Fetch credit risk signals for a loan application.",
)
register_function(
write_audit_log,
caller=loan_underwriter,
executor=operator,
name="write_audit_log",
description="Persist an immutable audit event.",
)
4) Run the approval flow with structured output
The assistant should receive the precomputed policy result plus any tool outputs. Keep the final response machine-readable so downstream systems can route it.
application = {
"application_id": "APP-10021",
"country": "US",
"income_monthly": 8200,
"monthly_debt_payments": 2400,
"sanctions_match": False,
}
policy_result = policy_check(application)
risk_result = get_risk_score(application["application_id"])
prompt = f"""
Application:
{json.dumps(application)}
Policy result:
{json.dumps(policy_result)}
Risk result:
{json.dumps(risk_result)}
Decision rules:
- If sanctions_hit is true, reject.
- If dti_above_threshold is true or risk_score < 580, send to manual review.
- Otherwise approve if no missing info exists.
"""
response = operator.initiate_chat(
loan_underwriter,
message=prompt,
)
print(response.summary)
A practical production version usually wraps this in a service endpoint that returns:
- •
approved - •
rejected - •
manual_review - •
reason_codes - •
audit_id
That keeps your frontend and underwriting queue simple.
Production Considerations
- •
Compliance controls
- •Keep hard eligibility rules outside the model.
- •Log inputs, outputs, model version, prompt version, and tool versions for every decision.
- •Make adverse action reason codes deterministic and mapped to policy language.
- •
Data residency and privacy
- •Do not send raw PII to external services unless your legal/compliance posture allows it.
- •Tokenize or redact sensitive fields like SSN, account numbers, and full DOB before agent calls.
- •Route EU or region-specific applications through in-region infrastructure when required.
- •
Monitoring
- •Track approval rate drift, manual review rate, false positives on fraud flags, and model refusal rates.
- •Alert when risk score distributions shift or when one reason code dominates approvals/rejections.
- •Store traces by
application_idso ops can replay decisions during audits.
- •
Guardrails
- •Force JSON-only outputs from the assistant.
- •Validate responses against a schema before acting on them.
- •Never let the LLM override sanctions hits or other non-negotiable policy checks.
Common Pitfalls
- •
Letting the model make hard compliance decisions
- •Don’t ask the LLM to decide sanctions eligibility or jurisdictional acceptance.
- •Put those checks in deterministic code and use the agent only for synthesis and explanation.
- •
Sending raw sensitive data into prompts
- •Full bank statements, SSNs, addresses, and DOBs do not belong in free-form prompts.
- •Redact or tokenize first, then pass only what is needed for decisioning.
- •
Skipping auditability
- •If you cannot reconstruct why an application was approved or rejected, you will have problems in production.
- •Persist prompt text versions, tool outputs, policy results, final response JSON, and timestamps for every run.
If you build this pattern correctly, AutoGen gives you orchestration without turning your underwriting flow into prompt spaghetti. The key is simple: deterministic policy first, model reasoning second, human review where uncertainty remains.
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