How to Build a claims processing Agent Using LangChain in TypeScript for banking
A claims processing agent in banking takes an incoming claim, classifies it, extracts the relevant facts, checks policy and account context, and routes the case to the right outcome: straight-through approval, manual review, or rejection with reasons. It matters because claims are high-volume, high-friction workflows where bad routing, missing evidence, or weak auditability creates cost, delays, and compliance risk.
Architecture
- •
Ingress layer
- •Receives claims from API, portal, or case-management events.
- •Normalizes payloads into a strict schema before any LLM call.
- •
LLM orchestration
- •Uses LangChain
ChatOpenAIfor classification, extraction, and decision support. - •Keeps prompts narrow and structured to reduce variance.
- •Uses LangChain
- •
Policy and rules layer
- •Applies deterministic checks for eligibility, thresholds, KYC status, and product rules.
- •Never lets the model override hard banking controls.
- •
Evidence retrieval
- •Pulls policy docs, customer history, transaction references, and prior cases via
VectorStoreRetriever. - •Grounds the agent in approved internal sources only.
- •Pulls policy docs, customer history, transaction references, and prior cases via
- •
Decision router
- •Converts model output into one of a few controlled actions: approve, request more info, escalate.
- •Emits an audit record with inputs, outputs, confidence, and rule hits.
- •
Audit and observability
- •Stores prompts, model responses, tool calls, and final decisions for review.
- •Supports compliance reviews, incident analysis, and model governance.
Implementation
1) Define the claim schema and load the model
Start with strict types. In banking workflows, free-form JSON is how bad decisions get into production.
import { z } from "zod";
import { ChatOpenAI } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";
import { StructuredOutputParser } from "langchain/output_parsers";
const ClaimSchema = z.object({
claimId: z.string(),
customerId: z.string(),
productType: z.enum(["card", "loan", "account", "wire"]),
amount: z.number().positive(),
currency: z.string().length(3),
description: z.string(),
});
const DecisionSchema = z.object({
classification: z.enum(["fraud_suspected", "needs_review", "eligible", "ineligible"]),
reason: z.string(),
requiredActions: z.array(z.string()),
});
const llm = new ChatOpenAI({
model: "gpt-4o-mini",
temperature: 0,
});
2) Build a structured extraction chain
Use StructuredOutputParser so the agent returns predictable fields. This is the pattern you want when downstream systems need stable JSON.
const parser = StructuredOutputParser.fromZodSchema(DecisionSchema);
const prompt = PromptTemplate.fromTemplate(`
You are a banking claims triage assistant.
Classify this claim using only the provided information.
Do not invent facts. If information is missing, mark it as needs_review.
Claim:
{claim}
{format_instructions}
`);
async function triageClaim(rawClaim: unknown) {
const claim = ClaimSchema.parse(rawClaim);
const formattedPrompt = await prompt.format({
claim: JSON.stringify(claim),
format_instructions: parser.getFormatInstructions(),
});
const response = await llm.invoke(formattedPrompt);
return parser.parse(response.content.toString());
}
This gives you a typed decision object that can be consumed by your workflow engine or case-management service.
3) Add deterministic banking rules before final action
The model should classify; your rules engine should decide whether action is allowed. That separation is important for auditability and regulatory review.
type RuleResult = {
allowed: boolean;
reason?: string;
};
function applyBankingRules(claim: z.infer<typeof ClaimSchema>): RuleResult {
if (claim.amount > 10000 && claim.productType === "wire") {
return { allowed: false, reason: "High-value wire claims require manual review" };
}
if (claim.currency !== "USD") {
return { allowed: false, reason: "Non-domestic currency requires compliance review" };
}
return { allowed: true };
}
export async function processClaim(rawClaim: unknown) {
const claim = ClaimSchema.parse(rawClaim);
const triage = await triageClaim(claim);
const ruleCheck = applyBankingRules(claim);
if (!ruleCheck.allowed) {
return {
...triage,
finalDecision: "needs_review",
escalationReason: ruleCheck.reason,
};
}
return {
...triage,
finalDecision:
triage.classification === "eligible" ? "approve" : "needs_review",
escalationReason: null,
};
}
4) Wire in retrieval for policy grounding
If you want fewer hallucinations, retrieve policy text from approved documents instead of asking the model to guess. LangChain’s retriever abstraction fits this cleanly.
import { MemoryVectorStore } from "@langchain/community/vectorstores/memory";
import { OpenAIEmbeddings } from "@langchain/openai";
import { Document } from "@langchain/core/documents";
const docs = [
new Document({ pageContent: "Claims over $10k require manual review." }),
new Document({ pageContent: "Wire-related claims must be reviewed by compliance." }),
];
const vectorStore = await MemoryVectorStore.fromDocuments(
docs,
new OpenAIEmbeddings()
);
const retriever = vectorStore.asRetriever();
async function fetchPolicyContext(query: string) {
const relevantDocs = await retriever.invoke(query);
.
return relevantDocs.map((d) => d.pageContent).join("\n");
}
Use this context in your prompt when deciding whether a claim is eligible. Keep retrieval sources limited to approved internal content with data residency controls applied at storage level.
Production Considerations
- •Deploy regionally
- •Keep embeddings stores and logs inside approved banking regions.
- •Log everything needed for audit
- •Persist claim input hash, retrieved policy snippets, prompt version, model version, decision output.
- •Add guardrails around PII
- •Redact account numbers, card PANs, SSNs before sending text to the LLM.
- •Use tokenization or masking at ingress.
- •Set hard confidence thresholds
- •Low-confidence classifications should default to manual review.
- •Never auto-close edge cases involving sanctions flags or fraud signals.
Common Pitfalls
- •
Letting the LLM make final business decisions
Don’t do this. Use LangChain for interpretation and routing; keep approval logic in deterministic code with explicit thresholds and policy checks.
- •
Skipping structured outputs
Free-text responses break workflows fast. Use
StructuredOutputParseror Zod-backed parsing so downstream services always receive valid fields. - •
Ignoring audit requirements
If you can’t explain why a claim was routed a certain way, you can’t run it in banking. Store prompt templates, retrieved documents, rule outcomes, and final decisions per case.
- •
Sending raw sensitive data to the model
Mask PII before
llm.invoke(). For claims processing in banking, data minimization is not optional; it’s part of your control surface.
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