In the realm of enterprise financial services, fraud detection is a high-stakes race against milliseconds. Traditional machine learning models and rule engines can process millions of transactions a day with sub-millisecond latency. However, they struggle with novel, complex fraud patterns (like sophisticated account takeovers or social engineering) and suffer from high false-positive rates.
Generative AI and Large Language Models (LLMs) offer unparalleled reasoning capabilities to solve this. But there is a massive architectural paradox: LLMs are too slow and expensive to sit in the critical path of 100% of transactions.
This article details how to design a scalable, low-latency, high-availability multi-agent fraud detection system using LangGraph, integrating RAG, memory, and state management to handle millions of daily transactions without breaking the bank or the SLA.
1. The Architecture: The "Shadow & Escalation" Pattern
To process millions of transactions in real-time, you cannot send every transaction to an LLM. Instead, we use a Tiered Escalation Architecture.
Tier 1: The Fast Path (Sub-50ms)
Tech Stack: Apache Kafka, Redis, Traditional ML (XGBoost), Rule Engines.
Function: Processes 99% of transactions. If a transaction is clearly safe (low risk) or clearly fraudulent (high risk based on hard rules/ML), it is resolved instantly.
Action: Only transactions that fall into the "ambiguous" middle zone (e.g., ML risk score between 40% and 70%) are escalated.
Tier 2: The Smart Path (LangGraph Multi-Agent System)
Tech Stack: LangGraph, Vector DB (Milvus/Qdrant), LLM Gateway, Redis (State/Memory).
Function: Processes the 1% of escalated, ambiguous transactions. The multi-agent system uses RAG to pull historical fraud cases and user behavioral memory to make a nuanced, context-aware decision.
2. Ensuring Low Latency and High Availability
When dealing with real-time payments, a 2-second delay means a declined transaction at the point of sale. Here is how we engineer for speed and resilience:
Aggressive Timeouts & Circuit Breakers: If the LLM or Vector DB takes longer than 800ms, the circuit breaker trips. The system immediately falls back to the Tier 1 ML model's score to prevent blocking the user.
Pre-computed Embeddings & Caching: User behavioral profiles and common merchant categories are pre-embedded and cached in Redis. The RAG step only queries the Vector DB for novel fraud patterns.
Asynchronous State Updates: The LangGraph system returns the decision synchronously to the payment gateway, but writes the final audit logs and updates the user's long-term memory asynchronously via Kafka.
Active-Active Multi-Region Deployment: LangGraph Server is deployed on Kubernetes across multiple regions. State is replicated using distributed databases (like PostgreSQL with Citus or Redis Cluster) to ensure zero downtime during regional failovers.
3. The Multi-Agent LangGraph Design
For the Tier 2 Smart Path, we deploy three specialized agents orchestrated by LangGraph:
Context & Memory Agent: Retrieves the user's historical transaction memory (from Redis/Vector DB) and similar past fraud cases (RAG).
Reasoning Agent: An LLM that analyzes the current transaction against the retrieved context to identify anomalies.
Decision & Action Agent: Synthesizes the reasoning into a final, deterministic decision (Approve, Decline, or Step-Up Authentication) and formats the API response.
4. Code Implementation
Below is an end-to-end implementation of the Tier 2 LangGraph system.
Note: For this runnable example, we use in-memory stores and mock LLM calls. In production, these are swapped for Redis, Milvus, and an LLM Gateway.
Step 1: Define the State and Memory
We define a robust state that carries the transaction details, retrieved context, and conversational/user memory.
import json
import time
import numpy as np
from typing import TypedDict, List, Literal, Annotated
from langgraph.graph import StateGraph, END, START
from langgraph.checkpoint.memory import MemorySaver
from langchain_community.vectorstores import FAISS
from langchain_core.embeddings import Embeddings
# --- Mock Embeddings for Runnable Example ---
class MockEmbeddings(Embeddings):
def embed_documents(self, texts: List[str]) -> List[List[float]]:
return [np.random.rand(128).tolist() for _ in texts]
def embed_query(self, text: str) -> List[float]:
return np.random.rand(128).tolist()
# --- RAG Vector Store (Simulating Milvus/Qdrant) ---
# In production, this contains historical fraud case studies and typologies
fraud_knowledge_base = [
"SIM Swap Attack: User suddenly changes device fingerprint and requests wire transfer to new crypto exchange.",
"Account Takeover: Login from new geo-location followed by immediate max-out of credit limit.",
"Mule Account: Rapid succession of small incoming transfers followed by a single large outgoing wire."
]
vectorstore = FAISS.from_texts(fraud_knowledge_base, MockEmbeddings())
# --- LangGraph State Definition ---
class FraudState(TypedDict):
transaction_id: str
user_id: str
amount: float
merchant: str
risk_score_tier1: float
# Memory & Context
user_behavior_memory: List[str]
retrieved_fraud_cases: List[str]
# Agent Outputs
analysis: str
final_decision: Literal["APPROVE", "DECLINE", "STEP_UP_AUTH"]
latency_ms: float
Step 2: Implement the Agents (Nodes)
A. Context & Memory Agent (RAG & State Retrieval)
def context_agent(state: FraudState):
start_time = time.time()
# 1. Retrieve User Memory (Simulating Redis lookup)
# In prod: fetch from Redis based on user_id
user_memory = [
f"User {state['user_id']} usually spends <$500 at grocery stores.",
f"User {state['user_id']} has never wired money internationally before."
]
# 2. RAG: Retrieve similar historical fraud cases
query = f"Transaction: {state['amount']} at {state['merchant']}. User behavior: {user_memory[1]}"
docs = vectorstore.similarity_search(query, k=2)
retrieved_cases = [doc.page_content for doc in docs]
state["user_behavior_memory"] = user_memory
state["retrieved_fraud_cases"] = retrieved_cases
state["latency_ms"] += (time.time() - start_time) * 1000
return state
B. Reasoning Agent (LLM Analysis)
def reasoning_agent(state: FraudState):
start_time = time.time()
# Format prompt context
context_str = "\n".join(state["retrieved_fraud_cases"])
memory_str = "\n".join(state["user_behavior_memory"])
prompt = f"""
Analyze this transaction for fraud.
Transaction: ${state['amount']} at {state['merchant']}
User History: {memory_str}
Similar Past Fraud Cases: {context_str}
Provide a brief reasoning on why this is or isn't fraudulent.
"""
# Mock LLM Call (In prod: invoke ChatOpenAI or internal LLM Gateway)
# Simulating LLM latency
time.sleep(0.1)
analysis = f"Analysis: The amount ${state['amount']} at {state['merchant']} deviates from normal grocery spending. Combined with the lack of international wire history, this mimics an Account Takeover pattern."
state["analysis"] = analysis
state["latency_ms"] += (time.time() - start_time) * 1000
return state
C. Decision & Action Agent (Final Routing)
def decision_agent(state: FraudState):
start_time = time.time()
# Mock LLM Call to extract final decision based on analysis
# In prod, use structured output (Pydantic) to guarantee the decision format
if "Account Takeover" in state["analysis"] or "SIM Swap" in state["analysis"]:
decision = "STEP_UP_AUTH" # Require 2FA/Biometrics
elif "Mule Account" in state["analysis"]:
decision = "DECLINE"
else:
decision = "APPROVE"
state["final_decision"] = decision
state["latency_ms"] += (time.time() - start_time) * 1000
return state
Step 3: Build and Compile the LangGraph
We wire the agents together. We also add a Circuit Breaker / Timeout check conceptually (handled via LangGraph's execution limits or custom wrapper in production).
def build_fraud_graph():
workflow = StateGraph(FraudState)
# Add Nodes
workflow.add_node("context_agent", context_agent)
workflow.add_node("reasoning_agent", reasoning_agent)
workflow.add_node("decision_agent", decision_agent)
# Define Edges
workflow.add_edge(START, "context_agent")
workflow.add_edge("context_agent", "reasoning_agent")
workflow.add_edge("reasoning_agent", "decision_agent")
workflow.add_edge("decision_agent", END)
# Compile with Memory (Checkpointer)
# In production, use PostgresSaver or RedisSaver for distributed state
memory = MemorySaver()
return workflow.compile(checkpointer=memory)
app = build_fraud_graph()
Step 4: Execution and Observability
Let's simulate an escalated transaction and observe the state, memory, and latency.
def process_transaction(transaction_data, thread_id):
print(f"\n{'='*50}")
print(f"Processing Transaction: {transaction_data['transaction_id']}")
print(f"{'='*50}")
config = {"configurable": {"thread_id": thread_id}}
# Initialize State
initial_state = {
"transaction_id": transaction_data["transaction_id"],
"user_id": transaction_data["user_id"],
"amount": transaction_data["amount"],
"merchant": transaction_data["merchant"],
"risk_score_tier1": 0.65, # Escalated because score is between 0.4 and 0.7
"user_behavior_memory": [],
"retrieved_fraud_cases": [],
"analysis": "",
"final_decision": "",
"latency_ms": 0.0
}
# Invoke Graph
final_state = app.invoke(initial_state, config=config)
# Output Results
print(f"User Memory Retrieved: {final_state['user_behavior_memory']}")
print(f"RAG Cases Retrieved: {final_state['retrieved_fraud_cases']}")
print(f"LLM Analysis: {final_state['analysis']}")
print(f"FINAL DECISION: {final_state['final_decision']}")
print(f"Total LangGraph Latency: {final_state['latency_ms']:.2f} ms")
# Run the simulation
if __name__ == "__main__":
# Scenario: User buying groceries (Normal) vs User wiring money (Suspicious)
# 1. Suspicious Transaction (Escalated from Tier 1)
suspicious_tx = {
"transaction_id": "TXN-9981",
"user_id": "USR-442",
"amount": 8500.00,
"merchant": "Global Crypto Exchange"
}
process_transaction(suspicious_tx, thread_id="session_tx_9981")
5. Enterprise Production Considerations
To take this from a prototype to a system handling millions of daily transactions, implement the following:
State Management at Scale
Do not use the in-memory MemorySaver. Use LangGraph's PostgresSaver or RedisSaver. This allows the LangGraph server to be horizontally scaled across dozens of pods, as the state is externalized and highly available.
Structured Outputs for Decisions
Never trust raw LLM text for a financial decision. Use LangChain's with_structured_output (Pydantic) in the Decision Agent to force the LLM to return a strict JSON schema. If the LLM fails to parse, the system defaults to a safe fallback (e.g., STEP_UP_AUTH).
Observability (LangSmith)
Integrate LangSmith immediately. In fraud detection, you must be able to trace exactly which RAG document caused the LLM to decline a transaction. LangSmith provides the trace-level observability required for regulatory audits and model debugging.
Continuous Learning (Memory Updates)
After a transaction is resolved, an asynchronous background agent should update the Vector DB. If a transaction was confirmed as fraud by the user later, that case is embedded and added to the RAG knowledge base, making the system smarter over time.
Conclusion
Building a real-time fraud detection system with GenAI is not about replacing traditional ML; it is about augmenting it. By using a tiered architecture, you preserve the sub-millisecond latency required for 99% of transactions. For the complex edge cases, LangGraph provides the deterministic orchestration, RAG provides the historical context, and state management ensures every decision is auditable. This hybrid approach delivers the intelligence of AI with the reliability and speed required by enterprise financial systems.

Join the conversation! Your thoughts help the community grow.