How to Build a KYC verification Agent Using AutoGen in Python for fintech
A KYC verification agent automates the boring but high-stakes parts of onboarding: collecting customer data, checking identity documents, screening against watchlists, and deciding whether a case is clean, needs review, or must be rejected. For fintech, this matters because every manual review adds cost and latency, while every bad decision creates compliance risk, fraud exposure, and audit pain.
Architecture
- •
User intake layer
- •Accepts customer-submitted fields like name, DOB, address, nationality, and document metadata.
- •Normalizes inputs before they hit the agent workflow.
- •
Document analysis component
- •Extracts structured data from passports, IDs, utility bills, or bank statements.
- •Flags missing fields, expired documents, or mismatched identity attributes.
- •
Screening component
- •Checks names against sanctions/PEP/watchlist sources.
- •Produces explainable matches with confidence scores and reasons.
- •
KYC policy engine
- •Encodes your fintech’s rules: pass, manual review, or reject.
- •Applies jurisdiction-specific requirements like residency rules and retention policies.
- •
Audit and evidence store
- •Persists every model output, tool call, rule decision, and timestamp.
- •Makes the workflow defensible during compliance reviews.
- •
Human review handoff
- •Routes ambiguous cases to an analyst with a compact evidence bundle.
- •Prevents the agent from making unsupported final decisions on edge cases.
Implementation
1) Install AutoGen and define the agent roles
For this pattern I use a coordinator agent plus specialist agents for extraction and compliance reasoning. AutoGen’s AssistantAgent is enough for the LLM side; tools are exposed through register_function.
from autogen import AssistantAgent
llm_config = {
"model": "gpt-4o-mini",
"api_key": "YOUR_OPENAI_API_KEY",
"temperature": 0,
}
kyc_coordinator = AssistantAgent(
name="kyc_coordinator",
llm_config=llm_config,
system_message=(
"You coordinate KYC verification. "
"Do not approve customers without evidence. "
"If sanctions or document mismatch is present, route to manual review."
),
)
document_agent = AssistantAgent(
name="document_agent",
llm_config=llm_config,
system_message=(
"Extract identity fields from submitted KYC documents. "
"Return JSON only with normalized fields and confidence."
),
)
compliance_agent = AssistantAgent(
name="compliance_agent",
llm_config=llm_config,
system_message=(
"Evaluate KYC evidence against policy. "
"Return one of: PASS, REVIEW, REJECT with reasons."
),
)
2) Register deterministic tools for screening and policy checks
Do not let the model “invent” sanctions logic. Keep screening deterministic and auditable. In AutoGen you can expose Python functions as callable tools.
from autogen import register_function
WATCHLIST = {"john doe", "ivan petrov"}
SANCTIONS = {"acme front ltd"}
def screen_name(name: str) -> dict:
normalized = name.strip().lower()
return {
"name": name,
"hit": normalized in WATCHLIST or normalized in SANCTIONS,
"source": "internal_watchlist",
"matched_term": normalized if normalized in WATCHLIST or normalized in SANCTIONS else None,
}
def apply_kyc_policy(extracted: dict, screening: dict) -> dict:
if screening["hit"]:
return {"decision": "REVIEW", "reason": "watchlist_hit"}
if extracted.get("doc_expired"):
return {"decision": "REJECT", "reason": "expired_document"}
if extracted.get("name_confidence", 0) < 0.85:
return {"decision": "REVIEW", "reason": "low_extraction_confidence"}
return {"decision": "PASS", "reason": "meets_policy"}
register_function(
screen_name,
caller=compliance_agent,
executor=kyc_coordinator,
description="Screen a customer name against internal watchlists.",
)
register_function(
apply_kyc_policy,
caller=compliance_agent,
executor=kyc_coordinator,
description="Apply KYC policy to extracted document data and screening result.",
)
3) Run the verification flow with structured inputs
The practical pattern is: extract first, screen second, decide last. The coordinator passes evidence between agents instead of asking one model to do everything.
customer_case = {
"full_name": "Jane Smith",
"dob": "1991-02-14",
"country": "GB",
"document_type": "passport",
"doc_expired": False,
}
extraction_prompt = f"""
Extract KYC fields from this case as JSON:
{customer_case}
Include full_name_normalized and name_confidence.
"""
extracted_result = document_agent.generate_reply(messages=[{"role": "user", "content": extraction_prompt}])
screening_result = screen_name(customer_case["full_name"])
policy_input = {
"extracted": extracted_result,
"screening": screening_result,
}
decision_prompt = f"""
Use this evidence to decide KYC outcome:
{policy_input}
Call apply_kyc_policy if needed.
Return a concise verdict with reason.
"""
final_decision = compliance_agent.generate_reply(messages=[{"role": "user", "content": decision_prompt}])
print("EXTRACTION:", extracted_result)
print("SCREENING:", screening_result)
print("DECISION:", final_decision)
4) Add audit logging before you ship it
Fintech teams need traceability more than clever prompts. Store inputs, outputs, tool results, model version, and timestamps so compliance can reconstruct every decision later.
import json
from datetime import datetime
from pathlib import Path
audit_path = Path("kyc_audit_log.jsonl")
def write_audit_event(case_id: str, stage: str, payload: dict):
event = {
"case_id": case_id,
"stage": stage,
"timestamp_utc": datetime.utcnow().isoformat(),
**payload,
}
with audit_path.open("a", encoding="utf-8") as f:
f.write(json.dumps(event) + "\n")
write_audit_event("case_001", "input", customer_case)
write_audit_event("case_001", "screening", screening_result)
write_audit_event("case_001", "final_decision", {"result": str(final_decision)})
Production Considerations
- •
Deploy with strict data residency controls
- •Keep PII processing in-region where your regulator requires it.
- •If you use hosted LLM endpoints, confirm where prompts and logs are stored.
- •
Monitor decision drift and override rates
- •Track pass/review/reject ratios by country, product line, and document type.
- •A spike in manual overrides usually means your extraction prompts or policy thresholds are off.
- •
Build hard guardrails around final actions
- •The agent should recommend; your backend should execute the actual onboarding state change.
- •Require deterministic policy checks for sanctions hits and expired documents.
- •
Separate PII from model context where possible
- •Mask passport numbers, national IDs, and account numbers before sending text to the model.
- •Keep raw documents in encrypted storage; send only the minimum necessary fields into AutoGen.
Common Pitfalls
- •
Letting the LLM make compliance decisions directly
- •Don’t ask a model to “approve if it looks good.”
- •Use deterministic rules for hard failures like sanctions hits and expired IDs.
- •
Ignoring explainability
- •A verdict without evidence is useless in fintech reviews.
- •Always persist which fields matched, which rule fired, and which agent produced the output.
- •
Mixing jurisdictions in one policy blob
- •KYC rules differ across markets; UK onboarding is not identical to UAE or EU flows.
- •Split policy by region so residency requirements and retention rules stay explicit.
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