How to Build a underwriting Agent Using LlamaIndex in Python for investment banking
An underwriting agent in investment banking reviews deal documents, extracts issuer and transaction facts, checks them against internal policy, and drafts a credit or capital-markets recommendation with citations. It matters because underwriting teams spend too much time reconciling PDFs, decks, financial models, and policy manuals by hand, and every missed clause or stale assumption creates compliance and execution risk.
Architecture
- •
Document ingestion layer
- •Pulls from deal rooms, shared drives, S3, or internal DMS.
- •Normalizes PDFs, Word docs, pitch books, covenant sheets, and term sheets into text.
- •
Indexing layer
- •Uses LlamaIndex to build a searchable knowledge base over:
- •underwriting policy
- •prior deals
- •issuer financials
- •regulatory guidance
- •Keeps sources separated by domain so retrieval stays precise.
- •Uses LlamaIndex to build a searchable knowledge base over:
- •
Retrieval + synthesis layer
- •Retrieves the most relevant clauses, ratios, and precedent transactions.
- •Produces a structured underwriting summary with citations.
- •
Tooling layer
- •Adds deterministic tools for calculations like leverage ratios, DSCR, or spread comparisons.
- •Prevents the model from inventing numbers when a formula is available.
- •
Guardrails layer
- •Enforces compliance checks before any recommendation is surfaced.
- •Blocks outputs without source citations or with missing required fields.
- •
Audit layer
- •Logs prompts, retrieved chunks, model outputs, and final decisions.
- •Supports review by risk, legal, and model governance teams.
Implementation
1. Install dependencies and load documents
Use LlamaIndex to load underwriting policies and deal documents into Document objects. In production you would replace local files with controlled access to your document store.
from pathlib import Path
from llama_index.core import VectorStoreIndex
from llama_index.core import Settings
from llama_index.core import SimpleDirectoryReader
from llama_index.core.llms import OpenAI
# Configure your LLM once for the app
Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)
# Load policy docs and deal docs from separate folders
policy_docs = SimpleDirectoryReader(
input_dir="./data/policies",
recursive=True,
).load_data()
deal_docs = SimpleDirectoryReader(
input_dir="./data/deals",
recursive=True,
).load_data()
all_docs = policy_docs + deal_docs
print(f"Loaded {len(all_docs)} documents")
The key pattern here is separation of concerns. Keep policy material distinct from live deal data so you can later filter retrieval by document type.
2. Build an index with metadata-aware retrieval
For underwriting work, metadata matters. Tag documents with source type so you can retrieve policy clauses differently from issuer materials.
from llama_index.core import Document
tagged_docs = []
for doc in all_docs:
source_path = doc.metadata.get("file_path", "")
if "policies" in source_path:
doc.metadata["doc_type"] = "policy"
elif "deals" in source_path:
doc.metadata["doc_type"] = "deal"
else:
doc.metadata["doc_type"] = "unknown"
tagged_docs.append(doc)
index = VectorStoreIndex.from_documents(tagged_docs)
query_engine = index.as_query_engine(similarity_top_k=5)
This gives you a single index for the tutorial. In a real platform you may split indexes by business line or jurisdiction to satisfy data residency controls.
3. Query the agent for an underwriting summary
The simplest useful agent is not a free-form chatbot. It is a retrieval-backed summarizer that answers only from source material and cites what it used.
question = """
Assess whether this transaction appears consistent with underwriting policy.
Focus on leverage limits, covenant requirements, disclosure completeness,
and any red flags in the latest issuer materials.
"""
response = query_engine.query(question)
print(str(response))
If you want more control over output shape, use StructuredPlanner patterns later. For now the important part is that query_engine.query() returns grounded answers from indexed sources rather than a blank-slate generation.
4. Add deterministic checks for bank-specific controls
Underwriting should not rely on the model to compute ratios or validate thresholds. Use Python for calculations and let LlamaIndex handle retrieval plus narrative synthesis.
def leverage_ratio(total_debt: float, ebitda: float) -> float:
if ebitda <= 0:
raise ValueError("EBITDA must be positive")
return total_debt / ebitda
deal_total_debt = 825_000_000
deal_ebitda = 210_000_000
ratio = leverage_ratio(deal_total_debt, deal_ebitda)
policy_check_prompt = f"""
The transaction has debt/EBITDA of {ratio:.2f}x.
Using the indexed underwriting policies and deal documents,
state whether this appears within allowed limits and cite supporting sources.
If the evidence is insufficient, say so explicitly.
"""
policy_response = query_engine.query(policy_check_prompt)
print(policy_response)
That pattern matters because investment banking workflows need traceable logic. The model can explain implications; your code should own arithmetic and threshold enforcement.
Production Considerations
- •
Auditability
- •Store every prompt, retrieved chunk IDs, document version hash, and final response.
- •Reviewers should be able to reconstruct why the agent recommended approval or escalation.
- •
Compliance controls
- •Add mandatory citation checks before surfacing output to bankers.
- •Block responses if the agent cannot cite current policy language or approved issuer disclosures.
- •
Data residency and access control
- •Keep indices inside the approved region for the desk or legal entity.
- •Enforce document-level ACLs so one deal team cannot retrieve another team’s confidential materials.
- •
Monitoring
- •Track retrieval hit rate, citation coverage, refusal rate, and hallucination reports.
- •Alert when the agent starts answering with stale policy versions or low-confidence evidence.
Common Pitfalls
- •
Using one giant index for everything
- •This mixes policies, past deals, research notes, and draft materials.
- •Fix it by segmenting indexes by business function, jurisdiction, and confidentiality level.
- •
Letting the model do numeric validation
- •Models are bad at reliable calculations under pressure.
- •Compute ratios in Python first, then ask LlamaIndex to explain them against policy.
- •
Skipping source versioning
- •An underwriting recommendation based on last quarter’s covenant rules is a governance problem.
- •Store document hashes and effective dates so every answer ties back to an auditable source set.
A good underwriting agent does not replace banker judgment. It removes manual retrieval work, standardizes first-pass analysis, and makes every recommendation easier to defend in front of risk, legal, and audit.
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