How to Build a fraud detection Agent Using LangGraph in Python for investment banking

By Cyprian AaronsUpdated 2026-04-21
fraud-detectionlanggraphpythoninvestment-banking

A fraud detection agent in investment banking is not a chatbot that “spots suspicious activity.” It is a workflow engine that ingests trade, payment, and client activity signals, checks them against policy and historical patterns, escalates risky cases, and writes an audit trail that compliance can defend. That matters because the bank needs fast triage without losing control over explainability, data residency, and regulatory evidence.

Architecture

  • Input normalization layer

    • Converts raw events from FIX messages, payment rails, CRM actions, and case management systems into a single typed payload.
    • Enforces schema validation before any model or rule runs.
  • Risk scoring node

    • Combines deterministic rules with model output.
    • Flags patterns like unusual trade size, repeated order cancellations, beneficiary changes, or out-of-hours activity.
  • Policy and compliance gate

    • Applies investment banking controls.
    • Checks jurisdiction constraints, restricted lists, KYC status, AML thresholds, and escalation rules.
  • Investigation planner

    • Decides whether to enrich with more context or escalate immediately.
    • Pulls in client history, account metadata, recent orders, and prior alerts.
  • Case writer

    • Produces a structured fraud summary for downstream case management.
    • Stores reason codes, evidence references, and decision trace for audit.
  • Human escalation path

    • Routes high-risk or ambiguous cases to an analyst.
    • Ensures the agent never auto-closes regulated scenarios without approval.

Implementation

1. Define the state and the graph nodes

Use StateGraph for explicit control over each step. In investment banking, keep the state structured so every decision can be audited later.

from typing import TypedDict, List, Literal
from langgraph.graph import StateGraph, START, END

class FraudState(TypedDict):
    event_id: str
    client_id: str
    amount: float
    jurisdiction: str
    risk_score: float
    reasons: List[str]
    decision: Literal["review", "escalate", "clear"]
    case_summary: str

def normalize_event(state: FraudState) -> FraudState:
    return state

def score_risk(state: FraudState) -> FraudState:
    score = 0.0
    reasons = []

    if state["amount"] > 500000:
        score += 0.45
        reasons.append("Large transaction amount")

    if state["jurisdiction"] in {"IR", "RU", "KP"}:
        score += 0.35
        reasons.append("High-risk jurisdiction")

    if score >= 0.7:
        decision = "escalate"
    elif score >= 0.4:
        decision = "review"
    else:
        decision = "clear"

    return {
        **state,
        "risk_score": min(score, 1.0),
        "reasons": reasons,
        "decision": decision,
    }

def write_case(state: FraudState) -> FraudState:
    summary = (
        f"Event {state['event_id']} for client {state['client_id']} "
        f"scored {state['risk_score']:.2f}. Reasons: {', '.join(state['reasons']) or 'none'}."
    )
    return {**state, "case_summary": summary}

2. Add conditional routing with add_conditional_edges

This is where LangGraph is useful. You can encode policy-driven branching instead of burying logic inside one large function.

def route_case(state: FraudState) -> str:
    if state["decision"] == "escalate":
        return "write_case"
    if state["decision"] == "review":
        return "write_case"
    return END

graph = StateGraph(FraudState)
graph.add_node("normalize_event", normalize_event)
graph.add_node("score_risk", score_risk)
graph.add_node("write_case", write_case)

graph.add_edge(START, "normalize_event")
graph.add_edge("normalize_event", "score_risk")
graph.add_conditional_edges(
    "score_risk",
    route_case,
    {
        "write_case": "write_case",
        END: END,
    },
)
graph.add_edge("write_case", END)

app = graph.compile()

3. Run the agent on an event and persist the output

In production you would store this result in your case management system and push the trace to your audit store.

input_state: FraudState = {
    "event_id": "EVT-2026-00091",
    "client_id": "C10293",
    "amount": 1250000.00,
    "jurisdiction": "GB",
    "risk_score": 0.0,
    "reasons": [],
    "decision": "clear",
    "case_summary": "",
}

result = app.invoke(input_state)
print(result["decision"])
print(result["case_summary"])

4. Extend with human review hooks

For real banking workflows, add a manual approval path for borderline cases using interrupt/resume patterns or a separate analyst queue. The key point is that the graph should make escalation explicit instead of pretending every alert can be auto-resolved.

Production Considerations

  • Compliance logging

    • Store every node input/output with timestamps and versioned policy IDs.
    • Regulators will care about why the agent escalated a trade or payment alert.
  • Data residency

    • Keep client data in-region.
    • If your bank operates across EMEA and APAC, do not send raw PII or trade details to a model endpoint outside approved jurisdictions.
  • Monitoring

    • Track false positive rate, analyst override rate, average time to triage, and alert volume by desk or product.
    • A spike in one desk may indicate rule drift or bad upstream data.
  • Guardrails

    • Hard-code restricted-list checks outside the model path.
    • For investment banking use cases, deterministic controls must win over probabilistic outputs when AML/KYC policy is involved.

Common Pitfalls

  • Putting all logic into one LLM call

    • That makes auditability weak and failure modes messy.
    • Split normalization, scoring, routing, and case writing into separate LangGraph nodes.
  • Using unstructured state

    • Free-form dictionaries become impossible to validate at scale.
    • Use TypedDict or Pydantic-style schemas so downstream systems know exactly what each field means.
  • Skipping human escalation design

    • Some alerts must go to an analyst no matter how confident the model feels.
    • Build explicit review branches for high-value trades, sanctioned jurisdictions, unusual counterparties, and anything touching regulated client accounts.

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