How to Build a KYC verification Agent Using LlamaIndex in Python for wealth management

By Cyprian AaronsUpdated 2026-04-21
kyc-verificationllamaindexpythonwealth-management

A KYC verification agent for wealth management takes client documents, extracts identity and source-of-funds evidence, checks them against policy, and produces a review packet for compliance teams. It matters because onboarding high-net-worth clients is slow, regulated, and expensive; the agent reduces manual review time without weakening auditability or control.

Architecture

  • Document ingestion layer

    • Pulls PDFs, scans, ID images, bank statements, and proof-of-address files from secure storage.
    • Normalizes text with OCR before indexing.
  • LlamaIndex knowledge layer

    • Stores KYC policy docs, jurisdiction rules, acceptable document lists, and escalation playbooks.
    • Uses VectorStoreIndex for retrieval over internal policy content.
  • Verification workflow

    • Runs deterministic checks first: document presence, expiry dates, name matching, address consistency.
    • Uses an LLM only for structured extraction and gap analysis.
  • Compliance decision engine

    • Maps findings to outcomes like pass, needs_review, or reject.
    • Keeps human-in-the-loop approval for borderline cases.
  • Audit trail store

    • Persists every prompt, retrieved chunk, extracted field, and final decision.
    • Required for regulator review and internal model governance.
  • Secure API layer

    • Exposes the agent to onboarding systems through authenticated endpoints.
    • Enforces tenant isolation and data residency constraints.

Implementation

1) Index your KYC policy and onboarding rules

The agent needs a retrieval source for internal policy. In wealth management, this is not optional; the model should not guess acceptable evidence requirements.

from llama_index.core import Document, VectorStoreIndex

policy_docs = [
    Document(text="""
    KYC Policy v3:
    - Collect government-issued ID
    - Collect proof of address dated within 90 days
    - Collect source of funds evidence for accounts above threshold
    - Escalate PEP and sanctions matches to manual review
    - Do not approve if name mismatch cannot be resolved
    """, metadata={"source": "kyc_policy_v3"}),
    Document(text="""
    Wealth Management Onboarding Rules:
    - Non-resident clients require enhanced due diligence
    - Trust structures require beneficial ownership documentation
    - Accept bank statements only if they show account holder name and recent activity
    """, metadata={"source": "wm_onboarding_rules"}),
]

index = VectorStoreIndex.from_documents(policy_docs)
query_engine = index.as_query_engine(similarity_top_k=2)

2) Extract structured KYC fields from client documents

Use the LLM to transform document text into a strict schema. In production, feed it OCR output from PDFs or scanned images.

from pydantic import BaseModel, Field
from llama_index.core.prompts import PromptTemplate

class KYCExtraction(BaseModel):
    full_name: str = Field(description="Client full legal name")
    date_of_birth: str = Field(description="Date of birth in YYYY-MM-DD")
    address: str = Field(description="Residential address")
    id_type: str = Field(description="Type of government ID")
    id_expiry: str = Field(description="ID expiry date in YYYY-MM-DD")
    source_of_funds: str = Field(description="Declared source of funds")

extract_prompt = PromptTemplate(
    """Extract KYC fields from the following client document text.
Return only values that are explicitly supported by the text.

Document:
{doc_text}

Fields:
- full_name
- date_of_birth
- address
- id_type
- id_expiry
- source_of_funds
"""
)

def extract_kyc_fields(llm, doc_text: str) -> KYCExtraction:
    response = llm.complete(extract_prompt.format(doc_text=doc_text))
    return KYCExtraction.model_validate_json(response.text)

3) Compare extracted data against policy and produce a decision

This is where LlamaIndex helps most: retrieve the relevant rules before making a decision. That keeps the agent grounded in your actual KYC policy instead of generic banking advice.

from llama_index.core.llms import ChatMessage

def verify_client(llm, query_engine, extracted: KYCExtraction):
    rules_response = query_engine.query(
        f"What KYC rules apply to a client with source of funds '{extracted.source_of_funds}' "
        f"and ID type '{extracted.id_type}'?"
    )

    decision_prompt = f"""
You are a KYC reviewer for wealth management.
Use only the policy context below.

Policy context:
{rules_response.response}

Client extracted data:
{extracted.model_dump_json(indent=2)}

Return JSON with:
- decision: pass | needs_review | reject
- reasons: array of short strings
- missing_items: array of strings
"""

    result = llm.complete(decision_prompt)
    return result.text

# Example usage assumes you already configured an LLM instance in LlamaIndex.
# from llama_index.llms.openai import OpenAI
# llm = OpenAI(model="gpt-4o-mini")

# extracted = extract_kyc_fields(llm, ocr_text)
# verdict = verify_client(llm, query_engine, extracted)
# print(verdict)

4) Add human review and audit logging

Wealth management onboarding needs traceability. Store what was retrieved, what was extracted, and why the system escalated.

import json
from datetime import datetime

def audit_event(client_id: str, payload: dict):
    record = {
        "client_id": client_id,
        "timestamp": datetime.utcnow().isoformat(),
        "payload": payload,
    }
    with open("kyc_audit_log.jsonl", "a", encoding="utf-8") as f:
        f.write(json.dumps(record) + "\n")

# Example audit write
audit_event("client_123", {
    "document_source": "s3://secure-bucket/onboarding/client_123/id.pdf",
    "decision": "needs_review",
})

Production Considerations

  • Deploy inside your controlled boundary

    • Keep OCR output, embeddings, and prompts in the same region as client data.
    • For cross-border wealth clients, enforce residency by jurisdiction.
  • Log every decision path

    • Persist retrieved policy chunks from query_engine.query().
    • Store final outputs from llm.complete() alongside model version and prompt hash.
  • Add guardrails before auto-decisioning

    • Hard-fail on missing ID expiry dates or unresolved name mismatches.
    • Route PEP/sanctions hits to manual compliance review even if the model looks confident.
  • Monitor drift in document types

    • Track false positives on passports, utility bills, trust deeds, and bank statements.
    • Wealth clients often submit complex entity structures that break simple extraction pipelines.

Common Pitfalls

  1. Using the LLM as the source of truth

    • Don’t let it invent policy or fill missing facts.
    • Retrieve policy with VectorStoreIndex first, then ask the model to classify against that context.
  2. Skipping deterministic checks

    • Expiry dates, file presence, and exact name matching should be code-driven.
    • The model should assist review, not replace basic validation logic.
  3. Ignoring audit requirements

    • If you cannot reconstruct why a client was approved or escalated, you do not have a compliant workflow.
    • Log prompts, retrieved context, extracted fields, and final decisions per case.

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