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

By Cyprian AaronsUpdated 2026-04-21
fraud-detectionlanggraphpythoninsurance

A fraud detection agent for insurance triages claims, policy events, and supporting documents to decide whether a case looks normal, needs manual review, or should be escalated for investigation. It matters because false positives slow down legitimate claims, while false negatives leak money through staged accidents, inflated repair bills, and organized fraud rings.

Architecture

  • Claim intake node
    • Accepts structured claim data: policy number, loss type, amount, date of loss, claimant metadata, and document references.
  • Feature extraction node
    • Derives fraud signals such as claim frequency, payout history, duplicate address patterns, repair shop reuse, and late filing.
  • Rules + LLM reasoning node
    • Applies deterministic insurance rules first, then uses an LLM to explain why the claim is suspicious or benign.
  • Risk scoring node
    • Converts evidence into a normalized risk score and a decision: approve, review, or escalate.
  • Audit trail node
    • Persists every input, intermediate result, and final decision for compliance and model governance.
  • Human review handoff
    • Routes high-risk claims to SIU or claims adjusters with a concise summary and evidence bundle.

Implementation

1) Define the state model and helper functions

Use a typed state so every node reads and writes predictable fields. In insurance workflows, you want this because auditability beats cleverness.

from typing import TypedDict, Annotated, Literal
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages

class FraudState(TypedDict):
    claim_id: str
    policy_id: str
    claim_amount: float
    loss_type: str
    claimant_history: dict
    documents: list[str]
    signals: list[str]
    risk_score: int
    decision: Literal["approve", "review", "escalate"]
    explanation: str

def extract_signals(state: FraudState) -> FraudState:
    signals = []

    if state["claim_amount"] > 25000:
        signals.append("high_claim_amount")

    if state["claimant_history"].get("prior_claims_12m", 0) >= 3:
        signals.append("frequent_claims")

    if state["claimant_history"].get("same_repair_shop", False):
        signals.append("repeat_repair_shop")

    if state["loss_type"] in {"theft", "water_damage"}:
        signals.append(f"high_abuse_category:{state['loss_type']}")

    state["signals"] = signals
    return state

def score_claim(state: FraudState) -> FraudState:
    score = 0
    weights = {
        "high_claim_amount": 30,
        "frequent_claims": 25,
        "repeat_repair_shop": 20,
        "high_abuse_category:theft": 15,
        "high_abuse_category:water_damage": 10,
    }

    for signal in state["signals"]:
        score += weights.get(signal, 0)

    state["risk_score"] = min(score, 100)
    return state

2) Add an LLM reasoning node with LangGraph’s real API

For production insurance systems, keep the LLM out of raw decision authority. Use it to summarize evidence and justify a recommendation after deterministic scoring.

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

def llm_explain(state: FraudState) -> FraudState:
    prompt = f"""
You are a fraud triage assistant for insurance claims.
Return a concise explanation for the following case.

Claim ID: {state['claim_id']}
Policy ID: {state['policy_id']}
Amount: {state['claim_amount']}
Loss type: {state['loss_type']}
Signals: {state['signals']}
Risk score: {state['risk_score']}

Explain whether this should be approved, reviewed, or escalated.
"""
    response = llm.invoke(prompt)
    state["explanation"] = response.content.strip()
    return state

def decide(state: FraudState) -> FraudState:
    if state["risk_score"] >= 60:
        state["decision"] = "escalate"
    elif state["risk_score"] >= 30:
        state["decision"] = "review"
    else:
        state["decision"] = "approve"
    return state

3) Build the graph with conditional routing

This is where LangGraph earns its keep. You define the workflow explicitly with StateGraph, connect nodes with add_node and add_edge, then route based on the computed decision.

def route_by_decision(state: FraudState):
    return state["decision"]

graph = StateGraph(FraudState)

graph.add_node("extract_signals", extract_signals)
graph.add_node("score_claim", score_claim)
graph.add_node("llm_explain", llm_explain)
graph.add_node("decide", decide)

graph.add_edge(START, "extract_signals")
graph.add_edge("extract_signals", "score_claim")
graph.add_edge("score_claim", "llm_explain")
graph.add_edge("llm_explain", "decide")

graph.add_conditional_edges(
    "decide",
    route_by_decision,
    {
        "approve": END,
        "review": END,
        "escalate": END,
    },
)

app = graph.compile()

4) Run the agent on a claim payload

The compiled graph behaves like an executable workflow. In practice you would call this from your claims platform or SIU queue consumer.

initial_state = {
    "claim_id": "CLM-10492",
    "policy_id": "POL-77821",
    "claim_amount": 42000.0,
    "loss_type": "theft",
    "claimant_history": {
        "prior_claims_12m": 4,
        "same_repair_shop": True,
    },
    "documents": ["police_report.pdf", "repair_estimate.pdf"],
}

result = app.invoke(initial_state)

print(result["decision"])
print(result["risk_score"])
print(result["explanation"])

Production Considerations

  • Keep PII inside your boundary
    • Mask names, addresses, and government IDs before sending anything to an external model endpoint. For regulated carriers, that means controlling data residency and vendor access.
  • Persist full audit traces
    • Store input payloads, extracted signals, final decisions, model version, prompt version, and timestamps. Claims teams need reproducibility during disputes and regulatory reviews.
  • Separate deterministic logic from generative reasoning
    • Rules should drive routing; the LLM should explain or summarize. That keeps your system defensible when underwriting or compliance asks why a claim was escalated.
  • Add human-in-the-loop thresholds
    • Never auto-deny based only on an agent output. Route borderline cases to adjusters or SIU with evidence snippets and confidence bands.

Common Pitfalls

  • Using the LLM as the sole fraud detector
    • Bad pattern. Insurance fraud detection needs stable thresholds and explainable rules; use the model for narrative support, not final authority.
  • Ignoring auditability
    • If you cannot reconstruct why a claim was escalated six months later, you have a governance problem. Log every node output and version every prompt.
  • Mixing all policy lines into one generic workflow
    • Auto claims behave differently from health or property claims. Build line-of-business specific graphs so your signals match actual fraud patterns.

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