How to Build a policy Q&A Agent Using LangChain in TypeScript for investment banking
A policy Q&A agent for investment banking answers staff questions about internal controls, compliance rules, trading restrictions, client confidentiality, and operational procedures. It matters because bankers need fast answers without guessing, and every wrong answer can create regulatory exposure, audit gaps, or a breach of firm policy.
Architecture
- •
Policy document loader
- •Pulls from approved sources only: SharePoint, Confluence, PDF policy manuals, and controlled S3 buckets.
- •Normalizes documents into text chunks with metadata like
policy_id,version,owner, andjurisdiction.
- •
Vector store retriever
- •Indexes policy content for semantic search.
- •Uses metadata filters so the agent only searches the right region, business line, or policy version.
- •
LLM answer generator
- •Produces concise answers grounded in retrieved policy text.
- •Must cite source snippets or document references for auditability.
- •
Guardrail layer
- •Blocks unsupported advice, trading recommendations, legal interpretation beyond policy text, and requests that violate access controls.
- •Detects when the model is uncertain and forces escalation to compliance.
- •
Audit logger
- •Stores user question, retrieved documents, model response, timestamp, and correlation ID.
- •Needed for internal review, incident response, and regulatory traceability.
Implementation
1) Install the LangChain packages you actually need
For a TypeScript service, keep the stack explicit. You need a chat model wrapper, embeddings, document loaders if you ingest files directly, and a vector store.
npm install langchain @langchain/openai @langchain/community @langchain/core zod
Set your environment variables for model access and any storage backend:
export OPENAI_API_KEY="..."
2) Load policy docs and build the retriever
This example uses PDFs as an ingestion source and Chroma as the vector store. In production you would usually run ingestion as a separate job and persist the index in a controlled environment.
import { PDFLoader } from "@langchain/community/document_loaders/fs/pdf";
import { Chroma } from "@langchain/community/vectorstores/chroma";
import { OpenAIEmbeddings } from "@langchain/openai";
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
async function buildRetriever() {
const loader = new PDFLoader("./policies/investment-banking-policy.pdf");
const docs = await loader.load();
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
chunkOverlap: 150,
});
const chunks = await splitter.splitDocuments(docs);
// Attach metadata that matters for compliance filtering
const enrichedChunks = chunks.map((doc) => ({
...doc,
metadata: {
...doc.metadata,
policy_id: "IB-POL-001",
version: "2025.01",
jurisdiction: "US",
business_line: "investment-banking",
},
}));
const vectorStore = await Chroma.fromDocuments(
enrichedChunks,
new OpenAIEmbeddings({ model: "text-embedding-3-small" }),
{ collectionName: "ib-policy-qna" }
);
return vectorStore.asRetriever({
k: 4,
filter: {
jurisdiction: "US",
business_line: "investment-banking",
},
});
}
The important part is not the loader. It is the metadata discipline. If your bank operates across regions or desks, retrieval must respect those boundaries from day one.
3) Build a grounded Q&A chain with citations
Use ChatOpenAI with createStuffDocumentsChain so the model gets only the retrieved context. Then wrap it in createRetrievalChain to keep retrieval and generation separated.
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { createStuffDocumentsChain } from "langchain/chains/combine_documents";
import { createRetrievalChain } from "langchain/chains/retrieval";
const prompt = ChatPromptTemplate.fromMessages([
[
"system",
[
"You are a policy Q&A assistant for investment banking.",
"Answer only from the provided context.",
"If the context does not contain the answer, say you don't have enough information.",
"Do not provide legal advice or speculate.",
"Include references to policy identifiers when available.",
].join(" "),
],
["human", "{input}"],
]);
export async function createPolicyQAAgent() {
const retriever = await buildRetriever();
const llm = new ChatOpenAI({
model: "gpt-4o-mini",
temperature: 0,
});
const combineDocsChain = await createStuffDocumentsChain({
llm,
prompt,
});
return createRetrievalChain({
retriever,
combineDocsChain,
});
}
This pattern gives you a deterministic baseline. For policy workflows in banking, low temperature is not optional; it reduces variance when users ask repeated questions about restricted topics.
4) Add an approval gate before returning answers
Do not let every response go straight to users. Add a simple validator that checks for unsupported claims and escalates ambiguous cases to compliance.
type AgentResult = {
answer: string;
};
function needsEscalation(answer: string): boolean {
const riskyPatterns = [
/guarantee/i,
/should trade/i,
/legal/i,
/regulatory interpretation/i,
/not sure/i,
/maybe/i,
];
return riskyPatterns.some((pattern) => pattern.test(answer));
}
export async function askPolicyQuestion(question: string): Promise<AgentResult> {
const chain = await createPolicyQAAgent();
const result = await chain.invoke({
input: question,
});
const answer =
typeof result.answer === "string"
? result.answer
: JSON.stringify(result.answer);
if (needsEscalation(answer)) {
return {
answer:
"I can't confirm this from policy text alone. Escalate to Compliance or Legal for review.",
};
}
return { answer };
}
In production you would make this stricter. Use structured outputs with zod, log retrieval evidence, and route high-risk questions like MNPI handling or gifts-and-entertainment exceptions to humans immediately.
Production Considerations
- •
Deploy inside your bank’s approved network boundary
- •Keep embeddings storage, vector DBs, and logs in-region where data residency rules apply.
- •Do not send confidential policies to unmanaged SaaS systems without explicit approval.
- •
Log everything needed for audit
- •Store question text, user identity, retrieved document IDs, policy versions, prompt version, model name, and final answer.
- •Make logs immutable or write-once where possible.
- •
Add role-based access control to retrieval
- •A junior analyst should not retrieve desk-specific exception memos they cannot access.
- •
Monitor refusal rates and escalation rates
- •A sudden drop in refusals can mean your guardrails are too loose.
Common Pitfalls
- •
Indexing stale policies
- •If your vector store contains old versions of procedure manuals, the agent will confidently quote obsolete rules.
- •Fix this by versioning documents and filtering retrieval by active policy status.
- •
Letting the model answer beyond evidence
- •If your prompt says “be helpful” without hard grounding instructions, the model will fill gaps.
- •Fix this with strict system instructions plus an escalation path when context is insufficient.
- •
Ignoring access boundaries
- •A single shared index across all desks can leak restricted information between teams.
- •Fix this with metadata filters keyed on business line, region, role, and entitlement group.
A policy Q&A agent is useful only if it behaves like a controlled internal tool, not a general chatbot. In investment banking that means grounded answers, traceable sources, strict access control, and a clear handoff to compliance when the question crosses into judgment territory.
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