How to Build a fraud detection Agent Using AutoGen in Python for payments

By Cyprian AaronsUpdated 2026-04-21
fraud-detectionautogenpythonpayments

A fraud detection agent for payments watches transaction events, reasons over risk signals, and decides whether to approve, step-up, hold, or escalate a payment for review. For card-not-present flows, instant transfers, and wallet payouts, that matters because the wrong decision costs real money: chargebacks, false declines, manual review load, and compliance exposure.

Architecture

  • Transaction intake service

    • Receives payment events from your gateway, PSP, or internal ledger.
    • Normalizes fields like amount, currency, merchant_id, customer_id, device_id, IP, and velocity counters.
  • Risk feature builder

    • Computes signals the agent can reason over:
      • amount anomalies
      • geo mismatch
      • device fingerprint changes
      • velocity spikes
      • beneficiary novelty
    • Keep this deterministic and outside the LLM.
  • AutoGen risk analyst agent

    • Uses AssistantAgent to interpret the structured transaction context.
    • Produces a decision plus rationale in a strict schema.
  • Policy / approval agent

    • Enforces payment rules such as:
      • block high-risk transfers above threshold
      • require step-up auth for medium risk
      • route edge cases to human review
    • This is where compliance rules live.
  • Audit logger

    • Stores input features, model output, final action, timestamps, and reviewer overrides.
    • Required for disputes, internal controls, and regulatory traceability.
  • Human review queue

    • Handles uncertain cases.
    • Prevents the agent from being the final authority on high-value or regulated flows.

Implementation

1) Install AutoGen and define a strict transaction schema

Use a structured payload so the agent sees consistent inputs. In payments systems, free-form text is how you get bad decisions and bad audits.

from pydantic import BaseModel, Field
from typing import Literal

class TransactionRiskInput(BaseModel):
    transaction_id: str
    amount: float = Field(gt=0)
    currency: str
    merchant_id: str
    customer_id: str
    country: str
    ip_country: str
    device_trust_score: float = Field(ge=0.0, le=1.0)
    velocity_5m: int = Field(ge=0)
    account_age_days: int = Field(ge=0)
    payment_method: Literal["card", "bank_transfer", "wallet", "crypto"]

2) Create an AutoGen assistant that returns a decision in JSON

This uses autogen.AssistantAgent and a plain UserProxyAgent. The assistant should only analyze the provided data and return a compact decision object.

import autogen

llm_config = {
    "config_list": [
        {
            "model": "gpt-4o-mini",
            "api_key": os.environ["OPENAI_API_KEY"],
        }
    ],
    "temperature": 0,
}

fraud_analyst = autogen.AssistantAgent(
    name="fraud_analyst",
    llm_config=llm_config,
    system_message=(
        "You are a payments fraud analyst. "
        "Given structured transaction data, return JSON with keys: "
        "decision (approve|step_up|hold|block), risk_score (0-100), "
        "reason_codes (array of short strings), and summary. "
        "Do not invent missing fields."
    ),
)

user_proxy = autogen.UserProxyAgent(
    name="risk_system",
    human_input_mode="NEVER",
)

3) Run the analysis and parse the result into an enforcement layer

The key pattern is: LLM analyzes, deterministic code enforces. Do not let the model directly move money.

import json
import os

def assess_transaction(txn: TransactionRiskInput) -> dict:
    prompt = f"""
Analyze this payment transaction and return JSON only:

{txn.model_dump_json(indent=2)}
"""
    user_proxy.initiate_chat(
        fraud_analyst,
        message=prompt,
        clear_history=True,
        max_turns=1,
    )

def enforce_policy(risk_result: dict) -> str:
    score = risk_result["risk_score"]
    decision = risk_result["decision"]

    if score >= 85:
        return "block"
    if score >= 60:
        return "hold"
    if decision == "step_up":
        return "step_up"
    return "approve"

txn = TransactionRiskInput(
    transaction_id="tx_10001",
    amount=1250.00,
    currency="USD",
    merchant_id="m_7788",
    customer_id="c_4421",
    country="US",
    ip_country="NG",
    device_trust_score=0.18,
    velocity_5m=7,
)

4) Add audit logging before any downstream action

For payments, every decision needs an evidence trail. Store both the model output and the policy override so you can explain why a transfer was blocked or approved.

from datetime import datetime

def audit_event(txn: TransactionRiskInput, analysis_text: str, final_action: str) -> dict:
    record = {
        "timestamp": datetime.utcnow().isoformat(),
        "transaction_id": txn.transaction_id,
        "input": txn.model_dump(),
        "analysis_text": analysis_text,
        "final_action": final_action,
        "model_name": llm_config["config_list"][0]["model"],
        "policy_version": "payments-fraud-v1",
    }
    
# Example usage would persist `record` to your audit store.

Production Considerations

  • Keep sensitive payment data out of prompts

    • Mask PANs, tokenize account numbers, and avoid raw PII where possible.
    • Use feature IDs or derived signals instead of full customer profiles.
  • Enforce data residency

    • If transactions must stay in-region for regulatory reasons, route model calls through region-specific infrastructure.
    • Do not send EU payment data to an unauthorized region just because the API is available there.
  • Build hard guardrails around actions

    • The agent should recommend; your policy engine should decide.
    • High-value transfers should require deterministic thresholds plus human approval for edge cases.
  • Monitor fraud ops metrics

    • Track false positives, false negatives, chargeback rate, manual review rate, latency per decision.
    • Split metrics by corridor, payment method, issuer country, merchant category code, and customer segment.

Common Pitfalls

  1. Letting the LLM make the final payment decision

    • Mistake: using model output directly as approve or block.
    • Avoid it by placing a deterministic policy layer between analysis and execution.
  2. Sending raw cardholder or bank data into prompts

    • Mistake: passing PANs, CVVs, IBANs, or full addresses to the model.
    • Avoid it by tokenizing sensitive fields and only sending derived features needed for reasoning.
  3. Ignoring auditability and replay

    • Mistake: storing only the final label with no evidence trail.
    • Avoid it by logging input features, model output text or structured JSON, policy version, and reviewer overrides.
  4. Treating all corridors the same

    • Mistake: one global threshold for every market.
    • Avoid it by tuning policies per region because fraud patterns differ across card-present vs card-not-present flows, domestic vs cross-border payments, and regulated vs unregulated rails.

Keep learning

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

Related Guides