How to Build a claims processing Agent Using LangChain in Python for retail banking

By Cyprian AaronsUpdated 2026-04-21
claims-processinglangchainpythonretail-banking

A claims processing agent for retail banking triages incoming customer claims, extracts the relevant facts, checks policy and account context, and routes the case to the right resolution path. It matters because most claim handling is repetitive, time-sensitive, and compliance-heavy; if you automate the first pass correctly, you reduce turnaround time without losing auditability or control.

Architecture

  • Ingress layer

    • Receives claim requests from a web form, CRM, or case management system.
    • Normalizes inputs like transaction ID, customer ID, claim type, and narrative.
  • LLM extraction layer

    • Uses LangChain to turn unstructured customer text into structured claim fields.
    • Produces a schema-friendly object for downstream validation.
  • Policy and eligibility tools

    • Checks bank rules such as dispute windows, card-not-present fraud policy, chargeback eligibility, and account status.
    • Pulls data from internal systems through controlled tool calls.
  • Decision router

    • Classifies the claim into outcomes like auto-approve, request more information, escalate to human review, or reject.
    • Applies deterministic rules before any LLM-driven recommendation.
  • Audit and trace layer

    • Persists prompts, outputs, tool calls, timestamps, and final decisions.
    • Required for complaint handling, internal audit, and regulator review.
  • Human-in-the-loop handoff

    • Escalates edge cases to a claims analyst with full context.
    • Prevents unsafe automation on ambiguous or high-value claims.

Implementation

1) Define the claim schema and load the model

Use a structured output model so the agent returns fields you can validate. For retail banking, keep the schema narrow: claim type, transaction date, amount, merchant name, reason code, and whether supporting documents were attached.

from typing import Literal
from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI

class ClaimCase(BaseModel):
    customer_id: str = Field(..., description="Retail banking customer identifier")
    transaction_id: str = Field(..., description="Card or transfer transaction reference")
    claim_type: Literal["card_fraud", "unauthorized_transfer", "billing_error", "atm_dispute"]
    amount: float
    currency: str = "USD"
    transaction_date: str
    merchant_name: str | None = None
    summary: str
    has_supporting_docs: bool = False

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
structured_llm = llm.with_structured_output(ClaimCase)

2) Extract structured facts from the customer narrative

This is the core pattern. The model reads the complaint text and returns a validated object instead of free-form prose. That makes it much easier to apply policy checks and produce an auditable case record.

from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system",
     "You are a retail banking claims intake assistant. "
     "Extract only factual fields from the customer's message. "
     "Do not infer missing values."),
    ("human",
     "Customer ID: {customer_id}\n"
     "Transaction ID: {transaction_id}\n"
     "Message:\n{message}")
])

extract_chain = prompt | structured_llm

result = extract_chain.invoke({
    "customer_id": "C12345678",
    "transaction_id": "TXN987654",
    "message": (
        "I did not authorize a card payment of $129.50 at Northside Electronics "
        "on 2026-04-10. I noticed it after checking my app today."
    )
})

print(result.model_dump())

3) Add deterministic policy checks before any decision

In banking, do not let an LLM make the final call on eligibility. Use Python rules for things like dispute window checks and then ask LangChain to draft a recommendation only after those rules pass.

from datetime import datetime

def within_dispute_window(transaction_date: str, max_days: int = 60) -> bool:
    tx_date = datetime.strptime(transaction_date, "%Y-%m-%d").date()
    today = datetime.utcnow().date()
    return (today - tx_date).days <= max_days

def decide_next_step(claim: ClaimCase) -> str:
    if not within_dispute_window(claim.transaction_date):
        return "reject_out_of_window"
    if claim.amount >= 1000:
        return "human_review"
    if claim.claim_type in {"card_fraud", "unauthorized_transfer"}:
        return "auto_triage"
    return "human_review"

next_step = decide_next_step(result)
print(next_step)

4) Build a small agent for controlled lookups

If you need account status or prior disputes, expose only narrow tools. Use create_tool_calling_agent with AgentExecutor, but keep tool scope tight and log every invocation.

from langchain_core.tools import tool
from langchain.agents import create_tool_calling_agent, AgentExecutor

@tool
def get_account_status(customer_id: str) -> str:
    """Fetch minimal account status for claims processing."""
    # Replace with internal API call.
    return f"customer={customer_id}, status=active, kyc=verified"

@tool
def get_prior_claim_count(customer_id: str) -> int:
    """Return number of prior claims in last 12 months."""
    # Replace with internal API call.
    return 1

agent_prompt = ChatPromptTemplate.from_messages([
    ("system",
     "You are a retail banking claims assistant. "
     "Use tools only when necessary. "
     "Never expose sensitive data in your response."),
    ("human", "{input}")
])

tools = [get_account_status, get_prior_claim_count]
agent = create_tool_calling_agent(llm=llm, tools=tools, prompt=agent_prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

response = executor.invoke({
    "input": f"Check whether customer {result.customer_id} has active status "
             f"and summarize whether this looks like a standard fraud claim."
})

print(response["output"])

Production Considerations

  • Auditability

    • Store raw input, extracted schema output, tool calls, final decision label, model version (gpt-4o-mini vs another), and timestamps.
    • Keep immutable logs for complaint handling and internal audit trails.
  • Data residency

    • If your bank requires regional storage or processing boundaries, ensure prompts and retrieved data stay inside approved infrastructure.
    • Redact PANs, account numbers, SSNs/NINs before sending anything to an external model endpoint.
  • Guardrails

    • Use deterministic rules for deadlines, thresholds, sanctions screening flags, and account eligibility.
    • Make the LLM classify and summarize; do not let it approve payouts directly.
  • Monitoring

    • Track extraction accuracy by field type: transaction date errors hurt more than merchant-name misses.
    • Alert on spikes in human escalations because they often indicate prompt drift or upstream system changes.

Common Pitfalls

  1. Letting the model decide eligibility end-to-end

    • Fix this by separating extraction from policy enforcement.
    • The LLM should structure data; Python should enforce bank rules.
  2. Sending too much customer data into prompts

    • Fix this with field-level redaction and minimum necessary context.
    • Claims handling usually does not need full statements or full account history in the prompt.
  3. Skipping schema validation

    • Fix this by using with_structured_output() plus Pydantic validation.
    • Free-form text is hard to audit and easy to break when you add downstream automation.
  4. Ignoring human review paths

    • Fix this by routing high-value claims or ambiguous narratives to analysts.
    • Retail banking needs explainable escalation paths when customers dispute outcomes.

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