How to Build a fraud detection Agent Using AutoGen in TypeScript for retail banking
A fraud detection agent in retail banking triages suspicious activity, gathers context from internal systems, and recommends an action: approve, step-up authenticate, hold for review, or escalate to a human analyst. It matters because fraud losses move fast, but false positives are just as expensive when they block legitimate card payments, transfers, or account access.
Architecture
- •
Event ingress
- •Receives transaction events from card auth, ACH, wire, login, and beneficiary change streams.
- •Normalizes payloads into a single case format before the agent sees them.
- •
Risk context fetchers
- •Pulls customer profile, device history, velocity metrics, account tenure, sanctions flags, and prior disputes.
- •Keep these as deterministic tools; don’t ask the model to “guess” from raw logs.
- •
AutoGen agent team
- •A primary investigator agent summarizes the case.
- •A policy/compliance agent checks actions against bank rules.
- •A reviewer/human-escalation path handles ambiguous or high-risk cases.
- •
Decision engine
- •Converts agent output into one of a small set of actions:
ALLOW,STEP_UP,HOLD,ESCALATE. - •Never let the model directly execute blocking decisions without policy gating.
- •Converts agent output into one of a small set of actions:
- •
Audit trail
- •Stores prompts, tool calls, model outputs, final decision, timestamps, and case IDs.
- •Required for model governance, dispute handling, and regulator review.
- •
Controls layer
- •Enforces data minimization, residency constraints, redaction of PII, and model allowlists.
- •This is where you stop sensitive fields from leaking into prompts.
Implementation
1) Set up AutoGen agents and tools
Use AutoGen’s TypeScript API to create an assistant that investigates fraud cases and a user proxy that can call tools. For retail banking, keep the tools narrow: fetch only what the agent needs to make a decision.
import { AssistantAgent, UserProxyAgent } from "@autogenai/autogen";
import { z } from "zod";
type FraudCase = {
caseId: string;
customerId: string;
amount: number;
currency: string;
channel: "card" | "ach" | "wire" | "login";
};
const getRiskContext = async (input: FraudCase) => {
return {
customerTenureDays: 820,
deviceSeenBefore: false,
velocity24hCount: 7,
priorChargebacks90d: 2,
sanctionsHit: false,
countryMismatch: true,
residentCountry: "ZA",
txnCountry: "NG",
kycTier: "standard",
};
};
const investigator = new AssistantAgent({
name: "fraud_investigator",
systemMessage:
"You assess retail banking fraud cases. Return only one of ALLOW, STEP_UP, HOLD, ESCALATE with a short reason. Do not invent facts.",
});
const proxy = new UserProxyAgent({
name: "fraud_proxy",
humanInputMode: "NEVER",
});
2) Register deterministic tools and enforce structured output
The model should inspect context fetched from systems of record. Use a strict schema so downstream services can parse the result without brittle string matching.
const DecisionSchema = z.object({
action: z.enum(["ALLOW", "STEP_UP", "HOLD", "ESCALATE"]),
reason: z.string().min(10),
});
proxy.registerFunction(
{
name: "get_risk_context",
description: "Fetch bank-approved risk signals for a fraud case",
parameters: z.object({
caseId: z.string(),
customerId: z.string(),
amount: z.number(),
currency: z.string(),
channel: z.enum(["card", "ach", "wire", "login"]),
}),
execute: async (args) => getRiskContext(args),
},
);
async function runFraudCase(input: FraudCase) {
const context = await getRiskContext(input);
const message = `
Case:
${JSON.stringify(input)}
Risk context:
${JSON.stringify(context)}
Decide the safest action for retail banking.
`;
const result = await proxy.initiateChat(investigator, message);
return result;
}
3) Add policy gating before any action is taken
In banking you do not want “model says hold” to become an automatic decline if policy requires human review above a threshold. Gate the output with explicit rules before writing back to your case management system.
function applyPolicy(caseInput: FraudCase, decisionText: string) {
const parsed = DecisionSchema.parse(JSON.parse(decisionText));
if (caseInput.amount > 10000 && parsed.action === "ALLOW") {
return {
action: "ESCALATE",
reason: "High-value transaction requires analyst review under bank policy.",
};
}
if (parsed.action === "HOLD" && caseInput.channel === "login") {
return {
action: "STEP_UP",
reason: parsed.reason,
};
}
return parsed;
}
4) Wire it into an execution path with audit logging
Store every decision with enough detail to reconstruct why it happened. That includes model version, prompt hash, tool outputs, and final policy override.
async function handleFraudEvent(input: FraudCase) {
if (!input.caseId || !input.customerId) throw new Error("Invalid case");
const rawResult = await runFraudCase(input);
const finalDecision = applyPolicy(input as FraudCase, rawResult.chatHistory?.at(-1)?.content ?? "{}");
await auditLog.write({
caseId: input.caseId,
customerIdHash: hash(input.customerId),
channel: input.channel,
amountBucketed: Math.floor(input.amount / 1000) * 1000,
decisionTextIndexableHash:
hash(JSON.stringify(finalDecision)),
modelName:"gpt-4o-mini",
timestamp:new Date().toISOString(),
});
return finalDecision;
}
Production Considerations
- •
Deployment
- •Run the agent inside your bank’s controlled network boundary or approved cloud region.
- •Keep prompts and tool outputs in-region if your data residency policy requires it.
- •
Monitoring
- •Track precision/recall by channel (
card,ACH,wire,login) and by customer segment.
- •Track precision/recall by channel (
- •
Alert on spikes in
ESCALATEorSTEP_UP; those often indicate prompt drift or upstream data quality issues. - •
Guardrails
- •
Redact PANs, account numbers, national IDs, and free-text notes before sending anything to the model.
- •
Enforce allowlisted actions only; never let the model emit arbitrary remediation steps.
- •
Compliance
- •
Log immutable audit trails for model inputs/outputs and policy overrides.
- •
Make retention windows explicit so you don’t violate internal records policies or local banking regulations.
Common Pitfalls
- •
Letting the LLM see raw sensitive data
- •Don’t pass full statements, full PANs, or unredacted KYC documents into prompts.
- •Fix it by pre-processing inputs into risk signals and masked identifiers.
- •
Using the model as the final authority
- •If you let the assistant directly decline transactions above threshold amounts, you will create avoidable customer harm and governance problems.
- •Fix it with deterministic policy gates before any external action.
- •
Skipping auditability
- •If you can’t explain why a transaction was held or escalated three months later, you will struggle during disputes and internal reviews.
- •Fix it by persisting prompt hashes, tool results summaries, final decisions, and policy overrides for every case.
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