How to Build a compliance checking Agent Using LangChain in Python for investment banking
A compliance checking agent in investment banking reviews drafts, emails, chat transcripts, trade notes, and client-facing documents against policy. Its job is to catch prohibited language, missing disclosures, MNPI risk, suitability issues, and recordkeeping gaps before a human sends anything out.
This matters because the failure mode is expensive: regulatory breaches, audit findings, and reputational damage. The agent should not “decide” compliance on its own; it should flag risk, cite policy, and route borderline cases to a human reviewer.
Architecture
- •
Input layer
- •Accepts text from emails, pitch books, research notes, chat messages, or trade commentary.
- •Normalizes metadata like desk, region, client type, timestamp, and document source.
- •
Policy retrieval layer
- •Pulls relevant internal policies from a controlled corpus.
- •Uses vector search over approved compliance manuals, surveillance rules, and regional regulations.
- •
Compliance reasoning layer
- •Uses an LLM chain to compare the input against policy snippets.
- •Produces structured findings: issue type, severity, rationale, and policy citations.
- •
Decision layer
- •Applies deterministic rules for auto-pass vs escalate.
- •Escalates anything involving MNPI, sanctions exposure, suitability conflicts, or missing approvals.
- •
Audit logging layer
- •Stores the original input, retrieved policy context, model output, timestamps, and reviewer actions.
- •Keeps an immutable trail for internal audit and regulator review.
- •
Human review interface
- •Lets compliance officers approve, reject, or annotate findings.
- •Feeds reviewed cases back into evaluation datasets.
Implementation
1) Load policies into a retriever
Use LangChain’s TextLoader, RecursiveCharacterTextSplitter, FAISS, and embeddings to build a retrieval index from approved compliance docs. Keep the corpus scoped by jurisdiction and business line; don’t mix US equities policy with EMEA debt desk guidance unless you mean to.
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
loader = TextLoader("policies/investment_banking_compliance.txt", encoding="utf-8")
docs = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=900, chunk_overlap=150)
chunks = splitter.split_documents(docs)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = FAISS.from_documents(chunks, embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
2) Define a structured compliance output
You want machine-readable output so your app can route decisions cleanly. Use Pydantic with PydanticOutputParser instead of parsing free-form prose.
from typing import List
from pydantic import BaseModel, Field
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import PydanticOutputParser
class ComplianceFinding(BaseModel):
risk_level: str = Field(description="low|medium|high")
issues: List[str]
action: str = Field(description="approve|escalate|reject")
citations: List[str]
parser = PydanticOutputParser(pydantic_object=ComplianceFinding)
prompt = ChatPromptTemplate.from_messages([
("system",
"You are a compliance assistant for investment banking. "
"Use only the provided policy context. "
"Flag potential violations conservatively."),
("user",
"Document:\n{document}\n\nPolicy context:\n{context}\n\n{format_instructions}")
]).partial(format_instructions=parser.get_format_instructions())
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
3) Build the LangChain pipeline
Use the retriever to fetch policy context and pipe it into the prompt. The key pattern here is RunnablePassthrough.assign(...), which keeps your original document while adding retrieved context.
from langchain_core.runnables import RunnablePassthrough
def format_docs(docs):
return "\n\n".join(
f"[Source {i+1}] {doc.page_content}"
for i, doc in enumerate(docs)
)
compliance_chain = (
{
"document": RunnablePassthrough(),
"context": retriever | format_docs,
}
| prompt
| llm
| parser
)
sample_doc = """
Client email draft:
We can guarantee strong returns if you move ahead today.
Also please keep this off the formal record until after execution.
"""
result = compliance_chain.invoke(sample_doc)
print(result.model_dump())
That pattern gives you a single callable chain that returns structured output. In production you would wrap this behind an API endpoint and attach metadata like user ID, desk name, and document ID before invocation.
4) Add deterministic escalation rules
LLMs are good at spotting risk patterns. They are not your final control point. Add hard rules for specific phrases and categories that must always escalate in investment banking workflows.
HIGH_RISK_TRIGGERS = [
"guarantee returns",
"off the record",
"non-public information",
"MNPI",
"inside information",
]
def deterministic_escalation(text: str) -> bool:
lowered = text.lower()
return any(trigger in lowered for trigger in HIGH_RISK_TRIGGERS)
doc_text = sample_doc.strip()
if deterministic_escalation(doc_text):
decision = {"action": "escalate", "reason": "hard_rule_trigger"}
else:
decision = result.model_dump()
print(decision)
This hybrid design is what you want in regulated environments. The model proposes; the rule engine constrains; humans close the loop on anything ambiguous.
Production Considerations
- •
Deploy in-region
- •Keep processing and storage inside approved data residency boundaries.
- •If your bank requires EU-only or US-only handling for certain desks or clients, enforce that at the infrastructure layer.
- •
Log everything needed for audit
- •Persist input text hashes or encrypted originals, retrieved policy chunks, model version, prompt version, output JSON, and reviewer action.
- •Make logs immutable and time-stamped so audit can reconstruct exactly why a document was escalated.
- •
Add monitoring for drift and false negatives
- •Track escalation rate by desk and document type.
- •Sample “approved” outputs for human review because false negatives are more dangerous than noisy escalations.
- •
Use guardrails around scope
- •Restrict the agent to compliance triage only.
- •Do not let it draft legal advice or override surveillance systems; route those cases to legal/compliance owners.
Common Pitfalls
- •
Using one generic policy index for all regions
- •Investment banking policies vary by jurisdiction.
- •Split indexes by region and business line so retrieval stays relevant and defensible.
- •
Letting the LLM make final decisions on high-risk cases
- •A model can classify risk; it should not be the final arbiter for MNPI, sanctions hits, or client suitability issues.
- •Add hard escalation rules plus human approval gates.
- •
Skipping provenance in outputs
- •A bare “high risk” label is not enough for auditors.
- •Always return citations to specific policy snippets and store the exact model response used in the workflow.
If you build this correctly, you get a control point that is useful to bankers and defensible to compliance teams. The standard is simple: every decision should be explainable with source policy text, reproducible from logs, and safe under human oversight.
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