How to Build a fraud detection Agent Using LangChain in TypeScript for healthcare
A fraud detection agent in healthcare reviews claims, prior authorizations, and billing events to flag patterns that look inconsistent with policy, medical necessity, or historical behavior. It matters because healthcare fraud is expensive, but so is false positive automation: the agent has to reduce manual review load without creating compliance risk or blocking legitimate care.
Architecture
- •
Event ingestion layer
- •Pulls claim submissions, authorization requests, provider metadata, and member context from your internal systems.
- •Normalizes each event into a single structured payload before it reaches the agent.
- •
Rules and policy engine
- •Applies deterministic checks first: missing CPT/ICD codes, impossible date combinations, duplicate submissions, out-of-network exceptions.
- •Keeps obvious violations out of the LLM path.
- •
LangChain decision agent
- •Uses
ChatOpenAIplus a structured output schema to classify risk and explain why. - •Produces a severity score, rationale, and recommended next action.
- •Uses
- •
Evidence retrieval layer
- •Uses
VectorStoreRetrieveragainst internal policy docs, billing rules, and prior adjudication guidance. - •Grounds the agent in current payer and compliance policy.
- •Uses
- •
Audit and case logging
- •Persists input payloads, retrieved evidence IDs, model output, and final disposition.
- •Supports HIPAA audits, appeals, and internal review.
- •
Human review workflow
- •Routes medium/high-risk cases to investigators.
- •Keeps final adjudication with a human for anything ambiguous or high impact.
Implementation
1) Define the fraud signal schema
Use a strict schema so the model cannot drift into free-form prose. In healthcare, you want machine-readable outputs for audit trails and downstream routing.
import { z } from "zod";
export const FraudAssessmentSchema = z.object({
riskLevel: z.enum(["low", "medium", "high"]),
score: z.number().min(0).max(100),
reasons: z.array(z.string()).min(1),
recommendedAction: z.enum([
"auto_approve",
"manual_review",
"request_additional_docs",
"deny_for_review"
]),
});
export type FraudAssessment = z.infer<typeof FraudAssessmentSchema>;
export const ClaimEventSchema = z.object({
claimId: z.string(),
providerId: z.string(),
memberId: z.string(),
procedureCodes: z.array(z.string()),
diagnosisCodes: z.array(z.string()),
amountBilled: z.number(),
placeOfService: z.string(),
submittedAt: z.string(),
});
2) Build the LangChain chain with structured output
This pattern uses ChatOpenAI, ChatPromptTemplate, and RunnableSequence. The model gets claim data plus retrieved policy snippets, then returns a validated object.
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { RunnableSequence } from "@langchain/core/runnables";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { zodToJsonSchema } from "zod-to-json-schema";
import { FraudAssessmentSchema } from "./schemas";
const llm = new ChatOpenAI({
modelName: "gpt-4o-mini",
temperature: 0,
});
const prompt = ChatPromptTemplate.fromMessages([
[
"system",
`You are a healthcare fraud detection analyst.
Return only valid JSON that matches this schema:
{schema}
Use the provided claim data and policy evidence.
Focus on billing anomalies, medical necessity conflicts, duplicate patterns,
and inconsistencies with payer rules.
Do not mention protected health information beyond what is necessary for analysis.`,
],
["human", "Claim data:\n{claim}\n\nPolicy evidence:\n{evidence}"],
]);
export function buildFraudChain() {
return RunnableSequence.from([
async (input: { claim: string; evidence: string }) => input,
prompt.partial({
schema: JSON.stringify(zodToJsonSchema(FraudAssessmentSchema)),
}),
llm,
new StringOutputParser(),
async (text) => FraudAssessmentSchema.parse(JSON.parse(text)),
]);
}
3) Add retrieval for policy grounding
For healthcare use cases, don’t rely on the model’s memory. Retrieve current payer policies, coding guidance, and internal SOPs before scoring the claim.
import { MemoryVectorStore } from "langchain/vectorstores/memory";
import { OpenAIEmbeddings } from "@langchain/openai";
import { Document } from "@langchain/core/documents";
const docs = [
new Document({
pageContent:
"Rule: Outpatient physical therapy claims above threshold require prior authorization.",
metadata: { source: "policy_pt_01" },
}),
new Document({
pageContent:
"Rule: Duplicate claims for same member/provider/date/CPT require manual review.",
metadata: { source: "policy_dup_02" },
}),
];
const vectorStore = await MemoryVectorStore.fromDocuments(
docs,
new OpenAIEmbeddings()
);
const retriever = vectorStore.asRetriever(3);
export async function assessClaim(claimPayload: unknown) {
const claim = ClaimEventSchema.parse(claimPayload);
const query = `${claim.procedureCodes.join(",")} ${claim.diagnosisCodes.join(",")} ${claim.placeOfService}`;
const retrievedDocs = await retriever.invoke(query);
const chain = buildFraudChain();
const result = await chain.invoke({
claim: JSON.stringify(claim),
evidence: retrievedDocs.map((d) => d.pageContent).join("\n---\n"),
});
return {
...result,
evidenceSources: retrievedDocs.map((d) => d.metadata.source),
claimId: claim.claimId,
};
}
Step-by-step flow
- •
Validate inbound event
- •Parse the claim with Zod before anything else.
- •Reject malformed events early.
- •
Retrieve relevant policy evidence
- •Query your vector store with procedure codes and context.
- •Pass only necessary excerpts to the model.
- •
Run structured assessment
- •Use
RunnableSequenceto combine prompt, LLM call, parsing, and schema validation. - •Keep temperature at
0for stable outputs.
- •Use
- •
Persist audit artifacts
- •Store raw input hash, retrieved doc IDs, model output, timestamp, and reviewer outcome.
- •This is what you need when auditors ask why a case was flagged.
Production Considerations
- •
HIPAA and PHI minimization
- •Strip direct identifiers unless they are required for adjudication.
- •Send only the fields needed for fraud analysis to the LLM provider.
- •
Data residency
- •Keep embeddings stores and logs in-region if your contracts require it.
- •If your organization operates across states or countries, pin workloads to approved regions.
- •
Human-in-the-loop thresholds
- •Auto-approve only low-risk cases with strong deterministic signals.
- •Route medium/high-risk cases to investigators; never let the agent deny care by itself without review.
- •
Monitoring and auditability
- •Track false positive rate by provider specialty and region.
- •Log every prompt version, retrieved source ID, model version, and final decision for traceability.
- •Track false positive rate by provider specialty and region.
Common Pitfalls
- •
Letting the LLM make decisions without deterministic checks first
- •Fix this by running rule-based validation before the LangChain call.
- •Reserve the model for ambiguous cases where pattern interpretation matters.
- •
Passing raw PHI into prompts
- •Fix this by redacting names, addresses, MRNs, and unnecessary identifiers.
- •Use claim-level features instead of full clinical notes unless clinical necessity is part of the investigation.
- •
Using free-form text outputs in production
- •Fix this by enforcing Zod validation on every response.
- •If parsing fails or fields are missing, route the case to manual review instead of guessing.
A healthcare fraud agent is useful when it reduces investigator workload without becoming another compliance problem. Keep it structured, auditable, grounded in policy text, and constrained by human review where patient impact exists.
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