How to Build a loan approval Agent Using LlamaIndex in TypeScript for pension funds

By Cyprian AaronsUpdated 2026-04-21
loan-approvalllamaindextypescriptpension-funds

A loan approval agent for pension funds takes a loan application, pulls the right policy and member data, checks it against fund rules, and returns a decision with an audit trail. It matters because pension funds are not just optimizing credit risk; they also have fiduciary duties, strict compliance requirements, and hard limits on where member data can move.

Architecture

  • Application intake layer

    • Accepts loan requests from a CRM, portal, or internal workflow tool.
    • Normalizes applicant data into a structured schema before any LLM call.
  • Policy retrieval layer

    • Stores pension fund lending policies, eligibility rules, and regulatory documents.
    • Uses LlamaIndex retrieval over approved internal documents only.
  • Decision engine

    • Combines deterministic checks with LLM-assisted reasoning.
    • Produces one of: approve, reject, or review.
  • Audit and trace layer

    • Captures retrieved chunks, model output, timestamps, and final decision.
    • Required for compliance review and dispute handling.
  • Human review fallback

    • Routes borderline cases to an underwriter or compliance officer.
    • Prevents the agent from making unsupported decisions on edge cases.
  • Security and residency controls

    • Keeps member data in approved regions.
    • Redacts sensitive fields before sending anything to the model provider.

Implementation

1. Define the application schema and policy index

Use structured input first. Don’t let the LLM infer basic facts like income or tenure when you already have them in the request payload.

import {
  Document,
  VectorStoreIndex,
  Settings,
  OpenAI,
} from "llamaindex";

type LoanApplication = {
  memberId: string;
  requestedAmount: number;
  monthlyIncome: number;
  existingDebt: number;
  employmentStatus: "active" | "retired" | "deferred";
  yearsInFund: number;
};

const policyDocs = [
  new Document({
    text: `
Pension Fund Loan Policy:
- Maximum loan amount is 30% of vested balance.
- Applicant must be an active or deferred member.
- Debt service ratio must not exceed 35%.
- Any exception requires manual review.
`,
    metadata: { source: "fund-policy-v1", region: "za-south-1" },
  }),
];

Settings.llm = new OpenAI({
  model: "gpt-4o-mini",
});

const policyIndex = await VectorStoreIndex.fromDocuments(policyDocs);

2. Build a retriever-backed decision function

This pattern keeps the model grounded in policy text. The agent should explain its decision using retrieved evidence, not free-form memory.

import { QueryEngineTool } from "llamaindex";

const queryEngine = policyIndex.asQueryEngine({
  similarityTopK: 3,
});

const policyTool = QueryEngineTool.fromDefaults({
  queryEngine,
  name: "pension_policy_search",
  description: "Search approved pension fund lending policies and eligibility rules.",
});

function computeDebtServiceRatio(app: LoanApplication): number {
  const estimatedMonthlyPayment = app.requestedAmount / 36;
  return (app.existingDebt + estimatedMonthlyPayment) / app.monthlyIncome;
}

export async function assessLoan(app: LoanApplication) {
  const dsrc = computeDebtServiceRatio(app);

    const prompt = `
You are a loan approval assistant for a pension fund.

Applicant:
- Member ID: ${app.memberId}
- Requested amount: ${app.requestedAmount}
- Employment status: ${app.employmentStatus}
- Years in fund: ${app.yearsInFund}
- Debt service ratio estimate: ${(dsrc * 100).toFixed(2)}%

Task:
Use the pension policy tool to determine whether this application should be APPROVE, REJECT, or REVIEW.
Return:
1) decision
2) reason
3) policy citations
4) audit notes
`;

    const response = await queryEngine.query({ query: prompt });

    return {
      memberId: app.memberId,
      dsrc,
      result: response.toString(),
    };
}

3. Add deterministic guardrails before the model decides

For pension funds, hard rules should short-circuit obvious rejects. This reduces risk and keeps the model out of decisions it should never make.

export function precheck(app: LoanApplication) {
  if (app.employmentStatus === "retired") {
    return { decision: "REJECT", reason: "Retired members are not eligible under current policy." };
    }

    if (app.yearsInFund < 1) {
      return { decision: "REVIEW", reason: "Membership tenure below minimum threshold." };
    }

    const dsrc = computeDebtServiceRatio(app);
    if (dsrc > 0.35) {
      return { decision: "REJECT", reason: `Debt service ratio ${Math.round(dsrc * 100)}% exceeds limit.` };
    }

    return null;
}

4. Wrap it into an approval flow with audit output

The final response should be machine-readable and easy to store in your case management system.

export async function processLoanApplication(app: LoanApplication) {
    const precheckResult = precheck(app);
    if (precheckResult) {
      return {
        ...precheckResult,
        memberId: app.memberId,
        source: "deterministic-rule",
        timestampUtc: new Date().toISOString(),
      };
    }

    const aiResult = await assessLoan(app);

    return {
      memberId: app.memberId,
      source: "llamaindex-policy-agent",
      timestampUtc: new Date().toISOString(),
      dsrcPercent: Math.round(aiResult.dsrc * 100),
      aiDecisionText: aiResult.result,
    };
}

Production Considerations

  • Deploy in-region

    • Keep vector stores, logs, and inference endpoints inside approved jurisdictions.
    • Pension data often has residency constraints that override convenience.
  • Log every retrieval

    • Store retrieved policy chunks, timestamps, model version, and final outcome.
    • Auditors will ask why a specific rule was applied or ignored.
  • Redact sensitive fields

    • Mask identity numbers, bank details, and medical-related benefit data before prompting.
    • Only send what the decision actually needs.
  • Route exceptions to humans

    • Any missing data, conflicting rules, or borderline ratios should become REVIEW.
    • Do not let the model invent justification for incomplete applications.

Common Pitfalls

  1. Letting the LLM decide without deterministic checks

    • Mistake: asking the model to approve/reject everything directly.
    • Fix: enforce hard business rules first, then use LlamaIndex for policy grounding and explanation.
  2. Indexing raw member records

    • Mistake: stuffing PII-heavy HR or payroll exports into the vector index.
    • Fix: index only approved policy docs and reference member facts from your transactional system at runtime.
  3. Skipping auditability

    • Mistake: returning only "approved" or "rejected" with no evidence trail.
    • Fix: persist retrieved sources, prompt version, model name, decision timestamp, and reviewer overrides.
  4. Ignoring residency and retention rules

    • Mistake: sending pension data to an external endpoint without checking region controls.
    • Fix: pin storage and inference to approved regions and set retention policies that match fund governance requirements.

Keep learning

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

Related Guides