How to Build a compliance checking Agent Using LangChain in Python for wealth management

By Cyprian AaronsUpdated 2026-04-21
compliance-checkinglangchainpythonwealth-management

A compliance checking agent for wealth management reviews client communications, portfolio actions, and proposed recommendations against firm policy and regulatory rules before anything is sent or executed. It matters because a bad recommendation, missing suitability check, or unlogged exception can turn into a regulatory issue fast.

Architecture

  • Input layer

    • Ingests advisor notes, client emails, trade proposals, and meeting summaries.
    • Normalizes text and attaches metadata like client jurisdiction, account type, and timestamp.
  • Policy retrieval layer

    • Pulls the relevant compliance policies from a curated document store.
    • Uses Chroma or another vector store with RetrievalQA-style retrieval patterns.
  • Rule evaluation layer

    • Applies deterministic checks for hard rules like restricted securities, concentration limits, KYC status, and suitability flags.
    • Keeps these checks outside the LLM so they are auditable.
  • LLM reasoning layer

    • Uses LangChain to classify risk, explain policy conflicts, and draft remediation notes.
    • Produces structured output so downstream systems can route approvals or block actions.
  • Audit logging layer

    • Stores the input, retrieved policy snippets, model output, and final decision.
    • Required for review trails in wealth management.
  • Decision gateway

    • Returns one of: approve, needs_review, or block.
    • Pushes high-risk cases to a human compliance officer.

Implementation

  1. Load policies and build a retriever

    Keep your policy corpus tight. Do not dump the entire compliance manual into the prompt; retrieve only the relevant sections for the client context.

    from langchain_community.document_loaders import TextLoader
    from langchain_text_splitters import RecursiveCharacterTextSplitter
    from langchain_community.vectorstores import Chroma
    from langchain_openai import OpenAIEmbeddings
    
    loader = TextLoader("wealth_compliance_policy.txt", encoding="utf-8")
    docs = loader.load()
    
    splitter = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=120)
    chunks = splitter.split_documents(docs)
    
    embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
    vectorstore = Chroma.from_documents(
        documents=chunks,
        embedding=embeddings,
        persist_directory="./chroma_compliance_db"
    )
    
    retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
    
  2. Define a structured compliance verdict

    For wealth management, free-form text is not enough. You want a schema that captures decision, rationale, cited policy references, and escalation level.

    from pydantic import BaseModel, Field
    from typing import Literal, List
    
    class ComplianceVerdict(BaseModel):
        decision: Literal["approve", "needs_review", "block"]
        risk_level: Literal["low", "medium", "high"]
        rationale: str
        policy_refs: List[str] = Field(default_factory=list)
        escalation_required: bool
    
  3. Build the LangChain agent chain

    Use ChatOpenAI, ChatPromptTemplate, and with_structured_output() so the model returns machine-readable output. This pattern is easier to audit than parsing raw prose.

    from langchain_openai import ChatOpenAI
    from langchain_core.prompts import ChatPromptTemplate
    
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
    
    prompt = ChatPromptTemplate.from_messages([
        ("system",
         "You are a compliance checking agent for wealth management. "
         "Assess suitability, restricted securities, concentration risk, "
         "and whether the recommendation conflicts with policy. "
         "Use only the provided context."),
        ("human",
         "Client context:\n{client_context}\n\n"
         "Proposed action:\n{proposed_action}\n\n"
         "Retrieved policy context:\n{policy_context}\n\n"
         "Return a compliance verdict.")
    ])
    
    structured_llm = llm.with_structured_output(ComplianceVerdict)
    
    def check_compliance(client_context: str, proposed_action: str):
        docs = retriever.invoke(client_context + "\n" + proposed_action)
        policy_context = "\n\n".join([d.page_content for d in docs])
    
        messages = prompt.format_messages(
            client_context=client_context,
            proposed_action=proposed_action,
            policy_context=policy_context,
        )
    
        verdict = structured_llm.invoke(messages)
        return verdict
    
    result = check_compliance(
        client_context="Client is a US resident with moderate risk tolerance. Account is discretionary.",
        proposed_action="Recommend increasing exposure to a single biotech stock to 18% of portfolio."
    )
    
    print(result.model_dump())
    
  4. Add deterministic pre-checks before the LLM

    Hard rules should block bad cases before model inference. This reduces cost and prevents the model from being asked to justify something it should never approve.

    def hard_rule_checks(client_context: str, proposed_action: str) -> list[str]:
        issues = []
    
        if "restricted security" in proposed_action.lower():
            issues.append("Restricted security flagged.")
    
        if "%" in proposed_action:
            # Replace with real parsing in production.
            issues.append("Concentration limit may be exceeded; manual review required.")
    
        if "non-us" in client_context.lower() and "us-only product" in proposed_action.lower():
            issues.append("Jurisdiction mismatch.")
    
        return issues
    
    issues = hard_rule_checks(
        "Client is a US resident with moderate risk tolerance.",
        "Recommend increasing exposure to a single biotech stock to 18% of portfolio."
    )
    
    if issues:
        print({"decision": "needs_review", "issues": issues})
    else:
        print(check_compliance(...))
    

Production Considerations

  • Auditability

    • Log every request with client metadata, retrieved policy chunks, model version, and final verdict.
    • Store immutable audit records in your SIEM or regulated archive.
  • Data residency

    • Keep client data and embeddings in-region if your book includes EU/UK clients or local residency requirements.
    • Avoid sending sensitive PII to external services unless your legal team has approved that path.
  • Guardrails

    • Use hard-coded blocks for restricted lists, sanction hits, KYC failures, and concentration thresholds.
    • Treat the LLM as an explainer and classifier, not the source of truth for regulatory decisions.
  • Monitoring

    • Track false approvals, false blocks, escalation rate, and average time-to-decision.
    • Sample decisions weekly with compliance officers to catch drift in model behavior or policy coverage.

Common Pitfalls

  1. Letting the model make final decisions on hard rules

    If restricted securities or jurisdiction checks are deterministic, enforce them in code first. The LLM should explain exceptions, not override them.

  2. Feeding too much policy text into the prompt

    Large prompts increase noise and reduce precision. Use retrieval to pass only the relevant clauses for that client action.

  3. Skipping structured output

    Free-text responses are painful to audit and automate against. Use with_structured_output() with Pydantic models so downstream systems can reliably route approvals or escalate cases.


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