How to Build a compliance checking Agent Using LangGraph in Python for insurance

By Cyprian AaronsUpdated 2026-04-21
compliance-checkinglanggraphpythoninsurance

A compliance checking agent for insurance reviews policy language, claims notes, customer communications, and underwriting decisions against internal rules and regulatory constraints. It matters because a missed disclosure, an unapproved phrase, or a bad escalation path can turn into fines, rework, or a denied claim dispute that should have been caught earlier.

Architecture

  • Input normalizer

    • Cleans and structures incoming text from emails, PDFs, CRM notes, or claim forms.
    • Extracts the document type, jurisdiction, and line of business before any policy checks run.
  • Policy rules loader

    • Pulls insurer-specific controls from a versioned store.
    • Keeps rules separate by product line and region so you can enforce different requirements for auto, health, life, or property.
  • Compliance checker node

    • Evaluates the content against rules like required disclosures, prohibited phrases, retention obligations, and escalation triggers.
    • Returns structured findings instead of free-form prose.
  • Audit trail writer

    • Stores inputs, rule versions, outputs, and timestamps.
    • This is non-negotiable in insurance when regulators ask why a decision was made.
  • Human review gate

    • Routes high-risk findings to an underwriter, compliance analyst, or claims supervisor.
    • Prevents the agent from making final decisions on regulated outcomes.
  • Output formatter

    • Produces a clean result for downstream systems: pass/fail, reasons, severity, and next action.

Implementation

1) Define the state and the checks

Use TypedDict for graph state so every node knows exactly what it receives and returns. Keep the output structured; don’t let the model decide your schema.

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

class ComplianceState(TypedDict):
    text: str
    jurisdiction: str
    line_of_business: str
    findings: List[str]
    risk_level: Literal["low", "medium", "high"]
    approved: bool

def normalize_input(state: ComplianceState) -> ComplianceState:
    text = state["text"].strip()
    return {**state, "text": text}

def check_compliance(state: ComplianceState) -> ComplianceState:
    findings = []
    text = state["text"].lower()

    if "guaranteed approval" in text:
        findings.append("Prohibited promise detected: guaranteed approval")

    if state["jurisdiction"] == "CA" and "privacy notice" not in text:
        findings.append("Missing privacy notice reference for California")

    risk_level = "high" if findings else "low"
    approved = len(findings) == 0
    return {**state, "findings": findings, "risk_level": risk_level, "approved": approved}

2) Add a human review branch with LangGraph routing

This is where LangGraph helps more than a plain chain. Use conditional edges to send high-risk cases to review instead of auto-approving them.

def route_after_check(state: ComplianceState):
    if state["risk_level"] == "high":
        return "human_review"
    return END

def human_review(state: ComplianceState) -> ComplianceState:
    # Replace this with an actual queue/task assignment in production
    findings = state["findings"] + ["Escalated to compliance reviewer"]
    return {**state, "findings": findings}

graph = StateGraph(ComplianceState)
graph.add_node("normalize_input", normalize_input)
graph.add_node("check_compliance", check_compliance)
graph.add_node("human_review", human_review)

graph.add_edge(START, "normalize_input")
graph.add_edge("normalize_input", "check_compliance")
graph.add_conditional_edges("check_compliance", route_after_check)

app = graph.compile()

3) Run the graph on an insurance use case

This example checks a customer-facing message before it goes out. In insurance workflows this can sit between CRM drafting and outbound sending.

initial_state = {
    "text": """
        Thanks for your application.
        You are guaranteed approval based on this quote.
        Please review our terms.
    """,
    "jurisdiction": "CA",
    "line_of_business": "auto",
    "findings": [],
    "risk_level": "low",
    "approved": False,
}

result = app.invoke(initial_state)
print(result["approved"])
print(result["risk_level"])
print(result["findings"])

4) Make the agent auditable

For insurance you need traceability. Store the input payload, rule version, output state, reviewer action, and timestamp in your logging layer or case management system.

A practical pattern is to wrap app.invoke() with persistence:

  • write the raw input to immutable storage
  • write the final graph state to your audit table
  • include document hash and rule set version
  • keep reviewer overrides separate from model output

If you want multi-step enrichment later, LangGraph supports add_node, add_edge, add_conditional_edges, and compile() cleanly enough that you can extend this into extraction → rules → review → publish without rewriting the orchestration layer.

Production Considerations

  • Data residency

    • Keep policy documents and customer PII in-region.
    • If you operate across EU/US/APAC lines of business, pin storage and inference endpoints to the correct jurisdiction.
  • Auditability

    • Persist every decision with a deterministic rule version.
    • Regulators care about what rule fired at the time of decision, not what your current prompt says today.
  • Guardrails

    • Never let the agent auto-finalize adverse decisions on its own.
    • Use hard-coded thresholds for escalation on high-risk phrases like “denied coverage,” “excluded,” or “non-renewal.”
  • Monitoring

    • Track false positives by line of business and jurisdiction.
    • A noisy compliance agent gets ignored by operations teams fast.

Common Pitfalls

  • Using free-form LLM output as the final decision

    • Fix it by forcing structured fields like approved, risk_level, and findings.
    • Insurance workflows need deterministic downstream behavior.
  • Mixing jurisdictions in one rule set

    • Fix it by loading controls per region and product line.
    • California privacy language is not the same as UK FCA disclosure requirements.
  • Skipping audit metadata

    • Fix it by storing input hash, rule version, graph version, timestamp, and reviewer ID.
    • Without that trail you cannot defend the decision during an internal audit or regulator review.

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