How to Build a underwriting Agent Using AutoGen in Python for banking
An underwriting agent automates the first pass of credit decisioning: it gathers applicant data, checks policy rules, scores risk, and produces a recommendation with an audit trail. In banking, that matters because the bottleneck is rarely the model itself; it’s the repeatable control layer around compliance, explainability, and human review.
Architecture
- •
Ingress layer
- •Accepts a loan application payload from your core banking or CRM system.
- •Normalizes fields like income, liabilities, collateral, and jurisdiction.
- •
Policy engine
- •Encodes underwriting rules: minimum DTI, LTV thresholds, KYC status, blacklist checks.
- •Keeps deterministic decisions out of the LLM.
- •
AutoGen agent group
- •Uses
AssistantAgentfor analysis andUserProxyAgentfor tool execution and orchestration. - •Produces a structured recommendation instead of free-form chat.
- •Uses
- •
Risk scoring tools
- •Python functions exposed to the agent for credit score bands, affordability calculations, and document checks.
- •These should be auditable and versioned.
- •
Decision logger
- •Persists prompts, tool calls, outputs, and final recommendations.
- •Required for model governance, internal audit, and regulator review.
- •
Human override workflow
- •Routes edge cases to an underwriter when policy confidence is low or a rule fails.
- •Prevents automated adverse action without review where required.
Implementation
- •Install AutoGen and define the underwriting tools
Use real Python functions for deterministic checks. The agent should call these tools instead of inventing values in natural language.
from typing import Dict
def calculate_dti(monthly_debt: float, monthly_income: float) -> float:
if monthly_income <= 0:
raise ValueError("monthly_income must be > 0")
return round(monthly_debt / monthly_income, 4)
def calculate_ltv(loan_amount: float, collateral_value: float) -> float:
if collateral_value <= 0:
raise ValueError("collateral_value must be > 0")
return round(loan_amount / collateral_value, 4)
def underwriting_policy(applicant: Dict) -> Dict:
dti = calculate_dti(applicant["monthly_debt"], applicant["monthly_income"])
ltv = calculate_ltv(applicant["loan_amount"], applicant["collateral_value"])
approved = (
applicant["kyc_passed"]
and applicant["credit_score"] >= 680
and dti <= 0.43
and ltv <= 0.80
)
return {
"approved": approved,
"dti": dti,
"ltv": ltv,
"reason": "meets policy" if approved else "fails one or more policy thresholds",
}
- •Create the AutoGen agents
In AutoGen, AssistantAgent handles reasoning and UserProxyAgent executes code or acts as the operational proxy. For banking workflows, keep the assistant constrained to analysis plus structured output.
import autogen
llm_config = {
"model": "gpt-4o-mini",
"api_key": os.environ["OPENAI_API_KEY"],
}
assistant = autogen.AssistantAgent(
name="underwriting_assistant",
llm_config=llm_config,
system_message=(
"You are a banking underwriting analyst. "
"Use only provided facts and tool outputs. "
"Return concise underwriting recommendations with reasons."
),
)
user_proxy = autogen.UserProxyAgent(
name="underwriting_orchestrator",
human_input_mode="NEVER",
code_execution_config=False,
)
- •Register tools and run a controlled conversation
The pattern below uses register_function so the assistant can invoke Python functions through AutoGen’s tool-calling flow. This keeps your policy logic in code while letting the model explain the result.
import os
from typing import Any
# Register callable tools with AutoGen
autogen.register_function(
underwriting_policy,
caller=assistant,
executor=user_proxy,
name="underwriting_policy",
description="Evaluate loan application against bank underwriting policy.",
)
application = {
"applicant_id": "A-10021",
"monthly_income": 8500.00,
"monthly_debt": 2600.00,
"loan_amount": 240000.00,
"collateral_value": 320000.00,
"credit_score": 712,
"kyc_passed": True,
}
prompt = f"""
Evaluate this application using underwriting_policy:
{application}
Return:
- approve/decline
- key ratios
- short reason
- whether human review is needed
"""
result = user_proxy.initiate_chat(
assistant,
message=prompt,
)
print(result)
- •Add a structured decision layer before release
Do not ship raw chat output into production systems. Parse the result into a schema your LOS or decision engine can consume.
from dataclasses import dataclass
@dataclass
class UnderwritingDecision:
applicant_id: str
decision: str
dti: float
ltv: float
reason: str
human_review_required: bool
policy_result = underwriting_policy(application)
decision = UnderwritingDecision(
applicant_id=application["applicant_id"],
decision="approve" if policy_result["approved"] else "decline",
dti=policy_result["dti"],
ltv=policy_result["ltv"],
reason=policy_result["reason"],
human_review_required=not policy_result["approved"],
)
print(decision)
Production Considerations
- •
Keep regulated decisions deterministic
- •Use the LLM for explanation and triage, not final approval logic.
- •Final decisions should come from versioned policy code and scored inputs.
- •
Log everything needed for audit
- •Store input payloads, tool invocations, model version, prompt text, response text, timestamps, and reviewer overrides.
- •This is non-negotiable for model risk management and adverse action traceability.
- •
Respect data residency and privacy
- •Route customer PII through approved regions only.
- •Mask account numbers, SSNs/NINs, and income docs before sending anything to the model endpoint.
- •
Add guardrails for compliance
- •Block unsupported attributes like race, religion, health data, or proxies for protected classes.
- •Enforce threshold-based escalation when confidence is low or documents conflict.
Common Pitfalls
- •
Letting the LLM make final credit decisions
- •Avoid this by separating reasoning from decisioning.
- •Use
AssistantAgentfor narrative analysis and Python policy functions for approval logic.
- •
Skipping schema validation
- •Free-form JSON-ish output will break downstream systems.
- •Validate every request and response with Pydantic or dataclasses before persistence or execution.
- •
Ignoring jurisdiction-specific rules
- •Banking policy changes by country, state, product type, and customer segment.
- •Version your rules per jurisdiction so one model path does not silently apply across all markets.
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