How to Build a policy Q&A Agent Using LlamaIndex in TypeScript for lending
A policy Q&A agent for lending answers questions like “Can we approve this borrower under current DTI rules?” or “What docs are required for a self-employed applicant?” It matters because loan officers, operations teams, and support staff need fast, consistent answers grounded in policy, not memory or tribal knowledge.
Architecture
- •
Policy document ingestion
- •Pull underwriting guides, product matrices, compliance memos, and exception policies from approved sources.
- •Convert them into indexed text chunks with metadata like product type, jurisdiction, version, and effective date.
- •
Vector index for semantic retrieval
- •Use
VectorStoreIndexto retrieve the most relevant policy passages for a user question. - •Keep chunks small enough to preserve citation quality and large enough to retain rule context.
- •Use
- •
Query engine with grounded answers
- •Use
index.asQueryEngine()so every response is generated from retrieved policy text. - •Configure the prompt to force concise answers and cite source sections.
- •Use
- •
Audit trail and traceability
- •Log the question, retrieved nodes, answer, policy version, and timestamp.
- •This is non-negotiable in lending where decisions can be reviewed by compliance or regulators.
- •
Access control layer
- •Filter documents by product line, region, or user role before retrieval.
- •A mortgage ops user should not get auto lending exceptions if they are outside scope.
Implementation
1) Install the TypeScript packages
Use the LlamaIndex TypeScript SDK plus an embedding provider. For production lending systems, I usually keep ingestion and query runtime separate so policy updates can be reindexed safely.
npm install llamaindex dotenv
npm install @llamaindex/openai
Set your environment variables:
export OPENAI_API_KEY="your-key"
2) Load policy docs with metadata
The important part here is metadata. In lending, you need to know which product a rule applies to, whether it is active, and when it became effective.
import "dotenv/config";
import { Document } from "llamaindex";
const policyDocs = [
new Document({
text: `
DTI Policy:
Maximum front-end DTI is 28% for conforming loans.
Maximum back-end DTI is 36% unless compensating factors are documented.
Exceptions require second-level approval.
`,
metadata: {
source: "underwriting-guide",
product: "conforming-mortgage",
jurisdiction: "US",
effectiveDate: "2025-01-01",
version: "v12",
},
}),
new Document({
text: `
Income Documentation:
Self-employed borrowers must provide two years of personal and business tax returns.
Year-to-date P&L statements are required if income declined in the last tax year.
`,
metadata: {
source: "income-doc-policy",
product: "mortgage",
jurisdiction: "US",
effectiveDate: "2025-01-01",
version: "v7",
},
}),
];
3) Build the index and query engine
This is the core pattern. VectorStoreIndex.fromDocuments() creates the searchable index, and asQueryEngine() turns it into a policy Q&A interface. For lending use cases, keep temperature low and make citations mandatory in your system prompt.
import {
VectorStoreIndex,
Settings,
} from "llamaindex";
import { OpenAIEmbedding } from "@llamaindex/openai";
Settings.embedModel = new OpenAIEmbedding({
model: "text-embedding-3-small",
});
async function main() {
const index = await VectorStoreIndex.fromDocuments(policyDocs);
const queryEngine = index.asQueryEngine({
similarityTopK: 3,
// You can also pass a custom response synthesizer/prompt setup here if needed.
});
const question =
"What documentation is required for a self-employed borrower applying for a mortgage?";
const response = await queryEngine.query({
query: question,
});
console.log("Question:", question);
console.log("Answer:", response.toString());
const sourceNodes = response.sourceNodes ?? [];
for (const node of sourceNodes) {
console.log("Source:", node.node.metadata);
console.log("Text:", node.node.getText());
console.log("---");
}
}
main().catch(console.error);
That code gives you the basic retrieval loop. In production lending systems, I also persist the source nodes and answer text so compliance can reconstruct what the agent saw at response time.
4) Add a thin API layer with audit logging
Do not expose raw retrieval directly to users. Put a service layer in front of it so you can log questions, enforce scope checks, and attach request IDs.
import express from "express";
import { randomUUID } from "crypto";
const app = express();
app.use(express.json());
app.post("/policy-qna", async (req, res) => {
const requestId = randomUUID();
const { question, product } = req.body as {
question: string;
product: string;
};
// Replace this with real authz logic
if (product !== "conforming-mortgage") {
return res.status(403).json({ error: "Unauthorized policy scope" });
}
const result = await queryEngine.query({ query: question });
const sources =
result.sourceNodes?.map((s) => ({
metadata: s.node.metadata,
textPreview: s.node.getText().slice(0, 200),
})) ?? [];
console.log(
JSON.stringify({
requestId,
question,
product,
answer: result.toString(),
sources,
timestamp: new Date().toISOString(),
})
);
res.json({
requestId,
answer: result.toString(),
sources,
});
});
app.listen(3000);
Production Considerations
- •
Deployment
- •Keep ingestion jobs separate from query serving.
- •Rebuild indexes when policies change; do not mix stale and current versions in one store without version tags.
- •
Monitoring
- •Track retrieval hit rate, empty-answer rate, latency, and top failed questions.
- •In lending workflows, unanswered questions usually mean missing coverage in policy docs or broken metadata filters.
- •
Guardrails
- •Force citations in every response.
- •If no relevant source is found above threshold confidence, return “I don’t know” instead of inventing a rule.
- •
Data residency and access control
- •Store borrower-related artifacts only in approved regions.
- •Policy content may be global, but some underwriting overlays are jurisdiction-specific; filter at retrieval time by region and product.
Common Pitfalls
- •
Mixing policy versions without metadata
- •If you ingest old and new underwriting guides into one index without version tags, the agent will answer with stale rules.
- •Fix it by tagging every document with
version,effectiveDate,product, andjurisdiction, then filtering at query time.
- •
Letting the model answer without grounding
- •A generic chat prompt will produce confident but unusable lending advice.
- •Fix it by using retrieval-first answers and requiring citations from
sourceNodes.
- •
Ignoring scope boundaries
- •A mortgage agent should not answer HELOC or auto-lending rules unless explicitly authorized.
- •Fix it with role-based filtering before
queryEngine.query()runs.
- •
Skipping audit logs
- •If you cannot reconstruct what policy text was used for an answer, compliance will treat the system as opaque.
- •Fix it by logging request ID, user identity, retrieved node metadata, answer text, and timestamp on every call.
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