How to Build a KYC verification Agent Using LangChain in TypeScript for wealth management
A KYC verification agent for wealth management collects client identity data, checks it against policy and external sources, flags missing or inconsistent information, and produces an auditable decision trail. That matters because onboarding high-net-worth clients is slow, regulated, and expensive; if your agent gets the basics wrong, you create compliance risk, operational drag, and a bad client experience.
Architecture
A production KYC agent for wealth management usually needs these components:
- •
Client intake layer
- •Accepts structured inputs like name, DOB, address, tax residency, source of funds, and beneficial ownership.
- •Normalizes data before it reaches the model.
- •
Document extraction layer
- •Pulls fields from passports, utility bills, corporate docs, trust deeds, and bank statements.
- •Keeps extracted values tied to document provenance.
- •
Policy and rules engine
- •Enforces wealth-management-specific rules such as PEP/sanctions escalation, UBO thresholds, jurisdiction restrictions, and enhanced due diligence triggers.
- •Should not rely on the LLM for final policy decisions.
- •
LangChain orchestration layer
- •Uses
ChatOpenAI,PromptTemplate,StructuredOutputParser, and tools to route tasks. - •Handles classification, missing-field detection, and explanation generation.
- •Uses
- •
Audit and evidence store
- •Persists every input, model output, tool call, timestamp, and decision reason.
- •Required for internal audit and regulator review.
- •
Human review queue
- •Sends borderline cases to compliance ops.
- •Needed for sanctions hits, complex ownership structures, or inconsistent residency data.
Implementation
1. Define the KYC schema and structured output
For KYC work, free-form text is a liability. Force the model into a typed shape so downstream systems can validate it deterministically.
import { z } from "zod";
export const KycResultSchema = z.object({
clientName: z.string(),
dateOfBirth: z.string().optional(),
countryOfResidence: z.string().optional(),
taxResidencies: z.array(z.string()).default([]),
riskLevel: z.enum(["low", "medium", "high"]),
missingFields: z.array(z.string()).default([]),
redFlags: z.array(z.string()).default([]),
nextAction: z.enum(["approve", "review", "reject"]),
});
export type KycResult = z.infer<typeof KycResultSchema>;
This schema gives you a stable contract for onboarding workflows. In wealth management, that contract matters because compliance teams need consistent outputs they can map to case management systems.
2. Build the LangChain chain with a real prompt and parser
Use ChatOpenAI plus StructuredOutputParser so the model returns machine-readable JSON. Keep the prompt focused on extraction and risk flagging rather than open-ended reasoning.
import { ChatOpenAI } from "@langchain/openai";
import {
StructuredOutputParser,
} from "langchain/output_parsers";
import { PromptTemplate } from "@langchain/core/prompts";
import { RunnableSequence } from "@langchain/core/runnables";
import { KycResultSchema } from "./kyc-schema";
const parser = StructuredOutputParser.fromZodSchema(KycResultSchema);
const prompt = PromptTemplate.fromTemplate(`
You are a KYC verification assistant for a wealth management firm.
Extract KYC fields from the provided client profile.
Identify missing mandatory fields and red flags.
Do not invent data.
Client profile:
{profile}
{format_instructions}
`);
const model = new ChatOpenAI({
model: "gpt-4o-mini",
temperature: 0,
});
export const kycChain = RunnableSequence.from([
{
profile: (input: { profile: string }) => input.profile,
format_instructions: () => parser.getFormatInstructions(),
},
prompt,
model,
]);
This is the core pattern. RunnableSequence.from keeps the pipeline explicit, which is easier to test than hiding logic inside one large agent loop.
3. Add policy checks outside the model
The LLM should extract and classify; your policy code should decide whether onboarding can proceed. That separation is important for auditability and for avoiding accidental approval of risky clients.
import { KycResult } from "./kyc-schema";
function applyWealthManagementPolicy(result: KycResult) {
const flags = [...result.redFlags];
if (!result.dateOfBirth) flags.push("missing_date_of_birth");
if (!result.countryOfResidence) flags.push("missing_country_of_residence");
const highRiskTriggers = [
"pep_match",
"sanctions_screen_hit",
"complex_trust_structure",
"unexplained_source_of_wealth",
"high_risk_jurisdiction",
];
const hasHighRiskTrigger = flags.some((flag) =>
highRiskTriggers.includes(flag)
);
if (result.missingFields.length > 0) {
return { ...result, nextAction: "review" as const };
}
if (hasHighRiskTrigger) {
return { ...result, riskLevel: "high" as const, nextAction: "review" as const };
}
return result;
}
In real deployments you would replace those string checks with your screening vendor results and internal policy matrix. The point is simple: deterministic rules own the decision boundary.
4. Run the chain and persist an audit record
The final step is orchestration plus storage. Save both raw inputs and normalized outputs so compliance can reconstruct why a case was approved or escalated.
async function verifyKyc(profile: string) {
const raw = await kycChain.invoke({ profile });
const parsedText =
typeof raw === "string" ? raw : JSON.stringify(raw);
const result = parser.parse(parsedText);
const finalDecision = applyWealthManagementPolicy(result);
await saveAuditRecord({
inputProfile: profile,
modelOutput: parsedText,
normalizedResult: result,
finalDecision,
timestamp: new Date().toISOString(),
});
return finalDecision;
}
async function saveAuditRecord(record: unknown) {
// Replace with Postgres/S3/WORM storage in production
console.log(JSON.stringify(record));
}
If you need retries or tool calls later, add them around this core flow. Don’t bury audit logging inside prompt code; keep it at the workflow boundary where every execution path passes through it.
Production Considerations
- •
Deployment
- •Keep PII in-region if your firm has residency constraints.
- •Use separate environments for development, UAT, and production with masked client data in non-prod.
- •
Monitoring
- •Track approval rate by jurisdiction, false positive rate on sanctions/PEP hits, human-review volume, and average onboarding time.
- •
Guardrails
- •Never let the model make final approval decisions for high-risk cases.
- •Require human review for trusts, offshore entities, politically exposed persons (PEPs), or incomplete source-of-wealth evidence.
- •
Auditability
- •Store prompts, outputs, timestamps, policy version IDs, screening vendor responses, and reviewer actions.
- •Regulators will ask how a decision was made; “the model said so” is not acceptable.
Common Pitfalls
- •
Using the LLM as the policy engine
- •Mistake: asking the model to decide approve/reject end-to-end.
- •Fix: use LangChain for extraction and explanation; keep policy in deterministic TypeScript code.
- •
Not preserving evidence lineage
- •Mistake: storing only final decisions without source documents or intermediate outputs.
- •Fix: persist document IDs, extracted fields per document, screening results, prompt versions, and reviewer overrides.
- •
Ignoring jurisdiction-specific requirements
- •Mistake: applying one global KYC flow across all clients.
- •Fix: parameterize rules by entity type and region; wealth management often needs different handling for local residents, cross-border clients, trusts, SPVs, and family offices.
- •
Letting unstructured text leak into downstream systems
- •Mistake: passing raw model text directly into onboarding APIs.
- •Fix: validate with Zod before any side effects; reject malformed outputs early instead of repairing them later in the workflow.
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