How to Build a policy Q&A Agent Using LangChain in TypeScript for insurance
A policy Q&A agent answers customer or agent questions against insurance policy documents, endorsements, and product guides. It matters because most policy confusion comes from dense language, version drift, and inconsistent human interpretation; a good agent reduces call center load while keeping responses grounded in the actual contract text.
Architecture
- •
Document ingestion
- •Load PDFs, DOCX, and HTML policy docs into text chunks.
- •Preserve metadata like
policy_id,jurisdiction,effective_date, andversion.
- •
Vector store retrieval
- •Embed chunks and store them in a vector database for semantic search.
- •Retrieve only the most relevant clauses before answering.
- •
LLM answer chain
- •Use a chat model to synthesize an answer from retrieved context.
- •Force the model to cite source chunks and avoid unsupported claims.
- •
Guardrails layer
- •Detect missing context, out-of-scope questions, and unsafe requests.
- •Route ambiguous cases to a human underwriter or claims handler.
- •
Audit logging
- •Persist question, retrieved sources, answer, model version, and timestamps.
- •This is non-negotiable for compliance and dispute handling.
Implementation
- •Load policy documents with metadata
Use PDFLoader or another loader that fits your source format. The important part is attaching metadata early so you can filter by jurisdiction or policy version later.
import { PDFLoader } from "@langchain/community/document_loaders/fs/pdf";
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
import type { Document } from "@langchain/core/documents";
async function loadPolicyDocs(path: string): Promise<Document[]> {
const loader = new PDFLoader(path);
const docs = await loader.load();
return docs.map((doc) => ({
...doc,
metadata: {
...doc.metadata,
policy_id: "AUTO-PLATINUM-2024",
jurisdiction: "US-NY",
effective_date: "2024-01-01",
source_type: "policy_pdf",
},
}));
}
async function chunkDocs(docs: Document[]) {
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
chunkOverlap: 150,
});
return await splitter.splitDocuments(docs);
}
- •Index chunks in a vector store
For production insurance workloads, use a real vector DB. The pattern below uses MemoryVectorStore for local development, but the retriever interface stays the same when you swap in Pinecone, pgvector, or Weaviate.
import { OpenAIEmbeddings } from "@langchain/openai";
import { MemoryVectorStore } from "langchain/vectorstores/memory";
import type { Document } from "@langchain/core/documents";
export async function buildRetriever(chunks: Document[]) {
const embeddings = new OpenAIEmbeddings({
model: "text-embedding-3-large",
apiKey: process.env.OPENAI_API_KEY,
});
const vectorStore = await MemoryVectorStore.fromDocuments(chunks, embeddings);
return vectorStore.asRetriever(4);
}
- •Create the Q&A chain with citations
Use ChatOpenAI plus createStuffDocumentsChain and createRetrievalChain. The prompt should tell the model to answer only from provided context and say “I don’t know” when the policy text does not support an answer.
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 an insurance policy Q&A assistant.
Answer only using the provided context.
If the context does not contain enough information, say you cannot confirm it from the policy.
Always include citations using source metadata.`,
],
["human", "{input}"],
]);
export async function buildPolicyQA(retriever: any) {
const llm = new ChatOpenAI({
model: "gpt-4o-mini",
temperature: 0,
apiKey: process.env.OPENAI_API_KEY,
});
const combineDocsChain = await createStuffDocumentsChain({
llm,
prompt,
});
return createRetrievalChain({
retriever,
combineDocsChain,
});
}
- •Run queries and return auditable output
In insurance, the response should include both the answer and the source set used to produce it. That makes it easier to review borderline cases during complaints handling or regulator review.
import type { RunnableConfig } from "@langchain/core/runnables";
export async function askPolicyQuestion(
qaChain: any,
question: string
) {
const result = await qaChain.invoke(
{ input: question },
{
configurable: {
thread_id: `policy-qa-${Date.now()}`,
},
} as RunnableConfig
);
return {
question,
answer: result.answer,
sources: (result.context ?? []).map((doc: any) => ({
pageContent: doc.pageContent.slice(0, 200),
metadata: doc.metadata,
})),
timestamp: new Date().toISOString(),
};
}
Production Considerations
- •
Enforce jurisdiction filtering
- •Don’t retrieve UK motor wording for a US homeowner question.
- •Filter by
jurisdiction, product line, and effective date before retrieval.
- •
Log every decision path
- •Store user input, retrieved chunk IDs, prompt version, model name, and final answer.
- •This supports auditability and complaint investigations.
- •
Keep data residency controlled
- •Some carriers require documents and prompts to stay in-region.
- •Use region-bound storage and an LLM deployment that matches your regulatory constraints.
- •
Add human handoff rules
- •If confidence is low or the question touches exclusions, claims denial, or coverage disputes, escalate.
- •A policy agent should assist interpretation, not make final coverage determinations.
Common Pitfalls
- •
Answering without version control
- •Mistake: indexing multiple policy versions together and letting retrieval mix them.
- •Fix: partition by
policy_idandeffective_date, then filter aggressively at query time.
- •
Treating citations as decoration
- •Mistake: generating a fluent answer without tying it back to actual clauses.
- •Fix: require source metadata in every response and reject answers with no supporting chunks.
- •
Ignoring sensitive data handling
- •Mistake: sending personal claim details into prompts when they are not needed.
- •Fix: redact PII before LLM calls and minimize context to only what is required for the question.
- •
Skipping fallback logic
- •Mistake: forcing an answer even when the policy text is silent.
- •Fix: have the chain explicitly say it cannot confirm coverage from the provided documents and route to a human reviewer.
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