How to Build a underwriting Agent Using CrewAI in Python for fintech
An underwriting agent automates the first pass of credit or risk review: it gathers applicant data, checks policy rules, scores risk, and produces a decision recommendation with an audit trail. For fintech, that matters because underwriting is where speed, consistency, and compliance collide — you want faster approvals without turning your model into a black box.
Architecture
A production underwriting agent in CrewAI needs these pieces:
- •
Intake layer
- •Accepts application payloads from your API or queue.
- •Normalizes fields like income, debt, business type, tenure, and geography.
- •
Policy retrieval layer
- •Pulls underwriting rules from a versioned source of truth.
- •Keeps product-specific thresholds out of prompts and code.
- •
Risk analysis agent
- •Reviews applicant data against policy and returns a structured recommendation.
- •Flags missing docs, inconsistent values, and high-risk indicators.
- •
Compliance checker
- •Verifies the decision path against KYC/AML, fair lending, and internal policy constraints.
- •Produces explainability notes for audit.
- •
Decision orchestrator
- •Combines outputs from multiple agents into approve / refer / decline.
- •Enforces deterministic business rules outside the LLM.
- •
Audit sink
- •Stores inputs, outputs, prompt versions, model versions, and final decisions.
- •Required for disputes, regulator reviews, and internal QA.
Implementation
1) Define the underwriting task inputs and agents
Use CrewAI agents for analysis and compliance review, but keep final decision logic in Python. That gives you traceability and avoids letting the LLM make uncontrolled policy calls.
from crewai import Agent, Task, Crew, Process
from pydantic import BaseModel
from typing import Literal
class UnderwritingInput(BaseModel):
applicant_id: str
monthly_income: float
monthly_debt: float
credit_score: int
country: str
requested_amount: float
employment_years: float
risk_agent = Agent(
role="Risk Analyst",
goal="Assess underwriting risk using applicant financial data and policy rules",
backstory="You are a conservative credit risk analyst for a fintech lender.",
verbose=True,
)
compliance_agent = Agent(
role="Compliance Reviewer",
goal="Check the recommendation for policy violations and auditability",
backstory="You review decisions for KYC/AML, fair lending, and documentation gaps.",
verbose=True,
)
risk_task = Task(
description=(
"Review the applicant data and return a concise risk assessment with "
"a recommended action: approve, refer, or decline."
),
expected_output="A short structured assessment with key risk factors.",
agent=risk_agent,
)
compliance_task = Task(
description=(
"Review the risk assessment for compliance issues. Highlight missing evidence "
"or policy concerns that require manual review."
),
expected_output="A short compliance note with any blockers.",
agent=compliance_agent,
)
2) Run the crew with sequential process
For underwriting, Process.sequential is usually the right default. Risk analysis should happen before compliance review so the second agent can inspect the first output.
def run_underwriting(application: UnderwritingInput):
crew = Crew(
agents=[risk_agent, compliance_agent],
tasks=[risk_task, compliance_task],
process=Process.sequential,
verbose=True,
)
result = crew.kickoff(inputs={
"applicant_id": application.applicant_id,
"monthly_income": application.monthly_income,
"monthly_debt": application.monthly_debt,
"credit_score": application.credit_score,
"country": application.country,
"requested_amount": application.requested_amount,
"employment_years": application.employment_years,
"dti": round(application.monthly_debt / max(application.monthly_income, 1), 4),
})
return result
3) Add deterministic decision logic outside the LLM
This is where fintech teams usually get burned. The model can recommend; your code decides based on hard thresholds and policy gates.
def decide(application: UnderwritingInput):
dti = application.monthly_debt / max(application.monthly_income, 1)
if application.country not in {"ZA", "KE", "NG"}:
return "refer"
if application.credit_score < 580:
return "decline"
if dti > 0.45:
return "refer"
if application.requested_amount > application.monthly_income * 12:
return "refer"
return "approve"
if __name__ == "__main__":
app = UnderwritingInput(
applicant_id="app_123",
monthly_income=25000,
monthly_debt=8000,
credit_score=640,
country="ZA",
requested_amount=120000,
employment_years=3.5,
)
crew_result = run_underwriting(app)
final_decision = decide(app)
print("Crew output:", crew_result)
print("Final decision:", final_decision)
4) Store an audit record
For regulated lending or insurance underwriting, log every input and output. You need to reconstruct why a decision happened months later.
import json
from datetime import datetime
def build_audit_record(application: UnderwritingInput, crew_result: str, decision: str):
record = {
"timestamp": datetime.utcnow().isoformat(),
"applicant_id": application.applicant_id,
"inputs": application.model_dump(),
"crew_result": str(crew_result),
"decision": decision,
"policy_version": "uw_policy_v7",
"model_provider": "your-configured-crewai-llm",
}
return record
audit_record = build_audit_record(app, crew_result, final_decision)
print(json.dumps(audit_record, indent=2))
Production Considerations
- •
Keep sensitive data out of prompts where possible
- •Mask national IDs, bank account numbers, and full addresses before sending data to agents.
- •Use tokenized references when you only need joins from internal systems.
- •
Enforce data residency
- •If your regulatory boundary requires local processing, pin your model provider and storage region.
- •Don’t let an external tool call move PII across borders without legal approval.
- •
Add observability around every step
- •Log task latency, token usage, refusal rates, manual-review rates, and false positive declines.
- •Track prompt version and model version alongside each decision.
- •
Build guardrails into code
- •Never let an agent directly approve or decline without deterministic rules.
- •Require human review for edge cases like thin-file applicants or mismatched identity signals.
Common Pitfalls
- •
Letting the LLM make the final credit decision
- •Fix it by keeping approval thresholds in Python or a rules engine.
- •The agent should explain; your system should decide.
- •
Using free-form outputs with no schema
- •Fix it by forcing structured summaries in your task design.
- •Parseable outputs make downstream orchestration and audits much easier.
- •
Ignoring compliance metadata
- •Fix it by storing prompt text, policy version, model version, timestamps, and reviewer identity.
- •Without this trail you will struggle during audits or dispute resolution.
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