How to Build a fraud detection Agent Using LangChain in Python for pension funds
A fraud detection agent for pension funds inspects transactions, member profile changes, and claim events to flag suspicious activity before money moves or records are corrupted. It matters because pension operations carry long-lived liabilities, strict audit requirements, and a low tolerance for false negatives: one missed fraudulent withdrawal or identity takeover can create regulatory exposure and real financial loss.
Architecture
- •
Data ingestion layer
- •Pulls events from pension admin systems, claims platforms, CRM, and payment rails.
- •Normalizes fields like member ID, change type, amount, device fingerprint, IP, and approver.
- •
Risk scoring tools
- •Deterministic checks for policy violations, duplicate bank accounts, unusual payout timing, and beneficiary changes.
- •Fast rules should run before any LLM call.
- •
LangChain agent
- •Uses
ChatOpenAIwith tool calling to decide which checks to run. - •Produces a structured fraud assessment instead of free-form text.
- •Uses
- •
Evidence store
- •Keeps the exact inputs, tool outputs, model response, and final decision.
- •Required for auditability and internal investigations.
- •
Case management integration
- •Escalates high-risk cases into a queue for human review.
- •Sends the risk summary to a ticketing or GRC system.
- •
Policy guardrails
- •Enforces data residency, PII redaction, and no-autonomous-action rules.
- •Prevents the agent from approving payouts or changing member records directly.
Implementation
- •Define the fraud signals and tools
Use simple Python functions wrapped as LangChain tools. In pension funds, the most useful first-pass signals are not exotic ML features; they are policy breaches, repeated bank detail changes, high-value withdrawals near retirement date changes, and mismatches between member profile data and transaction metadata.
from typing import Dict, Any
from langchain_core.tools import tool
@tool
def check_duplicate_bank_account(member_id: str) -> Dict[str, Any]:
"""Check whether the member bank account is shared with other members."""
# Replace with real DB query
shared_accounts = {"M123": True}
return {
"member_id": member_id,
"shared_bank_account": shared_accounts.get(member_id, False),
"risk": "high" if shared_accounts.get(member_id, False) else "low",
}
@tool
def check_recent_profile_changes(member_id: str) -> Dict[str, Any]:
"""Check for recent changes to address, phone number, or beneficiary."""
# Replace with real event lookup
recent_changes = {"M123": ["bank_account", "beneficiary"]}
changes = recent_changes.get(member_id, [])
return {
"member_id": member_id,
"recent_changes": changes,
"risk": "high" if len(changes) >= 2 else "medium" if changes else "low",
}
@tool
def check_withdrawal_pattern(amount: float) -> Dict[str, Any]:
"""Flag unusually large withdrawals."""
threshold = 25000.0
return {
"amount": amount,
"threshold": threshold,
"risk": "high" if amount >= threshold else "low",
}
- •Build the LangChain agent with tool calling
This pattern uses ChatOpenAI, bind_tools, and create_tool_calling_agent. The model chooses which checks to run based on the case payload. Keep the prompt explicit: the agent is a triage assistant that returns a risk assessment and escalation recommendation, not an auto-decider.
import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain.agents import create_tool_calling_agent, AgentExecutor
llm = ChatOpenAI(
model="gpt-4o-mini",
temperature=0,
api_key=os.environ["OPENAI_API_KEY"],
)
tools = [
check_duplicate_bank_account,
check_recent_profile_changes,
check_withdrawal_pattern,
]
prompt = ChatPromptTemplate.from_messages([
("system", """
You are a fraud triage agent for a pension fund.
Your job is to assess suspicious activity using available tools.
Do not approve transactions. Do not modify records.
Return concise reasoning with a final risk level: low, medium, or high.
If evidence suggests identity theft, account takeover, or policy breach,
recommend escalation to human review immediately.
"""),
("human", """
Review this case:
member_id: {member_id}
amount: {amount}
event_type: {event_type}
"""),
])
agent = create_tool_calling_agent(llm=llm, tools=tools, prompt=prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=False)
result = executor.invoke({
"member_id": "M123",
"amount": 42000.0,
"event_type": "withdrawal_request",
})
print(result["output"])
- •Add structured output for downstream systems
For production use in pension operations you want machine-readable output. Use PydanticOutputParser so your case management system can consume consistent fields like risk_level, escalate, and evidence.
from pydantic import BaseModel, Field
from langchain_core.output_parsers import PydanticOutputParser
class FraudAssessment(BaseModel):
risk_level: str = Field(description="low, medium, or high")
escalate: bool = Field(description="Whether this case needs human review")
summary: str = Field(description="Short explanation of why the case was flagged")
parser = PydanticOutputParser(pydantic_object=FraudAssessment)
structured_prompt = ChatPromptTemplate.from_messages([
("system", f"""
You are a pension fraud triage assistant.
{parser.get_format_instructions()}
"""),
("human", "{case_text}"),
])
chain = structured_prompt | llm | parser
assessment = chain.invoke({
"case_text": """
Member M123 requested a £42,000 withdrawal after two bank detail changes
and a beneficiary update within 48 hours.
"""
})
print(assessment.model_dump())
- •Persist evidence for audit
Every decision should be reproducible. Store raw input events, tool outputs, model output, timestamp, model version, and reviewer outcome in your audit log or case table. Pension funds need this for internal control testing and regulator queries.
Production Considerations
- •
Data residency
- •Keep member data in-region if your jurisdiction requires it.
- •If you use hosted LLMs outside your primary region boundary without controls you will create compliance work you do not want.
- •
Auditability
- •Log every tool call with request parameters and returned values.
- •Version prompts and models so investigators can reconstruct why a case was escalated.
- •
Guardrails
- •Redact national IDs, bank account numbers, and medical/benefit-related notes before sending text to the model.
- •Restrict the agent to read-only actions; only humans should approve payouts or update member records.
- •
Monitoring
- •Track false positives by case type: withdrawals will behave differently from beneficiary changes or address updates.
- •Watch latency because fraud triage often sits on operational workflows that expect sub-second responses.
Common Pitfalls
- •
Letting the LLM make final decisions
- •The agent should recommend escalation; it should not authorize payments or reject claims on its own.
- •Fix this by keeping deterministic thresholds in code and using the LLM only for triage summaries.
- •
Sending raw PII into prompts
- •Pension data often includes bank details, tax IDs, addresses, dependants’ information, and sensitive employment history.
- •Fix this with preprocessing that masks sensitive fields before invoking
ChatOpenAI.
- •
Skipping evidence capture
- •If you cannot explain why a case was flagged six months later you will struggle during audits.
- •Fix this by storing prompt text versions
, tool results, model name, timestamps, and reviewer disposition in immutable logs.
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