How to Build a compliance checking Agent Using LangChain in Python for pension funds

By Cyprian AaronsUpdated 2026-04-21
compliance-checkinglangchainpythonpension-funds

A compliance checking agent for pension funds reviews requests, documents, and transactions against internal policy and regulatory rules before a human approves them. It matters because pension operations are high-trust, heavily audited, and full of edge cases where a small mistake can trigger regulatory exposure, member harm, or costly remediation.

Architecture

  • Document ingestion layer

    • Pulls policy PDFs, fund rules, investment mandates, and member communication standards.
    • Normalizes them into text chunks for retrieval.
  • Policy retrieval layer

    • Uses embeddings and a vector store to find the exact clauses relevant to a request.
    • Keeps the agent grounded in source documents instead of model memory.
  • Compliance reasoning layer

    • Runs the LLM over the request plus retrieved policy context.
    • Produces a structured decision: approve, reject, or needs_review.
  • Audit logging layer

    • Stores the input, retrieved clauses, model output, timestamps, and decision rationale.
    • Gives compliance teams a defensible trail for every recommendation.
  • Guardrails layer

    • Blocks unsupported actions like final approval without human sign-off.
    • Enforces data minimization and redaction for member PII.
  • Human review integration

    • Escalates ambiguous or high-risk cases to a compliance officer.
    • Prevents automation from crossing control boundaries.

Implementation

1) Load policy documents and build a retriever

For pension funds, your source of truth is usually policy PDFs, scheme rules, investment guidelines, and communication templates. Start by chunking those documents and indexing them with a vector store so the agent can cite the right clause.

from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings

# Load pension fund policy docs
loaders = [
    PyPDFLoader("policies/investment_mandate.pdf"),
    PyPDFLoader("policies/member_comms_policy.pdf"),
    PyPDFLoader("policies/escalation_rules.pdf"),
]

docs = []
for loader in loaders:
    docs.extend(loader.load())

splitter = RecursiveCharacterTextSplitter(chunk_size=1000, 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

Do not let the model free-text its way through a regulated workflow. Use PydanticOutputParser so your downstream system gets predictable fields for decisioning and audit.

from pydantic import BaseModel, Field
from typing import Literal, List

class ComplianceResult(BaseModel):
    decision: Literal["approve", "reject", "needs_review"] = Field(...)
    risk_level: Literal["low", "medium", "high"] = Field(...)
    reasons: List[str] = Field(...)
    cited_clauses: List[str] = Field(...)

3) Build the LangChain prompt + chain

This pattern uses ChatPromptTemplate, create_retrieval_chain, and StrOutputParser style composition. The key is to pass retrieved policy context into the prompt and force the model to justify every outcome with citations.

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import PydanticOutputParser
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain.chains.retrieval import create_retrieval_chain

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

parser = PydanticOutputParser(pydantic_object=ComplianceResult)

prompt = ChatPromptTemplate.from_messages([
    ("system",
     "You are a compliance checking assistant for a pension fund. "
     "Use only the provided policy context. "
     "If policy evidence is insufficient, return needs_review."),
    ("human",
     "Request:\n{request}\n\n"
     "Policy context:\n{context}\n\n"
     "{format_instructions}")
])

document_chain = create_stuff_documents_chain(
    llm=llm,
    prompt=prompt.partial(format_instructions=parser.get_format_instructions())
)

compliance_chain = create_retrieval_chain(retriever, document_chain)

result = compliance_chain.invoke({
    "request": (
        "A trustee wants to approve an exception to the standard asset allocation "
        "for a new alternative investment product."
    )
})

parsed = parser.parse(result["answer"])
print(parsed.model_dump())

4) Add an approval gate before any downstream action

A compliance agent should recommend; it should not execute. Put a hard gate between model output and business action so only low-risk approvals with strong citations move forward automatically.

def route_decision(compliance_result: ComplianceResult) -> str:
    if compliance_result.decision == "approve" and compliance_result.risk_level == "low":
        return "auto_approved_for_human_confirmation"
    if compliance_result.decision == "needs_review":
        return "send_to_compliance_officer"
    return "blocked"

route = route_decision(parsed)
print(route)

Production Considerations

  • Keep data residency explicit

    • Pension fund records often have jurisdictional constraints.
    • Pin your model endpoint and vector store region to approved geographies and avoid sending member PII outside that boundary.
  • Log every retrieval and decision

    • Store document IDs, clause text, prompt version, model version, output JSON, and final human disposition.
    • Auditors will ask why the agent made a recommendation; “the model said so” is not acceptable.
  • Add deterministic guardrails

    • Reject outputs that do not parse into ComplianceResult.
    • Block any recommendation that lacks cited clauses or returns empty evidence for high-risk requests.
  • Separate advisory from execution

    • The agent should never update records or approve exceptions directly.
    • Use human-in-the-loop review for trustee decisions, benefit exceptions, complaints handling, and any case involving regulated communications.

Common Pitfalls

  1. Using the LLM without retrieval

    • Problem: The agent hallucinates policy interpretations from general training data.
    • Fix: Always ground decisions in retrieved pension fund policies using Retriever + create_retrieval_chain.
  2. Letting free-text answers drive automation

    • Problem: Unstructured responses are hard to validate and impossible to audit reliably.
    • Fix: Force structured outputs with PydanticOutputParser or equivalent schema validation before any workflow step.
  3. Ignoring jurisdiction and residency constraints

    • Problem: Member data crosses borders or lands in non-approved systems.
    • Fix: Keep document stores regional, redact PII before prompting where possible, and restrict model providers to approved processing locations.
  4. Skipping human escalation paths

    • Problem: Edge cases get auto-classified when they should be reviewed by compliance staff.
    • Fix: Route ambiguous cases to needs_review whenever evidence is thin, conflicts exist between clauses, or the request touches trustee discretion.

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