How to Build a underwriting Agent Using AutoGen in Python for fintech
An underwriting agent automates the first pass of credit or risk assessment: it gathers applicant data, checks policy rules, summarizes findings, and produces a decision recommendation with an audit trail. For fintech, that matters because you need faster approvals without losing control over compliance, explainability, and data handling.
Architecture
- •
Applicant intake layer
- •Normalizes application payloads from API, CRM, or loan origination systems.
- •Validates required fields before any model call.
- •
Policy/rules engine
- •Encodes hard constraints like minimum income, debt-to-income thresholds, KYC status, and country restrictions.
- •Keeps deterministic decisions outside the LLM.
- •
Underwriting analyst agent
- •Uses AutoGen to reason over structured applicant data and policy outputs.
- •Produces a recommendation: approve, reject, or manual review.
- •
Compliance reviewer agent
- •Checks whether the recommendation includes required rationale, adverse action reasons, and missing disclosures.
- •Flags anything that should not be auto-approved.
- •
Audit logger
- •Persists prompts, tool outputs, model responses, timestamps, and final decision.
- •Supports internal audit and regulatory review.
- •
Decision API
- •Exposes a single endpoint for downstream systems.
- •Returns both the decision and machine-readable reasons.
Implementation
- •Install AutoGen and define the input schema
Use pyautogen with a strict payload shape. In fintech, structured input beats free-form text because you can validate before the agent sees anything sensitive.
from dataclasses import dataclass
from typing import Literal, List
@dataclass
class Applicant:
applicant_id: str
country: str
income_monthly: float
debt_monthly: float
kyc_status: Literal["verified", "pending", "failed"]
employment_status: Literal["employed", "self_employed", "unemployed"]
requested_amount: float
credit_score: int
existing_defaults: int
- •Create an assistant agent with tools for deterministic checks
AutoGen’s AssistantAgent should not be the source of truth for policy. Put your underwriting rules in Python functions and expose them as tools via register_function.
import os
from autogen import AssistantAgent, UserProxyAgent
def calculate_dti(income_monthly: float, debt_monthly: float) -> float:
if income_monthly <= 0:
raise ValueError("income_monthly must be > 0")
return round(debt_monthly / income_monthly, 4)
def policy_check(applicant: dict) -> dict:
dti = calculate_dti(applicant["income_monthly"], applicant["debt_monthly"])
reasons = []
if applicant["kyc_status"] != "verified":
reasons.append("KYC not verified")
if applicant["country"] in {"IR", "KP", "SY"}:
reasons.append("Restricted jurisdiction")
if applicant["credit_score"] < 620:
reasons.append("Credit score below threshold")
if dti > 0.45:
reasons.append("DTI above threshold")
if applicant["existing_defaults"] > 0:
reasons.append("Existing defaults present")
return {"dti": dti, "reasons": reasons}
llm_config = {
"config_list": [
{
"model": os.environ["OPENAI_MODEL"],
"api_key": os.environ["OPENAI_API_KEY"],
}
],
"temperature": 0,
}
underwriter = AssistantAgent(
name="underwriter",
llm_config=llm_config,
)
user_proxy = UserProxyAgent(
name="system",
human_input_mode="NEVER",
)
user_proxy.register_function(
function_map={
"policy_check": policy_check,
"calculate_dti": calculate_dti,
}
)
- •Run the underwriting conversation and force structured output
The pattern here is simple: compute deterministic facts first, then let the agent write a recommendation using only those facts. This keeps the model out of policy enforcement and makes audit easier.
import json
applicant = Applicant(
applicant_id="A-10021",
country="KE",
income_monthly=2500.0,
debt_monthly=700.0,
kyc_status="verified",
employment_status="employed",
requested_amount=5000.0,
credit_score=681,
existing_defaults=0,
)
facts = policy_check(applicant.__dict__)
prompt = f"""
You are an underwriting assistant for a fintech lender.
Use only the provided facts.
Applicant:
{json.dumps(applicant.__dict__, indent=2)}
Policy facts:
{json.dumps(facts, indent=2)}
Return JSON with keys:
decision: approve|reject|manual_review
rationale: list of strings
risk_summary: string
"""
result = user_proxy.initiate_chat(
underwriter,
message=prompt,
)
print(result.chat_history[-1]["content"])
- •Add a compliance reviewer pass
In production you want a second agent that checks whether the recommendation is legally usable. This is where you catch missing adverse-action reasons or unsupported claims before they leave your system.
compliance_agent = AssistantAgent(
name="compliance_reviewer",
llm_config=llm_config,
)
review_prompt = """
Review this underwriting decision for compliance issues.
Check for missing rationale, unsupported claims, vague language,
and whether the output is suitable for audit logging.
Return JSON with keys:
status: pass|fail
issues: list of strings
"""
review_result = user_proxy.initiate_chat(
compliance_agent,
message=f"{review_prompt}\n\nDecision:\n{result.chat_history[-1]['content']}",
)
print(review_result.chat_history[-1]["content"])
Production Considerations
- •
Keep hard rules outside the LLM
- •Credit thresholds, jurisdiction blocks, fraud flags, and KYC status should be enforced in code.
- •The agent should explain decisions, not invent them.
- •
Log everything needed for audit
- •Store input payloads, rule outputs, model version, prompt text, response text, timestamps, and reviewer outcome.
- •Fintech audits usually care about reproducibility more than raw model quality.
- •
Control data residency and PII exposure
- •Redact SSNs, national IDs, account numbers, and salary slips before sending anything to the model.
- •Route EU or local-market applications to region-specific infrastructure if residency rules apply.
- •
Add approval gates for adverse outcomes
- •Rejects and manual reviews can often be automated; approvals above certain limits may need extra human sign-off.
- •Keep a human-in-the-loop path for borderline cases.
Common Pitfalls
- •
Letting the LLM decide policy
- •Mistake: asking the model to infer whether someone qualifies based on raw text alone.
- •Fix: compute DTI, KYC status checks, blacklist hits, and score thresholds in deterministic code first.
- •
Using free-form outputs in downstream systems
- •Mistake: parsing natural language from the agent as if it were a stable contract.
- •Fix: require JSON output with fixed keys and validate it before writing to core lending systems.
- •
Ignoring compliance language
- •Mistake: generating vague explanations like “high risk profile” with no traceable reason.
- •Fix: map every rejection to explicit adverse-action reasons such as low score, high DTI, unverifiable identity, or restricted jurisdiction.
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