How to Build a underwriting Agent Using LlamaIndex in Python for payments

By Cyprian AaronsUpdated 2026-04-21
underwritingllamaindexpythonpayments

An underwriting agent for payments takes a merchant application, pulls the right internal and external signals, scores risk, and produces a decision package your ops or risk team can review. It matters because payment businesses lose money when they approve bad merchants too quickly or block good ones too aggressively, so the agent has to be fast, explainable, and auditable.

Architecture

  • Application intake layer
    • Receives merchant data: legal entity, MCC, volume estimates, geographies, chargeback history, website URL, and beneficial owners.
  • Policy and risk rules
    • Encodes hard stops like sanctioned countries, prohibited MCCs, missing KYC/KYB fields, or high-risk verticals.
  • Document retrieval layer
    • Uses VectorStoreIndex plus QueryEngine to pull evidence from policies, prior cases, underwriting playbooks, and compliance docs.
  • LLM reasoning layer
    • Uses OpenAI through LlamaIndex to summarize evidence and draft a recommendation with rationale.
  • Decision output layer
    • Produces a structured underwriting result: approve, manual review, decline, plus reasons and required follow-ups.
  • Audit and observability layer
    • Stores prompts, retrieved sources, decision outputs, timestamps, and model version for compliance review.

Implementation

1) Install dependencies and load underwriting knowledge

You want your agent grounded in your own policy documents first. For payments underwriting, that means product policy PDFs, prohibited business lists, chargeback thresholds, reserves policy, and regional compliance guidance.

pip install llama-index llama-index-llms-openai llama-index-embeddings-openai pydantic
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.llms.openai import OpenAI
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.core.settings import Settings

Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")

docs = SimpleDirectoryReader("./underwriting_docs").load_data()
index = VectorStoreIndex.from_documents(docs)
query_engine = index.as_query_engine(similarity_top_k=4)

This gives you a retrieval layer that can answer questions like “what is our threshold for reserves on subscription merchants?” using your internal docs instead of model memory.

2) Define a strict underwriting schema

For payments, free-form text is not enough. You need a structured response so downstream systems can route approvals into onboarding and declines into case management.

from pydantic import BaseModel, Field
from typing import List, Literal

class UnderwritingDecision(BaseModel):
    decision: Literal["approve", "manual_review", "decline"]
    risk_score: int = Field(ge=0, le=100)
    reasons: List[str]
    required_actions: List[str]
    cited_sources: List[str]

This schema keeps the output machine-readable. It also gives you an audit trail of why the agent made the call.

3) Build the underwriting function with retrieval + LLM synthesis

The pattern here is simple: retrieve policy evidence first, then ask the model to reason only over that evidence plus merchant facts. Don’t let it invent policy.

import json
from llama_index.core.prompts import PromptTemplate

UNDERWRITE_PROMPT = PromptTemplate(
    """
You are an underwriting analyst for a payments company.
Use only the provided policy context and merchant facts.

Policy context:
{context_str}

Merchant facts:
{merchant_json}

Return a concise underwriting recommendation with:
- decision: approve | manual_review | decline
- risk_score: integer 0-100
- reasons: list of short bullets
- required_actions: list of follow-up items if any
- cited_sources: list of document names or excerpts used

Be strict about compliance issues such as sanctions exposure,
prohibited businesses, KYB gaps, data residency constraints,
and chargeback risk.
"""
)

def underwrite_merchant(merchant_facts: dict) -> UnderwritingDecision:
    query = (
        f"Underwrite this payment merchant. Focus on KYB/KYC,"
        f" prohibited business checks, chargeback exposure,"
        f" cross-border risk, and reserve requirements.\n"
        f"Merchant facts: {json.dumps(merchant_facts)}"
    )
    response = query_engine.query(query)
    prompt = UNDERWRITE_PROMPT.format(
        context_str=str(response),
        merchant_json=json.dumps(merchant_facts)
    )
    llm_response = Settings.llm.complete(prompt)
    return UnderwritingDecision.model_validate_json(llm_response.text)

merchant = {
    "legal_name": "Acme Subscriptions Ltd",
    "mcc": "5968",
    "country": "GB",
    "monthly_volume_usd": 250000,
    "chargeback_ratio": 0.012,
    "website": "https://acmesubscriptions.example",
    "products": ["digital subscriptions"],
    "kyb_status": "complete"
}

decision = underwrite_merchant(merchant)
print(decision.model_dump())

This is the core pattern I use in regulated workflows:

  1. Retrieve policy evidence with query_engine.query(...).
  2. Force the model to reason over that evidence with a fixed prompt.
  3. Parse into a typed object with Pydantic.

4) Add guardrails before sending to production

Payments decisions need deterministic checks before LLM reasoning. Hard blocks should never depend on model judgment.

SANCTIONED_COUNTRIES = {"IR", "KP", "SY", "CU"}

def precheck(merchant_facts: dict):
    if merchant_facts.get("country") in SANCTIONED_COUNTRIES:
        return UnderwritingDecision(
            decision="decline",
            risk_score=100,
            reasons=["Sanctions exposure"],
            required_actions=["Block onboarding and escalate to compliance"],
            cited_sources=["internal_policy:sanctions"]
        )
    if merchant_facts.get("kyb_status") != "complete":
        return UnderwritingDecision(
            decision="manual_review",
            risk_score=75,
            reasons=["Incomplete KYB"],
            required_actions=["Collect beneficial owner documents"],
            cited_sources=["internal_policy:kyb_requirements"]
        )
    return underwrite_merchant(merchant_facts)

That keeps obvious compliance failures out of the LLM path entirely.

Production Considerations

  • Deploy with region-aware data handling
    • Keep customer data in the required jurisdiction. If you operate in the EU or UK, make sure document storage and vector indexes respect residency requirements.
  • Log every decision artifact
    • Store input payloads, retrieved chunks, prompt version, model version (gpt-4o-mini or whatever you run), output JSON, and final human override status.
  • Add deterministic guardrails
    • Sanctions screening, prohibited MCC blocks, velocity thresholds, and KYB completeness checks should happen before any LLM call.
  • Monitor drift by segment
    • Track approval rates by country, MCC, ticket size range, and processor route. If one segment suddenly shifts after a prompt change or doc update, stop rollout.

Common Pitfalls

  1. Letting the model decide without policy grounding

    • Avoid this by always retrieving from approved underwriting docs first using VectorStoreIndex and QueryEngine.
  2. Using free-form text outputs in production

    • Avoid this by forcing structured responses with Pydantic models like UnderwritingDecision. Downstream systems need stable fields.
  3. Skipping hard compliance checks

    • Avoid this by running sanctions lists, KYB completeness checks, prohibited business rules, and data residency constraints before LLM inference.

A payments underwriting agent is not just an LLM wrapped around a prompt. It is a controlled decision system that combines retrieval from internal policy docs with deterministic compliance gates and structured outputs your ops team can trust. If you build it that way from day one with LlamaIndex’s VectorStoreIndex, QueryEngine, prompt templates, and typed validation flow you get something that can actually survive contact with auditors and production traffic.


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