How to Build a customer support Agent Using LangChain in TypeScript for banking
A banking customer support agent handles routine account questions, transaction lookups, card status checks, and policy explanations without forcing a human into every interaction. It matters because banks need fast response times, consistent answers, and strict control over what data the agent can access or reveal.
Architecture
- •
Chat model layer
- •Use a hosted LLM through LangChain’s
ChatOpenAIor your approved internal model wrapper. - •Keep temperature low for deterministic support responses.
- •Use a hosted LLM through LangChain’s
- •
Retrieval layer
- •Store policy docs, fee schedules, card terms, and support playbooks in a vector store.
- •Use retrieval to ground answers in bank-approved content instead of model memory.
- •
Tool layer
- •Expose only narrow tools: account summary, card replacement status, branch lookup, dispute initiation.
- •Each tool should enforce authentication and authorization before returning anything.
- •
Conversation memory
- •Keep short-lived session context for the current support case.
- •Avoid storing sensitive data unless you have a retention policy and audit trail.
- •
Guardrails and policy checks
- •Add input/output filters for PII, fraud language, prohibited requests, and escalation triggers.
- •Force human handoff when confidence is low or the user requests regulated advice.
- •
Audit logging
- •Log prompts, tool calls, retrieved documents, and final responses with redaction.
- •This is mandatory for traceability in banking environments.
Implementation
1) Install the LangChain packages you actually need
For a TypeScript banking assistant, keep dependencies tight. You want chat models, prompts, retrieval, and a vector store.
npm install langchain @langchain/openai @langchain/community zod
Set your environment variables for model access and any internal service credentials:
export OPENAI_API_KEY="..."
2) Build a grounded answer chain with retrieval
This pattern keeps the agent anchored to bank-approved content. The retriever pulls policy snippets first, then the model answers using only that context.
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { RunnableSequence } from "@langchain/core/runnables";
import { MemoryVectorStore } from "langchain/vectorstores/memory";
import { OpenAIEmbeddings } from "@langchain/openai";
import { Document } from "@langchain/core/documents";
const docs = [
new Document({
pageContent: "Debit card replacement takes 5-7 business days. Expedited shipping requires branch approval.",
metadata: { source: "card-policy", jurisdiction: "US" },
}),
new Document({
pageContent: "Fee waivers may be granted once per calendar year for eligible premium accounts.",
metadata: { source: "fees-policy", jurisdiction: "US" },
}),
];
const embeddings = new OpenAIEmbeddings();
const vectorStore = await MemoryVectorStore.fromDocuments(docs, embeddings);
const retriever = vectorStore.asRetriever(3);
const prompt = ChatPromptTemplate.fromMessages([
["system", "You are a banking support agent. Answer only using the provided context. If the answer is not in context, say you need to escalate."],
["human", "Question: {question}\n\nContext:\n{context}"],
]);
const llm = new ChatOpenAI({
model: "gpt-4o-mini",
temperature: 0,
});
export async function answerSupportQuestion(question: string) {
const relevantDocs = await retriever.invoke(question);
const context = relevantDocs.map((d) => d.pageContent).join("\n---\n");
const chain = RunnableSequence.from([
{
question: (input: string) => input,
context: async (input: string) => {
const docs = await retriever.invoke(input);
return docs.map((d) => d.pageContent).join("\n---\n");
},
},
prompt,
llm,
new StringOutputParser(),
]);
return chain.invoke(question);
}
This gives you a safe default behavior:
- •no context, no answer
- •low temperature for consistency
- •explicit escalation when the policy corpus does not cover the request
3) Add tools for controlled account actions
For banking support, do not let the model “decide” on its own. Wrap each backend capability in a tool with strict validation. LangChain’s tool helper is enough for this pattern.
import { tool } from "@langchain/core/tools";
import { z } from "zod";
export const getCardStatus = tool(
async ({ customerId }: { customerId: string }) => {
// Replace with real authenticated service call.
return JSON.stringify({
customerId,
status: "active",
last4: "4821",
replacementEligible: true,
});
},
{
name: "get_card_status",
description: "Fetch the authenticated customer's debit or credit card status.",
schema: z.object({
customerId: z.string().min(1),
}),
}
);
Use this only after your auth layer has already established identity. The tool should never accept raw PANs or secrets from chat text.
4) Route sensitive cases to humans
A banking assistant should not handle everything automatically. If the user mentions fraud, chargebacks beyond basic guidance, legal threats, sanctions screening issues, or account takeover indicators, escalate immediately.
function needsEscalation(message: string): boolean {
const text = message.toLowerCase();
return [
"fraud",
"stolen card",
"chargeback",
"lawsuit",
"account takeover",
"wire recall",
"sanctions",
].some((term) => text.includes(term));
}
In production you would combine this with:
- •classifier output
- •confidence thresholds
- •risk rules from compliance
- •human queue integration
Production Considerations
- •
Deployment
- •Run the agent behind your bank’s API gateway and identity provider.
- •Pin model endpoints to approved regions to satisfy data residency requirements.
- •Separate public support traffic from internal operations traffic.
- •
Monitoring
- •Track tool invocation rates, fallback rates, escalation rates, and hallucination reports.
- •Log every retrieved document source so audit teams can reconstruct why an answer was given.
- •Redact account numbers, SSNs, CVVs, addresses, and free-text PII before storage.
- •
Guardrails
- •Block requests that ask for secrets, credentials, OTPs, or bypass instructions.
- •Enforce response templates for regulated topics like fees and disputes.
- •Require human approval before any action that changes account state.
- •
Compliance
- •Keep immutable logs for prompt/response/tool activity with retention aligned to policy.
- •Review whether your vendor setup meets GDPR/GLBA/PCI obligations.
- •Make sure customers can be informed when they are interacting with an automated assistant if local rules require it.
Common Pitfalls
- •
Letting the model talk directly to backend systems
- •Avoid this by exposing only narrow LangChain tools with Zod validation.
- •Every tool call should go through authenticated service layers that enforce RBAC.
- •
Using retrieval without source control
- •Don’t index random PDFs or stale wiki pages.
- •Maintain a curated knowledge base with document ownership, versioning, and review dates.
- •
Storing too much conversation history
- •Banking support does not need long-term raw chat logs in memory.
- •Keep session context minimal and persist only what compliance requires after redaction.
- •
Skipping escalation logic
- •If fraud or legal language appears and you keep chatting like it’s a normal FAQ bot, you create risk.
- •Build hard escalation rules before launch and test them with adversarial prompts.
If you build this pattern correctly, LangChain gives you a clean separation between knowledge retrieval, controlled actions, and compliance boundaries. That is what makes a banking support agent usable in production instead of just impressive in a demo.
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