How to Build a loan approval Agent Using AutoGen in TypeScript for fintech

By Cyprian AaronsUpdated 2026-04-21
loan-approvalautogentypescriptfintech

A loan approval agent automates the first pass of a credit decision: it gathers applicant data, checks policy rules, scores risk, and produces a recommendation with an audit trail. For fintech, this matters because you need fast decisions without losing control over compliance, explainability, and human review for borderline cases.

Architecture

  • Application intake service

    • Normalizes borrower data from your web app, CRM, or underwriting system.
    • Validates required fields before the agent runs.
  • Policy and eligibility checker

    • Encodes hard rules like minimum income, DTI thresholds, geography restrictions, and KYC status.
    • Returns deterministic pass/fail results.
  • LLM orchestration layer

    • Uses AutoGen to coordinate one or more agents.
    • One agent can summarize the application, another can draft the recommendation.
  • Decision engine

    • Combines model output with rule-based constraints.
    • Produces approve, reject, or manual review.
  • Audit and logging pipeline

    • Stores prompts, tool calls, model outputs, and final decisions.
    • Required for internal review and regulator-facing traceability.
  • Human escalation path

    • Routes incomplete or borderline applications to an underwriter.
    • Prevents the model from making unsupported decisions.

Implementation

1) Install AutoGen and set up your TypeScript project

Use the AutoGen TypeScript package and keep your runtime configuration separate from business logic. In fintech, that separation matters because environment handling often changes between local dev, staging, and a regulated production region.

npm install @autogenai/autogen openai zod
npm install -D typescript tsx @types/node

Create a typed config object for your model client and decision thresholds.

// config.ts
export const loanPolicy = {
  minIncome: 3000,
  maxDebtToIncome: 0.42,
  minCreditScore: 620,
};

export const appConfig = {
  model: "gpt-4o-mini",
  maxTurns: 2,
};

2) Define the underwriting agent and its constraints

AutoGen’s core pattern is to create agents with explicit roles. For this use case, keep the LLM narrow: it should explain policy-based recommendations, not invent lending criteria.

// loan-agent.ts
import { AssistantAgent } from "@autogenai/autogen";
import { OpenAI } from "openai";
import { loanPolicy } from "./config";

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

export const underwritingAgent = new AssistantAgent({
  name: "underwriting_agent",
  modelClient: {
    create: async (messages) => {
      const response = await openai.chat.completions.create({
        model: "gpt-4o-mini",
        messages,
        temperature: 0,
      });
      return response.choices[0]?.message?.content ?? "";
    },
  },
  systemMessage: `
You are a loan underwriting assistant for a fintech lender.
Only use the provided applicant facts and policy thresholds.
Do not recommend approval if any required field is missing.
Return concise JSON with:
decision, reason, missing_fields, policy_checks
Policy:
- minIncome=${loanPolicy.minIncome}
- maxDebtToIncome=${loanPolicy.maxDebtToIncome}
- minCreditScore=${loanPolicy.minCreditScore}
`,
});

That temperature: 0 setting is intentional. For credit workflows you want stable outputs that are easier to audit and compare across runs.

3) Add deterministic pre-checks before calling the agent

Do not ask the model to calculate everything from scratch. Compute hard checks in code first, then let AutoGen explain the result and handle edge cases.

// evaluate.ts
import { z } from "zod";
import { underwritingAgent } from "./loan-agent";

const ApplicantSchema = z.object({
  applicantId: z.string(),
  incomeMonthly: z.number(),
  debtMonthly: z.number(),
  creditScore: z.number(),
  kycPassed: z.boolean(),
});

export async function evaluateLoan(input: unknown) {
  const applicant = ApplicantSchema.parse(input);

  const dti = applicant.debtMonthly / applicant.incomeMonthly;
  const missingFields = [];
  
  if (!applicant.kycPassed) missingFields.push("kycPassed");

  const prompt = `
Applicant:
${JSON.stringify({ ...applicant, dti }, null, 2)}

Hard checks:
- incomeMonthly >= ${3000}
- dti <= ${0.42}
- creditScore >= ${620}
- kycPassed must be true

If any hard check fails, recommend REJECT or MANUAL_REVIEW.
`;

  const result = await underwritingAgent.run([{ role: "user", content: prompt }], {
    maxTurns: 1,
    outputContentType: "text",
    cancellationToken: undefined,
    timeoutMs: undefined,
    metadata: {
      applicantId: applicant.applicantId,
      source: "loan-origination",
    },
  });

  return {
    applicantId: applicant.applicantId,
    dti,
    agentOutput: result.content,
    missingFields,
  };
}

The important pattern here is that the business rules live outside the LLM. The agent gets context and writes a recommendation; it does not become your credit policy engine.

4) Wrap the result in a decision gate

You should never directly expose raw LLM output as the final lending decision. Parse it into a controlled schema and apply final guardrails in code.

// decision-gate.ts
type Decision = "APPROVE" | "REJECT" | "MANUAL_REVIEW";

export function finalizeDecision(agentText: string): Decision {
  const normalized = agentText.toUpperCase();

  if (normalized.includes('"decision":"APPROVE"')) return "APPROVE";
  
   if (
    normalized.includes('"decision":"REJECT"') ||
    normalized.includes("REJECT")
   ) return "REJECT";

   return "MANUAL_REVIEW";
}

In production you should replace string matching with strict JSON parsing plus schema validation. The point is the same: keep the final state machine deterministic.

Production Considerations

  • Auditability

    • Store input payloads, computed features like DTI, agent prompts, model version, and final decision.
    • Keep immutable logs for compliance review and dispute resolution.
  • Data residency

    • Route EU or region-specific applicants to compliant infrastructure only.
    • Do not send PII to non-approved regions or unvetted third-party tools.
  • Guardrails

    • Block approvals when KYC is false or required fields are missing.

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